"""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.audit import log_audit from app.core.auth import get_redis from app.core.db import get_db from app.core.permission_registry import get_permission_registry from app.core.permissions import invalidate_all_user_permissions from app.deps import require_permission 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": "companies:delete", "label": "Companies: Delete", "category": "system"}, {"key": "contacts:read", "label": "Contacts: Read", "category": "system"}, {"key": "contacts:write", "label": "Contacts: Write", "category": "system"}, {"key": "contacts:delete", "label": "Contacts: Delete", "category": "system"}, {"key": "users:read", "label": "Users: Read", "category": "system"}, {"key": "users:write", "label": "Users: Write", "category": "system"}, {"key": "users:delete", "label": "Users: Delete", "category": "system"}, {"key": "roles:read", "label": "Roles: Read", "category": "system"}, {"key": "roles:write", "label": "Roles: Write", "category": "system"}, {"key": "roles:delete", "label": "Roles: Delete", "category": "system"}, {"key": "groups:read", "label": "Groups: Read", "category": "system"}, {"key": "groups:write", "label": "Groups: Write", "category": "system"}, {"key": "groups:delete", "label": "Groups: Delete", "category": "system"}, {"key": "audit:read", "label": "Audit Log: Read", "category": "system"}, {"key": "settings:read", "label": "Settings: Read", "category": "system"}, {"key": "settings:write", "label": "Settings: Write", "category": "system"}, {"key": "plugins:read", "label": "Plugins: Read", "category": "system"}, {"key": "plugins:install", "label": "Plugins: Install", "category": "system"}, {"key": "plugins:configure", "label": "Plugins: Configure", "category": "system"}, {"key": "tenants:read", "label": "Tenants: Read", "category": "system"}, {"key": "tenants:write", "label": "Tenants: Write", "category": "system"}, {"key": "tenants:delete", "label": "Tenants: Delete", "category": "system"}, {"key": "notifications:read", "label": "Notifications: Read", "category": "system"}, {"key": "notifications:write", "label": "Notifications: Write", "category": "system"}, {"key": "attachments:read", "label": "Attachments: Read", "category": "system"}, {"key": "attachments:write", "label": "Attachments: Write", "category": "system"}, {"key": "attachments:delete", "label": "Attachments: Delete", "category": "system"}, {"key": "workflows:read", "label": "Workflows: Read", "category": "system"}, {"key": "workflows:write", "label": "Workflows: Write", "category": "system"}, {"key": "sequences:read", "label": "Sequences: Read", "category": "system"}, {"key": "sequences:write", "label": "Sequences: Write", "category": "system"}, {"key": "addresses:read", "label": "Addresses: Read", "category": "system"}, {"key": "addresses:write", "label": "Addresses: Write", "category": "system"}, {"key": "addresses:delete", "label": "Addresses: Delete", "category": "system"}, {"key": "taxes:read", "label": "Taxes: Read", "category": "system"}, {"key": "taxes:write", "label": "Taxes: Write", "category": "system"}, {"key": "currencies:read", "label": "Currencies: Read", "category": "system"}, {"key": "currencies:write", "label": "Currencies: Write", "category": "system"}, {"key": "import_export:read", "label": "Import/Export: Read", "category": "system"}, {"key": "import_export:write", "label": "Import/Export: Write", "category": "system"}, ] @router.get("/permissions") async def list_permissions( db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("roles:read")), ): """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, "field_definitions": get_permission_registry().get_all_field_definitions(), } @router.get("") async def list_roles( db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_permission("roles:read")), ): """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_permission("roles:write")), ): """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_permission("roles:write")), ): """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"}) # Invalidate permission cache for all users in this tenant redis = get_redis() await invalidate_all_user_permissions(redis, tenant_id) # Audit log acting_user_id = uuid.UUID(current_user["user_id"]) await log_audit( db, tenant_id, acting_user_id, "update", "role", rid, changes=body.model_dump(exclude_none=True), ) 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_permission("roles:write")), ): """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"}) # Invalidate permission cache for all users in this tenant redis = get_redis() await invalidate_all_user_permissions(redis, tenant_id) acting_user_id = uuid.UUID(current_user["user_id"]) await log_audit(db, tenant_id, acting_user_id, "delete", "role", rid) return Response(status_code=status.HTTP_204_NO_CONTENT)