openapi: 3.0.3
info:
  title: Exhibition Calendar Mock API
  version: "0.1.0"
  description: |
    給展覽日曆前端對接用的 mock 後端。
    展覽資料寫在 worker/src/exhibition/seed.json；來源網址擷取是唯一會出站的動作。
    真實 KLOOK／KKDAY 網址不要寫在前端，請只帶 offerId 走分潤轉向。
    前綴固定 /api/exhibition；舊的 /api/exhibitions、/api/affiliate、/api/ingest 已移除。
servers:
  - url: https://ai-dev.succ.work
    description: Tunnel 即時預覽（筆電要開）
  - url: https://ai-dev-lab.a8092947.workers.dev
    description: Cloudflare 公開 demo（筆電可關）
  - url: http://127.0.0.1:8788
    description: 本機直打 Worker
paths:
  /api/exhibition/health:
    get:
      summary: 健康檢查
      responses:
        "200":
          description: OK
  /api/exhibition/exhibitions:
    get:
      summary: 展覽清單
      parameters:
        - in: query
          name: from
          schema: { type: string, format: date }
          description: 區間起日 YYYY-MM-DD，與展期重疊即列入
        - in: query
          name: to
          schema: { type: string, format: date }
        - in: query
          name: city
          schema: { type: string, example: 台北市 }
        - in: query
          name: category
          schema: { type: string, example: craft }
        - in: query
          name: q
          schema: { type: string }
          description: 展名／場館／縣市模糊搜尋
      responses:
        "200":
          description: 清單
          content:
            application/json:
              schema:
                type: object
                properties:
                  count: { type: integer }
                  items:
                    type: array
                    items: { $ref: "#/components/schemas/Exhibition" }
  /api/exhibition/exhibitions/{id}:
    get:
      summary: 展覽詳情（含 offers 與順路安排）
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: 詳情
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ExhibitionDetail" }
        "404":
          description: 找不到
  /api/exhibition/exhibitions/{id}/offers:
    get:
      summary: 該展票券／體驗 offer
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: offer 清單
  /api/exhibition/exhibitions/{id}/side-trips:
    get:
      summary: 看展前後順路安排
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string }
      responses:
        "200":
          description: 順路清單
  /api/exhibition/affiliate/redirect:
    get:
      summary: 分潤轉向（前端只傳 offerId）
      parameters:
        - in: query
          name: offerId
          required: true
          schema: { type: string, example: offer-ceramic-klook }
      responses:
        "302":
          description: 導向白名單後的合作平台
        "404":
          description: offerId 不在白名單
  /api/exhibition/ingest/source:
    post:
      summary: 簡易爬蟲：抓來源頁 OG／JSON-LD
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url]
              properties:
                url: { type: string, format: uri }
      responses:
        "200":
          description: 擷取結果（不寫入展覽主檔）
        "400":
          description: 網址不合法或內網
  /api/exhibition/ingest/recent:
    get:
      summary: 最近擷取紀錄（記憶體，重啟即消失）
      responses:
        "200":
          description: 最近最多 10 筆
components:
  schemas:
    Exhibition:
      type: object
      properties:
        id: { type: string, example: demo-ceramic-life }
        title: { type: string }
        city: { type: string }
        venue: { type: string }
        startDate: { type: string, format: date }
        endDate: { type: string, format: date }
        dateText: { type: string, example: "5/23 - 5/28" }
        priceFrom: { type: number }
        currency: { type: string, example: TWD }
        priceText: { type: string, example: "NT$280 起" }
        category: { type: string, example: craft }
        categoryLabel: { type: string, example: 工藝 }
        imageUrl: { type: string }
        summary: { type: string }
        highlights:
          type: array
          items: { type: string }
        geo:
          type: object
          properties:
            lat: { type: number }
            lng: { type: number }
    Offer:
      type: object
      properties:
        id: { type: string, example: offer-ceramic-klook }
        exhibitionId: { type: string }
        type:
          type: string
          enum: [exhibition_ticket, local_experience, nearby_attraction, transport]
        provider: { type: string }
        title: { type: string }
        priceFrom: { type: number }
        currency: { type: string }
        instantConfirm: { type: boolean }
        cancellable: { type: boolean }
        availableDate: { type: string, format: date, nullable: true }
    ExhibitionDetail:
      allOf:
        - $ref: "#/components/schemas/Exhibition"
        - type: object
          properties:
            offers:
              type: array
              items: { $ref: "#/components/schemas/Offer" }
            sideTrips:
              type: array
              items: { type: object }
    Error:
      type: object
      properties:
        error:
          type: object
          properties:
            code: { type: string }
            message: { type: string }
