from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile from app.core.dependencies import get_current_user from app.core.media_signing import build_signed_media_url from app.core.redis import check_rate_limit from app.modules.media.service import AvatarValidationError from app.modules.users.schemas import PasswordChangeIn, UserPatchIn from app.modules.users.service import change_password, get_me, update_me, upload_avatar router = APIRouter() def _profile_response(data: dict) -> dict: return { "user": { "id": data["user"].id, "email": data["user"].email, "role": data["user"].role, "status": data["user"].status, }, "profile": { "display_name": data["profile"].display_name, "avatar_url": build_signed_media_url(data["profile"].avatar_url), }, } @router.get("/me") async def me_route(current_user=Depends(get_current_user)): return _profile_response(get_me(current_user)) @router.patch("/me") async def patch_me_route(payload: UserPatchIn, current_user=Depends(get_current_user)): return _profile_response(update_me(current_user, payload.display_name)) @router.post("/me/password") async def change_password_route(payload: PasswordChangeIn, current_user=Depends(get_current_user)): try: change_password(current_user, payload.current_password, payload.new_password) except ValueError: raise HTTPException(status_code=400, detail="INVALID_CURRENT_PASSWORD") return {"message": "password_changed"} @router.post("/me/avatar") async def upload_avatar_route( request: Request, file: UploadFile = File(...), current_user=Depends(get_current_user), ): check_rate_limit(f"avatar:{current_user.id}", limit=10, window_seconds=3600) content = await file.read() try: data = upload_avatar(current_user, content) except AvatarValidationError as exc: raise HTTPException(status_code=400, detail=str(exc)) return _profile_response(data)