| from typing import Any, cast |
|
|
| from ..config.logfire_config import get_logger |
| from ..utils import get_supabase_client |
|
|
| logger = get_logger(__name__) |
|
|
|
|
| class AdminService: |
| @staticmethod |
| async def get_all_users(limit: int = 100, role_filter: str | None = None) -> list[dict[str, Any]]: |
| """ |
| Fetch all users from public.profiles. |
| In a real production app, we should iterate/paginate properly. |
| """ |
| try: |
| supabase = get_supabase_client() |
| query = supabase.table("profiles").select("*").order("name") |
|
|
| if role_filter and role_filter != "all": |
| query = query.eq("role", role_filter) |
|
|
| response = query.limit(limit).execute() |
| data = response.data if response.data else [] |
| return cast(list[dict[str, Any]], data) |
| except Exception as e: |
| logger.error(f"AdminService: Failed to fetch users: {e}") |
| raise |
|
|
| @staticmethod |
| async def update_user_role(user_id: str, new_role: str, current_admin_email: str) -> dict[str, Any]: |
| """ |
| Update a user's role in both public.profiles and auth.users metadata. |
| Synchronizing with auth metadata ensures the change is reflected in JWT tokens immediately. |
| """ |
| try: |
| supabase = get_supabase_client() |
|
|
| |
| logger.info(f"AdminService: Role update | target={user_id} | role={new_role} | by={current_admin_email}") |
|
|
| |
| payload = { |
| "role": new_role, |
| |
| } |
| res = supabase.table("profiles").update(payload).eq("id", user_id).execute() |
|
|
| if not res.data: |
| raise ValueError("User not found in profiles or update failed") |
|
|
| |
| |
| try: |
| |
| supabase.auth.admin.update_user_by_id(user_id, {"user_metadata": {"role": new_role}}) |
| logger.info(f"AdminService: Synced metadata role for {user_id}") |
| except Exception as auth_err: |
| |
| |
| logger.error(f"AdminService: Auth metadata sync failed for {user_id}: {auth_err}") |
|
|
| data = res.data[0] |
|
|
| |
| try: |
| audit_log = { |
| "source": "admin_action", |
| "level": "INFO", |
| "message": f"User role updated: {user_id} -> {new_role}", |
| "type": "audit", |
| "details": { |
| "target_user_id": user_id, |
| "new_role": new_role, |
| "updated_by": current_admin_email, |
| "version": "v4.6.31", |
| }, |
| } |
| supabase.table("archon_logs").insert(audit_log).execute() |
| except Exception as log_err: |
| logger.error(f"AdminService: Audit logging failed: {log_err}") |
|
|
| return cast(dict[str, Any], data) |
|
|
| except Exception as e: |
| logger.error(f"AdminService: Failed to update role: {e}") |
| raise |
|
|
| @staticmethod |
| async def get_rbac_matrix() -> list[dict[str, Any]]: |
| """Fetch the full role-permission matrix from the database.""" |
| try: |
| supabase = get_supabase_client() |
| res = supabase.table("archon_roles_permissions").select("*").order("role").execute() |
| return cast(list[dict[str, Any]], res.data or []) |
| except Exception as e: |
| logger.error(f"AdminService: Failed to fetch RBAC matrix: {e}") |
| raise |
|
|
| @staticmethod |
| async def update_rbac_role(role: str, permissions: list[str], description: str | None = None) -> dict[str, Any]: |
| """Update or create a role's permissions in the dynamic matrix.""" |
| try: |
| supabase = get_supabase_client() |
| payload: dict[str, Any] = { |
| "role": role.lower(), |
| "permissions": permissions, |
| } |
| if description is not None: |
| payload["description"] = description |
|
|
| res = supabase.table("archon_roles_permissions").upsert(payload).execute() |
|
|
| if not res.data: |
| raise ValueError(f"Failed to update role {role}") |
|
|
| |
| from .rbac_service import RBACService |
|
|
| RBACService._matrix_cache = None |
|
|
| |
| try: |
| audit_log = { |
| "source": "admin_action", |
| "level": "INFO", |
| "message": f"RBAC Matrix updated for role: {role}", |
| "type": "audit", |
| "details": {"role": role, "permissions": permissions, "version": "v4.6.31"}, |
| } |
| supabase.table("archon_logs").insert(audit_log).execute() |
| except Exception as log_err: |
| logger.error(f"AdminService: RBAC audit logging failed: {log_err}") |
|
|
| return cast(dict[str, Any], res.data[0]) |
| except Exception as e: |
| logger.error(f"AdminService: Failed to update RBAC role {role}: {e}") |
| raise |
|
|