feat: 实现用户画像、占卜历史与后端用户管理模块
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
# Profile Protocol (Frontend <-> Backend)
|
||||
|
||||
This document defines the canonical backend contract for user profile read/write and avatar upload signing.
|
||||
|
||||
Protocol verification status:
|
||||
|
||||
- Backend model source: `backend/src/models/profile.py`
|
||||
- Storage config source: `backend/src/core/config/settings.py`
|
||||
- Current status: planned
|
||||
|
||||
## Compatibility strategy
|
||||
|
||||
- Current strategy: breaking changes allowed during implementation phase (no production compatibility burden).
|
||||
- Once production compatibility is required, switch to additive-only evolution.
|
||||
|
||||
## Route overview
|
||||
|
||||
- Get profile: `GET /api/v1/users/me/profile`
|
||||
- Update profile: `PATCH /api/v1/users/me/profile`
|
||||
- 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`.
|
||||
|
||||
## 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`.
|
||||
Reference in New Issue
Block a user