# app/services/admin_platform_service.py """ Admin Platform service for managing admin-platform assignments. This module provides: - Assigning platform admins to platforms - Removing platform admin access - Listing platforms for an admin - Listing admins for a platform - Promoting/demoting super admin status """ import logging from datetime import UTC, datetime from sqlalchemy.orm import Session, joinedload from app.exceptions import ( AdminOperationException, CannotModifySelfException, ValidationException, ) from models.database.admin_platform import AdminPlatform from models.database.platform import Platform from models.database.user import User logger = logging.getLogger(__name__) class AdminPlatformService: """Service class for admin-platform assignment operations.""" # ============================================================================ # ADMIN-PLATFORM ASSIGNMENTS # ============================================================================ def assign_admin_to_platform( self, db: Session, admin_user_id: int, platform_id: int, assigned_by_user_id: int, ) -> AdminPlatform: """ Assign a platform admin to a platform. Args: db: Database session admin_user_id: User ID of the admin to assign platform_id: Platform ID to assign to assigned_by_user_id: Super admin making the assignment Returns: AdminPlatform: The created assignment Raises: ValidationException: If user is not an admin or is a super admin AdminOperationException: If assignment already exists """ # Verify target user exists and is an admin user = db.query(User).filter(User.id == admin_user_id).first() if not user: raise ValidationException("User not found", field="admin_user_id") if not user.is_admin: raise ValidationException( "User must be an admin to be assigned to platforms", field="admin_user_id", ) if user.is_super_admin: raise ValidationException( "Super admins don't need platform assignments - they have access to all platforms", field="admin_user_id", ) # Verify platform exists platform = db.query(Platform).filter(Platform.id == platform_id).first() if not platform: raise ValidationException("Platform not found", field="platform_id") # Check if assignment already exists existing = ( db.query(AdminPlatform) .filter( AdminPlatform.user_id == admin_user_id, AdminPlatform.platform_id == platform_id, ) .first() ) if existing: if existing.is_active: raise AdminOperationException( operation="assign_admin_to_platform", reason=f"Admin already assigned to platform '{platform.code}'", ) # Reactivate existing assignment existing.is_active = True existing.assigned_at = datetime.now(UTC) existing.assigned_by_user_id = assigned_by_user_id existing.updated_at = datetime.now(UTC) db.flush() db.refresh(existing) logger.info( f"Reactivated admin {admin_user_id} access to platform {platform.code} " f"by admin {assigned_by_user_id}" ) return existing # Create new assignment assignment = AdminPlatform( user_id=admin_user_id, platform_id=platform_id, assigned_by_user_id=assigned_by_user_id, is_active=True, ) db.add(assignment) db.flush() db.refresh(assignment) logger.info( f"Assigned admin {admin_user_id} to platform {platform.code} " f"by admin {assigned_by_user_id}" ) return assignment def remove_admin_from_platform( self, db: Session, admin_user_id: int, platform_id: int, removed_by_user_id: int, ) -> None: """ Remove admin's access to a platform. This soft-deletes by setting is_active=False for audit purposes. Args: db: Database session admin_user_id: User ID of the admin to remove platform_id: Platform ID to remove from removed_by_user_id: Super admin making the removal Raises: ValidationException: If assignment doesn't exist """ assignment = ( db.query(AdminPlatform) .filter( AdminPlatform.user_id == admin_user_id, AdminPlatform.platform_id == platform_id, ) .first() ) if not assignment: raise ValidationException( "Admin is not assigned to this platform", field="platform_id", ) assignment.is_active = False assignment.updated_at = datetime.now(UTC) db.flush() logger.info( f"Removed admin {admin_user_id} from platform {platform_id} " f"by admin {removed_by_user_id}" ) def get_platforms_for_admin( self, db: Session, admin_user_id: int, include_inactive: bool = False, ) -> list[Platform]: """ Get all platforms an admin can access. Args: db: Database session admin_user_id: User ID of the admin include_inactive: Whether to include inactive assignments Returns: List of Platform objects the admin can access """ query = ( db.query(Platform) .join(AdminPlatform) .filter(AdminPlatform.user_id == admin_user_id) ) if not include_inactive: query = query.filter(AdminPlatform.is_active == True) return query.all() def get_admins_for_platform( self, db: Session, platform_id: int, include_inactive: bool = False, ) -> list[User]: """ Get all admins assigned to a platform. Args: db: Database session platform_id: Platform ID include_inactive: Whether to include inactive assignments Returns: List of User objects assigned to the platform """ # Explicit join condition needed because AdminPlatform has two FKs to User # (user_id and assigned_by_user_id) query = ( db.query(User) .join(AdminPlatform, AdminPlatform.user_id == User.id) .filter(AdminPlatform.platform_id == platform_id) ) if not include_inactive: query = query.filter(AdminPlatform.is_active == True) return query.all() def get_admin_assignments( self, db: Session, admin_user_id: int, ) -> list[AdminPlatform]: """ Get all platform assignments for an admin with platform details. Args: db: Database session admin_user_id: User ID of the admin Returns: List of AdminPlatform objects with platform relationship loaded """ return ( db.query(AdminPlatform) .options(joinedload(AdminPlatform.platform)) .filter( AdminPlatform.user_id == admin_user_id, AdminPlatform.is_active == True, ) .all() ) # ============================================================================ # SUPER ADMIN MANAGEMENT # ============================================================================ def toggle_super_admin( self, db: Session, user_id: int, is_super_admin: bool, current_admin_id: int, ) -> User: """ Promote or demote a user to/from super admin. When demoting from super admin, the admin will have no platform access until explicitly assigned via assign_admin_to_platform. Args: db: Database session user_id: User ID to modify is_super_admin: True to promote, False to demote current_admin_id: Super admin making the change Returns: Updated User object Raises: CannotModifySelfException: If trying to demote self ValidationException: If user is not an admin """ if user_id == current_admin_id and not is_super_admin: raise CannotModifySelfException( user_id=user_id, operation="demote from super admin", ) user = db.query(User).filter(User.id == user_id).first() if not user: raise ValidationException("User not found", field="user_id") if not user.is_admin: raise ValidationException( "User must be an admin to be promoted to super admin", field="user_id", ) old_status = user.is_super_admin user.is_super_admin = is_super_admin user.updated_at = datetime.now(UTC) db.flush() db.refresh(user) action = "promoted to" if is_super_admin else "demoted from" logger.info( f"User {user.username} {action} super admin by admin {current_admin_id}" ) return user def create_platform_admin( self, db: Session, email: str, username: str, password: str, platform_ids: list[int], created_by_user_id: int, first_name: str | None = None, last_name: str | None = None, ) -> tuple[User, list[AdminPlatform]]: """ Create a new platform admin with platform assignments. Args: db: Database session email: Admin email username: Admin username password: Admin password platform_ids: List of platform IDs to assign created_by_user_id: Super admin creating the account first_name: Optional first name last_name: Optional last name Returns: Tuple of (User, list of AdminPlatform assignments) """ from middleware.auth import AuthManager auth_manager = AuthManager() # Check for existing user existing = db.query(User).filter( (User.email == email) | (User.username == username) ).first() if existing: field = "email" if existing.email == email else "username" raise ValidationException(f"{field.capitalize()} already exists", field=field) # Create admin user user = User( email=email, username=username, hashed_password=auth_manager.hash_password(password), first_name=first_name, last_name=last_name, role="admin", is_active=True, is_super_admin=False, # Platform admin, not super admin ) db.add(user) db.flush() # Create platform assignments assignments = [] for platform_id in platform_ids: assignment = AdminPlatform( user_id=user.id, platform_id=platform_id, assigned_by_user_id=created_by_user_id, is_active=True, ) db.add(assignment) assignments.append(assignment) db.flush() db.refresh(user) logger.info( f"Created platform admin {username} with access to platforms " f"{platform_ids} by admin {created_by_user_id}" ) return user, assignments # Singleton instance admin_platform_service = AdminPlatformService()