transcription/backend/auth/routes.py

150 lines
4.9 KiB
Python
Raw Normal View History

"""Auth HTTP routes."""
from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from backend.auth.deps import get_current_user, require_admin
from backend.auth.models import ROLES, UserContext
from backend.auth import database as db
from backend.auth.service import (
authenticate,
create_org_user,
create_personal_project,
delete_personal_project,
get_user_context,
list_accessible_projects,
normalize_project_slug,
update_user_projects,
user_to_dict,
)
from src.config import load_config
router = APIRouter(prefix="/api/auth", tags=["auth"])
admin_router = APIRouter(prefix="/api/admin", tags=["admin"])
class LoginRequest(BaseModel):
org_slug: str = Field(default="merakom")
username: str
password: str
class CreateUserRequest(BaseModel):
username: str
password: str
role: str = "user"
projects: List[str] = Field(default_factory=list)
class CreateProjectRequest(BaseModel):
slug: str
name: str
class UpdateUserProjectsRequest(BaseModel):
projects: List[str] = Field(default_factory=list)
@router.post("/login")
async def login(payload: LoginRequest):
result = authenticate(payload.org_slug, payload.username, payload.password)
if not result:
raise HTTPException(status_code=401, detail="Неверная организация, логин или пароль")
return result
@router.get("/me")
async def me(user: UserContext = Depends(get_current_user)):
return user_to_dict(user)
@router.get("/projects")
async def my_projects(user: UserContext = Depends(get_current_user)):
return {"projects": list_accessible_projects(user)}
@router.post("/projects")
async def create_my_project(payload: CreateProjectRequest, user: UserContext = Depends(get_current_user)):
try:
project = create_personal_project(user, payload.slug, payload.name)
return {"project": project}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
except Exception as e:
if "UNIQUE" in str(e):
raise HTTPException(status_code=409, detail="Проект уже существует") from e
raise HTTPException(status_code=400, detail=str(e)) from e
@router.delete("/projects/{slug}")
async def delete_my_project(slug: str, user: UserContext = Depends(get_current_user)):
try:
delete_personal_project(user, slug)
return {"deleted": slug}
except PermissionError as e:
raise HTTPException(status_code=403, detail=str(e)) from e
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e)) from e
@admin_router.get("/users")
async def admin_list_users(admin: UserContext = Depends(require_admin)):
config = load_config()
rows = db.list_users(admin.org_id, config)
users = []
for row in rows:
ctx = get_user_context(row["id"])
if ctx:
users.append(user_to_dict(ctx))
return {"users": users}
@admin_router.post("/users")
async def admin_create_user(payload: CreateUserRequest, admin: UserContext = Depends(require_admin)):
if payload.role not in ROLES:
raise HTTPException(status_code=400, detail=f"role must be one of: {', '.join(ROLES)}")
try:
user = create_org_user(admin, payload.username, payload.password, payload.role, payload.projects)
return {"user": user}
except Exception as e:
if "UNIQUE" in str(e):
raise HTTPException(status_code=409, detail="Пользователь уже существует") from e
raise HTTPException(status_code=400, detail=str(e)) from e
@admin_router.put("/users/{user_id}/projects")
async def admin_set_user_projects(
user_id: int,
payload: UpdateUserProjectsRequest,
admin: UserContext = Depends(require_admin),
):
try:
user = update_user_projects(admin, user_id, payload.projects)
return {"user": user}
except ValueError as e:
raise HTTPException(status_code=404, detail=str(e)) from e
@admin_router.get("/projects")
async def admin_list_projects(admin: UserContext = Depends(require_admin)):
config = load_config()
return {"projects": db.list_projects(admin.org_id, config)}
@admin_router.post("/projects")
async def admin_create_project(payload: CreateProjectRequest, admin: UserContext = Depends(require_admin)):
try:
slug = normalize_project_slug(payload.slug)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
display_name = payload.name.strip() or slug
try:
project = db.create_project(admin.org_id, slug, display_name, owner_user_id=None, config=load_config())
return {"project": project}
except Exception as e:
if "UNIQUE" in str(e):
raise HTTPException(status_code=409, detail="Проект уже существует") from e
raise HTTPException(status_code=400, detail=str(e)) from e