Files
agent_delta/backend/app/api/routes/v1/users.py
T
2026-06-15 14:22:07 +08:00

134 lines
3.6 KiB
Python

# ruff: noqa: I001 - Imports structured for Jinja2 template conditionals
"""User management routes."""
from typing import Any
from uuid import UUID
from fastapi import APIRouter, File, HTTPException, UploadFile, status
from fastapi.responses import FileResponse
from fastapi_pagination import Page
from app.api.deps import (
CurrentAdmin,
CurrentUser,
UserSvc,
)
from app.db.models.user import UserRole
from app.schemas.user import UserRead, UserUpdate
from app.services.file_storage import get_file_storage
router = APIRouter()
@router.get("/me", response_model=UserRead)
async def read_current_user(
current_user: CurrentUser,
) -> Any:
"""Get current user.
Returns the authenticated user's profile including their role.
"""
return current_user
@router.patch("/me", response_model=UserRead)
async def update_current_user(
user_in: UserUpdate,
current_user: CurrentUser,
user_service: UserSvc,
) -> Any:
"""Update current user.
Users can update their own profile (email, full_name).
Role changes require admin privileges.
"""
# Prevent non-admin users from changing their own role
if user_in.role is not None and not current_user.has_role(UserRole.ADMIN):
user_in.role = None
user = await user_service.update(current_user.id, user_in)
return user
@router.post("/me/avatar", response_model=UserRead)
async def upload_avatar(
user_service: UserSvc,
current_user: CurrentUser,
file: UploadFile = File(...),
) -> Any:
"""Upload or replace avatar image for the current user."""
data = await file.read()
try:
user = await user_service.update_avatar(
current_user.id, data, file.filename or "avatar.jpg", file.content_type or ""
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e)) from None
return user
@router.get("/avatar/{user_id}")
async def get_avatar(user_id: UUID, user_service: UserSvc) -> Any:
"""Get user avatar image."""
user = await user_service.get_by_id(user_id)
if not user.avatar_url:
raise HTTPException(status_code=404, detail="No avatar set")
storage = get_file_storage()
file_path = storage.get_full_path(user.avatar_url)
if not file_path:
raise HTTPException(status_code=404, detail="Avatar file not found")
return FileResponse(path=file_path, media_type="image/jpeg")
@router.get("", response_model=Page[UserRead])
async def read_users(
user_service: UserSvc,
_: CurrentAdmin,
) -> Any:
"""Get all users (admin only)."""
return await user_service.list_paginated()
@router.get("/{user_id}", response_model=UserRead)
async def read_user(
user_id: UUID,
user_service: UserSvc,
_: CurrentAdmin,
) -> Any:
"""Get user by ID (admin only).
Raises NotFoundError if user does not exist.
"""
user = await user_service.get_by_id(user_id)
return user
@router.patch("/{user_id}", response_model=UserRead)
async def update_user_by_id(
user_id: UUID,
user_in: UserUpdate,
user_service: UserSvc,
_: CurrentAdmin,
) -> Any:
"""Update user by ID (admin only).
Admins can update any user including their role.
Raises NotFoundError if user does not exist.
"""
user = await user_service.update(user_id, user_in)
return user
@router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT, response_model=None)
async def delete_user_by_id(
user_id: UUID,
user_service: UserSvc,
_: CurrentAdmin,
) -> None:
"""Delete user by ID (admin only).
Raises NotFoundError if user does not exist.
"""
await user_service.delete(user_id)