Files
eryao/docs/protocols/profile/profile-protocol.md
T
2026-04-10 10:40:44 +08:00

6.5 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: profile/avatar aligned; account deletion backend implemented (frontend wiring pending)

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_id is derived from verified JWT sub; 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": {
      "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:

{
  "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:

{
  "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:

{
  "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:

  • 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.

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:

  1. Deletion target is always the authenticated user (sub), never a client-supplied user_id.
  2. Deletion must remove account identity and associated user data for this product scope.
  3. Deletion must be irreversible from client perspective.
  4. After successful deletion, existing local session must be treated as invalid by client and backend.

Deletion scope (current product contract)

The delete operation must remove data owned by the authenticated user in the following domains:

  • Identity: auth.users row for current user.
  • Profile: profiles row.
  • Points: user_points, points_ledger rows linked to user.
  • Chat: sessions, messages rows linked to user/session ownership.
  • 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:

  • 401 when auth is missing/invalid.
  • 403 when auth context is valid but action is not permitted by policy.
  • 409 when server cannot complete deletion due to a conflict that requires user action.
  • 5xx for 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.