openapi: "3.0.3"
info:
  title: 家庭文档 API V1
  version: "1.0.0"
  description: 家庭文档 CRUD、列表与语义搜索接口。

servers:
  - url: /api/v1

paths:
  /family/documents:
    get:
      operationId: listFamilyDocuments
      x-auth-level: user_family
      summary: 查询家庭文档
      tags: [family-document]
      parameters:
        - $ref: '#/components/parameters/LimitParam'
        - $ref: '#/components/parameters/CursorParam'
      responses:
        '200':
          description: 成功返回家庭文档列表。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentCollectionEnvelope'
    post:
      operationId: createFamilyDocument
      x-auth-level: user_family
      summary: 创建家庭文档
      tags: [family-document]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateDocumentRequest'
      responses:
        '200':
          description: 成功返回新建文档。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentDetailEnvelope'
        '413':
          $ref: '#/components/responses/DocumentTooLargeResponse'

  /family/documents/search:
    post:
      operationId: searchFamilyDocuments
      x-auth-level: user_family
      summary: 搜索最相似的家庭文档
      tags: [family-document]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SearchDocumentsRequest'
      responses:
        '200':
          description: 返回最相似文档；没有可搜索文档时 data 为 null。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentSearchEnvelope'

  /family/documents/{document_id}:
    get:
      operationId: getFamilyDocument
      x-auth-level: user_family
      summary: 查询家庭文档详情
      tags: [family-document]
      parameters:
        - $ref: '#/components/parameters/DocumentIDParam'
      responses:
        '200':
          description: 成功返回家庭文档详情。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentDetailEnvelope'
        '404':
          $ref: '#/components/responses/DocumentNotFoundResponse'
    patch:
      operationId: updateFamilyDocument
      x-auth-level: user_family
      summary: 部分更新家庭文档
      tags: [family-document]
      parameters:
        - $ref: '#/components/parameters/DocumentIDParam'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/UpdateDocumentRequest'
      responses:
        '200':
          description: 成功返回更新后的家庭文档。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/DocumentDetailEnvelope'
        '404':
          $ref: '#/components/responses/DocumentNotFoundResponse'
        '409':
          $ref: '#/components/responses/DocumentRevisionConflictResponse'
        '413':
          $ref: '#/components/responses/DocumentTooLargeResponse'
    delete:
      operationId: deleteFamilyDocument
      x-auth-level: user_family
      summary: 删除家庭文档
      tags: [family-document]
      parameters:
        - $ref: '#/components/parameters/DocumentIDParam'
        - $ref: '#/components/parameters/RevisionParam'
      responses:
        '200':
          description: 成功软删除家庭文档。
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/EmptyEnvelope'
        '404':
          $ref: '#/components/responses/DocumentNotFoundResponse'
        '409':
          $ref: '#/components/responses/DocumentRevisionConflictResponse'

components:
  parameters:
    DocumentIDParam:
      name: document_id
      in: path
      required: true
      schema:
        type: string
        format: uuid
    RevisionParam:
      name: revision
      in: query
      required: true
      schema:
        type: integer
        format: int64
        minimum: 1
    LimitParam:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        minimum: 1
        maximum: 200
        default: 50
    CursorParam:
      name: cursor
      in: query
      required: false
      schema:
        type: string

  responses:
    DocumentNotFoundResponse:
      description: 家庭文档不存在。
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    DocumentRevisionConflictResponse:
      description: revision 与当前文档不一致。
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    DocumentTooLargeResponse:
      description: 文档正文超过 1 MiB UTF-8 字节限制。
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'

  schemas:
    Envelope:
      type: object
      additionalProperties: false
      required: [code, data]
      properties:
        code:
          type: integer
          format: int32
          description: 业务错误码，0 表示成功。
          default: 0
        message:
          type: string
          nullable: true
        data:
          description: 统一信封中的业务数据载荷。

    PageCursor:
      type: object
      additionalProperties: false
      required: [has_more, total]
      description: |
        稳定分页信息。next_cursor 是不透明分页令牌，客户端只能原样回传。
        total 表示忽略 cursor 和 limit 后的总命中数。
      properties:
        next_cursor:
          type: string
          nullable: true
        has_more:
          type: boolean
        total:
          type: integer
          format: int64
          minimum: 0

    CreateDocumentRequest:
      type: object
      additionalProperties: false
      required: [title, summary, content]
      properties:
        title:
          type: string
          minLength: 1
          maxLength: 200
          x-log-exclude: true
        summary:
          type: string
          minLength: 1
          maxLength: 2000
          x-log-exclude: true
        content:
          type: string
          minLength: 1
          x-max-utf8-bytes: 1048576
          x-log-exclude: true
          description: 完整 Markdown，最多 1 MiB UTF-8 字节。

    UpdateDocumentRequest:
      type: object
      additionalProperties: false
      required: [revision]
      anyOf:
        - required: [title]
        - required: [summary]
        - required: [content]
      properties:
        revision:
          type: integer
          format: int64
          minimum: 1
        title:
          type: string
          minLength: 1
          maxLength: 200
          x-log-exclude: true
        summary:
          type: string
          minLength: 1
          maxLength: 2000
          x-log-exclude: true
        content:
          type: string
          minLength: 1
          x-max-utf8-bytes: 1048576
          x-log-exclude: true
          description: 完整 Markdown，最多 1 MiB UTF-8 字节；不传表示不修改。

    SearchDocumentsRequest:
      type: object
      additionalProperties: false
      required: [query]
      properties:
        query:
          type: string
          minLength: 1
          maxLength: 2000
          x-log-exclude: true

    EmbeddingStatus:
      type: string
      enum: [pending, ready, failed]

    DocumentDetail:
      type: object
      additionalProperties: false
      required: [id, title, summary, content, embedding_status, revision, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
          x-log-exclude: true
        summary:
          type: string
          x-log-exclude: true
        content:
          type: string
          x-log-exclude: true
        embedding_status:
          $ref: '#/components/schemas/EmbeddingStatus'
        revision:
          type: integer
          format: int64
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    DocumentListItem:
      type: object
      additionalProperties: false
      required: [id, title, summary, embedding_status, revision, created_at, updated_at]
      properties:
        id:
          type: string
          format: uuid
        title:
          type: string
          x-log-exclude: true
        summary:
          type: string
          x-log-exclude: true
        embedding_status:
          $ref: '#/components/schemas/EmbeddingStatus'
        revision:
          type: integer
          format: int64
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    DocumentCollectionData:
      type: object
      additionalProperties: false
      required: [items, page]
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/DocumentListItem'
        page:
          $ref: '#/components/schemas/PageCursor'

    DocumentSearchData:
      type: object
      additionalProperties: false
      required: [document, score]
      properties:
        document:
          $ref: '#/components/schemas/DocumentDetail'
        score:
          type: number
          format: double

    DocumentDetailEnvelope:
      allOf:
        - $ref: '#/components/schemas/Envelope'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/DocumentDetail'

    DocumentCollectionEnvelope:
      allOf:
        - $ref: '#/components/schemas/Envelope'
        - type: object
          properties:
            data:
              $ref: '#/components/schemas/DocumentCollectionData'

    DocumentSearchEnvelope:
      allOf:
        - $ref: '#/components/schemas/Envelope'
        - type: object
          properties:
            data:
              nullable: true
              allOf:
                - $ref: '#/components/schemas/DocumentSearchData'

    EmptyEnvelope:
      allOf:
        - $ref: '#/components/schemas/Envelope'
        - type: object
          properties:
            data:
              type: object
              nullable: true

    ErrorEnvelope:
      allOf:
        - $ref: '#/components/schemas/Envelope'
        - type: object
          properties:
            data:
              type: object
              nullable: true
