openapi: 3.1.0
info:
  title: HTML Publisher API
  version: 1.0.0
  description: 使用 API KEY 把静态 HTML 网站自动发布到 Cloudflare Workers 与 R2。
servers:
  - url: https://web.tomszhou.cc
    description: 老周维新发布服务
tags:
  - name: Deployments
    description: 创建、上传并完成静态网站发布
  - name: Operations
    description: 服务状态
paths:
  /api/v1/health:
    get:
      tags: [Operations]
      summary: 健康检查
      operationId: getHealth
      responses:
        "200":
          description: 服务正常
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/HealthResponse"
  /api/v1/sites:
    get:
      tags: [Operations]
      summary: 获取最近发布的作品目录
      operationId: listPublishedSites
      parameters:
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
      responses:
        "200":
          description: 按发布时间从新到旧排列的作品
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SiteCatalogResponse"
  /api/v1/deployments:
    post:
      tags: [Deployments]
      summary: 创建发布任务
      operationId: createDeployment
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateDeploymentRequest"
      responses:
        "200":
          description: 发布任务已创建
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CreateDeploymentResponse"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "413":
          description: 文件清单过大
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/deployments/{deploymentId}/files:
    put:
      tags: [Deployments]
      summary: 上传单个文件
      operationId: uploadDeploymentFile
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - $ref: "#/components/parameters/DeploymentId"
        - name: siteKey
          in: query
          required: true
          description: 创建任务时返回的站点访问 KEY
          schema:
            type: string
            pattern: "^[a-z0-9][a-z0-9-]{2,46}[a-z0-9]$"
        - name: path
          in: query
          required: true
          description: 文件相对于网站根目录的路径
          schema:
            type: string
            example: images/cover.png
        - name: X-File-Size
          in: header
          required: true
          description: 文件字节数，必须与发布清单一致
          schema:
            type: integer
            minimum: 0
            maximum: 94371840
      requestBody:
        required: true
        content:
          application/octet-stream:
            schema:
              type: string
              format: binary
      responses:
        "204":
          description: 文件上传成功
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: 发布任务不存在或已失效
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
  /api/v1/deployments/{deploymentId}/complete:
    post:
      tags: [Deployments]
      summary: 完成发布并切换线上版本
      operationId: completeDeployment
      security:
        - bearerAuth: []
        - apiKeyAuth: []
      parameters:
        - $ref: "#/components/parameters/DeploymentId"
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [siteKey]
              properties:
                siteKey:
                  type: string
                  example: product-demo
      responses:
        "200":
          description: 发布完成
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CompleteDeploymentResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: 发布任务不存在或已失效
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
        "409":
          description: 文件尚未全部上传
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ErrorResponse"
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
    apiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
  parameters:
    DeploymentId:
      name: deploymentId
      in: path
      required: true
      schema:
        type: string
        format: uuid
  responses:
    BadRequest:
      description: 请求参数不正确
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
    Unauthorized:
      description: API KEY 不正确
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorResponse"
  schemas:
    DeclaredFile:
      type: object
      required: [path, size, type]
      properties:
        path:
          type: string
          example: index.html
        size:
          type: integer
          minimum: 0
          maximum: 94371840
          example: 2048
        type:
          type: string
          example: text/html; charset=utf-8
    CreateDeploymentRequest:
      type: object
      required: [files]
      properties:
        siteName:
          type: string
          maxLength: 80
          example: 产品演示
        siteKey:
          type: string
          minLength: 4
          maxLength: 48
          description: 省略时由服务端自动生成；复用同一个值可更新原浏览地址
          example: product-demo
        category:
          type: string
          maxLength: 20
          default: 作品
          example: 报告
        description:
          type: string
          maxLength: 240
          example: 本期业务分析与关键结论。
        files:
          type: array
          minItems: 1
          maxItems: 5000
          items:
            $ref: "#/components/schemas/DeclaredFile"
    CreateDeploymentResponse:
      type: object
      required: [apiVersion, deploymentId, siteKey, viewUrl]
      properties:
        apiVersion:
          type: string
          const: v1
        deploymentId:
          type: string
          format: uuid
        siteKey:
          type: string
        viewUrl:
          type: string
          format: uri
    CompleteDeploymentResponse:
      type: object
      required: [apiVersion, siteKey, viewUrl, fileCount]
      properties:
        apiVersion:
          type: string
          const: v1
        siteKey:
          type: string
        viewUrl:
          type: string
          format: uri
        fileCount:
          type: integer
    HealthResponse:
      type: object
      required: [apiVersion, ok, storage]
      properties:
        apiVersion:
          type: string
          const: v1
        ok:
          type: boolean
        storage:
          type: boolean
    PublishedSite:
      type: object
      required: [siteKey, siteName, category, description, publishedAt, fileCount, viewUrl]
      properties:
        siteKey:
          type: string
        siteName:
          type: string
        category:
          type: string
        description:
          type: string
        publishedAt:
          type: string
          format: date-time
        fileCount:
          type: integer
        viewUrl:
          type: string
          format: uri
    SiteCatalogResponse:
      type: object
      required: [apiVersion, sites, total]
      properties:
        apiVersion:
          type: string
          const: v1
        sites:
          type: array
          items:
            $ref: "#/components/schemas/PublishedSite"
        total:
          type: integer
    ErrorResponse:
      type: object
      required: [error]
      properties:
        error:
          type: string
