"""Role management routes.""" from __future__ import annotations import uuid from fastapi import APIRouter, Depends, HTTPException, Response, status from fastapi.responses import JSONResponse from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.db import get_db from app.deps import get_current_user, require_admin from app.models.plugin import Plugin as PluginModel from app.plugins.registry import get_registry from app.schemas.role import RoleCreate, RoleUpdate from app.services.role_service import role_service router = APIRouter(prefix="/api/v1/roles", tags=["roles"]) SYSTEM_PERMISSIONS: list[dict[str, str]] = [ {"key": "companies:read", "label": "Companies: Read", "category": "system"}, {"key": "companies:write", "label": "Companies: Write", "category": "system"}, {"key": "contacts:read", "label": "Contacts: Read", "category": "system"}, {"key": "contacts:write", "label": "Contacts: Write", "category": "system"}, {"key": "users:read", "label": "Users: Read", "category": "system"}, {"key": "users:write", "label": "Users: Write", "category": "system"}, {"key": "audit:read", "label": "Audit Log: Read", "category": "system"}, {"key": "settings:write", "label": "Settings: Write", "category": "system"}, {"key": "plugins:install", "label": "Plugins: Install", "category": "system"}, {"key": "plugins:configure", "label": "Plugins: Configure", "category": "system"}, {"key": "roles:read", "label": "Roles: Read", "category": "system"}, {"key": "roles:write", "label": "Roles: Write", "category": "system"}, ] @router.get("/permissions") async def list_permissions( db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_admin), ): """List all available permissions (system + active plugin permissions). Returns permissions grouped by category: - ``system``: built-in CRM permissions - ``plugins``: permissions from active plugin manifests """ registry = get_registry() plugin_perms: list[dict[str, str]] = [] # Fetch active plugin records from DB result = await db.execute( select(PluginModel).where(PluginModel.active == True) # noqa: E712 ) active_records = {row.name: row for row in result.scalars().all()} # Collect permissions from active plugins' manifests seen_keys: set[str] = set() for name, plugin in registry._plugins.items(): if name not in active_records: continue for perm in plugin.manifest.permissions: if perm in seen_keys: continue seen_keys.add(perm) plugin_perms.append({ "key": perm, "label": perm.replace("_", " ").replace(":", ": ").title(), "category": "plugins", "plugin_name": name, }) return { "system": SYSTEM_PERMISSIONS, "plugins": plugin_perms, "all": SYSTEM_PERMISSIONS + plugin_perms, } @router.get("") async def list_roles( db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ): """List roles for the current tenant.""" tenant_id = uuid.UUID(current_user["tenant_id"]) roles = await role_service.list_roles(db, tenant_id) return {"items": roles} @router.post("") async def create_role( body: RoleCreate, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_admin), ): """Create a custom role (admin only).""" tenant_id = uuid.UUID(current_user["tenant_id"]) role = await role_service.create_role( db, tenant_id, body.name, body.permissions, body.field_permissions, ) return JSONResponse( status_code=status.HTTP_201_CREATED, content={ "id": str(role.id), "name": role.name, "permissions": role.permissions, "field_permissions": role.field_permissions, }, ) @router.patch("/{role_id}") async def update_role( role_id: str, body: RoleUpdate, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_admin), ): """Update a role (admin only).""" tenant_id = uuid.UUID(current_user["tenant_id"]) try: rid = uuid.UUID(role_id) except ValueError: raise HTTPException( 400, detail={"detail": "Invalid role_id", "code": "invalid_id"} ) from None role = await role_service.update_role( db, tenant_id, rid, body.name, body.permissions, body.field_permissions, ) if role is None: raise HTTPException(404, detail={"detail": "Role not found", "code": "not_found"}) return { "id": str(role.id), "name": role.name, "permissions": role.permissions, "field_permissions": role.field_permissions, } @router.delete("/{role_id}") async def delete_role( role_id: str, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_admin), ): """Delete a role (admin only).""" tenant_id = uuid.UUID(current_user["tenant_id"]) try: rid = uuid.UUID(role_id) except ValueError: raise HTTPException( 400, detail={"detail": "Invalid role_id", "code": "invalid_id"} ) from None success = await role_service.delete_role(db, tenant_id, rid) if not success: raise HTTPException(404, detail={"detail": "Role not found", "code": "not_found"}) return Response(status_code=status.HTTP_204_NO_CONTENT)