feat: 实现用户画像、占卜历史与后端用户管理模块
This commit is contained in:
@@ -25,6 +25,23 @@ This document is the source of truth for backend RFC7807 `code` values consumed
|
||||
|---|---:|---|---|
|
||||
| `AGENT_SESSION_RUN_LIMIT_EXCEEDED` | 409 | Session already reached max run count (start + 3 follow-ups) | Show run-limit message and require starting a new session |
|
||||
| `AGENT_DIVINATION_PAYLOAD_REQUIRED` | 422 | Missing required `forwardedProps.divinationPayload` in run request | Prompt user to restart casting flow and resubmit |
|
||||
| `AGENT_OUTPUT_DIVINATION_INVALID` | 422 | Worker output contains invalid `divination_derived` payload shape | Show generic history parse error and suggest retrying latest run |
|
||||
|
||||
## Profile
|
||||
|
||||
| code | status | meaning | frontend handling |
|
||||
|---|---:|---|---|
|
||||
| `PROFILE_PAYLOAD_INVALID` | 422 | Profile update payload invalid (length/type/empty constraints) | Highlight invalid fields and block submit |
|
||||
| `PROFILE_NOT_FOUND` | 404 | User profile row missing | Show retry and optionally trigger profile bootstrap |
|
||||
|
||||
## Avatar
|
||||
|
||||
| code | status | meaning | frontend handling |
|
||||
|---|---:|---|---|
|
||||
| `AVATAR_FILE_INVALID` | 422 | Avatar mime type or size is invalid | Show file validation hint and ask user to pick another image |
|
||||
| `AVATAR_PATH_SCOPE_INVALID` | 422 | Avatar path does not belong to current user scope | Show generic security error and force refresh |
|
||||
| `AVATAR_SIGNED_URL_FAILED` | 502 | Backend failed to generate avatar signed upload URL | Show retry toast and keep previous avatar |
|
||||
| `AVATAR_UPLOAD_FAILED` | 502 | Backend failed to upload avatar bytes to storage | Show retry toast and keep previous avatar |
|
||||
|
||||
Compatibility strategy:
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ Protocol verification status:
|
||||
|
||||
- Submit run: `POST /api/v1/agent/runs`
|
||||
- Stream events: `GET /api/v1/agent/runs/{threadId}/events?runId=...`
|
||||
- History snapshot: `GET /api/v1/agent/history`
|
||||
|
||||
## Run request contract
|
||||
|
||||
@@ -166,6 +167,73 @@ Frontend should combine:
|
||||
- structural divination data from `DIVINATION_DERIVED`
|
||||
- interpretation text from `TEXT_MESSAGE_END`
|
||||
|
||||
## History snapshot contract
|
||||
|
||||
`GET /api/v1/agent/history` is the canonical replay source for frontend history list and result reconstruction.
|
||||
|
||||
### Required response shape
|
||||
|
||||
```json
|
||||
{
|
||||
"scope": "history_day",
|
||||
"threadId": "uuid|null",
|
||||
"day": "2026-04-05|null",
|
||||
"hasMore": false,
|
||||
"messages": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"seq": 12,
|
||||
"role": "assistant",
|
||||
"content": "...",
|
||||
"timestamp": "2026-04-05T12:34:56+00:00",
|
||||
"agent_output": {
|
||||
"status": "success",
|
||||
"sign_level": "中上签",
|
||||
"summary": "...",
|
||||
"conclusion": ["..."],
|
||||
"focus_points": ["..."],
|
||||
"advice": ["..."],
|
||||
"keywords": ["..."],
|
||||
"answer": "...",
|
||||
"key_points": ["..."],
|
||||
"result_type": "structured_payload",
|
||||
"suggested_actions": ["..."],
|
||||
"divination_derived": {
|
||||
"binaryCode": "101001",
|
||||
"changedBinaryCode": "100001",
|
||||
"guaName": "山火贲"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "uuid",
|
||||
"seq": 11,
|
||||
"role": "user",
|
||||
"content": "我最近换工作是否合适?",
|
||||
"timestamp": "2026-04-05T12:34:12+00:00",
|
||||
"attachments": [
|
||||
{
|
||||
"mimeType": "image/png",
|
||||
"url": "https://...signed..."
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `assistant` message MUST provide `agent_output` when backend has valid worker output metadata.
|
||||
- `agent_output.divination_derived` uses the same shape as `DIVINATION_DERIVED.divination` payload.
|
||||
- Frontend reconstructs divination result page from `agent_output` data, not from local mock data.
|
||||
- `agent_output.sign_level` allowed values: `上上签` / `中上签` / `中下签` / `下下签`.
|
||||
|
||||
### Breaking change note
|
||||
|
||||
- `ui_schema` is removed from history response and is no longer part of this project protocol.
|
||||
- This repository currently accepts non-backward-compatible protocol evolution (no production compatibility burden).
|
||||
|
||||
## Error contract linkage
|
||||
|
||||
- All errors use RFC7807 with extension `code` and optional `params`.
|
||||
|
||||
@@ -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