from typing import Annotated from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from app.deps import DB, CurrentUser, require from app.models import Role, User from app.permissions import PERMISSIONS from app.schemas import RoleIn, RoleOut router = APIRouter(prefix="/api/roles", tags=["roles"]) RoleAdmin = Annotated[User, Depends(require("roles.manage"))] def _check_perms(perms: list[str]) -> list[str]: unknown = [p for p in perms if p not in PERMISSIONS] if unknown: raise HTTPException(status.HTTP_400_BAD_REQUEST, f"Неизвестные права: {', '.join(unknown)}") return sorted(set(perms)) @router.get("/permissions") async def list_permissions(_: CurrentUser) -> dict[str, str]: return PERMISSIONS @router.get("", response_model=list[RoleOut]) async def list_roles(user: CurrentUser, db: DB) -> list[Role]: # role list is needed by the user admin page too if not {"roles.manage", "users.manage"} & set(user.permissions): raise HTTPException(status.HTTP_403_FORBIDDEN, "Нет прав") return list((await db.scalars(select(Role).order_by(Role.id))).all()) @router.post("", response_model=RoleOut, status_code=201) async def create_role(body: RoleIn, _: RoleAdmin, db: DB) -> Role: if await db.scalar(select(Role.id).where(Role.name == body.name)): raise HTTPException(status.HTTP_409_CONFLICT, "Роль с таким именем уже есть") role = Role(name=body.name, permissions=_check_perms(body.permissions)) db.add(role) await db.commit() return role @router.put("/{role_id}", response_model=RoleOut) async def update_role(role_id: int, body: RoleIn, _: RoleAdmin, db: DB) -> Role: role = await db.get(Role, role_id) if not role: raise HTTPException(status.HTTP_404_NOT_FOUND, "Роль не найдена") if role.name == "admin": raise HTTPException(status.HTTP_400_BAD_REQUEST, "Роль admin нельзя изменить") if role.is_system and body.name != role.name: raise HTTPException(status.HTTP_400_BAD_REQUEST, "Системную роль нельзя переименовать") role.name = body.name role.permissions = _check_perms(body.permissions) await db.commit() return role @router.delete("/{role_id}", status_code=204) async def delete_role(role_id: int, _: RoleAdmin, db: DB) -> None: role = await db.get(Role, role_id) if not role: raise HTTPException(status.HTTP_404_NOT_FOUND, "Роль не найдена") if role.is_system: raise HTTPException(status.HTTP_400_BAD_REQUEST, "Системную роль нельзя удалить") if await db.scalar(select(User.id).where(User.role_id == role_id).limit(1)): raise HTTPException(status.HTTP_400_BAD_REQUEST, "Роль назначена пользователям") await db.delete(role) await db.commit()