Files
eryao/docs/protocols/profile/profile-protocol.md
T

3.4 KiB

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:

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

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.