Files
CFDivePlatform/openspec/changes/archive/2026-05-10-coach-portal/tasks.md
T
a620906209 da48a3652d feat:實作 Coach Portal — 教練後台課程管理
後端:
- Migration:diving_offers 新增 provider_id(nullable FK)
- Migration:users.role ENUM 加入 provider 值
- Migration:diving_offers.spot 改為 nullable
- AuthController:registerProvider business_name 改為選填
- AuthController:updateProviderProfile 補上 certifications / dive_sites / services / facilities / website / social_media
- ProviderOfferController:教練課程 CRUD(index/show/store/update/destroy),實作 provider_id 所有權不變式(404 → 403 兩步驟)

前端(frontend/):
- coachAuth store、coachAxios(獨立於 member auth)
- /coach/* 路由群組 + beforeEach guard
- CoachNavBar、CoachLayout(教練頁隱藏會員 NavBar)
- LoginView、RegisterView、DashboardView(表格 + 刪除確認)
- OfferFormView(新增/編輯共用)、ProfileView

OpenSpec:
- openspec/config.yaml 補入專案 context 與 rules
- 新增 specs:coach-offers-api / coach-portal-ui / provider-auth
- 更新 spec:diving-offers-api 加入 provider_id
- 歸檔:openspec/changes/archive/2026-05-10-coach-portal

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-05-10 03:34:14 +08:00

6.1 KiB
Raw Blame History

1. [後端] 資料庫 Migration

  • 1.1 建立 migrationdiving_offers 新增 provider_id 欄位(unsignedBigInteger nullable,外鍵關聯 users.idonDelete set null
  • 1.2 執行 docker exec cfdive-app php artisan migrate,確認欄位新增成功
  • 1.3 更新 DivingOffer Model$fillable 加入 provider_id

2. [後端] Provider Auth API 調整

  • 2.1 修改 registerProvider():將 business_name 改為 nullable(單人教練不一定有業者名稱),驗證規則從 required 改為 nullable|string|max:255
  • 2.2 loginProvider() 直接可用,不需改動(role 驗證、token、load profile 皆正確)
  • 2.3 logoutProvider() 直接可用,不需改動
  • 2.4 providerProfile() 直接可用,不需改動
  • 2.5 補完 updateProviderProfile():在現有更新邏輯後補上以下欄位的更新處理:
    • certificationsPADI / SSI 等認證資訊)
    • dive_sites(常駐潛點,逗號分隔字串)
    • services(提供服務類型)
    • facilities(設施說明)
    • website(官網連結)
    • social_media(社群媒體連結)
    • 同時在 Validator 規則加入這六個欄位(皆為 nullable|string
  • 2.6 用 Postman 驗證:registerbusiness_name 選填)→ login → GET profile → PUT profile(含新增欄位)→ logout

3. [後端] Coach 課程管理 API

  • 3.1 建立 ProviderOfferController,實作 index():只回傳 provider_id = auth()->id() 的課程,含分頁
  • 3.2 實作 show($id)find() null → 404provider_id !== auth()->id() → 403;否則回傳課程資料
  • 3.3 實作 store():驗證必填欄位(title / location / spot / price / region),強制將 provider_id 設為 auth()->id()(忽略 body 傳入值),回傳 201
  • 3.4 實作 update($id)find() null → 404provider_id !== auth()->id() → 403;更新欄位回傳 200
  • 3.5 實作 destroy($id)find() null → 404provider_id !== auth()->id() → 403;刪除回傳 200
  • 3.6 在 routes/api.phpprovider middleware group 新增課程路由:GET(index) / GET(show) / POST / PUT / DELETE
  • 3.7 用 Postman 驗證:新增 → 列表 → 單筆詳情 → 更新 → 刪除;另測試跨教練操作:存在課程回 403、不存在 ID 回 404

4. [前端] coachAuth Store 與基礎設施

  • 4.1 建立 frontend/src/stores/coachAuth.js:管理 coach_token / coach_user,實作 init() / setAuth() / logout()
  • 4.2 建立 frontend/src/api/coachAxios.js:獨立 Axios instancerequest interceptor 讀 coach_token
  • 4.3 在 frontend/src/router/index.js 新增 /coach/* 路由群組:login / dashboard / offers/new / offers/:id/edit / profile
  • 4.4 /coach/*login 除外)加上 beforeEach guard,未登入導向 /coach/login
  • 4.5 在 App.vueonMounted 加入 coachAuth.init()

5. [前端] Coach Layout 與導覽

  • 5.1 建立 frontend/src/components/CoachNavBar.vue:顯示教練姓名、「我的課程」、「個人資料」連結與登出按鈕
  • 5.2 建立 frontend/src/layouts/CoachLayout.vue:包含 CoachNavBar + <RouterView>,供所有 /coach/* 頁面使用

6. [前端] 教練認證頁面

  • 6.1 建立 frontend/src/views/coach/RegisterView.vue:帳號資訊 + 業者資訊兩段表單,送出呼叫 POST /api/provider/register,成功導向 /coach/login?registered=1,失敗顯示欄位錯誤;business_name 選填
  • 6.2 建立 frontend/src/views/coach/LoginView.vueemail/password 表單,送出呼叫 POST /api/provider/login,成功存 token 並導向 /coach/dashboard,失敗顯示錯誤;若 query 有 ?registered=1 顯示「註冊成功,請登入」

7. [前端] 課程 Dashboard

  • 7.1 建立 frontend/src/views/coach/DashboardView.vue:掛載時呼叫 GET /api/provider/offers,以表格列出課程(標題、地點、價格)
  • 7.2 新增「新增課程」按鈕,點擊導向 /coach/offers/new
  • 7.3 每列新增「編輯」按鈕,點擊導向 /coach/offers/:id/edit
  • 7.4 每列新增「刪除」按鈕:顯示確認 dialog,確認後呼叫 DELETE /api/provider/offers/{id},成功後重新載入列表
  • 7.5 無課程時顯示空狀態提示

8. [前端] 課程表單(新增 / 編輯)

  • 8.1 建立 frontend/src/views/coach/OfferFormView.vue(新增與編輯共用同一個組件,以 route param 判斷模式)
  • 8.2 欄位:title(必填)、location(必填)、spot、price(必填)、region、tag、badges(多選或逗號分隔輸入)、description
  • 8.3 新增模式:送出呼叫 POST /api/provider/offers,成功後導向 Dashboard
  • 8.4 編輯模式:掛載時取得課程資料預填,送出呼叫 PUT /api/provider/offers/{id},成功後導向 Dashboard
  • 8.5 前端必填欄位驗證(title / location / price 為空時不送出)

9. [前端] 教練個人資料頁

  • 9.1 建立 frontend/src/views/coach/ProfileView.vue:掛載時呼叫 GET /api/provider/profile,顯示以下欄位:
    • 基本:name、email、phone
    • 業者:business_name(工作室/個人教練名稱)、description(自我介紹)
    • 專業:certifications(認證)、dive_sites(常駐潛點)、services(服務類型)
    • 聯絡:contact_person、contact_phone、contact_email、address、business_hours
    • 網路:website、social_media
    • 唯讀顯示(不可自改):is_verified、rating
  • 9.2 實作編輯表單,送出呼叫 PUT /api/provider/profile(包含 task 2.5 補完的新欄位),成功顯示「資料已更新」提示

10. [整合測試] 端對端驗證

  • 10.1 驗證教練完整認證流程:註冊 → 登入 → 登出 → 重新登入
  • 10.2 驗證課程 CRUD:新增 → Dashboard 出現 → 編輯 → 刪除
  • 10.3 驗證 route guard:未登入訪問 /coach/dashboard 自動跳轉 /coach/login
  • 10.4 驗證權限隔離:教練 A 無法編輯/刪除教練 B 的課程(API 層回傳 403)
  • 10.5 驗證公開課程列表(/courses)能看到教練新增的課程