e80a82bef4
- 更新 http-error-codes, user-points-chat-data-protocol - 更新 divination-run-protocol, profile-protocol - 删除废弃的后端和前端设计计划文档
182 lines
4.2 KiB
Markdown
182 lines
4.2 KiB
Markdown
# Profile Protocol (Frontend <-> Backend)
|
|
|
|
This document defines the canonical backend contract for user profile read/write and avatar upload signing.
|
|
|
|
Protocol verification status:
|
|
|
|
- Backend route source: `backend/src/v1/users/router.py`
|
|
- Backend schema source: `backend/src/v1/users/schemas.py`
|
|
- Backend service source: `backend/src/v1/users/service.py`
|
|
- Frontend mapping source: `apps/lib/features/settings/data/apis/profile_api.dart`
|
|
- Storage config source: `backend/src/core/config/settings.py`
|
|
- Current status: aligned
|
|
|
|
## Compatibility strategy
|
|
|
|
- Current strategy: additive evolution (`backward-compatible`).
|
|
- Breaking change requires explicit migration + rollback notes (`requires-migration`).
|
|
|
|
## Route overview
|
|
|
|
- Get profile: `GET /api/v1/users/me/profile`
|
|
- Update profile: `PATCH /api/v1/users/me/profile`
|
|
- Update settings: `PATCH /api/v1/users/me/settings`
|
|
- Create avatar upload url: `POST /api/v1/users/me/avatar/upload-url`
|
|
- Upload avatar directly: `POST /api/v1/users/me/avatar` (multipart)
|
|
|
|
## Auth and trust boundary
|
|
|
|
- All routes require authenticated user context.
|
|
- `user_id` is derived from verified JWT `sub`; never accepted from client payload.
|
|
|
|
## Profile read contract
|
|
|
|
### `GET /api/v1/users/me/profile`
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"user_id": "uuid",
|
|
"display_name": "string",
|
|
"bio": "string|null",
|
|
"avatar_path": "avatars/{user_id}/{file}",
|
|
"avatar_url": "https://...signed-or-public...",
|
|
"settings": {
|
|
"version": 1,
|
|
"preferences": {
|
|
"interface_language": "zh-CN",
|
|
"ai_language": "zh-CN",
|
|
"timezone": "Asia/Shanghai",
|
|
"country": "CN"
|
|
},
|
|
"privacy": {},
|
|
"notification": {}
|
|
},
|
|
"updated_at": "2026-04-05T12:34:56+00:00"
|
|
}
|
|
```
|
|
|
|
Mapping note:
|
|
|
|
- `display_name` maps to `profiles.username`.
|
|
- `avatar_path` is stored in profile layer.
|
|
- `avatar_url` is render-ready URL generated from storage strategy.
|
|
|
|
## Profile update contract
|
|
|
|
### `PATCH /api/v1/users/me/profile`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"display_name": "string(1..30)",
|
|
"bio": "string(0..200)",
|
|
"avatar_path": "avatars/{user_id}/{file}"
|
|
}
|
|
```
|
|
|
|
Rules:
|
|
|
|
- At least one field must be provided.
|
|
- `display_name` must be non-empty after trim.
|
|
- `bio` can be empty string and should be normalized to `null` only if agreed by API implementation.
|
|
- `avatar_path` must stay in current user prefix: `avatars/{current_user.id}/`.
|
|
|
|
Response:
|
|
|
|
- Returns the same shape as `GET /users/me/profile`.
|
|
|
|
## Settings update contract
|
|
|
|
### `PATCH /api/v1/users/me/settings`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"settings": {
|
|
"version": 1,
|
|
"preferences": {
|
|
"interface_language": "zh-CN",
|
|
"ai_language": "zh-CN",
|
|
"timezone": "Asia/Shanghai",
|
|
"country": "CN"
|
|
},
|
|
"privacy": {},
|
|
"notification": {
|
|
"allow_notifications": true,
|
|
"allow_vibration": true
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
Rules:
|
|
|
|
- `settings` must conform to `ProfileSettingsV1`.
|
|
- Additional fields are forbidden.
|
|
|
|
Response:
|
|
|
|
- Returns the same shape as `GET /users/me/profile`.
|
|
|
|
## Avatar upload signing contract
|
|
|
|
### `POST /api/v1/users/me/avatar/upload-url`
|
|
|
|
Request:
|
|
|
|
```json
|
|
{
|
|
"mime_type": "image/png|image/jpeg|image/webp",
|
|
"file_size": 123456,
|
|
"ext": "png|jpg|jpeg|webp"
|
|
}
|
|
```
|
|
|
|
Response:
|
|
|
|
```json
|
|
{
|
|
"bucket": "avatars",
|
|
"path": "avatars/{user_id}/{uuid}.png",
|
|
"upload_url": "https://...signed...",
|
|
"expires_in": 600
|
|
}
|
|
```
|
|
|
|
Validation rules:
|
|
|
|
- `bucket` must equal `config.storage.avatar.bucket`.
|
|
- `file_size` must be `>0` and `<= config.storage.avatar.max_size_mb`.
|
|
- Only image mime types are allowed.
|
|
- Path must be server-generated and never trusted from client.
|
|
|
|
## Direct avatar upload contract
|
|
|
|
### `POST /api/v1/users/me/avatar`
|
|
|
|
Request:
|
|
|
|
- `multipart/form-data`
|
|
- field name: `file`
|
|
|
|
Validation rules:
|
|
|
|
- extension must be one of `png|jpg|jpeg|webp`
|
|
- mime must map to image type (`image/png|image/jpeg|image/webp`)
|
|
- payload size must be `<= config.storage.avatar.max_size_mb`
|
|
|
|
Behavior:
|
|
|
|
- backend writes avatar bytes to `bucket=config.storage.avatar.bucket`
|
|
- backend stores canonical path in profile
|
|
- response returns latest profile payload (`ProfileResponse`)
|
|
|
|
## Error contract linkage
|
|
|
|
- All errors must follow RFC7807 `application/problem+json`.
|
|
- `code` values must be registered in `docs/protocols/common/http-error-codes.md`.
|