feat(profile): avatar presets, upload, nickname editing
- FastAPI: preset assets 01–08, GET list/static, PUT /me/avatar/preset, safer uploaded-avatar path validation, preset_avatars + HTTP tests. - Expo: personal-info (library + presets), profile tab avatar, resolveApiMediaUrl, auth hooks cache sync, Web multipart helper, partial-save messaging + profile i18n. - Includes existing edits to conversation screen and voice use-player. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -9,7 +9,15 @@ from app.core.config import settings
|
||||
from app.core.dependencies import get_current_user
|
||||
from app.core.logging import get_logger
|
||||
from app.features.auth.deps import get_auth_service
|
||||
from app.features.auth.preset_avatars import (
|
||||
avatar_url_for_preset_filename,
|
||||
list_preset_items,
|
||||
preset_filename_for_id,
|
||||
preset_file_path,
|
||||
safe_avatar_upload_path,
|
||||
)
|
||||
from app.features.auth.schemas import (
|
||||
AvatarPresetItem,
|
||||
ChangePasswordRequest,
|
||||
ChangePhoneRequest,
|
||||
LoginRequest,
|
||||
@@ -18,6 +26,7 @@ from app.features.auth.schemas import (
|
||||
RegisterRequest,
|
||||
ResetPasswordRequest,
|
||||
SendSmsRequest,
|
||||
SetAvatarPresetRequest,
|
||||
SmsLoginRequest,
|
||||
SmsRegisterRequest,
|
||||
TokenResponse,
|
||||
@@ -329,14 +338,72 @@ async def upload_avatar(
|
||||
) from e
|
||||
|
||||
|
||||
@router.get(
|
||||
"/avatar-presets",
|
||||
response_model=list[AvatarPresetItem],
|
||||
summary="预设头像列表",
|
||||
)
|
||||
async def list_avatar_presets():
|
||||
return [
|
||||
AvatarPresetItem(id=item_id, url=item_url)
|
||||
for item_id, item_url in list_preset_items()
|
||||
]
|
||||
|
||||
|
||||
@router.put(
|
||||
"/me/avatar/preset",
|
||||
response_model=UserResponse,
|
||||
summary="使用预设头像",
|
||||
responses={400: {"description": "无效的预设编号"}},
|
||||
)
|
||||
async def set_avatar_preset(
|
||||
request: SetAvatarPresetRequest,
|
||||
current_user: User = Depends(get_current_user),
|
||||
service: AuthService = Depends(get_auth_service),
|
||||
):
|
||||
filename = preset_filename_for_id(request.preset_id)
|
||||
if filename is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="无效的预设头像编号",
|
||||
)
|
||||
path = preset_file_path(filename)
|
||||
if path is None or not path.exists():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail="预设头像不可用",
|
||||
)
|
||||
avatar_url = avatar_url_for_preset_filename(filename)
|
||||
try:
|
||||
user = await service.update_avatar_url(current_user.id, avatar_url)
|
||||
except AuthError as e:
|
||||
raise _map_auth_error(e)
|
||||
return _user_response(user)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/avatar-presets/{filename}",
|
||||
summary="获取预设头像图片",
|
||||
responses={404: {"description": "预设不存在"}},
|
||||
)
|
||||
async def get_avatar_preset(filename: str):
|
||||
path = preset_file_path(filename)
|
||||
if path is None or not path.exists():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="预设头像不存在",
|
||||
)
|
||||
return FileResponse(path, media_type="image/png")
|
||||
|
||||
|
||||
@router.get(
|
||||
"/avatars/{filename}",
|
||||
summary="获取头像图片",
|
||||
responses={404: {"description": "头像不存在"}},
|
||||
)
|
||||
async def get_avatar(filename: str):
|
||||
file_path = AVATAR_DIR / filename
|
||||
if not file_path.exists():
|
||||
file_path = safe_avatar_upload_path(filename, AVATAR_DIR)
|
||||
if file_path is None or not file_path.exists():
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="头像不存在",
|
||||
|
||||
Reference in New Issue
Block a user