from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.core.db import get_db from app.core.deps import require_roles from app.core.errors import api_error from app.core.security import hash_password from app.models.models import User from app.schemas.user import UserCreate, UserOut, UserUpdate from app.services.audit import write_audit_log router = APIRouter() @router.get("", response_model=list[UserOut]) async def list_users(admin: User = Depends(require_roles("admin")), db: AsyncSession = Depends(get_db)) -> list[UserOut]: users = (await db.scalars(select(User).order_by(User.id.asc()))).all() _ = admin return [UserOut.model_validate(user) for user in users] @router.post("", response_model=UserOut, status_code=status.HTTP_201_CREATED) async def create_user(payload: UserCreate, admin: User = Depends(require_roles("admin")), db: AsyncSession = Depends(get_db)) -> UserOut: exists = await db.scalar(select(User).where(User.email == payload.email)) if exists: raise HTTPException(status_code=409, detail=api_error("email_exists", "Email already exists")) user = User( email=payload.email, first_name=payload.first_name, last_name=payload.last_name, password_hash=hash_password(payload.password), role=payload.role, ) db.add(user) await db.commit() await db.refresh(user) await write_audit_log(db, "admin.user.create", admin.id, {"created_user_id": user.id}) return UserOut.model_validate(user) @router.put("/{user_id}", response_model=UserOut) async def update_user( user_id: int, payload: UserUpdate, admin: User = Depends(require_roles("admin")), db: AsyncSession = Depends(get_db), ) -> UserOut: user = await db.scalar(select(User).where(User.id == user_id)) if not user: raise HTTPException(status_code=404, detail=api_error("user_not_found", "User not found")) update_data = payload.model_dump(exclude_unset=True) next_email = update_data.get("email") if next_email and next_email != user.email: existing = await db.scalar(select(User).where(User.email == next_email)) if existing and existing.id != user.id: raise HTTPException(status_code=409, detail=api_error("email_exists", "Email already exists")) if "password" in update_data: raw_password = update_data.pop("password") if raw_password: user.password_hash = hash_password(raw_password) for key, value in update_data.items(): setattr(user, key, value) await db.commit() await db.refresh(user) await write_audit_log(db, "admin.user.update", admin.id, {"updated_user_id": user.id}) return UserOut.model_validate(user) @router.delete("/{user_id}") async def delete_user(user_id: int, admin: User = Depends(require_roles("admin")), db: AsyncSession = Depends(get_db)) -> dict: if user_id == admin.id: raise HTTPException(status_code=400, detail=api_error("cannot_delete_self", "Cannot delete yourself")) user = await db.scalar(select(User).where(User.id == user_id)) if not user: raise HTTPException(status_code=404, detail=api_error("user_not_found", "User not found")) await db.delete(user) await db.commit() await write_audit_log(db, "admin.user.delete", admin.id, {"deleted_user_id": user_id}) return {"status": "deleted"}