openapi: 3.0.3
info:
  title: AI建站 Platform API
  version: 1.0.0
  description: |
    通用中台契约。业务行数据仅通过 apps/{slug}/{resource} CRUD 传输；
    行业字段由蓝图定义，不在此增加行业专用路由。
    动词约定：GET 读 / POST 创建或动作 / PUT 更新 / DELETE 删除。
servers:
  - url: http://127.0.0.1:8180
paths:
  /api/v1/meta/apis:
    get:
      operationId: listApis
      summary: API 目录
      responses:
        "200":
          description: OK
  /api/v1/meta/openapi.yaml:
    get:
      operationId: getOpenAPI
      summary: OpenAPI 原文
      responses:
        "200":
          description: YAML
  /api/v1/auth/register:
    post:
      operationId: authRegister
      summary: 注册
      responses:
        "201": { description: Created }
  /api/v1/auth/login:
    post:
      operationId: authLogin
      summary: 登录
      responses:
        "200": { description: OK }
  /api/v1/auth/token:
    post:
      operationId: authToken
      summary: 服务签发 JWT
      responses:
        "200": { description: OK }

  /api/v1/apps/{slug}/publish:
    post:
      operationId: publishApp
      summary: 发布蓝图
      parameters:
        - $ref: "#/components/parameters/Slug"
        - $ref: "#/components/parameters/Authorization"
      responses:
        "200": { description: OK }
  /api/v1/apps/{slug}/blueprint:
    get:
      operationId: getBlueprint
      summary: 读取蓝图
      parameters:
        - $ref: "#/components/parameters/Slug"
        - $ref: "#/components/parameters/Authorization"
      responses:
        "200": { description: OK }
  /api/v1/apps/{slug}/agent-capsule:
    get:
      operationId: getAgentCapsule
      summary: 智能体胶囊
      parameters:
        - $ref: "#/components/parameters/Slug"
        - $ref: "#/components/parameters/Authorization"
      responses:
        "200": { description: OK }

  /api/v1/apps/{slug}/{resource}:
    parameters:
      - $ref: "#/components/parameters/Slug"
      - $ref: "#/components/parameters/Resource"
      - $ref: "#/components/parameters/Authorization"
    get:
      operationId: listRows
      summary: 列表
      parameters:
        - name: page
          in: query
          schema: { type: integer, minimum: 1, default: 1 }
        - name: page_size
          in: query
          schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
        - name: sort
          in: query
          schema: { type: string }
        - name: filter.*
          in: query
          description: 如 filter.status=在售，键须在蓝图 allowed_filters
          schema: { type: string }
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PageResult"
    post:
      operationId: createRow
      summary: 创建
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "201":
          description: Created
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Row"

  /api/v1/apps/{slug}/{resource}/{id}:
    parameters:
      - $ref: "#/components/parameters/Slug"
      - $ref: "#/components/parameters/Resource"
      - $ref: "#/components/parameters/Id"
      - $ref: "#/components/parameters/Authorization"
    get:
      operationId: getRow
      summary: 详情
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Row"
        "404":
          $ref: "#/components/responses/NotFound"
    put:
      operationId: updateRow
      summary: 更新
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              additionalProperties: true
      responses:
        "200":
          description: OK
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Row"
    delete:
      operationId: deleteRow
      summary: 删除
      responses:
        "204": { description: No Content }

  /api/v1/apps/{slug}/{resource}/import:
    post:
      operationId: importRows
      summary: 导入
      parameters:
        - $ref: "#/components/parameters/Slug"
        - $ref: "#/components/parameters/Resource"
        - $ref: "#/components/parameters/Authorization"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file: { type: string, format: binary }
      responses:
        "200": { description: OK }

  /api/v1/apps/{slug}/{resource}/export:
    get:
      operationId: exportRows
      summary: 导出
      parameters:
        - $ref: "#/components/parameters/Slug"
        - $ref: "#/components/parameters/Resource"
        - $ref: "#/components/parameters/Authorization"
        - name: format
          in: query
          schema: { type: string, enum: [xlsx, csv], default: xlsx }
      responses:
        "200": { description: 文件流 }

  /api/v1/apps/{slug}/{resource}/aggregate:
    get:
      operationId: aggregateRows
      summary: 聚合
      parameters:
        - $ref: "#/components/parameters/Slug"
        - $ref: "#/components/parameters/Resource"
        - $ref: "#/components/parameters/Authorization"
        - name: group_by
          in: query
          schema: { type: string }
        - name: sum
          in: query
          schema: { type: string }
      responses:
        "200": { description: OK }

  /api/v1/audit/logs:
    get:
      operationId: listAuditLogs
      summary: 审计日志
      parameters:
        - $ref: "#/components/parameters/Authorization"
      responses:
        "200": { description: OK }

  /api/v1/storage:
    post:
      operationId: uploadObject
      summary: 上传
      parameters:
        - $ref: "#/components/parameters/Authorization"
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file: { type: string, format: binary }
      responses:
        "200": { description: OK }

  /api/v1/storage/{tenant}/{day}/{name}:
    get:
      operationId: downloadObject
      summary: 下载
      parameters:
        - $ref: "#/components/parameters/Authorization"
        - name: tenant
          in: path
          required: true
          schema: { type: string }
        - name: day
          in: path
          required: true
          schema: { type: string }
        - name: name
          in: path
          required: true
          schema: { type: string }
      responses:
        "200": { description: 文件流 }

  /api/v1/apps/generate:
    post:
      operationId: generateBlueprint
      summary: 生成蓝图（AI 服务，经网关 /ai 前缀）
      description: 实际请求 /ai/api/v1/apps/generate；勿在 platform 重复实现。
      requestBody:
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                prompt: { type: string }
                excel: { type: string, format: binary }
                images: { type: string, format: binary }
      responses:
        "200": { description: OK }

  /api/v1/llm/providers:
    get:
      operationId: listLlmProviders
      summary: LLM 厂商（AI 服务）
      responses:
        "200": { description: OK }

  /api/v1/admin/sync/channels:
    get:
      operationId: listSyncChannels
      summary: 列出同步通道
      parameters: [{ $ref: "#/components/parameters/Authorization" }]
      responses: { "200": { description: OK } }
    post:
      operationId: createSyncChannel
      summary: 创建同步通道（UUID PK + FK 闭包校验）
      parameters: [{ $ref: "#/components/parameters/Authorization" }]
      responses: { "200": { description: OK }, "400": { description: Bad Request } }
  /api/v1/admin/sync/channels/{id}:
    get:
      operationId: getSyncChannel
      summary: 获取同步通道
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK } }
    put:
      operationId: updateSyncChannel
      summary: 更新同步通道
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK } }
    delete:
      operationId: deleteSyncChannel
      summary: 删除同步通道
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK } }
  /api/v1/admin/sync/test:
    post:
      operationId: testSyncEndpoints
      summary: 测试本地/线上库连接
      parameters: [{ $ref: "#/components/parameters/Authorization" }]
      responses: { "200": { description: OK } }
  /api/v1/admin/sync/channels/{id}/prepare:
    post:
      operationId: prepareSyncChannel
      summary: 准备同步（outbox/触发器）
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK } }
  /api/v1/admin/sync/channels/{id}/start:
    post:
      operationId: startSyncChannel
      summary: 启动同步
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK } }
  /api/v1/admin/sync/channels/{id}/stop:
    post:
      operationId: stopSyncChannel
      summary: 停止同步
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK } }
  /api/v1/admin/sync/channels/{id}/reconcile:
    post:
      operationId: reconcileSyncChannel
      summary: 同步修复（对账，有限流）
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK }, "429": { description: Too Many Requests } }
  /api/v1/admin/sync/channels/{id}/ingest:
    post:
      operationId: ingestSyncRows
      summary: 外部行写入通道 local
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK } }
  /api/v1/admin/sync/channels/{id}/inspect:
    get:
      operationId: inspectSyncChannel
      summary: 列出通道线上/本机库表（验同步）
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK } }
  /api/v1/admin/sync/channels/{id}/preview:
    post:
      operationId: previewSyncTable
      summary: 预览单表行
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK } }
  /api/v1/admin/sync/channels/{id}/drop-table:
    post:
      operationId: dropSyncTable
      summary: 删除线上/本机业务表（仅当前 side，不同步 DDL）
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK } }
  /api/v1/admin/sync/bindings:
    get:
      operationId: listSyncBindings
      summary: 列出本机库↔线上库绑定（管理员全量；用户仅本人）
      parameters: [{ $ref: "#/components/parameters/Authorization" }]
      responses: { "200": { description: OK } }
    post:
      operationId: ensureSyncBinding
      summary: 登记/更新绑定（登录用户可自助；强制本人 user_id）
      parameters: [{ $ref: "#/components/parameters/Authorization" }]
      responses: { "200": { description: OK } }
  /api/v1/admin/sync/conflicts:
    get:
      operationId: listSyncConflicts
      summary: 已废弃（公司侧 403）
      parameters: [{ $ref: "#/components/parameters/Authorization" }]
      responses: { "403": { description: Forbidden } }
  /api/v1/agent/sync/channels/{id}/whitelist:
    get:
      operationId: agentSyncWhitelist
      summary: 拉取表白名单（「数据同步」或登录用户+本人 Binding）
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK } }
  /api/v1/agent/sync/channels/{id}/push:
    post:
      operationId: agentSyncPush
      summary: 推变更到线上 A（用户自助须带本人 online_db_id）
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses:
        "200":
          description: OK（含 applied / skipped 幂等）
          content:
            application/json:
              examples:
                applied:
                  value:
                    success: true
                    result:
                      ok: true
                      applied: true
                      skipped: false
                      applied_version: 1710000000000000000
                      message: applied
                skipped:
                  value:
                    success: true
                    result:
                      ok: true
                      applied: false
                      skipped: true
                      applied_version: 1710000000000000000
                      message: already applied (same version)
        "503":
          description: remote 暂不可达，可重试
          content:
            application/json:
              example:
                code: 503
                message: "open remote: ..."
                retryable: true
  /api/v1/agent/sync/channels/{id}/push/batch:
    post:
      operationId: agentSyncPushBatch
      summary: 本机 agent 批量推送
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses:
        "200": { description: OK }
        "503": { description: remote 暂不可达，retryable }
  /api/v1/agent/sync/channels/{id}/pull:
    post:
      operationId: agentSyncPull
      summary: 从线上 A 下行（bootstrap / rows / pks）；用户自助须 online_db_id
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses:
        "200":
          description: OK
          content:
            application/json:
              example:
                success: true
                result:
                  ok: true
                  mode: bootstrap
                  table: orders
                  pk_column: id
                  items:
                    - table: orders
                      op: upsert
                      row_pk: aaaa-bbbb
                      row: { id: aaaa-bbbb, title: x }
                      version: 1710000000000000000
                  next_after_pk: aaaa-bbbb
                  has_more: true
        "503": { description: remote 暂不可达，retryable }
  /api/v1/agent/sync/channels/{id}/bootstrap:
    post:
      operationId: agentSyncBootstrap
      summary: 全量灌库（等同 pull + mode=bootstrap）
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses:
        "200": { description: OK }
        "503": { description: remote 暂不可达，retryable }
  /api/v1/agent/sync/channels/{id}/schema:
    post:
      operationId: agentSyncSchemaDescribe
      summary: 拉取线上 A 表结构（含空表）；本机按 columns CREATE IF NOT EXISTS
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses:
        "200": { description: OK }
        "503": { description: remote 暂不可达，retryable }
  /api/v1/agent/sync/channels/{id}/schema/ensure:
    post:
      operationId: agentSyncSchemaEnsure
      summary: 本机空表结构推到线上 A（CREATE IF NOT EXISTS，无需 outbox 行）
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses:
        "200": { description: OK }
        "503": { description: remote 暂不可达，retryable }
  /api/v1/platform/dbsync/lww-overrides:
    get:
      operationId: platformLwwOverrides
      summary: 超管 LWW 覆盖审计
      parameters: [{ $ref: "#/components/parameters/Authorization" }]
      responses: { "200": { description: OK }, "403": { description: Forbidden } }
  /api/v1/platform/dbsync/lww-overrides/{id}/rollback:
    post:
      operationId: platformLwwRollback
      summary: 超管按落败快照回滚线上单行
      parameters:
        - { $ref: "#/components/parameters/Authorization" }
        - { $ref: "#/components/parameters/Id" }
      responses: { "200": { description: OK }, "400": { description: Bad Request } }

components:
  parameters:
    Authorization:
      name: Authorization
      in: header
      required: true
      schema: { type: string }
      description: Bearer JWT
    Slug:
      name: slug
      in: path
      required: true
      schema: { type: string, pattern: "^[a-z][a-z0-9_]{1,47}$" }
    Resource:
      name: resource
      in: path
      required: true
      schema: { type: string }
      description: 蓝图 apis.resources.path（无前导 /）；不可为保留名
    Id:
      name: id
      in: path
      required: true
      schema: { type: string }
  schemas:
    PageResult:
      type: object
      properties:
        items: { type: array, items: { $ref: "#/components/schemas/Row" } }
        total: { type: integer }
    Row:
      type: object
      additionalProperties: true
  responses:
    NotFound:
      description: Not Found
      content:
        application/json:
          schema:
            type: object
            properties:
              code: { type: integer }
              message: { type: string }
