openapi: 3.0.3
info:
  title: 图床 API
  description: |
    多用户图床系统 API，支持图片上传、管理、CDN 加速。

    ## 接入方式

    ### 人类用户
    1. 邮箱注册 → 邮箱激活 → 获得 10GB 免费配额
    2. 登录获取 JWT Token（密码登录或邮箱验证码登录）

    ### AI Agent（全自动，无需邮箱）
    1. POST /api/agent/register → 获取 BTC 地址
    2. 支付 ≥ 0.0001 BTC 到该地址
    3. GET /api/agent/status?address=xxx → 轮询获取 Access Key
    4. 使用 Access Key 签名调用所有 API

    ## 认证方式

    ### JWT Token（人类用户）
    ```
    Authorization: Bearer {token}
    ```

    ### Access Key（AI Agent / 程序调用）
    ```
    X-Access-Key: ak_xxx
    X-Signature: HMAC-SHA256(signatureKey, "METHOD\n/path\ntimestamp") 的 hex
    X-Timestamp: 1727000000000   # 毫秒时间戳，与服务器偏差须 < 5 分钟
    ```

    ## 配额
    - 免费用户：10 GB
    - 付费升级（/api/upgrade）：
      - 0.0001 BTC → 1 GB
      - 0.0002 BTC → 5 GB
      - 0.0005 BTC → 20 GB
      - 0.001 BTC → 100 GB
    - Agent 自助注册（/api/agent/register）：按实际支付金额计费，每 0.0001 BTC ≈ 2000 MB

    ## 图片访问
    - 原图：`https://img.qname.com/u/{user_id}/{hash}.webp`
    - 缩略图：`https://img.qname.com/t/{user_id}/{hash}.webp`
    - 音视频/文件：`https://res.qname.com/...`

    ## 支持项目

    如果这个项目对你有帮助，欢迎捐赠支持：

    - **ETH**: `0xf8C9fCa09EA38e4955053AE26a5F456fB8dEDFcb`
    - **BTC**: `bc1qwrq4cc6x8updeuggglrhfeszflu5uxl0kwvafv`

  version: 2.0.0
  contact:
    name: API Support

servers:
  - url: https://clawdrive.qname.com
    description: 生产环境

tags:
  - name: agent
    description: AI Agent 接入（自动注册 + BTC 支付）
  - name: auth
    description: 用户认证
  - name: upload
    description: 图片上传
  - name: images
    description: 图片管理
  - name: keys
    description: Access Key 管理
  - name: files
    description: 文件管理（任意格式，dashboard 使用）
  - name: quota
    description: 配额查询
  - name: upgrade
    description: 付费升级

paths:
  /api/agent/register:
    post:
      tags: [agent]
      summary: Agent 注册
      description: |
        AI Agent 专用注册接口，无需邮箱。
        返回 BTC 收款地址，支付后通过 /api/agent/status 获取 Access Key。

        **流程：**
        1. 调用此接口 → 获取 btcAddress
        2. 支付 ≥ minPaymentSat (10000 satoshi) 到该地址
        3. 轮询 GET /api/agent/status?address={btcAddress}
        4. 支付确认后自动激活，返回 Access Key + signatureKey
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [keyName]
              properties:
                keyName:
                  type: string
                  maxLength: 100
                  description: Key 名称标识
                  example: my-bot
                expiresIn:
                  type: integer
                  description: Access Key 过期天数，不填永不过期
                  example: 30
      responses:
        '200':
          description: 注册成功，返回 BTC 地址
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      btcAddress:
                        type: string
                        description: BTC 支付地址
                      minPayment:
                        type: string
                        description: 最低支付金额（可读格式）
                      minPaymentSat:
                        type: integer
                        description: 最低支付金额（satoshi）
                      deadline:
                        type: string
                        format: date-time
                        description: 支付截止时间（72小时）
                      tips:
                        type: string
        '429':
          description: 请求过于频繁

  /api/agent/status:
    get:
      tags: [agent]
      summary: 查询支付状态
      description: |
        轮询此接口查询 BTC 支付是否到账。
        支付确认后自动激活账户并返回 Access Key。
      parameters:
        - name: address
          in: query
          required: true
          schema:
            type: string
          description: BTC 地址（从 /api/agent/register 获取）
      responses:
        '200':
          description: 查询结果
          content:
            application/json:
              schema:
                oneOf:
                  - type: object
                    properties:
                      success:
                        type: boolean
                        example: false
                      status:
                        type: string
                        example: pending
                      currentBalance:
                        type: integer
                      requiredBalance:
                        type: integer
                      message:
                        type: string
                    description: 未支付
                  - type: object
                    properties:
                      success:
                        type: boolean
                        example: true
                      status:
                        type: string
                        example: active
                      accessKey:
                        type: string
                        description: Access Key ID
                      secretKey:
                        type: string
                        description: Secret Key（妥善保存）
                      signatureKey:
                        type: string
                        description: HMAC 签名密钥（妥善保存）
                      quotaLimit:
                        type: string
                        description: 配额上限（字节）
                      paidSat:
                        type: integer
                        description: 实际支付金额（satoshi）
                    description: 已支付，返回 Access Key

  /api/auth/register:
    post:
      tags: [auth]
      summary: 用户注册
      description: |
        使用 Gmail 邮箱注册，注册后发送激活邮件。
        激活后获得 100MB 免费配额。
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, username, password]
              properties:
                email:
                  type: string
                  format: email
                  description: Gmail 邮箱
                  example: user@gmail.com
                username:
                  type: string
                  minLength: 3
                  maxLength: 50
                  description: 用户名
                  example: myname
                password:
                  type: string
                  minLength: 6
                  description: 密码
                  example: "123456"
      responses:
        '200':
          description: 注册成功，请查收邮件激活
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                    example: 注册成功，请查收邮件激活账户
        '400':
          description: 参数错误
        '409':
          description: 邮箱或用户名已存在
        '429':
          description: 注册过于频繁

  /api/auth/verify:
    get:
      tags: [auth]
      summary: 邮箱验证
      description: 点击邮件中的链接激活账户
      parameters:
        - name: token
          in: query
          required: true
          schema:
            type: string
          description: 验证令牌
      responses:
        '200':
          description: 验证成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  message:
                    type: string
                    example: 邮箱验证成功
        '400':
          description: 验证链接无效或已过期

  /api/auth/login:
    post:
      tags: [auth]
      summary: 用户登录
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email:
                  type: string
                  format: email
                  description: 邮箱
                  example: user@gmail.com
                password:
                  type: string
                  description: 密码
                  example: "123456"
      responses:
        '200':
          description: 登录成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      token:
                        type: string
                        description: JWT Token，有效期 7 天
                      user:
                        $ref: '#/components/schemas/User'
        '401':
          description: 邮箱或密码错误
        '403':
          description: 账户未激活

  /api/auth/send-code:
    post:
      tags: [auth]
      summary: 发送登录验证码
      description: 向已注册且已激活的邮箱发送 6 位登录验证码，10 分钟内有效，60 秒内不可重发
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email:
                  type: string
                  format: email
                  example: user@gmail.com
      responses:
        '200':
          description: 验证码已发送
        '404':
          description: 用户不存在或未激活
        '429':
          description: 发送过于频繁

  /api/auth/login-code:
    post:
      tags: [auth]
      summary: 验证码登录
      description: 使用邮箱 + 6 位验证码登录，验证码一次性有效
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, code]
              properties:
                email:
                  type: string
                  format: email
                  example: user@gmail.com
                code:
                  type: string
                  description: 6 位数字验证码
                  example: "123456"
      responses:
        '200':
          description: 登录成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      token:
                        type: string
                        description: JWT Token，有效期 7 天
                      user:
                        $ref: '#/components/schemas/User'
        '401':
          description: 验证码错误或已过期

  /api/files:
    get:
      tags: [files]
      summary: 获取文件列表
      description: 分页获取当前用户的文件列表（含任意格式文件）
      security:
        - bearerAuth: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
        - name: keyword
          in: query
          description: 搜索关键词（匹配原始文件名）
          schema:
            type: string
        - name: folderId
          in: query
          description: 文件夹 ID
          schema:
            type: string
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/FileItem'
                  pagination:
                    type: object
                    properties:
                      page:
                        type: integer
                      limit:
                        type: integer
                      total:
                        type: integer
                      totalPages:
                        type: integer

  /api/files/upload:
    post:
      tags: [files]
      summary: 上传文件
      description: 上传任意格式文件，原样存储不做格式转换（图片会自动生成缩略图），默认最大 100MB
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                folderId:
                  type: string
                  description: 可选，目标文件夹 ID
      responses:
        '200':
          description: 上传成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      id:
                        type: string
                      originalName:
                        type: string
                      fileName:
                        type: string
                      fileSize:
                        type: string
                      mimeType:
                        type: string
                      url:
                        type: string
                        format: uri
                        description: 公开访问地址
                        example: https://img.qname.com/files/123/2026/09/abc123.webp
                      thumbnail:
                        type: string
                        format: uri
                        nullable: true
                        description: 缩略图地址（仅图片有）
                      category:
                        type: string
                      width:
                        type: integer
                        nullable: true
                      height:
                        type: integer
                        nullable: true
        '401':
          description: 未授权
        '403':
          description: 配额不足
        '400':
          description: 缺少文件或文件过大

  /api/files/{id}:
    delete:
      tags: [files]
      summary: 删除文件
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: 删除成功
        '404':
          description: 文件不存在

  /api/upgrade:
    get:
      tags: [upgrade]
      summary: 获取付费升级地址
      description: |
        返回当前用户的专属 BTC 支付地址和价目表。
        向该地址支付后系统自动检测并升级配额：
        - 0.0001 BTC → 1 GB
        - 0.0002 BTC → 5 GB
        - 0.0005 BTC → 20 GB
        - 0.001 BTC → 100 GB
      security:
        - bearerAuth: []
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  btcAddress:
                    type: string
                    description: BTC 支付地址
                  pricing:
                    type: array
                    items:
                      type: object
                      properties:
                        btc:
                          type: string
                          example: "0.0002"
                        quota:
                          type: integer
                          description: 配额（字节）
                          example: 5368709120
                        label:
                          type: string
                          example: 5GB
                  deadline:
                    type: string
                    format: date-time
                    description: 支付截止时间（72 小时）
        '401':
          description: 未授权

  /api/upload:
    post:
      tags: [upload]
      summary: 上传文件（图片/音频/视频）
      description: |
        按 Content-Type 分流：
        - image/*：转换为 WebP，最大边 5000px q75，自动生成 400px 缩略图
        - audio/mpeg / audio/mp4 / audio/ogg / video/mp4：原样存储，不转码不压缩
        - MD5 去重（相同文件秒传）

        伺服支持 Range 请求（206 Partial Content），可直接用于 `<audio>`/`<video>` 播放。
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
                  description: 图片文件（支持 jpg/png/gif/webp，最大 10MB）
      responses:
        '200':
          description: 上传成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Image'
        '401':
          description: 未授权
        '403':
          description: 配额不足
        '400':
          description: 缺少文件或文件过大

  /api/images:
    get:
      tags: [images]
      summary: 获取图片列表
      description: 分页获取当前用户的图片列表
      security:
        - bearerAuth: []
      parameters:
        - name: page
          in: query
          schema:
            type: integer
            default: 1
        - name: limit
          in: query
          schema:
            type: integer
            default: 20
            maximum: 100
        - name: keyword
          in: query
          description: 搜索关键词（匹配原始文件名）
          schema:
            type: string
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/Image'
                  pagination:
                    type: object
                    properties:
                      page:
                        type: integer
                      limit:
                        type: integer
                      total:
                        type: integer

  /api/images/{id}:
    delete:
      tags: [images]
      summary: 删除图片
      description: 删除指定图片（从对象存储和数据库中移除）
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: 删除成功
        '404':
          description: 图片不存在

  /api/quota:
    get:
      tags: [quota]
      summary: 查询配额
      description: 获取当前用户的存储配额使用情况
      security:
        - bearerAuth: []
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      used:
                        type: integer
                        description: 已使用字节数
                      limit:
                        type: integer
                        description: 配额上限字节数
                      percentage:
                        type: number
                        description: 使用百分比

  /api/keys:
    get:
      tags: [keys]
      summary: 获取 Access Key 列表
      security:
        - bearerAuth: []
      responses:
        '200':
          description: 成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: array
                    items:
                      $ref: '#/components/schemas/AccessKey'

    post:
      tags: [keys]
      summary: 创建 Access Key
      description: 创建用于程序调用的 Access Key
      security:
        - bearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [keyName]
              properties:
                keyName:
                  type: string
                  description: Key 名称
                  example: my-app
                expiresIn:
                  type: integer
                  description: 过期时间（天数），不填则永不过期
                  example: 30
      responses:
        '200':
          description: 创建成功
          content:
            application/json:
              schema:
                type: object
                properties:
                  success:
                    type: boolean
                  data:
                    type: object
                    properties:
                      accessKey:
                        type: string
                        description: Access Key ID
                      secretKey:
                        type: string
                        description: Secret Key（仅显示一次，请妥善保存）

  /api/keys/{id}:
    delete:
      tags: [keys]
      summary: 删除 Access Key
      security:
        - bearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        '200':
          description: 删除成功

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: JWT Token 认证

  schemas:
    User:
      type: object
      properties:
        id:
          type: string
          description: 用户ID
        email:
          type: string
          description: 邮箱
        username:
          type: string
          description: 用户名
        status:
          type: string
          enum: [unverified, pending_payment, active, premium]
          description: 账户状态
        quotaUsed:
          type: integer
          description: 已使用配额（字节）
        quotaLimit:
          type: integer
          description: 配额上限（字节）

    Image:
      type: object
      properties:
        id:
          type: string
          description: 图片ID
        url:
          type: string
          format: uri
          description: 图片 CDN 地址
          example: https://img.qname.com/u/123/abc123.webp
        thumbnail:
          type: string
          format: uri
          description: 缩略图 CDN 地址
          example: https://img.qname.com/t/123/abc123.webp
        size:
          type: integer
          description: 文件大小（字节）
        width:
          type: integer
          description: 图片宽度
        height:
          type: integer
          description: 图片高度

    FileItem:
      type: object
      properties:
        id:
          type: string
        originalName:
          type: string
          description: 原始文件名
        fileName:
          type: string
        fileSize:
          type: string
          description: 文件大小（字节，字符串）
        mimeType:
          type: string
        extension:
          type: string
        cdnUrl:
          type: string
          format: uri
          description: 公开访问地址
          example: https://img.qname.com/files/123/2026/09/abc123.webp
        thumbUrl:
          type: string
          format: uri
          nullable: true
          description: 缩略图地址（仅图片有）
          example: https://img.qname.com/thumbs/123/2026/09/abc123.webp
        folderId:
          type: string
          nullable: true
        isPublic:
          type: boolean
        downloadCount:
          type: string
        createdAt:
          type: string
          format: date-time

    AccessKey:
      type: object
      properties:
        id:
          type: string
        keyName:
          type: string
        accessKey:
          type: string
        lastUsedAt:
          type: string
          format: date-time
          nullable: true
        expiresAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time

    Error:
      type: object
      properties:
        error:
          type: string
          description: 错误信息