"""User management routes.""" from __future__ import annotations import uuid from typing import Any from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import Response from sqlalchemy.ext.asyncio import AsyncSession from app.core.audit import log_audit from app.core.db import get_db from app.core.notifications import create_notification from app.deps import get_current_user, require_admin, get_tenant_id, get_current_user_id from app.schemas.user import UserCreate, UserUpdate from app.services.user_service import user_service router = APIRouter(prefix="/api/v1/users", tags=["users"]) @router.get("") async def list_users( page: int = Query(1, ge=1), page_size: int = Query(25, ge=1, le=100), search: str | None = Query(None), db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_admin), ): """List users (admin only, paginated).""" tenant_id = uuid.UUID(current_user["tenant_id"]) return await user_service.list_users(db, tenant_id, page, page_size, search) @router.post("", status_code=status.HTTP_201_CREATED) async def create_user( body: UserCreate, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_admin), ): """Create a new user (admin only).""" tenant_id = uuid.UUID(current_user["tenant_id"]) user_id = uuid.UUID(current_user["user_id"]) user = await user_service.create_user( db, tenant_id, body.email, body.name, body.password, body.role, body.is_active, ) # Audit log await log_audit( db, tenant_id, user_id, "create", "user", user.id, changes={"email": body.email, "name": body.name, "role": body.role}, ) # Notification await create_notification( db, tenant_id, user.id, "info", "Account created", f"Your account has been created by {current_user['name']}.", ) return { "id": str(user.id), "email": user.email, "name": user.name, "role": user.role, "is_active": user.is_active, "tenant_id": str(user.tenant_id), } @router.get("/{user_id}") async def get_user( user_id: str, db: AsyncSession = Depends(get_db), current_user: dict = Depends(get_current_user), ): """Get a single user.""" tenant_id = uuid.UUID(current_user["tenant_id"]) try: uid = uuid.UUID(user_id) except ValueError: raise HTTPException(400, detail={"detail": "Invalid user_id", "code": "invalid_id"}) user = await user_service.get_user(db, tenant_id, uid) if user is None: raise HTTPException(404, detail={"detail": "User not found", "code": "not_found"}) return { "id": str(user.id), "email": user.email, "name": user.name, "role": user.role, "is_active": user.is_active, "tenant_id": str(user.tenant_id), } @router.patch("/{user_id}") async def update_user( user_id: str, body: UserUpdate, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_admin), ): """Update a user (admin only).""" tenant_id = uuid.UUID(current_user["tenant_id"]) acting_user_id = uuid.UUID(current_user["user_id"]) try: uid = uuid.UUID(user_id) except ValueError: raise HTTPException(400, detail={"detail": "Invalid user_id", "code": "invalid_id"}) changes: dict[str, Any] = {} if body.name is not None: changes["name"] = body.name if body.role is not None: changes["role"] = body.role if body.is_active is not None: changes["is_active"] = body.is_active user = await user_service.update_user( db, tenant_id, uid, body.name, body.role, body.is_active, ) if user is None: raise HTTPException(404, detail={"detail": "User not found", "code": "not_found"}) await log_audit(db, tenant_id, acting_user_id, "update", "user", uid, changes=changes) return { "id": str(user.id), "email": user.email, "name": user.name, "role": user.role, "is_active": user.is_active, "tenant_id": str(user.tenant_id), } @router.delete("/{user_id}") async def delete_user( user_id: str, db: AsyncSession = Depends(get_db), current_user: dict = Depends(require_admin), ): """Delete a user (admin only).""" tenant_id = uuid.UUID(current_user["tenant_id"]) acting_user_id = uuid.UUID(current_user["user_id"]) try: uid = uuid.UUID(user_id) except ValueError: raise HTTPException(400, detail={"detail": "Invalid user_id", "code": "invalid_id"}) # Get user snapshot for audit before deletion user = await user_service.get_user(db, tenant_id, uid) if user is None: raise HTTPException(404, detail={"detail": "User not found", "code": "not_found"}) success = await user_service.delete_user(db, tenant_id, uid) if not success: raise HTTPException(404, detail={"detail": "User not found", "code": "not_found"}) await log_audit( db, tenant_id, acting_user_id, "delete", "user", uid, changes={"email": user.email, "name": user.name}, ) return Response(status_code=status.HTTP_204_NO_CONTENT)