from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import List from database import get_db from models.role import Role, Permission from models.user import User from schemas.role import ( RoleResponse, RoleCreate, RoleUpdate, PermissionResponse ) from utils.auth import require_admin_permission router = APIRouter() def _role_to_response(role: Role) -> RoleResponse: return RoleResponse( id=role.id, name=role.name, description=role.description, is_system=role.is_system, permissions=[PermissionResponse.model_validate(p) for p in role.permissions], user_count=len(role.users), created_at=role.created_at, updated_at=role.updated_at, ) @router.get("/", response_model=List[RoleResponse]) async def list_roles( db: Session = Depends(get_db), current_user: User = Depends(require_admin_permission()) ): """List all roles with their permissions and assigned-user count (admin only).""" roles = db.query(Role).order_by(Role.is_system.desc(), Role.name).all() return [_role_to_response(role) for role in roles] @router.get("/permissions", response_model=List[PermissionResponse]) async def list_permissions( db: Session = Depends(get_db), current_user: User = Depends(require_admin_permission()) ): """List the full permission catalog (admin only).""" return db.query(Permission).order_by(Permission.resource, Permission.action).all() @router.post("/", response_model=RoleResponse, status_code=status.HTTP_201_CREATED) async def create_role( role_data: RoleCreate, db: Session = Depends(get_db), current_user: User = Depends(require_admin_permission()) ): """Create a custom role (admin only).""" existing = db.query(Role).filter(Role.name == role_data.name).first() if existing: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Role '{role_data.name}' already exists" ) permissions = [] if role_data.permission_ids: permissions = db.query(Permission).filter(Permission.id.in_(role_data.permission_ids)).all() if len(permissions) != len(set(role_data.permission_ids)): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="One or more permission_ids are invalid" ) role = Role( name=role_data.name, description=role_data.description, is_system=False, permissions=permissions, ) db.add(role) db.commit() db.refresh(role) return _role_to_response(role) @router.put("/{role_id}", response_model=RoleResponse) async def update_role( role_id: int, role_update: RoleUpdate, db: Session = Depends(get_db), current_user: User = Depends(require_admin_permission()) ): """Update a role's name/description/permissions (admin only). System roles cannot be renamed or have their description changed, but their permissions ARE editable, same as custom roles. """ role = db.query(Role).filter(Role.id == role_id).first() if not role: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Role not found") if role.is_system: if role_update.name is not None and role_update.name != role.name: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="System role name cannot be changed" ) if role_update.description is not None and role_update.description != role.description: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="System role description cannot be changed" ) else: if role_update.name is not None: existing = db.query(Role).filter(Role.name == role_update.name, Role.id != role_id).first() if existing: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"Role '{role_update.name}' already exists" ) role.name = role_update.name if role_update.description is not None: role.description = role_update.description if role_update.permission_ids is not None: permissions = db.query(Permission).filter(Permission.id.in_(role_update.permission_ids)).all() if len(permissions) != len(set(role_update.permission_ids)): raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="One or more permission_ids are invalid" ) role.permissions = permissions db.commit() db.refresh(role) return _role_to_response(role) @router.delete("/{role_id}") async def delete_role( role_id: int, db: Session = Depends(get_db), current_user: User = Depends(require_admin_permission()) ): """Delete a custom role (admin only). System roles and roles with assigned users cannot be deleted.""" role = db.query(Role).filter(Role.id == role_id).first() if not role: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Role not found") if role.is_system: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="System roles cannot be deleted" ) user_count = len(role.users) if user_count > 0: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=f"{user_count} user(s) still have this role — reassign them first" ) role.permissions = [] db.delete(role) db.commit() return {"message": f"Role '{role.name}' deleted successfully"}