b9617ae152
- 后端 Schema 将 interface_language 和 ai_language 合并为 language - 前端设置界面只保留一个语言选项 - AI 回复语言统一使用 language 设置 - 更新协议文档 - 新增数据库迁移脚本
7.8 KiB
7.8 KiB
Profile Protocol (Frontend <-> Backend)
This document defines the canonical backend contract for user profile read/write, avatar upload signing, and account hard deletion.
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 (profile/avatar/account deletion all implemented)
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) - Delete account and personal data (hard delete):
DELETE /api/v1/users/me
Auth and trust boundary
- All routes require authenticated user context.
user_idis derived from verified JWTsub; never accepted from client payload.
Profile read contract
GET /api/v1/users/me/profile
Response:
{
"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": {
"language": "zh-CN",
"timezone": "Asia/Shanghai",
"country": "CN"
},
"privacy": {
"can_sell": false,
"profile_visibility": "public"
},
"notification": {
"allow_notifications": true,
"allow_vibration": true
},
"divination_tutorial": {
"divination_entry_shown": false,
"auto_divination_shown": false,
"manual_divination_shown": false
}
},
"updated_at": "2026-04-05T12:34:56+00:00"
}
Mapping note:
display_namemaps toprofiles.username.avatar_pathis stored in profile layer.avatar_urlis render-ready URL generated from storage strategy.
Profile update contract
PATCH /api/v1/users/me/profile
Request:
{
"display_name": "string(1..30)",
"bio": "string(0..200)",
"avatar_path": "avatars/{user_id}/{file}"
}
Rules:
- At least one field must be provided.
display_namemust be non-empty after trim.bio: empty string after trim is normalized tonull.avatar_pathmust 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:
{
"settings": {
"version": 1,
"preferences": {
"language": "zh-CN",
"timezone": "Asia/Shanghai",
"country": "CN"
},
"privacy": {
"can_sell": false,
"profile_visibility": "public"
},
"notification": {
"allow_notifications": true,
"allow_vibration": true
},
"divination_tutorial": {
"divination_entry_shown": false,
"auto_divination_shown": false,
"manual_divination_shown": false
}
}
}
Rules:
settingsmust conform toProfileSettingsV1.- Additional fields are forbidden.
divination_tutorialtracks user's tutorial completion state for divination flows.
Privacy settings
| Field | Type | Default | Description |
|---|---|---|---|
can_sell |
bool |
false |
Whether user's personal info can be used for personalized ads. false = opt-out (privacy protective default). |
profile_visibility |
str |
"public" |
Profile visibility level. Reserved for future use. |
Compatibility note:
- Previous versions used
privacy: {}(empty object). This has been upgraded to a structuredPrivacySettingsschema. - Strategy:
backward-compatible— oldprivacy: {}payloads are accepted and normalized to defaultPrivacySettings(). - Migration: No client action required; backend normalizes empty/missing privacy to defaults.
Response:
- Returns the same shape as
GET /users/me/profile.
Avatar upload signing contract
POST /api/v1/users/me/avatar/upload-url
Request:
{
"mime_type": "image/png|image/jpeg|image/webp",
"file_size": 123456,
"ext": "png|jpg|jpeg|webp"
}
Response:
{
"bucket": "avatars",
"path": "avatars/{user_id}/{uuid}.png",
"upload_url": "https://...signed...",
"expires_in": 600
}
Validation rules:
bucketmust equalconfig.storage.avatar.bucket.file_sizemust be>0and<= 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. codevalues must be registered indocs/protocols/common/http-error-codes.md.
Account hard deletion contract
DELETE /api/v1/users/me
Purpose:
- Permanently delete the current account and associated personal data from developer records.
- This is hard delete behavior, not soft delete and not temporary deactivation.
Request:
- No request body.
- Auth required (same JWT trust boundary as other
/users/me/*routes).
Success response:
204 No Content
Behavior contract:
- Deletion target is always the authenticated user (
sub), never a client-supplieduser_id. - Deletion must remove account identity and associated user data for this product scope.
- Deletion must be irreversible from client perspective.
- After successful deletion, existing local session must be treated as invalid by client and backend.
Deletion scope (current product contract)
The delete operation removes data owned by the authenticated user in the following domains:
- Identity:
auth.usersrow for current user (cascade delete). - Profile:
profilesrow (FK cascade viaauth.users.id). - Points:
user_points,points_ledgerrows (FK cascade viaauth.users.id). - Chat:
sessionsrows are soft-deleted (deleted_atset);messagescascade viasessions.id FK. After deletion, sessions are hidden from history but not physically removed. - Avatar storage objects under prefix
avatars/{user_id}/.
Notes:
- If future legal/compliance requirements introduce mandatory retention, retained fields must be explicitly documented and user-visible in deletion UI copy.
- This protocol version assumes no regulated retention exemption for current product scope.
Error semantics
The route follows common RFC7807 error payload and registry codes. Expected HTTP classes:
401when auth is missing/invalid.403when auth context is valid but action is not permitted by policy.409when server cannot complete deletion due to a conflict that requires user action.5xxfor unexpected server/upstream failure (must not fail silently).
Consistency and idempotency expectations
- API behavior is request-idempotent at user intent level: once account is deleted, repeating the action should not recreate state and should not produce partial undeleted data.
- Client should treat any post-deletion authenticated call failure as terminal session invalidation and force logout flow.