Files
shaguabijia-app-server/app/api/v1/user.py
T
陈世睿 49379fd045 feat(user): 用户资料与反馈接口(昵称/头像/注销 + 帮助反馈)
- /api/v1/user: PATCH 改昵称、POST 上传头像、DELETE 注销账号(软删除+匿名化)
- /api/v1/feedback: 提交反馈(内容/联系方式/截图入库)
- 新增 media 模块(图片落盘+魔数校验)+ /media 静态服务
- feedback 表 + Alembic 迁移(部署需 alembic upgrade head)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-28 22:27:36 +08:00

60 lines
2.2 KiB
Python

"""用户资料 endpoint。
路由前缀 `/api/v1/user`,全部需 Bearer 鉴权(用户级数据,不同于 MVP 透传接口):
PATCH /profile 修改昵称
POST /avatar 上传头像(multipart,字段名 file)
DELETE / 注销账号(软删除 + 匿名化)
昵称/头像后端持久化到 user 表,登录后经 /auth/me 与登录响应回传客户端——这才是
"改完重登仍生效"的唯一数据源(此前客户端只存本地、退登即丢)。
"""
from __future__ import annotations
import logging
from fastapi import APIRouter, File, HTTPException, UploadFile
from app.api.deps import CurrentUser, DbSession
from app.core import media
from app.repositories import user as user_repo
from app.schemas.auth import UserOut
from app.schemas.user import OkResponse, ProfileUpdateRequest
logger = logging.getLogger("shagua.user")
router = APIRouter(prefix="/api/v1/user", tags=["user"])
@router.patch("/profile", response_model=UserOut, summary="修改昵称")
def update_profile(req: ProfileUpdateRequest, user: CurrentUser, db: DbSession) -> UserOut:
updated = user_repo.update_nickname(db, user, nickname=req.nickname)
logger.info("update nickname user_id=%d", user.id)
return UserOut.model_validate(updated)
@router.post("/avatar", response_model=UserOut, summary="上传头像")
async def upload_avatar(
user: CurrentUser,
db: DbSession,
file: UploadFile = File(..., description="头像图片(jpeg/png/webp,≤5MB)"),
) -> UserOut:
data = await file.read()
try:
url = media.save_avatar(user.id, data)
except media.MediaError as e:
raise HTTPException(status_code=400, detail=str(e)) from e
old_url = user.avatar_url
updated = user_repo.set_avatar_url(db, user, avatar_url=url)
media.delete_avatar(old_url) # 落库成功后再删旧文件,避免删了新的没存上
logger.info("update avatar user_id=%d url=%s", user.id, url)
return UserOut.model_validate(updated)
@router.delete("", response_model=OkResponse, summary="注销账号(软删除)")
def delete_account(user: CurrentUser, db: DbSession) -> OkResponse:
media.delete_avatar(user.avatar_url)
user_repo.soft_delete_account(db, user)
logger.info("delete account user_id=%d", user.id)
return OkResponse()