From 849c33e83ad598919c1fdc5233307003809e258f Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 25 May 2026 00:45:08 +0800 Subject: [PATCH 1/3] =?UTF-8?q?chore:=E6=AD=B8=E6=AA=94=20api-performance-?= =?UTF-8?q?optimization=EF=BC=8C=E5=90=8C=E6=AD=A5=E4=B8=BB=E8=A6=8F?= =?UTF-8?q?=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 移至 openspec/changes/archive/2026-05-25-api-performance-optimization/ - 新增主規格:api-cache-layer、db-index-optimization - 更新 review-lifecycle 規格(補分頁、eager load) Co-Authored-By: Claude Sonnet 4.6 --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/api-cache-layer/spec.md | 0 .../specs/db-index-optimization/spec.md | 0 .../specs/review-lifecycle/spec.md | 0 .../tasks.md | 0 openspec/specs/api-cache-layer/spec.md | 66 +++++++++++++++++++ openspec/specs/db-index-optimization/spec.md | 29 ++++++++ openspec/specs/review-lifecycle/spec.md | 16 +++-- 10 files changed, 107 insertions(+), 4 deletions(-) rename openspec/changes/{api-performance-optimization => archive/2026-05-25-api-performance-optimization}/.openspec.yaml (100%) rename openspec/changes/{api-performance-optimization => archive/2026-05-25-api-performance-optimization}/design.md (100%) rename openspec/changes/{api-performance-optimization => archive/2026-05-25-api-performance-optimization}/proposal.md (100%) rename openspec/changes/{api-performance-optimization => archive/2026-05-25-api-performance-optimization}/specs/api-cache-layer/spec.md (100%) rename openspec/changes/{api-performance-optimization => archive/2026-05-25-api-performance-optimization}/specs/db-index-optimization/spec.md (100%) rename openspec/changes/{api-performance-optimization => archive/2026-05-25-api-performance-optimization}/specs/review-lifecycle/spec.md (100%) rename openspec/changes/{api-performance-optimization => archive/2026-05-25-api-performance-optimization}/tasks.md (100%) create mode 100644 openspec/specs/api-cache-layer/spec.md create mode 100644 openspec/specs/db-index-optimization/spec.md diff --git a/openspec/changes/api-performance-optimization/.openspec.yaml b/openspec/changes/archive/2026-05-25-api-performance-optimization/.openspec.yaml similarity index 100% rename from openspec/changes/api-performance-optimization/.openspec.yaml rename to openspec/changes/archive/2026-05-25-api-performance-optimization/.openspec.yaml diff --git a/openspec/changes/api-performance-optimization/design.md b/openspec/changes/archive/2026-05-25-api-performance-optimization/design.md similarity index 100% rename from openspec/changes/api-performance-optimization/design.md rename to openspec/changes/archive/2026-05-25-api-performance-optimization/design.md diff --git a/openspec/changes/api-performance-optimization/proposal.md b/openspec/changes/archive/2026-05-25-api-performance-optimization/proposal.md similarity index 100% rename from openspec/changes/api-performance-optimization/proposal.md rename to openspec/changes/archive/2026-05-25-api-performance-optimization/proposal.md diff --git a/openspec/changes/api-performance-optimization/specs/api-cache-layer/spec.md b/openspec/changes/archive/2026-05-25-api-performance-optimization/specs/api-cache-layer/spec.md similarity index 100% rename from openspec/changes/api-performance-optimization/specs/api-cache-layer/spec.md rename to openspec/changes/archive/2026-05-25-api-performance-optimization/specs/api-cache-layer/spec.md diff --git a/openspec/changes/api-performance-optimization/specs/db-index-optimization/spec.md b/openspec/changes/archive/2026-05-25-api-performance-optimization/specs/db-index-optimization/spec.md similarity index 100% rename from openspec/changes/api-performance-optimization/specs/db-index-optimization/spec.md rename to openspec/changes/archive/2026-05-25-api-performance-optimization/specs/db-index-optimization/spec.md diff --git a/openspec/changes/api-performance-optimization/specs/review-lifecycle/spec.md b/openspec/changes/archive/2026-05-25-api-performance-optimization/specs/review-lifecycle/spec.md similarity index 100% rename from openspec/changes/api-performance-optimization/specs/review-lifecycle/spec.md rename to openspec/changes/archive/2026-05-25-api-performance-optimization/specs/review-lifecycle/spec.md diff --git a/openspec/changes/api-performance-optimization/tasks.md b/openspec/changes/archive/2026-05-25-api-performance-optimization/tasks.md similarity index 100% rename from openspec/changes/api-performance-optimization/tasks.md rename to openspec/changes/archive/2026-05-25-api-performance-optimization/tasks.md diff --git a/openspec/specs/api-cache-layer/spec.md b/openspec/specs/api-cache-layer/spec.md new file mode 100644 index 0000000..51dadc9 --- /dev/null +++ b/openspec/specs/api-cache-layer/spec.md @@ -0,0 +1,66 @@ +### Requirement: Redis 作為快取驅動 + +系統 SHALL 使用 Redis 作為 Laravel 快取驅動,取代現有的 `database` driver。 + +#### Scenario: 環境設定正確 + +- **WHEN** `.env` 中 `CACHE_STORE=redis`、`REDIS_HOST=redis`(Docker service name)、`REDIS_CLIENT=predis` +- **THEN** `docker-compose up` 後 `php artisan cache:clear` 執行成功,無連線錯誤 + +#### Scenario: Redis service 在 docker-compose 中存在 + +- **WHEN** 執行 `docker-compose up` +- **THEN** `redis` container 正常啟動,`cfdive-app` 可連線至 Redis + +--- + +### Requirement: Admin 統計數據快取 + +`GET /api/admin/stats` SHALL 使用 `Cache::remember()` 快取結果,TTL 5 分鐘,Cache key 為 `admin_stats`。 + +#### Scenario: 首次請求寫入快取 + +- **WHEN** Admin 送出 `GET /api/admin/stats`,且快取中無 `admin_stats` key +- **THEN** 系統執行 DB 查詢並將結果寫入 Redis,回傳統計數據 + +#### Scenario: 後續請求命中快取 + +- **WHEN** Admin 在 5 分鐘內再次送出 `GET /api/admin/stats` +- **THEN** 系統直接從 Redis 回傳,不執行任何 DB 查詢 + +#### Scenario: 快取過期後自動重整 + +- **WHEN** 5 分鐘 TTL 到期後再次請求 +- **THEN** 系統重新執行 DB 查詢並更新快取 + +--- + +### Requirement: 課程列表快取 + +`GET /api/diving-offers` SHALL 快取搜尋結果,TTL 3 分鐘,Cache key 包含查詢參數的 hash,並使用 `diving_offers` tag 管理失效。 + +#### Scenario: 相同搜尋條件命中快取 + +- **WHEN** 同樣的搜尋參數(region、tag、keyword 等)在 3 分鐘內再次請求 +- **THEN** 系統從 Redis 回傳,不執行 DB 查詢 + +#### Scenario: Provider 異動課程時清除快取 + +- **WHEN** Provider 成功新增、修改或刪除課程(`POST/PUT/DELETE /api/provider/offers`) +- **THEN** 系統呼叫 `Cache::tags(['diving_offers'])->flush()` 清除所有課程列表快取,下次請求重新查詢 + +--- + +### Requirement: 評價分布快取 + +`GET /api/diving-offers/{id}/reviews` 的 `distribution`(1–5 星分布統計)SHALL 獨立快取,TTL 10 分鐘,Cache key 為 `offer_review_distribution_{id}`。 + +#### Scenario: 分布統計命中快取 + +- **WHEN** 同一課程 reviews 端點在 10 分鐘內再次請求 +- **THEN** `distribution` 欄位從 Redis 取得,不執行 `GROUP BY` SQL + +#### Scenario: 新增/修改/刪除評價時清除分布快取 + +- **WHEN** Member 成功新增、修改或刪除某課程的評價 +- **THEN** 系統呼叫 `Cache::forget("offer_review_distribution_{offerId}")` 清除對應快取 diff --git a/openspec/specs/db-index-optimization/spec.md b/openspec/specs/db-index-optimization/spec.md new file mode 100644 index 0000000..01f72e9 --- /dev/null +++ b/openspec/specs/db-index-optimization/spec.md @@ -0,0 +1,29 @@ +### Requirement: Notifications 表補複合索引 + +`notifications` 表 SHALL 新增 `[notifiable_type, notifiable_id, read_at]` 複合索引,以加速 `unreadNotifications()` 查詢。 + +#### Scenario: Migration 執行成功 + +- **WHEN** 執行 `php artisan migrate` +- **THEN** `notifications` 表上存在 `notifications_notifiable_read_at_index` 複合索引,`EXPLAIN` 結果不再為 full table scan + +#### Scenario: 未讀通知查詢走索引 + +- **WHEN** 系統執行 `Notification::where('notifiable_type', User::class)->where('notifiable_id', $userId)->whereNull('read_at')->get()` +- **THEN** MySQL `EXPLAIN` 顯示使用複合索引,`type` 為 `ref` 而非 `ALL` + +--- + +### Requirement: DivingOffers 表補 provider_id 索引 + +`diving_offers` 表 SHALL 新增 `provider_id` 單欄索引,以加速 Provider 課程列表查詢。 + +#### Scenario: Migration 執行成功 + +- **WHEN** 執行 `php artisan migrate` +- **THEN** `diving_offers` 表上存在 `provider_id` 索引 + +#### Scenario: Provider 課程列表查詢走索引 + +- **WHEN** 系統執行 `DivingOffer::where('provider_id', $providerId)->get()` +- **THEN** MySQL `EXPLAIN` 顯示使用 `provider_id` 索引,`type` 為 `ref` diff --git a/openspec/specs/review-lifecycle/spec.md b/openspec/specs/review-lifecycle/spec.md index e5f23fc..a0d4e15 100644 --- a/openspec/specs/review-lifecycle/spec.md +++ b/openspec/specs/review-lifecycle/spec.md @@ -55,11 +55,15 @@ Member SHALL 能刪除自己的評價,Admin SHALL 能刪除任何評價。 - **THEN** 系統回傳 403 ### Requirement: 評價公開顯示(匿名) -任何人(含未登入)SHALL 能查看課程評價列表,評價人統一顯示為「匿名潛水者」。Provider 在 Coach Portal 亦可查看自己課程的評價(只讀)。 +任何人(含未登入)SHALL 能查看課程評價列表,評價人統一顯示為「匿名潛水者」。Provider 在 Coach Portal 亦可查看自己課程的評價(只讀)。評價列表 SHALL 支援分頁,預設每頁 20 筆,最大 50 筆。 -#### Scenario: 取得評價列表(含 summary) -- **WHEN** 任何人送出 `GET /api/diving-offers/{id}/reviews?sort=helpful|rating|newest` -- **THEN** 系統回傳 `summary`(平均星等、總數、1–5 星分布)與 `reviews` 列表;`reviewer_name` 一律為「匿名潛水者」;已登入 Member 額外回傳 `is_mine`;未登入 `has_voted` 固定為 `false`、`is_mine` 欄位省略 +#### Scenario: 取得評價列表(含 summary,含分頁) +- **WHEN** 任何人送出 `GET /api/diving-offers/{id}/reviews?sort=helpful|rating|newest&page=1&per_page=20` +- **THEN** 系統回傳 `summary`(平均星等、總數、1–5 星分布)與分頁後的 `reviews` 列表;`reviewer_name` 一律為「匿名潛水者」;已登入 Member 額外回傳 `is_mine`;未登入 `has_voted` 固定為 `false`、`is_mine` 欄位省略;回傳包含分頁 meta(`current_page`、`last_page`、`per_page`、`total`) + +#### Scenario: per_page 超出上限時截斷 +- **WHEN** 請求帶有 `per_page=200` +- **THEN** 系統以 `per_page=50` 處理,不回傳錯誤 #### Scenario: 三種排序 - **WHEN** `sort=helpful`(預設) @@ -69,6 +73,10 @@ Member SHALL 能刪除自己的評價,Admin SHALL 能刪除任何評價。 - **WHEN** `sort=newest` - **THEN** 依 `created_at DESC` 排序 +#### Scenario: votes 透過 eager loading 查詢 +- **WHEN** 已登入 Member 送出評價列表請求 +- **THEN** `has_voted` 欄位透過 eager loaded `votes` collection 判斷,不額外發 SQL 查詢 + ### Requirement: 課程完成標記(評價資格觸發) Provider 或 Admin SHALL 能手動將 confirmed 預約標記為 completed,讓 Member 可立即評價,不需等待排程。 From e8cd82cdee084bbad1c43169a16b864b8a8fc8d8 Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 25 May 2026 00:45:15 +0800 Subject: [PATCH 2/3] =?UTF-8?q?chore:=E6=96=B0=E5=A2=9E=20swagger-api-docs?= =?UTF-8?q?-completion=20change=20=E8=A6=8F=E5=8A=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit proposal / design / specs / tasks 全部就緒,21 tasks 待實作 Co-Authored-By: Claude Sonnet 4.6 --- .../.openspec.yaml | 2 + .../swagger-api-docs-completion/design.md | 63 +++++++++++++++++++ .../swagger-api-docs-completion/proposal.md | 37 +++++++++++ .../specs/swagger-admin-api/spec.md | 40 ++++++++++++ .../specs/swagger-auth-supplement/spec.md | 21 +++++++ .../specs/swagger-member-api/spec.md | 33 ++++++++++ .../specs/swagger-provider-api/spec.md | 43 +++++++++++++ .../specs/swagger-public-api/spec.md | 30 +++++++++ .../swagger-api-docs-completion/tasks.md | 44 +++++++++++++ 9 files changed, 313 insertions(+) create mode 100644 openspec/changes/swagger-api-docs-completion/.openspec.yaml create mode 100644 openspec/changes/swagger-api-docs-completion/design.md create mode 100644 openspec/changes/swagger-api-docs-completion/proposal.md create mode 100644 openspec/changes/swagger-api-docs-completion/specs/swagger-admin-api/spec.md create mode 100644 openspec/changes/swagger-api-docs-completion/specs/swagger-auth-supplement/spec.md create mode 100644 openspec/changes/swagger-api-docs-completion/specs/swagger-member-api/spec.md create mode 100644 openspec/changes/swagger-api-docs-completion/specs/swagger-provider-api/spec.md create mode 100644 openspec/changes/swagger-api-docs-completion/specs/swagger-public-api/spec.md create mode 100644 openspec/changes/swagger-api-docs-completion/tasks.md diff --git a/openspec/changes/swagger-api-docs-completion/.openspec.yaml b/openspec/changes/swagger-api-docs-completion/.openspec.yaml new file mode 100644 index 0000000..6894814 --- /dev/null +++ b/openspec/changes/swagger-api-docs-completion/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-24 diff --git a/openspec/changes/swagger-api-docs-completion/design.md b/openspec/changes/swagger-api-docs-completion/design.md new file mode 100644 index 0000000..8951750 --- /dev/null +++ b/openspec/changes/swagger-api-docs-completion/design.md @@ -0,0 +1,63 @@ +## Context + +現有 `app/Docs/AuthApiDoc.php` 已建立 Swagger 基礎設定(`@OA\Info`、`@OA\Server`、`@OA\SecurityScheme`)並文件化 15 個 Auth 端點。其餘 61 個端點分散在 8 個 Controller 中,沒有任何 `@OA` 標注。`darkaonline/l5-swagger` 已安裝,執行 `php artisan l5-swagger:generate` 即可重新產生 JSON。 + +## Goals / Non-Goals + +**Goals:** +- 補齊所有 61 個未文件化端點的 `@OA` 標注 +- 定義共用 Schema(分頁 meta、統一錯誤格式、DivingOffer、Review、Booking 等) +- 文件依模組分檔,每個新檔對應一個 `app/Docs/*Doc.php` +- 每個端點包含:summary、parameters、request body(POST/PUT)、response(200/201/400/401/403/422) + +**Non-Goals:** +- 不修改任何 Controller / Route / Model 邏輯 +- 不為尚未實作的端點(金流、訂閱)補文件 + +## Decisions + +### 決策 1:分檔策略 — 依模組建立獨立 Doc 類別 + +**選擇**:每個模組一個 `app/Docs/*Doc.php` + +| 檔案 | 涵蓋端點 | +|------|---------| +| `AuthApiDoc.php` | **修正路徑錯誤**(`/register/member` → `/member/register` 等);補 `POST /logout`、`GET /user` | +| `PublicApiDoc.php` | 公開端點(diving-offers、reviews、schedules) | +| `MemberApiDoc.php` | Member bookings、reviews、helpful、notifications | +| `ProviderApiDoc.php` | Provider offers、images、schedules、bookings | +| `AdminApiDoc.php` | Admin stats、users、offers、bookings、reviews | +| `AuthSupplementDoc.php` | **僅** Google OAuth 2 個端點(`SocialAuthController`) | + +**理由**:單一大檔難以維護;按模組分檔讓每個 Doc 類別職責清晰,對應 Controller 結構 + +**替代方案**:直接在 Controller 方法上加 `@OA` → 拒絕,Controller 文件與業務邏輯混雜,讀性差 + +### 決策 2:共用 Schema 集中定義在 `PublicApiDoc.php` + +共用 Schema(`DivingOfferSchema`、`ReviewSchema`、`BookingSchema`、`PaginationMeta`、`ApiErrorResponse`)集中放在 `PublicApiDoc.php` 頂層 `@OA\Schema` 標注。 + +**理由**:l5-swagger 掃描所有 `app/` 目錄,Schema 定義位置不影響其他檔案引用;放在 Public 層語意上最自然(核心資料結構) + +### 決策 3:Security 標注策略 + +- 公開端點:不加 `security` +- Member 端點:`security={{"bearerAuth": {}}}`(現有 scheme) +- Provider 端點:`security={{"bearerAuth": {}}}` +- Admin 端點:`security={{"bearerAuth": {}}}` + +三個 role 共用同一個 `bearerAuth` scheme,由後端 middleware 區分角色,不需要在 Swagger 定義三個不同 scheme。 + +## Risks / Trade-offs + +- **[Risk] 手寫標注與實際 response 不同步** → 以實際 Controller 程式碼為準手寫;未來有 API 變更時需同步更新 Doc 檔 +- **[Risk] Schema 巢狀複雜導致 Swagger UI 渲染慢** → 分頁 response 使用 `allOf` 組合,避免深層巢狀 + +## Migration Plan + +1. 依序建立 5 個新 Doc 檔案 +2. 在容器內執行 `php artisan l5-swagger:generate` +3. 開啟 `http://localhost:8080/api/documentation` 確認 UI 正確渲染 +4. 確認所有 tag 與端點均出現在 Swagger UI + +**Rollback**:直接刪除新增的 Doc 檔案,重新 generate 即可回復到只有 Auth 端點的狀態 diff --git a/openspec/changes/swagger-api-docs-completion/proposal.md b/openspec/changes/swagger-api-docs-completion/proposal.md new file mode 100644 index 0000000..9a49cc5 --- /dev/null +++ b/openspec/changes/swagger-api-docs-completion/proposal.md @@ -0,0 +1,37 @@ +## Why + +目前 Swagger 文件僅涵蓋 15 個 Auth 端點,其餘 61 個端點(課程、評價、預約、通知、圖片上傳、Coach Portal、Admin Panel)完全未文件化,導致前後端協作與第三方整合缺乏規格依據。現有 `darkaonline/l5-swagger` 套件已安裝,補全文件的邊際成本低。 + +## What Changes + +- 為所有未文件化端點補上 `@OA` PHPDoc 標注(`app/Docs/` 目錄下依模組分檔) +- 定義完整的 Request / Response Schema(含分頁 meta、錯誤格式) +- 補全 Swagger Security 設定(Bearer token per-role) +- 重新產生 `storage/api-docs/api-docs.json` + +**端點範圍(新增 61 個):** +- Public API:GET diving-offers 列表/詳情、reviews、schedules(4) +- Member API:bookings CRUD、reviews CRUD、helpful 投票、notifications(10) +- Provider API:offers CRUD + 圖片、schedules CRUD、bookings 管理(19) +- Admin API:stats、members、providers、offers、bookings、reviews 管理(17) +- Auth 補全:Google OAuth、change-password(3 roles)(7) + +## Capabilities + +### New Capabilities + +- `swagger-public-api`:公開端點 Swagger 文件(diving-offers、reviews、schedules) +- `swagger-member-api`:Member 端點 Swagger 文件(bookings、reviews、notifications) +- `swagger-provider-api`:Provider 端點 Swagger 文件(offers、images、schedules、bookings) +- `swagger-admin-api`:Admin 端點 Swagger 文件(stats、user/offer/booking/review 管理) +- `swagger-auth-supplement`:補全 Auth 端點(Google OAuth、change-password) + +### Modified Capabilities + +(無,本 change 純為文件補全,不變更任何 API 行為) + +## Impact + +- 新增:`app/Docs/PublicApiDoc.php`、`MemberApiDoc.php`、`ProviderApiDoc.php`、`AdminApiDoc.php`、`AuthSupplementDoc.php` +- 更新:`storage/api-docs/api-docs.json`(自動產生) +- 不影響任何 Controller、Model、Route、Migration diff --git a/openspec/changes/swagger-api-docs-completion/specs/swagger-admin-api/spec.md b/openspec/changes/swagger-api-docs-completion/specs/swagger-admin-api/spec.md new file mode 100644 index 0000000..c5b68e3 --- /dev/null +++ b/openspec/changes/swagger-api-docs-completion/specs/swagger-admin-api/spec.md @@ -0,0 +1,40 @@ +## ADDED Requirements + +### Requirement: Admin 端點 Swagger 文件 + +`app/Docs/AdminApiDoc.php` SHALL 文件化所有需要 Admin Bearer token 的管理端點。 + +#### Scenario: Admin stats 端點文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** `GET /admin/stats` 端點有文件,response 包含 `total_members`、`total_providers`、`total_offers`;403 非 admin 亦有定義 + +#### Scenario: Admin 會員管理端點文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 以下端點均有文件,標示 `security: bearerAuth`: + - `GET /admin/members`(分頁 response,含 member_profile) + - `GET /admin/members/{id}` + - `PUT /admin/members/{id}/toggle-active`(response: is_active 新狀態) + - `GET /admin/check-member/{id}` + +#### Scenario: Admin 教練管理端點文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 以下端點均有文件: + - `GET /admin/providers`(分頁 response,含 provider_profile) + - `GET /admin/providers/{id}` + - `PUT /admin/providers/{id}/toggle-active` + - `PUT /admin/providers/{id}/toggle-verified`(response: is_verified 新狀態) + - `GET /admin/check-provider/{id}` + +#### Scenario: Admin 課程、預約、評價管理端點文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 以下端點均有文件: + - `GET /admin/offers`(分頁 response) + - `DELETE /admin/offers/{id}` + - `GET /admin/bookings`(分頁 response) + - `PUT /admin/bookings/{id}/complete` + - `GET /admin/reviews`(分頁 response,含 per_page 參數,最大 100) + - `DELETE /admin/reviews/{id}` diff --git a/openspec/changes/swagger-api-docs-completion/specs/swagger-auth-supplement/spec.md b/openspec/changes/swagger-api-docs-completion/specs/swagger-auth-supplement/spec.md new file mode 100644 index 0000000..76771d8 --- /dev/null +++ b/openspec/changes/swagger-api-docs-completion/specs/swagger-auth-supplement/spec.md @@ -0,0 +1,21 @@ +## ADDED Requirements + +### Requirement: Auth 補全端點 Swagger 文件 + +`app/Docs/AuthSupplementDoc.php` SHALL 補全未文件化的 Auth 相關端點(Google OAuth、change-password)。 + +#### Scenario: Google OAuth 端點文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 以下端點均有文件: + - `GET /auth/google/redirect`(response: redirect_url,說明用途為取得 Google OAuth redirect URL) + - `GET /auth/google/callback`(response: token + user,說明此為 OAuth callback,通常由瀏覽器自動呼叫) + +#### Scenario: change-password 端點文件化(三個 role) + +- **WHEN** 開啟 Swagger UI +- **THEN** 以下端點均有文件,各自標示對應 role 的 `security: bearerAuth`: + - `PUT /member/change-password`(request: current_password、password、password_confirmation) + - `PUT /provider/change-password`(同上) + - `PUT /admin/change-password`(同上) + - 422 驗證失敗 response 亦有定義 diff --git a/openspec/changes/swagger-api-docs-completion/specs/swagger-member-api/spec.md b/openspec/changes/swagger-api-docs-completion/specs/swagger-member-api/spec.md new file mode 100644 index 0000000..be9acb0 --- /dev/null +++ b/openspec/changes/swagger-api-docs-completion/specs/swagger-member-api/spec.md @@ -0,0 +1,33 @@ +## ADDED Requirements + +### Requirement: Member 端點 Swagger 文件 + +`app/Docs/MemberApiDoc.php` SHALL 文件化所有需要 Member Bearer token 的端點(bookings、reviews、helpful 投票、notifications)。 + +#### Scenario: Member bookings 端點文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 以下端點均有文件,並標示 `security: bearerAuth`: + - `POST /member/bookings`(request: schedule_id;response 201: Booking) + - `GET /member/bookings`(response: Booking 陣列 + 分頁 meta) + - `GET /member/bookings/{id}`(response: Booking 詳情) + - `DELETE /member/bookings/{id}`(response 200: message) + +#### Scenario: Member reviews 端點文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 以下端點均有文件: + - `POST /member/reviews`(request: diving_offer_id、rating、comment;403 資格驗證失敗;422 重複評價) + - `PUT /member/reviews/{id}`(request: rating?、comment?;403 非本人) + - `DELETE /member/reviews/{id}`(403 非本人) + - `POST /reviews/{id}/helpful`(toggle,response: helpful_count、has_voted) + +#### Scenario: Member notifications 端點文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 以下端點均有文件: + - `GET /notifications`(response: 通知陣列 + 分頁 meta) + - `GET /notifications/unread-count`(response: count) + - `PATCH /notifications/{id}/read` + - `PATCH /notifications/read-all` + - `DELETE /notifications/{id}` diff --git a/openspec/changes/swagger-api-docs-completion/specs/swagger-provider-api/spec.md b/openspec/changes/swagger-api-docs-completion/specs/swagger-provider-api/spec.md new file mode 100644 index 0000000..ca52bb9 --- /dev/null +++ b/openspec/changes/swagger-api-docs-completion/specs/swagger-provider-api/spec.md @@ -0,0 +1,43 @@ +## ADDED Requirements + +### Requirement: Provider 端點 Swagger 文件 + +`app/Docs/ProviderApiDoc.php` SHALL 文件化所有需要 Provider Bearer token 的端點(offers CRUD、圖片、schedules、bookings)。 + +#### Scenario: Provider offers CRUD 端點文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 以下端點均有文件,標示 `security: bearerAuth`: + - `GET /provider/offers`(分頁 response) + - `POST /provider/offers`(request: title、location、spot?、price、region、tag?、badges?、description?) + - `GET /provider/offers/{id}`(403 非本人) + - `PUT /provider/offers/{id}`(request 同上,所有欄位 nullable) + - `DELETE /provider/offers/{id}` + +#### Scenario: Provider 圖片管理端點文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 以下端點均有文件: + - `POST /provider/offers/{id}/cover`(multipart/form-data: image;response: cover_image_url) + - `DELETE /provider/offers/{id}/cover` + - `POST /provider/offers/{id}/images`(multipart/form-data: images[];最多 10 張) + - `DELETE /provider/images/{id}` + +#### Scenario: Provider schedules 端點文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 以下端點均有文件: + - `GET /provider/schedules`(query: offer_id?;response: CourseSchedule 陣列) + - `POST /provider/schedules`(request: diving_offer_id、date、period、capacity) + - `PUT /provider/schedules/{id}` + - `DELETE /provider/schedules/{id}` + +#### Scenario: Provider bookings 端點文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 以下端點均有文件: + - `GET /provider/bookings`(response: Booking 陣列,含 member 資訊) + - `PUT /provider/bookings/{id}/confirm` + - `PUT /provider/bookings/{id}/reject` + - `PUT /provider/bookings/{id}/complete` + - `PUT /provider/bookings/{id}/cancel` diff --git a/openspec/changes/swagger-api-docs-completion/specs/swagger-public-api/spec.md b/openspec/changes/swagger-api-docs-completion/specs/swagger-public-api/spec.md new file mode 100644 index 0000000..1e00946 --- /dev/null +++ b/openspec/changes/swagger-api-docs-completion/specs/swagger-public-api/spec.md @@ -0,0 +1,30 @@ +## ADDED Requirements + +### Requirement: 公開端點 Swagger 文件及共用 Schema + +`app/Docs/PublicApiDoc.php` SHALL 文件化所有無需認證的公開端點,並定義全域共用 Schema。 + +#### Scenario: 共用 Schema 定義完整 + +- **WHEN** 執行 `php artisan l5-swagger:generate` +- **THEN** 以下 Schema 出現在產生的 JSON:`DivingOffer`、`Review`、`CourseSchedule`、`PaginationMeta`、`ApiErrorResponse` + +#### Scenario: GET /api/diving-offers 文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** `GET /diving-offers` 端點顯示 query parameters(`q`、`region`、`tag`、`per_page`、`page`)及包含分頁 meta 的 response schema + +#### Scenario: GET /api/diving-offers/{id} 文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** `GET /diving-offers/{id}` 端點顯示 path parameter `id` 及包含 `cover_image_url`、`images` 陣列的 response schema;404 response 亦有定義 + +#### Scenario: GET /api/diving-offers/{id}/reviews 文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 端點顯示 `sort`(helpful/rating/newest)、`page`、`per_page` 參數;response 包含 `summary`(average、total、distribution)與分頁 `meta` + +#### Scenario: GET /api/diving-offers/{id}/schedules 文件化 + +- **WHEN** 開啟 Swagger UI +- **THEN** 端點顯示 response 包含 `CourseSchedule` 陣列(id、date、period、capacity、booked_count、status) diff --git a/openspec/changes/swagger-api-docs-completion/tasks.md b/openspec/changes/swagger-api-docs-completion/tasks.md new file mode 100644 index 0000000..70166e9 --- /dev/null +++ b/openspec/changes/swagger-api-docs-completion/tasks.md @@ -0,0 +1,44 @@ +## 1. 修正 AuthApiDoc.php 既有錯誤 + +- [ ] 1.1 [後端] `AuthApiDoc.php` 修正所有路徑錯位:`/register/member` → `/member/register`、`/login/member` → `/member/login`、`/logout/member` → `/member/logout`、`/profile/member` → `/member/profile`(provider、admin 同樣修正) +- [ ] 1.2 [後端] `AuthApiDoc.php` 修正 change-password 路徑:`/password/member` → `/member/change-password`、`/password/provider` → `/provider/change-password`、`/password/admin` → `/admin/change-password` +- [ ] 1.3 [後端] `AuthApiDoc.php` 補上 `POST /logout`(通用登出)與 `GET /user`(取得當前使用者) + +## 2. AuthSupplementDoc(Google OAuth 專用) + +- [ ] 2.1 [後端] 建立 `app/Docs/AuthSupplementDoc.php`,補上 `GET /auth/google/redirect`(response: redirect_url)與 `GET /auth/google/callback`(response: token + user)兩個端點 + +## 3. 共用 Schema 與 PublicApiDoc + +- [ ] 3.1 [後端] 建立 `app/Docs/PublicApiDoc.php`,定義共用 Schema:`DivingOffer`、`Review`、`CourseSchedule`、`Booking`、`PaginationMeta`、`ApiErrorResponse` +- [ ] 3.2 [後端] `PublicApiDoc.php` 補上 `GET /diving-offers`(query: q、region、tag、per_page、page;response: DivingOffer 分頁) +- [ ] 3.3 [後端] `PublicApiDoc.php` 補上 `GET /diving-offers/{id}`(response: DivingOffer 含 cover_image_url、images 陣列;404) +- [ ] 3.4 [後端] `PublicApiDoc.php` 補上 `GET /diving-offers/{id}/reviews`(query: sort、page、per_page;response: summary + reviews 分頁 + meta) +- [ ] 3.5 [後端] `PublicApiDoc.php` 補上 `GET /diving-offers/{id}/schedules`(response: CourseSchedule 陣列) + +## 4. MemberApiDoc + +- [ ] 4.1 [後端] 建立 `app/Docs/MemberApiDoc.php`,補上 Member bookings:`POST /member/bookings`(201)、`GET /member/bookings`(分頁)、`GET /member/bookings/{id}`、`DELETE /member/bookings/{id}` +- [ ] 4.2 [後端] `MemberApiDoc.php` 補上 Member reviews:`POST /member/reviews`(403/422)、`PUT /member/reviews/{id}`(403)、`DELETE /member/reviews/{id}`(403) +- [ ] 4.3 [後端] `MemberApiDoc.php` 補上 `POST /reviews/{id}/helpful`(response: helpful_count、has_voted) +- [ ] 4.4 [後端] `MemberApiDoc.php` 補上 notifications:`GET /notifications`(分頁)、`GET /notifications/unread-count`、`PATCH /notifications/{id}/read`、`PATCH /notifications/read-all`、`DELETE /notifications/{id}` + +## 5. ProviderApiDoc + +- [ ] 5.1 [後端] 建立 `app/Docs/ProviderApiDoc.php`,補上 Provider offers CRUD:`GET /provider/offers`、`POST /provider/offers`、`GET /provider/offers/{id}`(403)、`PUT /provider/offers/{id}`、`DELETE /provider/offers/{id}` +- [ ] 5.2 [後端] `ProviderApiDoc.php` 補上圖片管理:`POST /provider/offers/{id}/cover`(multipart)、`DELETE /provider/offers/{id}/cover`、`POST /provider/offers/{id}/images`、`DELETE /provider/images/{id}` +- [ ] 5.3 [後端] `ProviderApiDoc.php` 補上 schedules:`GET /provider/schedules`(query: offer_id?)、`POST /provider/schedules`、`PUT /provider/schedules/{id}`、`DELETE /provider/schedules/{id}` +- [ ] 5.4 [後端] `ProviderApiDoc.php` 補上 bookings 管理:`GET /provider/bookings`、`PUT /provider/bookings/{id}/confirm`、`PUT /provider/bookings/{id}/reject`、`PUT /provider/bookings/{id}/complete`、`PUT /provider/bookings/{id}/cancel` + +## 6. AdminApiDoc + +- [ ] 6.1 [後端] 建立 `app/Docs/AdminApiDoc.php`,補上 `GET /admin/stats`(response: total_members/providers/offers;403) +- [ ] 6.2 [後端] `AdminApiDoc.php` 補上會員管理:`GET /admin/members`(分頁)、`GET /admin/members/{id}`、`PUT /admin/members/{id}/toggle-active`、`GET /admin/check-member/{id}` +- [ ] 6.3 [後端] `AdminApiDoc.php` 補上教練管理:`GET /admin/providers`(分頁)、`GET /admin/providers/{id}`、`PUT /admin/providers/{id}/toggle-active`、`PUT /admin/providers/{id}/toggle-verified`、`GET /admin/check-provider/{id}` +- [ ] 6.4 [後端] `AdminApiDoc.php` 補上課程/預約/評價管理:`GET /admin/offers`、`DELETE /admin/offers/{id}`、`GET /admin/bookings`、`PUT /admin/bookings/{id}/complete`、`GET /admin/reviews`(per_page 最大 100)、`DELETE /admin/reviews/{id}` + +## 7. 驗證 + +- [ ] 7.1 [整合測試] 執行 `docker exec cfdive-app php artisan l5-swagger:generate`,確認無 parse error +- [ ] 7.2 [整合測試] 開啟 `http://localhost:8080/api/documentation`,展開各 tag 確認路徑正確(`/member/register` 而非 `/register/member`) +- [ ] 7.3 [整合測試] 確認端點總數 ≥ 76,`GET /diving-offers/{id}/reviews` 顯示分頁 meta schema From 4feeeff94a573f48c1d29a19c6b0a78005ca9c94 Mon Sep 17 00:00:00 2001 From: Hank Date: Mon, 25 May 2026 01:17:25 +0800 Subject: [PATCH 3/3] =?UTF-8?q?docs:=E8=A3=9C=E5=85=A8=E6=89=80=E6=9C=89?= =?UTF-8?q?=20API=20=E7=AB=AF=E9=BB=9E=20Swagger=20=E6=96=87=E4=BB=B6?= =?UTF-8?q?=EF=BC=8873=20=E5=80=8B=E7=AB=AF=E9=BB=9E=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修正 AuthApiDoc.php 路徑錯位(/register/member → /member/register 等) 及 change-password 方法(Post→Put);補上 POST /logout、GET /user - 新增 AuthSupplementDoc.php(Google OAuth 2 端點) - 新增 PublicApiDoc.php(共用 Schema + 公開課程 4 端點) - 新增 MemberApiDoc.php(預約、評價、通知共 13 端點) - 新增 ProviderApiDoc.php(課程、圖片、時段、預約管理共 18 端點) - 新增 AdminApiDoc.php(統計、會員/教練/課程管理共 16 端點) - 更新 README.md - 歸檔 swagger-api-docs-completion change,同步主規格 Co-Authored-By: Claude Sonnet 4.6 --- README.md | 51 +- app/Docs/AdminApiDoc.php | 504 ++ app/Docs/AuthApiDoc.php | 113 +- app/Docs/AuthSupplementDoc.php | 96 + app/Docs/MemberApiDoc.php | 418 ++ app/Docs/ProviderApiDoc.php | 585 +++ app/Docs/PublicApiDoc.php | 247 + .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/swagger-admin-api/spec.md | 0 .../specs/swagger-auth-supplement/spec.md | 0 .../specs/swagger-member-api/spec.md | 0 .../specs/swagger-provider-api/spec.md | 0 .../specs/swagger-public-api/spec.md | 0 .../tasks.md | 48 +- openspec/specs/swagger-admin-api/spec.md | 40 + .../specs/swagger-auth-supplement/spec.md | 21 + openspec/specs/swagger-member-api/spec.md | 33 + openspec/specs/swagger-provider-api/spec.md | 43 + openspec/specs/swagger-public-api/spec.md | 30 + storage/api-docs/api-docs.json | 4329 ++++++++++++++++- 22 files changed, 6492 insertions(+), 66 deletions(-) create mode 100644 app/Docs/AdminApiDoc.php create mode 100644 app/Docs/AuthSupplementDoc.php create mode 100644 app/Docs/MemberApiDoc.php create mode 100644 app/Docs/ProviderApiDoc.php create mode 100644 app/Docs/PublicApiDoc.php rename openspec/changes/{swagger-api-docs-completion => archive/2026-05-25-swagger-api-docs-completion}/.openspec.yaml (100%) rename openspec/changes/{swagger-api-docs-completion => archive/2026-05-25-swagger-api-docs-completion}/design.md (100%) rename openspec/changes/{swagger-api-docs-completion => archive/2026-05-25-swagger-api-docs-completion}/proposal.md (100%) rename openspec/changes/{swagger-api-docs-completion => archive/2026-05-25-swagger-api-docs-completion}/specs/swagger-admin-api/spec.md (100%) rename openspec/changes/{swagger-api-docs-completion => archive/2026-05-25-swagger-api-docs-completion}/specs/swagger-auth-supplement/spec.md (100%) rename openspec/changes/{swagger-api-docs-completion => archive/2026-05-25-swagger-api-docs-completion}/specs/swagger-member-api/spec.md (100%) rename openspec/changes/{swagger-api-docs-completion => archive/2026-05-25-swagger-api-docs-completion}/specs/swagger-provider-api/spec.md (100%) rename openspec/changes/{swagger-api-docs-completion => archive/2026-05-25-swagger-api-docs-completion}/specs/swagger-public-api/spec.md (100%) rename openspec/changes/{swagger-api-docs-completion => archive/2026-05-25-swagger-api-docs-completion}/tasks.md (64%) create mode 100644 openspec/specs/swagger-admin-api/spec.md create mode 100644 openspec/specs/swagger-auth-supplement/spec.md create mode 100644 openspec/specs/swagger-member-api/spec.md create mode 100644 openspec/specs/swagger-provider-api/spec.md create mode 100644 openspec/specs/swagger-public-api/spec.md diff --git a/README.md b/README.md index 40d5fe5..3a9f240 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,49 @@ -![image](https://github.com/user-attachments/assets/332bf381-8faa-441f-8843-bff2c1fbde7a) -![image](https://github.com/user-attachments/assets/f0a765f5-5386-467e-80b8-4a5ef8ef0bcf) -![image](https://github.com/user-attachments/assets/5d6ad6ee-f100-4d45-9200-d61ce76ca7c4) +# CFDive Platform +潛水課程媒合平台 — 連結潛水教練與學員,提供課程瀏覽、線上預約、評價與通知等完整服務。 + +--- + +## 功能概覽 + +**會員(Member)** +- 註冊 / 登入(Email + Google OAuth) +- 瀏覽、搜尋、篩選潛水課程 +- 查看課程時段並送出預約 +- 對完成的課程留下評價(支援匿名、有幫助投票) +- 站內通知(Email + Polling) + +**教練(Provider)** +- 課程 CRUD(含封面 + 相簿圖片上傳) +- 課程時段管理 +- 預約管理(確認 / 拒絕 / 完成 / 取消) + +**管理員(Admin)** +- 平台統計數據(會員數、教練數、課程數) +- 會員與教練帳號管理(啟用 / 停用 / 審核) +- 課程、預約、評價管理 + +--- + +## 技術棧 + +| 層級 | 技術 | +|------|------| +| 後端 | PHP 8.x / Laravel 11 | +| 前端 | Vue 3 | +| 資料庫 | MySQL 8.0 | +| 快取 | Redis(predis)| +| 認證 | Laravel Sanctum + Google OAuth | +| 容器 | Docker / Docker Compose | +| API 文件 | Swagger UI(l5-swagger)| + +--- + +## API 文件 + +共 73 個端點,涵蓋: +- 認證(Email + Google OAuth) +- 公開課程查詢 +- 會員預約 / 評價 / 通知 +- 教練課程 / 時段 / 預約管理 +- 管理員後台 diff --git a/app/Docs/AdminApiDoc.php b/app/Docs/AdminApiDoc.php new file mode 100644 index 0000000..69ceeb4 --- /dev/null +++ b/app/Docs/AdminApiDoc.php @@ -0,0 +1,504 @@ +