- FastAPI backend: admin bootstrap with forced credential change, admin-created users with one-time generated passwords, roles as permission sets - Games and versions with content-addressed blob storage; forks copy only the manifest, so mods share assets with the original - Worker: archive/RPA extraction, unrpyc for .rpyc, own .rpy parser, Python subset translated via ast, media converted to WebP/Opus/WebM - React panel (library search, game page, upload, users, roles) and a framework-free web player with saves, history, skip/auto, transitions - docker compose: postgres, backend, worker, nginx on a single HTTP port Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
71 lines
2.9 KiB
Python
71 lines
2.9 KiB
Python
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()
|