107 lines
3.4 KiB
Python
107 lines
3.4 KiB
Python
|
|
"""Users router: admin-only user management (list, create, update, deactivate)."""
|
||
|
|
|
||
|
|
import uuid
|
||
|
|
|
||
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
from app.database import get_db
|
||
|
|
from app.dependencies import get_current_user, get_pagination, require_role
|
||
|
|
from app.models.user import User
|
||
|
|
from app.schemas.user import (
|
||
|
|
UserCreate,
|
||
|
|
UserListResponse,
|
||
|
|
UserResponse,
|
||
|
|
UserUpdate,
|
||
|
|
)
|
||
|
|
from app.services.auth_service import (
|
||
|
|
create_user as svc_create_user,
|
||
|
|
deactivate_user as svc_deactivate_user,
|
||
|
|
get_user_by_id,
|
||
|
|
list_users as svc_list_users,
|
||
|
|
update_user as svc_update_user,
|
||
|
|
)
|
||
|
|
|
||
|
|
router = APIRouter(prefix="/users", tags=["users"])
|
||
|
|
|
||
|
|
|
||
|
|
@router.get("/", response_model=UserListResponse, status_code=status.HTTP_200_OK)
|
||
|
|
async def list_users(
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
pagination: dict = Depends(get_pagination),
|
||
|
|
admin: User = Depends(require_role(["admin"])),
|
||
|
|
):
|
||
|
|
"""List all users with pagination. Admin only."""
|
||
|
|
users, total = await svc_list_users(
|
||
|
|
db, page=pagination["page"], page_size=pagination["page_size"]
|
||
|
|
)
|
||
|
|
return UserListResponse(
|
||
|
|
items=[UserResponse.model_validate(u) for u in users],
|
||
|
|
total=total,
|
||
|
|
page=pagination["page"],
|
||
|
|
page_size=pagination["page_size"],
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
@router.post("/", response_model=UserResponse, status_code=status.HTTP_201_CREATED)
|
||
|
|
async def create_user(
|
||
|
|
body: UserCreate,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
admin: User = Depends(require_role(["admin"])),
|
||
|
|
):
|
||
|
|
"""Create a new user. Admin only."""
|
||
|
|
try:
|
||
|
|
user = await svc_create_user(
|
||
|
|
db,
|
||
|
|
email=body.email,
|
||
|
|
password=body.password,
|
||
|
|
full_name=body.full_name,
|
||
|
|
role=body.role,
|
||
|
|
language=body.language,
|
||
|
|
)
|
||
|
|
except ValueError as exc:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_409_CONFLICT,
|
||
|
|
detail={"error": {"code": "DUPLICATE_EMAIL", "message": str(exc)}},
|
||
|
|
)
|
||
|
|
return UserResponse.model_validate(user)
|
||
|
|
|
||
|
|
|
||
|
|
@router.put("/{user_id}", response_model=UserResponse, status_code=status.HTTP_200_OK)
|
||
|
|
async def update_user(
|
||
|
|
user_id: uuid.UUID,
|
||
|
|
body: UserUpdate,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
admin: User = Depends(require_role(["admin"])),
|
||
|
|
):
|
||
|
|
"""Update a user's fields. Admin only."""
|
||
|
|
updates = body.model_dump(exclude_unset=True)
|
||
|
|
if not updates:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||
|
|
detail={"error": {"code": "NO_FIELDS", "message": "No fields to update"}},
|
||
|
|
)
|
||
|
|
user = await svc_update_user(db, user_id, **updates)
|
||
|
|
if user is None:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
||
|
|
detail={"error": {"code": "USER_NOT_FOUND", "message": "User not found"}},
|
||
|
|
)
|
||
|
|
return UserResponse.model_validate(user)
|
||
|
|
|
||
|
|
|
||
|
|
@router.delete("/{user_id}", response_model=UserResponse, status_code=status.HTTP_200_OK)
|
||
|
|
async def delete_user(
|
||
|
|
user_id: uuid.UUID,
|
||
|
|
db: AsyncSession = Depends(get_db),
|
||
|
|
admin: User = Depends(require_role(["admin"])),
|
||
|
|
):
|
||
|
|
"""Deactivate a user (soft delete). Admin only."""
|
||
|
|
user = await svc_deactivate_user(db, user_id)
|
||
|
|
if user is None:
|
||
|
|
raise HTTPException(
|
||
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
||
|
|
detail={"error": {"code": "USER_NOT_FOUND", "message": "User not found"}},
|
||
|
|
)
|
||
|
|
return UserResponse.model_validate(user)
|