feat: implement super admin and platform admin roles

Add multi-platform admin authorization system with:
- AdminPlatform junction table for admin-platform assignments
- is_super_admin flag on User model for global admin access
- Platform selection flow for platform admins after login
- JWT token updates to include platform context
- New API endpoints for admin user management (super admin only)
- Auth dependencies for super admin and platform access checks

Includes comprehensive test coverage:
- Unit tests for AdminPlatform model and User admin methods
- Unit tests for AdminPlatformService operations
- Integration tests for admin users API endpoints

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-24 18:44:49 +01:00
parent 7e39bb0564
commit 53e05dd497
18 changed files with 2792 additions and 6 deletions

View File

@@ -203,6 +203,174 @@ def get_current_admin_api(
return user
# ============================================================================
# SUPER ADMIN AUTHENTICATION
# ============================================================================
def get_current_super_admin(
request: Request,
credentials: HTTPAuthorizationCredentials | None = Depends(security),
admin_token: str | None = Cookie(None),
db: Session = Depends(get_db),
) -> User:
"""
Require super admin role.
Used for: Global settings, user management, platform creation/deletion,
admin-platform assignment management.
Args:
request: FastAPI request
credentials: Optional Bearer token from header
admin_token: Optional token from admin_token cookie
db: Database session
Returns:
User: Authenticated super admin user
Raises:
InvalidTokenException: If no token or invalid token
AdminRequiredException: If user is not admin or not super admin
"""
user = get_current_admin_from_cookie_or_header(request, credentials, admin_token, db)
if not user.is_super_admin:
logger.warning(
f"Platform admin {user.username} attempted super admin route: {request.url.path}"
)
raise AdminRequiredException("Super admin privileges required")
return user
def get_current_super_admin_api(
credentials: HTTPAuthorizationCredentials = Depends(security),
db: Session = Depends(get_db),
) -> User:
"""
Require super admin role (API header only).
Used for super admin API endpoints that should not accept cookies.
Args:
credentials: Bearer token from Authorization header
db: Database session
Returns:
User: Authenticated super admin user
Raises:
InvalidTokenException: If no token or invalid token
AdminRequiredException: If user is not admin or not super admin
"""
user = get_current_admin_api(credentials, db)
if not user.is_super_admin:
logger.warning(f"Platform admin {user.username} attempted super admin API")
raise AdminRequiredException("Super admin privileges required")
return user
def require_platform_access(platform_id: int):
"""
Dependency factory to require admin access to a specific platform.
Super admins can access all platforms.
Platform admins can only access their assigned platforms.
Usage:
@router.get("/platforms/{platform_id}/vendors")
def list_vendors(
platform_id: int,
admin: User = Depends(require_platform_access(platform_id))
):
...
"""
def _check_platform_access(
request: Request,
credentials: HTTPAuthorizationCredentials | None = Depends(security),
admin_token: str | None = Cookie(None),
db: Session = Depends(get_db),
) -> User:
user = get_current_admin_from_cookie_or_header(
request, credentials, admin_token, db
)
if not user.can_access_platform(platform_id):
logger.warning(
f"Admin {user.username} denied access to platform_id={platform_id}"
)
raise InsufficientPermissionsException(
f"Access denied to platform {platform_id}"
)
return user
return _check_platform_access
def get_admin_with_platform_context(
request: Request,
credentials: HTTPAuthorizationCredentials | None = Depends(security),
admin_token: str | None = Cookie(None),
db: Session = Depends(get_db),
) -> User:
"""
Get admin user and verify platform context from token.
For platform admins, extracts platform_id from JWT token and verifies access.
Stores platform in request.state.admin_platform for endpoint use.
Super admins bypass platform context check (they can access all platforms).
Args:
request: FastAPI request
credentials: Optional Bearer token from header
admin_token: Optional token from admin_token cookie
db: Database session
Returns:
User: Authenticated admin with platform context
Raises:
InvalidTokenException: If platform admin token missing platform info
InsufficientPermissionsException: If platform access revoked
"""
from models.database.platform import Platform
user = get_current_admin_from_cookie_or_header(request, credentials, admin_token, db)
# Super admins bypass platform context
if user.is_super_admin:
return user
# Platform admins need platform_id in token
if not hasattr(user, "token_platform_id"):
raise InvalidTokenException(
"Token missing platform information. Please select a platform."
)
platform_id = user.token_platform_id
# Verify admin still has access to this platform
if not user.can_access_platform(platform_id):
logger.warning(
f"Admin {user.username} lost access to platform_id={platform_id}"
)
raise InsufficientPermissionsException(
"Access to this platform has been revoked. Please login again."
)
# Load platform and store in request state
platform = db.query(Platform).filter(Platform.id == platform_id).first()
request.state.admin_platform = platform
return user
# ============================================================================
# VENDOR AUTHENTICATION
# ============================================================================

View File

@@ -25,6 +25,7 @@ from fastapi import APIRouter
# Import all admin routers
from . import (
admin_users,
audit,
auth,
background_tasks,
@@ -103,6 +104,9 @@ router.include_router(platforms.router, tags=["admin-platforms"])
# Include user management endpoints
router.include_router(users.router, tags=["admin-users"])
# Include admin user management endpoints (super admin only)
router.include_router(admin_users.router, tags=["admin-admin-users"])
# Include customer management endpoints
router.include_router(customers.router, tags=["admin-customers"])

View File

@@ -0,0 +1,373 @@
# app/api/v1/admin/admin_users.py
"""
Admin user management endpoints (Super Admin only).
This module provides endpoints for:
- Listing all admin users with their platform assignments
- Creating platform admins
- Assigning/removing platform access
- Promoting/demoting super admin status
"""
import logging
from typing import Optional
from fastapi import APIRouter, Body, Depends, Path, Query
from pydantic import BaseModel, EmailStr
from sqlalchemy.orm import Session
from app.api.deps import get_current_super_admin, get_current_super_admin_api
from app.core.database import get_db
from app.services.admin_platform_service import admin_platform_service
from models.database.user import User
router = APIRouter(prefix="/admin-users")
logger = logging.getLogger(__name__)
# ============================================================================
# SCHEMAS
# ============================================================================
class PlatformAssignmentResponse(BaseModel):
"""Response for a platform assignment."""
platform_id: int
platform_code: str
platform_name: str
is_active: bool
class Config:
from_attributes = True
class AdminUserResponse(BaseModel):
"""Response for an admin user."""
id: int
email: str
username: str
first_name: Optional[str] = None
last_name: Optional[str] = None
is_active: bool
is_super_admin: bool
platform_assignments: list[PlatformAssignmentResponse] = []
class Config:
from_attributes = True
class AdminUserListResponse(BaseModel):
"""Response for listing admin users."""
admins: list[AdminUserResponse]
total: int
class CreatePlatformAdminRequest(BaseModel):
"""Request to create a new platform admin."""
email: EmailStr
username: str
password: str
first_name: Optional[str] = None
last_name: Optional[str] = None
platform_ids: list[int]
class AssignPlatformRequest(BaseModel):
"""Request to assign admin to platform."""
platform_id: int
class ToggleSuperAdminRequest(BaseModel):
"""Request to toggle super admin status."""
is_super_admin: bool
# ============================================================================
# ENDPOINTS
# ============================================================================
@router.get("", response_model=AdminUserListResponse)
def list_admin_users(
skip: int = Query(0, ge=0),
limit: int = Query(100, ge=1, le=500),
include_super_admins: bool = Query(True),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_super_admin),
):
"""
List all admin users with their platform assignments.
Super admin only.
"""
from sqlalchemy.orm import joinedload
query = db.query(User).filter(User.role == "admin")
if not include_super_admins:
query = query.filter(User.is_super_admin == False)
total = query.count()
admins = (
query.options(joinedload(User.admin_platforms))
.offset(skip)
.limit(limit)
.all()
)
admin_responses = []
for admin in admins:
assignments = []
if not admin.is_super_admin:
for ap in admin.admin_platforms:
if ap.is_active and ap.platform:
assignments.append(
PlatformAssignmentResponse(
platform_id=ap.platform_id,
platform_code=ap.platform.code,
platform_name=ap.platform.name,
is_active=ap.is_active,
)
)
admin_responses.append(
AdminUserResponse(
id=admin.id,
email=admin.email,
username=admin.username,
first_name=admin.first_name,
last_name=admin.last_name,
is_active=admin.is_active,
is_super_admin=admin.is_super_admin,
platform_assignments=assignments,
)
)
return AdminUserListResponse(admins=admin_responses, total=total)
@router.post("", response_model=AdminUserResponse)
def create_platform_admin(
request: CreatePlatformAdminRequest,
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_super_admin_api),
):
"""
Create a new platform admin with platform assignments.
Super admin only.
"""
user, assignments = admin_platform_service.create_platform_admin(
db=db,
email=request.email,
username=request.username,
password=request.password,
platform_ids=request.platform_ids,
created_by_user_id=current_admin.id,
first_name=request.first_name,
last_name=request.last_name,
)
db.commit()
# Refresh to get relationships
db.refresh(user)
assignment_responses = [
PlatformAssignmentResponse(
platform_id=ap.platform_id,
platform_code=ap.platform.code if ap.platform else "",
platform_name=ap.platform.name if ap.platform else "",
is_active=ap.is_active,
)
for ap in user.admin_platforms
if ap.is_active
]
logger.info(f"Created platform admin {user.username} by {current_admin.username}")
return AdminUserResponse(
id=user.id,
email=user.email,
username=user.username,
first_name=user.first_name,
last_name=user.last_name,
is_active=user.is_active,
is_super_admin=user.is_super_admin,
platform_assignments=assignment_responses,
)
@router.get("/{user_id}", response_model=AdminUserResponse)
def get_admin_user(
user_id: int = Path(...),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_super_admin),
):
"""
Get admin user details with platform assignments.
Super admin only.
"""
from sqlalchemy.orm import joinedload
from app.exceptions import ValidationException
admin = (
db.query(User)
.options(joinedload(User.admin_platforms))
.filter(User.id == user_id, User.role == "admin")
.first()
)
if not admin:
raise ValidationException("Admin user not found", field="user_id")
assignments = []
if not admin.is_super_admin:
for ap in admin.admin_platforms:
if ap.is_active and ap.platform:
assignments.append(
PlatformAssignmentResponse(
platform_id=ap.platform_id,
platform_code=ap.platform.code,
platform_name=ap.platform.name,
is_active=ap.is_active,
)
)
return AdminUserResponse(
id=admin.id,
email=admin.email,
username=admin.username,
first_name=admin.first_name,
last_name=admin.last_name,
is_active=admin.is_active,
is_super_admin=admin.is_super_admin,
platform_assignments=assignments,
)
@router.post("/{user_id}/platforms/{platform_id}")
def assign_admin_to_platform(
user_id: int = Path(...),
platform_id: int = Path(...),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_super_admin_api),
):
"""
Assign an admin to a platform.
Super admin only.
"""
assignment = admin_platform_service.assign_admin_to_platform(
db=db,
admin_user_id=user_id,
platform_id=platform_id,
assigned_by_user_id=current_admin.id,
)
db.commit()
logger.info(
f"Assigned admin {user_id} to platform {platform_id} by {current_admin.username}"
)
return {
"message": "Admin assigned to platform successfully",
"platform_id": platform_id,
"user_id": user_id,
}
@router.delete("/{user_id}/platforms/{platform_id}")
def remove_admin_from_platform(
user_id: int = Path(...),
platform_id: int = Path(...),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_super_admin_api),
):
"""
Remove an admin's access to a platform.
Super admin only.
"""
admin_platform_service.remove_admin_from_platform(
db=db,
admin_user_id=user_id,
platform_id=platform_id,
removed_by_user_id=current_admin.id,
)
db.commit()
logger.info(
f"Removed admin {user_id} from platform {platform_id} by {current_admin.username}"
)
return {
"message": "Admin removed from platform successfully",
"platform_id": platform_id,
"user_id": user_id,
}
@router.put("/{user_id}/super-admin")
def toggle_super_admin_status(
user_id: int = Path(...),
request: ToggleSuperAdminRequest = Body(...),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_super_admin_api),
):
"""
Promote or demote an admin to/from super admin.
Super admin only.
"""
user = admin_platform_service.toggle_super_admin(
db=db,
user_id=user_id,
is_super_admin=request.is_super_admin,
current_admin_id=current_admin.id,
)
db.commit()
action = "promoted to" if request.is_super_admin else "demoted from"
logger.info(f"Admin {user.username} {action} super admin by {current_admin.username}")
return {
"message": f"Admin {action} super admin successfully",
"user_id": user_id,
"is_super_admin": user.is_super_admin,
}
@router.get("/{user_id}/platforms")
def get_admin_platforms(
user_id: int = Path(...),
db: Session = Depends(get_db),
current_admin: User = Depends(get_current_super_admin),
):
"""
Get all platforms assigned to an admin.
Super admin only.
"""
platforms = admin_platform_service.get_platforms_for_admin(db, user_id)
return {
"platforms": [
{
"id": p.id,
"code": p.code,
"name": p.name,
}
for p in platforms
],
"user_id": user_id,
}

View File

@@ -14,11 +14,14 @@ import logging
from fastapi import APIRouter, Depends, Response
from sqlalchemy.orm import Session
from app.api.deps import get_current_admin_api
from app.api.deps import get_current_admin_api, get_current_admin_from_cookie_or_header
from app.core.database import get_db
from app.core.environment import should_use_secure_cookies
from app.exceptions import InvalidCredentialsException
from app.exceptions import InsufficientPermissionsException, InvalidCredentialsException
from app.services.admin_platform_service import admin_platform_service
from app.services.auth_service import auth_service
from middleware.auth import AuthManager
from models.database.platform import Platform
from models.database.user import User
from models.schema.auth import LoginResponse, LogoutResponse, UserLogin, UserResponse
@@ -123,3 +126,99 @@ def admin_logout(response: Response):
logger.debug("Deleted admin_token cookies (both /admin and / paths)")
return LogoutResponse(message="Logged out successfully")
@router.get("/accessible-platforms")
def get_accessible_platforms(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_admin_from_cookie_or_header),
):
"""
Get list of platforms this admin can access.
Returns:
- For super admins: All active platforms
- For platform admins: Only assigned platforms
"""
if current_user.is_super_admin:
platforms = db.query(Platform).filter(Platform.is_active == True).all()
else:
platforms = admin_platform_service.get_platforms_for_admin(db, current_user.id)
return {
"platforms": [
{
"id": p.id,
"code": p.code,
"name": p.name,
"logo": p.logo,
}
for p in platforms
],
"is_super_admin": current_user.is_super_admin,
"requires_platform_selection": not current_user.is_super_admin and len(platforms) > 0,
}
@router.post("/select-platform")
def select_platform(
platform_id: int,
response: Response,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_admin_from_cookie_or_header),
):
"""
Select platform context for platform admin.
Issues a new JWT token with platform context.
Super admins skip this step (they have global access).
Args:
platform_id: Platform ID to select
Returns:
LoginResponse with new token containing platform context
"""
if current_user.is_super_admin:
raise InvalidCredentialsException(
"Super admins don't need platform selection - they have global access"
)
# Verify admin has access to this platform
if not current_user.can_access_platform(platform_id):
raise InsufficientPermissionsException(
f"You don't have access to this platform"
)
# Load platform
platform = db.query(Platform).filter(Platform.id == platform_id).first()
if not platform:
raise InvalidCredentialsException("Platform not found")
# Issue new token with platform context
auth_manager = AuthManager()
token_data = auth_manager.create_access_token(
user=current_user,
platform_id=platform.id,
platform_code=platform.code,
)
# Set cookie with new token
response.set_cookie(
key="admin_token",
value=token_data["access_token"],
httponly=True,
secure=should_use_secure_cookies(),
samesite="lax",
max_age=token_data["expires_in"],
path="/admin",
)
logger.info(f"Admin {current_user.username} selected platform {platform.code}")
return LoginResponse(
access_token=token_data["access_token"],
token_type=token_data["token_type"],
expires_in=token_data["expires_in"],
user=current_user,
)

View File

@@ -0,0 +1,389 @@
# 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()