inital
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
"""Services package."""
|
||||
from app.services.auth_service import AuthService
|
||||
from app.services.user_service import UserService
|
||||
from app.services.organization_service import OrganizationService
|
||||
from app.services.session_service import SessionService
|
||||
from app.services.audit_service import AuditService
|
||||
|
||||
__all__ = [
|
||||
"AuthService",
|
||||
"UserService",
|
||||
"OrganizationService",
|
||||
"SessionService",
|
||||
"AuditService",
|
||||
]
|
||||
@@ -0,0 +1,107 @@
|
||||
"""Audit service."""
|
||||
from flask import request, g
|
||||
from app.models.audit_log import AuditLog
|
||||
from app.utils.constants import AuditAction
|
||||
|
||||
|
||||
class AuditService:
|
||||
"""Service for audit logging."""
|
||||
|
||||
@staticmethod
|
||||
def log_action(
|
||||
action,
|
||||
user_id=None,
|
||||
organization_id=None,
|
||||
resource_type=None,
|
||||
resource_id=None,
|
||||
metadata=None,
|
||||
description=None,
|
||||
success=True,
|
||||
error_message=None,
|
||||
):
|
||||
"""
|
||||
Create an audit log entry.
|
||||
|
||||
Args:
|
||||
action: AuditAction enum value
|
||||
user_id: ID of user performing the action
|
||||
organization_id: ID of related organization
|
||||
resource_type: Type of resource being acted upon
|
||||
resource_id: ID of resource being acted upon
|
||||
metadata: Additional metadata dictionary
|
||||
description: Human-readable description
|
||||
success: Whether the action succeeded
|
||||
error_message: Error message if action failed
|
||||
|
||||
Returns:
|
||||
AuditLog instance
|
||||
"""
|
||||
# Get request details if available
|
||||
ip_address = None
|
||||
user_agent = None
|
||||
request_id = None
|
||||
|
||||
try:
|
||||
if request:
|
||||
ip_address = request.remote_addr
|
||||
user_agent = request.headers.get("User-Agent")
|
||||
request_id = g.get("request_id")
|
||||
except RuntimeError:
|
||||
# No request context
|
||||
pass
|
||||
|
||||
log_entry = AuditLog(
|
||||
action=action,
|
||||
user_id=user_id,
|
||||
organization_id=organization_id,
|
||||
resource_type=resource_type,
|
||||
resource_id=resource_id,
|
||||
ip_address=ip_address,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
metadata=metadata,
|
||||
description=description,
|
||||
success=success,
|
||||
error_message=error_message,
|
||||
)
|
||||
log_entry.save()
|
||||
|
||||
return log_entry
|
||||
|
||||
@staticmethod
|
||||
def get_user_activity(user_id, limit=50):
|
||||
"""
|
||||
Get recent activity for a user.
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
limit: Maximum number of records to return
|
||||
|
||||
Returns:
|
||||
List of AuditLog instances
|
||||
"""
|
||||
return (
|
||||
AuditLog.query.filter_by(user_id=user_id)
|
||||
.order_by(AuditLog.created_at.desc())
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_organization_activity(organization_id, limit=50):
|
||||
"""
|
||||
Get recent activity for an organization.
|
||||
|
||||
Args:
|
||||
organization_id: Organization ID
|
||||
limit: Maximum number of records to return
|
||||
|
||||
Returns:
|
||||
List of AuditLog instances
|
||||
"""
|
||||
return (
|
||||
AuditLog.query.filter_by(organization_id=organization_id)
|
||||
.order_by(AuditLog.created_at.desc())
|
||||
.limit(limit)
|
||||
.all()
|
||||
)
|
||||
@@ -0,0 +1,215 @@
|
||||
"""Authentication service."""
|
||||
from datetime import datetime, timedelta
|
||||
from flask import request, g
|
||||
from app.extensions import db, bcrypt
|
||||
from app.models.user import User
|
||||
from app.models.authentication_method import AuthenticationMethod
|
||||
from app.models.session import Session
|
||||
from app.utils.constants import AuthMethodType, SessionStatus, UserStatus, AuditAction
|
||||
from app.exceptions.auth_exceptions import InvalidCredentialsError, AccountSuspendedError, AccountInactiveError
|
||||
from app.exceptions.validation_exceptions import EmailAlreadyExistsError
|
||||
from app.services.audit_service import AuditService
|
||||
import secrets
|
||||
|
||||
|
||||
class AuthService:
|
||||
"""Service for authentication operations."""
|
||||
|
||||
@staticmethod
|
||||
def register_user(email, password, full_name=None):
|
||||
"""
|
||||
Register a new user with email/password.
|
||||
|
||||
Args:
|
||||
email: User email address
|
||||
password: Plain text password
|
||||
full_name: Optional full name
|
||||
|
||||
Returns:
|
||||
User instance
|
||||
|
||||
Raises:
|
||||
EmailAlreadyExistsError: If email is already registered
|
||||
"""
|
||||
# Check if email already exists
|
||||
existing_user = User.query.filter_by(email=email.lower()).first()
|
||||
if existing_user and existing_user.deleted_at is None:
|
||||
raise EmailAlreadyExistsError()
|
||||
|
||||
# Create user
|
||||
user = User(
|
||||
email=email.lower(),
|
||||
full_name=full_name,
|
||||
status=UserStatus.ACTIVE,
|
||||
)
|
||||
user.save()
|
||||
|
||||
# Create password authentication method
|
||||
password_hash = bcrypt.generate_password_hash(password).decode("utf-8")
|
||||
auth_method = AuthenticationMethod(
|
||||
user_id=user.id,
|
||||
method_type=AuthMethodType.PASSWORD,
|
||||
password_hash=password_hash,
|
||||
is_primary=True,
|
||||
verified=True,
|
||||
)
|
||||
auth_method.save()
|
||||
|
||||
# Log the registration
|
||||
AuditService.log_action(
|
||||
action=AuditAction.USER_REGISTER,
|
||||
user_id=user.id,
|
||||
resource_type="user",
|
||||
resource_id=user.id,
|
||||
description=f"User registered with email: {email}",
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def authenticate(email, password):
|
||||
"""
|
||||
Authenticate user with email/password.
|
||||
|
||||
Args:
|
||||
email: User email
|
||||
password: Plain text password
|
||||
|
||||
Returns:
|
||||
User instance if authentication succeeds
|
||||
|
||||
Raises:
|
||||
InvalidCredentialsError: If credentials are invalid
|
||||
AccountSuspendedError: If account is suspended
|
||||
AccountInactiveError: If account is inactive
|
||||
"""
|
||||
# Find user
|
||||
user = User.query.filter_by(email=email.lower(), deleted_at=None).first()
|
||||
if not user:
|
||||
raise InvalidCredentialsError()
|
||||
|
||||
# Check account status
|
||||
if user.status == UserStatus.SUSPENDED:
|
||||
raise AccountSuspendedError()
|
||||
if user.status == UserStatus.INACTIVE:
|
||||
raise AccountInactiveError()
|
||||
|
||||
# Find password auth method
|
||||
auth_method = AuthenticationMethod.query.filter_by(
|
||||
user_id=user.id,
|
||||
method_type=AuthMethodType.PASSWORD,
|
||||
deleted_at=None,
|
||||
).first()
|
||||
|
||||
if not auth_method or not auth_method.password_hash:
|
||||
raise InvalidCredentialsError()
|
||||
|
||||
# Verify password
|
||||
if not bcrypt.check_password_hash(auth_method.password_hash, password):
|
||||
raise InvalidCredentialsError()
|
||||
|
||||
# Update last login
|
||||
user.last_login_at = datetime.utcnow()
|
||||
user.last_login_ip = request.remote_addr
|
||||
auth_method.last_used_at = datetime.utcnow()
|
||||
db.session.commit()
|
||||
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def create_session(user, duration_seconds=86400):
|
||||
"""
|
||||
Create a new session for the user.
|
||||
|
||||
Args:
|
||||
user: User instance
|
||||
duration_seconds: Session duration in seconds
|
||||
|
||||
Returns:
|
||||
Session instance
|
||||
"""
|
||||
# Generate session token
|
||||
token = secrets.token_urlsafe(32)
|
||||
|
||||
# Create session
|
||||
session = Session(
|
||||
user_id=user.id,
|
||||
token=token,
|
||||
status=SessionStatus.ACTIVE,
|
||||
ip_address=request.remote_addr,
|
||||
user_agent=request.headers.get("User-Agent"),
|
||||
expires_at=datetime.utcnow() + timedelta(seconds=duration_seconds),
|
||||
last_activity_at=datetime.utcnow(),
|
||||
)
|
||||
session.save()
|
||||
|
||||
# Log session creation
|
||||
AuditService.log_action(
|
||||
action=AuditAction.SESSION_CREATE,
|
||||
user_id=user.id,
|
||||
resource_type="session",
|
||||
resource_id=session.id,
|
||||
description="User session created",
|
||||
)
|
||||
|
||||
return session
|
||||
|
||||
@staticmethod
|
||||
def change_password(user, current_password, new_password):
|
||||
"""
|
||||
Change user password.
|
||||
|
||||
Args:
|
||||
user: User instance
|
||||
current_password: Current password
|
||||
new_password: New password
|
||||
|
||||
Raises:
|
||||
InvalidCredentialsError: If current password is incorrect
|
||||
"""
|
||||
# Find password auth method
|
||||
auth_method = AuthenticationMethod.query.filter_by(
|
||||
user_id=user.id,
|
||||
method_type=AuthMethodType.PASSWORD,
|
||||
deleted_at=None,
|
||||
).first()
|
||||
|
||||
if not auth_method or not auth_method.password_hash:
|
||||
raise InvalidCredentialsError("No password authentication method found")
|
||||
|
||||
# Verify current password
|
||||
if not bcrypt.check_password_hash(auth_method.password_hash, current_password):
|
||||
raise InvalidCredentialsError("Current password is incorrect")
|
||||
|
||||
# Update password
|
||||
auth_method.password_hash = bcrypt.generate_password_hash(new_password).decode("utf-8")
|
||||
db.session.commit()
|
||||
|
||||
# Log password change
|
||||
AuditService.log_action(
|
||||
action=AuditAction.PASSWORD_CHANGE,
|
||||
user_id=user.id,
|
||||
description="User changed password",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def revoke_session(session_id, reason=None):
|
||||
"""
|
||||
Revoke a session.
|
||||
|
||||
Args:
|
||||
session_id: Session ID to revoke
|
||||
reason: Optional revocation reason
|
||||
"""
|
||||
session = Session.query.get(session_id)
|
||||
if session:
|
||||
session.revoke(reason=reason)
|
||||
|
||||
# Log session revocation
|
||||
AuditService.log_action(
|
||||
action=AuditAction.SESSION_REVOKE,
|
||||
user_id=session.user_id,
|
||||
resource_type="session",
|
||||
resource_id=session.id,
|
||||
description=f"Session revoked: {reason or 'User logout'}",
|
||||
)
|
||||
@@ -0,0 +1,280 @@
|
||||
"""Organization service."""
|
||||
from datetime import datetime
|
||||
from app.extensions import db
|
||||
from app.models.organization import Organization
|
||||
from app.models.organization_member import OrganizationMember
|
||||
from app.exceptions.validation_exceptions import OrganizationNotFoundError, ConflictError
|
||||
from app.utils.constants import OrganizationRole, AuditAction
|
||||
from app.services.audit_service import AuditService
|
||||
|
||||
|
||||
class OrganizationService:
|
||||
"""Service for organization operations."""
|
||||
|
||||
@staticmethod
|
||||
def create_organization(name, slug, owner_user_id, description=None, logo_url=None):
|
||||
"""
|
||||
Create a new organization.
|
||||
|
||||
Args:
|
||||
name: Organization name
|
||||
slug: Unique organization slug
|
||||
owner_user_id: ID of the user who will be the owner
|
||||
description: Optional description
|
||||
logo_url: Optional logo URL
|
||||
|
||||
Returns:
|
||||
Organization instance
|
||||
|
||||
Raises:
|
||||
ConflictError: If slug already exists
|
||||
"""
|
||||
# Check if slug already exists
|
||||
existing = Organization.query.filter_by(slug=slug, deleted_at=None).first()
|
||||
if existing:
|
||||
raise ConflictError("Organization slug already exists")
|
||||
|
||||
# Create organization
|
||||
org = Organization(
|
||||
name=name,
|
||||
slug=slug,
|
||||
description=description,
|
||||
logo_url=logo_url,
|
||||
is_active=True,
|
||||
)
|
||||
org.save()
|
||||
|
||||
# Add owner as member
|
||||
member = OrganizationMember(
|
||||
user_id=owner_user_id,
|
||||
organization_id=org.id,
|
||||
role=OrganizationRole.OWNER,
|
||||
joined_at=datetime.utcnow(),
|
||||
)
|
||||
member.save()
|
||||
|
||||
# Log organization creation
|
||||
AuditService.log_action(
|
||||
action=AuditAction.ORG_CREATE,
|
||||
user_id=owner_user_id,
|
||||
organization_id=org.id,
|
||||
resource_type="organization",
|
||||
resource_id=org.id,
|
||||
description=f"Organization created: {name}",
|
||||
)
|
||||
|
||||
return org
|
||||
|
||||
@staticmethod
|
||||
def get_organization_by_id(org_id):
|
||||
"""
|
||||
Get organization by ID.
|
||||
|
||||
Args:
|
||||
org_id: Organization ID
|
||||
|
||||
Returns:
|
||||
Organization instance
|
||||
|
||||
Raises:
|
||||
OrganizationNotFoundError: If organization not found
|
||||
"""
|
||||
org = Organization.query.filter_by(id=org_id, deleted_at=None).first()
|
||||
if not org:
|
||||
raise OrganizationNotFoundError()
|
||||
return org
|
||||
|
||||
@staticmethod
|
||||
def get_organization_by_slug(slug):
|
||||
"""
|
||||
Get organization by slug.
|
||||
|
||||
Args:
|
||||
slug: Organization slug
|
||||
|
||||
Returns:
|
||||
Organization instance or None
|
||||
"""
|
||||
return Organization.query.filter_by(slug=slug, deleted_at=None).first()
|
||||
|
||||
@staticmethod
|
||||
def update_organization(org, user_id, **kwargs):
|
||||
"""
|
||||
Update organization.
|
||||
|
||||
Args:
|
||||
org: Organization instance
|
||||
user_id: ID of user performing the update
|
||||
**kwargs: Fields to update
|
||||
|
||||
Returns:
|
||||
Updated Organization instance
|
||||
"""
|
||||
allowed_fields = ["name", "description", "logo_url"]
|
||||
update_data = {k: v for k, v in kwargs.items() if k in allowed_fields}
|
||||
|
||||
if update_data:
|
||||
org.update(**update_data)
|
||||
|
||||
# Log organization update
|
||||
AuditService.log_action(
|
||||
action=AuditAction.ORG_UPDATE,
|
||||
user_id=user_id,
|
||||
organization_id=org.id,
|
||||
resource_type="organization",
|
||||
resource_id=org.id,
|
||||
metadata=update_data,
|
||||
description="Organization updated",
|
||||
)
|
||||
|
||||
return org
|
||||
|
||||
@staticmethod
|
||||
def delete_organization(org, user_id, soft=True):
|
||||
"""
|
||||
Delete organization.
|
||||
|
||||
Args:
|
||||
org: Organization instance
|
||||
user_id: ID of user performing the delete
|
||||
soft: If True, performs soft delete
|
||||
|
||||
Returns:
|
||||
Deleted Organization instance
|
||||
"""
|
||||
org.delete(soft=soft)
|
||||
|
||||
# Log organization deletion
|
||||
AuditService.log_action(
|
||||
action=AuditAction.ORG_DELETE,
|
||||
user_id=user_id,
|
||||
organization_id=org.id,
|
||||
resource_type="organization",
|
||||
resource_id=org.id,
|
||||
description=f"Organization {'soft' if soft else 'hard'} deleted",
|
||||
)
|
||||
|
||||
return org
|
||||
|
||||
@staticmethod
|
||||
def add_member(org, user_id, role, inviter_id):
|
||||
"""
|
||||
Add a member to the organization.
|
||||
|
||||
Args:
|
||||
org: Organization instance
|
||||
user_id: ID of user to add
|
||||
role: OrganizationRole
|
||||
inviter_id: ID of user performing the invitation
|
||||
|
||||
Returns:
|
||||
OrganizationMember instance
|
||||
|
||||
Raises:
|
||||
ConflictError: If user is already a member
|
||||
"""
|
||||
# Check if already a member
|
||||
existing = OrganizationMember.query.filter_by(
|
||||
user_id=user_id,
|
||||
organization_id=org.id,
|
||||
deleted_at=None,
|
||||
).first()
|
||||
|
||||
if existing:
|
||||
raise ConflictError("User is already a member of this organization")
|
||||
|
||||
# Create membership
|
||||
member = OrganizationMember(
|
||||
user_id=user_id,
|
||||
organization_id=org.id,
|
||||
role=role,
|
||||
invited_by_id=inviter_id,
|
||||
invited_at=datetime.utcnow(),
|
||||
joined_at=datetime.utcnow(),
|
||||
)
|
||||
member.save()
|
||||
|
||||
# Log member addition
|
||||
AuditService.log_action(
|
||||
action=AuditAction.ORG_MEMBER_ADD,
|
||||
user_id=inviter_id,
|
||||
organization_id=org.id,
|
||||
resource_type="organization_member",
|
||||
resource_id=member.id,
|
||||
metadata={"added_user_id": user_id, "role": role.value},
|
||||
description=f"Member added to organization with role: {role.value}",
|
||||
)
|
||||
|
||||
return member
|
||||
|
||||
@staticmethod
|
||||
def remove_member(org, user_id, remover_id):
|
||||
"""
|
||||
Remove a member from the organization.
|
||||
|
||||
Args:
|
||||
org: Organization instance
|
||||
user_id: ID of user to remove
|
||||
remover_id: ID of user performing the removal
|
||||
"""
|
||||
member = OrganizationMember.query.filter_by(
|
||||
user_id=user_id,
|
||||
organization_id=org.id,
|
||||
deleted_at=None,
|
||||
).first()
|
||||
|
||||
if member:
|
||||
member.delete(soft=True)
|
||||
|
||||
# Log member removal
|
||||
AuditService.log_action(
|
||||
action=AuditAction.ORG_MEMBER_REMOVE,
|
||||
user_id=remover_id,
|
||||
organization_id=org.id,
|
||||
resource_type="organization_member",
|
||||
resource_id=member.id,
|
||||
metadata={"removed_user_id": user_id},
|
||||
description="Member removed from organization",
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update_member_role(org, user_id, new_role, updater_id):
|
||||
"""
|
||||
Update a member's role in the organization.
|
||||
|
||||
Args:
|
||||
org: Organization instance
|
||||
user_id: ID of user whose role to update
|
||||
new_role: New OrganizationRole
|
||||
updater_id: ID of user performing the update
|
||||
|
||||
Returns:
|
||||
Updated OrganizationMember instance
|
||||
"""
|
||||
member = OrganizationMember.query.filter_by(
|
||||
user_id=user_id,
|
||||
organization_id=org.id,
|
||||
deleted_at=None,
|
||||
).first()
|
||||
|
||||
if member:
|
||||
old_role = member.role
|
||||
member.role = new_role
|
||||
db.session.commit()
|
||||
|
||||
# Log role change
|
||||
AuditService.log_action(
|
||||
action=AuditAction.ORG_MEMBER_ROLE_CHANGE,
|
||||
user_id=updater_id,
|
||||
organization_id=org.id,
|
||||
resource_type="organization_member",
|
||||
resource_id=member.id,
|
||||
metadata={
|
||||
"target_user_id": user_id,
|
||||
"old_role": old_role.value,
|
||||
"new_role": new_role.value,
|
||||
},
|
||||
description=f"Member role changed from {old_role.value} to {new_role.value}",
|
||||
)
|
||||
|
||||
return member
|
||||
@@ -0,0 +1,76 @@
|
||||
"""Session service."""
|
||||
from datetime import datetime
|
||||
from app.models.session import Session
|
||||
from app.utils.constants import SessionStatus
|
||||
|
||||
|
||||
class SessionService:
|
||||
"""Service for session operations."""
|
||||
|
||||
@staticmethod
|
||||
def get_active_session_by_token(token):
|
||||
"""Get active session by token.
|
||||
|
||||
Args:
|
||||
token: The session token string
|
||||
|
||||
Returns:
|
||||
Session object if found and active, None otherwise
|
||||
"""
|
||||
from app.models.session import Session
|
||||
from app.utils.constants import SessionStatus
|
||||
return Session.query.filter_by(
|
||||
token=token,
|
||||
status=SessionStatus.ACTIVE,
|
||||
deleted_at=None
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def get_user_sessions(user_id, active_only=True):
|
||||
"""
|
||||
Get all sessions for a user.
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
active_only: If True, only return active sessions
|
||||
|
||||
Returns:
|
||||
List of Session instances
|
||||
"""
|
||||
query = Session.query.filter_by(user_id=user_id, deleted_at=None)
|
||||
|
||||
if active_only:
|
||||
query = query.filter_by(status=SessionStatus.ACTIVE).filter(
|
||||
Session.expires_at > datetime.utcnow()
|
||||
)
|
||||
|
||||
return query.all()
|
||||
|
||||
@staticmethod
|
||||
def revoke_user_sessions(user_id, reason="User logged out from all devices"):
|
||||
"""
|
||||
Revoke all active sessions for a user.
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
reason: Reason for revocation
|
||||
"""
|
||||
sessions = SessionService.get_user_sessions(user_id, active_only=True)
|
||||
|
||||
for session in sessions:
|
||||
session.revoke(reason=reason)
|
||||
|
||||
@staticmethod
|
||||
def cleanup_expired_sessions():
|
||||
"""Clean up expired sessions."""
|
||||
expired_sessions = Session.query.filter(
|
||||
Session.status == SessionStatus.ACTIVE,
|
||||
Session.expires_at < datetime.utcnow(),
|
||||
Session.deleted_at.is_(None),
|
||||
).all()
|
||||
|
||||
for session in expired_sessions:
|
||||
session.status = SessionStatus.EXPIRED
|
||||
session.save()
|
||||
|
||||
return len(expired_sessions)
|
||||
@@ -0,0 +1,110 @@
|
||||
"""User service."""
|
||||
from app.extensions import db
|
||||
from app.models.user import User
|
||||
from app.exceptions.validation_exceptions import UserNotFoundError
|
||||
from app.utils.constants import AuditAction
|
||||
from app.services.audit_service import AuditService
|
||||
|
||||
|
||||
class UserService:
|
||||
"""Service for user operations."""
|
||||
|
||||
@staticmethod
|
||||
def get_user_by_id(user_id):
|
||||
"""
|
||||
Get user by ID.
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
|
||||
Returns:
|
||||
User instance
|
||||
|
||||
Raises:
|
||||
UserNotFoundError: If user not found
|
||||
"""
|
||||
user = User.query.filter_by(id=user_id, deleted_at=None).first()
|
||||
if not user:
|
||||
raise UserNotFoundError()
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def get_user_by_email(email):
|
||||
"""
|
||||
Get user by email.
|
||||
|
||||
Args:
|
||||
email: User email
|
||||
|
||||
Returns:
|
||||
User instance or None
|
||||
"""
|
||||
return User.query.filter_by(email=email.lower(), deleted_at=None).first()
|
||||
|
||||
@staticmethod
|
||||
def update_user(user, **kwargs):
|
||||
"""
|
||||
Update user profile.
|
||||
|
||||
Args:
|
||||
user: User instance
|
||||
**kwargs: Fields to update
|
||||
|
||||
Returns:
|
||||
Updated User instance
|
||||
"""
|
||||
allowed_fields = ["full_name", "avatar_url"]
|
||||
update_data = {k: v for k, v in kwargs.items() if k in allowed_fields}
|
||||
|
||||
if update_data:
|
||||
user.update(**update_data)
|
||||
|
||||
# Log user update
|
||||
AuditService.log_action(
|
||||
action=AuditAction.USER_UPDATE,
|
||||
user_id=user.id,
|
||||
resource_type="user",
|
||||
resource_id=user.id,
|
||||
metadata=update_data,
|
||||
description="User profile updated",
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def delete_user(user, soft=True):
|
||||
"""
|
||||
Delete user account.
|
||||
|
||||
Args:
|
||||
user: User instance
|
||||
soft: If True, performs soft delete
|
||||
|
||||
Returns:
|
||||
Deleted User instance
|
||||
"""
|
||||
user.delete(soft=soft)
|
||||
|
||||
# Log user deletion
|
||||
AuditService.log_action(
|
||||
action=AuditAction.USER_DELETE,
|
||||
user_id=user.id,
|
||||
resource_type="user",
|
||||
resource_id=user.id,
|
||||
description=f"User account {'soft' if soft else 'hard'} deleted",
|
||||
)
|
||||
|
||||
return user
|
||||
|
||||
@staticmethod
|
||||
def get_user_organizations(user):
|
||||
"""
|
||||
Get all organizations the user is a member of.
|
||||
|
||||
Args:
|
||||
user: User instance
|
||||
|
||||
Returns:
|
||||
List of organizations
|
||||
"""
|
||||
return user.get_organizations()
|
||||
Reference in New Issue
Block a user