Files
CFDivePlatform/routes/api.php
T
a620906209 16ccb4c18c security(auth): 新增 member/provider 角色 middleware
補上待辦 H1:/api/member/*、/api/provider/* 原本只掛 auth:sanctum,
任何已登入帳號都能跨角色建立課程、預約與評價。

- 新增 EnsureMember、EnsureProvider(結構同 EnsureAdmin),註冊 alias
  member、provider,掛到對應路由群組;角色不符回 403
  { status: false, message: "無權限存取" },未登入仍由 auth:sanctum 回 401
- provider middleware 不檢查審核狀態,未通過審核的教練仍可送審
- 新增 RoleAuthorizationTest:member/provider/admin/未登入跨角色測試,
  並驗證越權請求不產生資料;跨角色共用路由(通知、未讀訊息數)不受影響
- Swagger:member/provider 端點的 403 描述補上角色不符,補齊列表端點 403
- 更新 BookingListTest docblock 的過時描述
- 完成 tasks 1.1–4.3;5.1、5.2 手動驗證尚未執行

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-17 23:57:16 +08:00

175 lines
10 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\API\AuthController;
use App\Http\Controllers\API\DivingOfferController;
use App\Http\Controllers\API\ProviderOfferController;
use App\Http\Controllers\API\ScheduleController;
use App\Http\Controllers\API\ProviderBookingController;
use App\Http\Controllers\API\MemberBookingController;
use App\Http\Controllers\API\ReviewController;
use App\Http\Controllers\API\AdminReviewController;
use App\Http\Controllers\API\AdminBookingController;
use App\Http\Controllers\API\CourseImageController;
use App\Http\Controllers\API\AdminStatsController;
use App\Http\Controllers\API\AdminUserController;
use App\Http\Controllers\API\AdminOfferController;
use App\Http\Controllers\API\BookingMessageController;
use App\Http\Controllers\API\NotificationController;
Broadcast::routes(['middleware' => ['auth:sanctum']]);
// 潛水課程(公開)
Route::get('/diving-offers', [DivingOfferController::class, 'index']);
Route::get('/diving-offers/{id}', [DivingOfferController::class, 'show']);
Route::get('/diving-offers/{id}/schedules', [ScheduleController::class, 'publicList']);
Route::get('/diving-offers/{id}/reviews', [ReviewController::class, 'publicList']);
// 會員註冊/登入
Route::post('/member/register', [AuthController::class, 'registerMember']);
Route::middleware('throttle:10,1')->post('/member/login', [AuthController::class, 'loginMember']);
// Google 第三方登入(僅會員)— 需要 StartSession 支援 OAuth state
Route::middleware([\Illuminate\Session\Middleware\StartSession::class])->group(function () {
Route::get('/auth/google/redirect', [\App\Http\Controllers\API\SocialAuthController::class, 'redirectToGoogle']);
Route::get('/auth/google/callback', [\App\Http\Controllers\API\SocialAuthController::class, 'handleGoogleCallback']);
});
// 會員專屬 API(需登入)
Route::middleware(['auth:sanctum', 'member'])->prefix('member')->group(function () {
// 會員登出
Route::post('/logout', [AuthController::class, 'logoutMember']);
// Token Refresh
Route::post('/refresh', [AuthController::class, 'refreshMember']);
// 取得會員個人資料
Route::get('/profile', [AuthController::class, 'memberProfile']);
// 更新會員個人資料
Route::put('/profile', [AuthController::class, 'updateMemberProfile']);
// 修改密碼
Route::put('/change-password', [AuthController::class, 'changeMemberPassword']);
// 預約
Route::get('/bookings', [MemberBookingController::class, 'index']);
Route::post('/bookings', [MemberBookingController::class, 'store']);
Route::get('/bookings/{id}', [MemberBookingController::class, 'show']);
Route::delete('/bookings/{id}', [MemberBookingController::class, 'destroy']);
// 評價
Route::post('/reviews', [ReviewController::class, 'store']);
Route::put('/reviews/{id}', [ReviewController::class, 'update']);
Route::delete('/reviews/{id}',[ReviewController::class, 'destroy']);
});
// 有幫助投票(需登入,但不限 member prefix)
Route::middleware('auth:sanctum')->post('/reviews/{id}/helpful', [ReviewController::class, 'toggleHelpful']);
// 服務提供者註冊/登入
Route::post('/provider/register', [AuthController::class, 'registerProvider']);
Route::middleware('throttle:10,1')->post('/provider/login', [AuthController::class, 'loginProvider']);
// 服務提供者專屬 API(需登入)
Route::middleware(['auth:sanctum', 'provider'])->prefix('provider')->group(function () {
// 服務提供者登出
Route::post('/logout', [AuthController::class, 'logoutProvider']);
// Token Refresh
Route::post('/refresh', [AuthController::class, 'refreshProvider']);
// 取得服務提供者資料
Route::get('/profile', [AuthController::class, 'providerProfile']);
// 更新服務提供者資料
Route::put('/profile', [AuthController::class, 'updateProviderProfile']);
// 修改密碼
Route::put('/change-password', [AuthController::class, 'changeProviderPassword']);
// 教練課程管理
Route::get('/offers', [ProviderOfferController::class, 'index']);
Route::post('/offers', [ProviderOfferController::class, 'store']);
Route::get('/offers/{id}', [ProviderOfferController::class, 'show']);
Route::put('/offers/{id}', [ProviderOfferController::class, 'update']);
Route::delete('/offers/{id}', [ProviderOfferController::class, 'destroy']);
// 驗證申請(證照送審)
Route::get('/verification', [\App\Http\Controllers\API\ProviderVerificationController::class, 'show']);
Route::post('/verification/certifications', [\App\Http\Controllers\API\ProviderVerificationController::class, 'uploadCertification']);
Route::delete('/verification/certifications/{id}', [\App\Http\Controllers\API\ProviderVerificationController::class, 'deleteCertification']);
Route::post('/verification/submit', [\App\Http\Controllers\API\ProviderVerificationController::class, 'submit']);
// 課程圖片
Route::post('/offers/{id}/cover', [CourseImageController::class, 'uploadCover']);
Route::delete('/offers/{id}/cover', [CourseImageController::class, 'deleteCover']);
Route::post('/offers/{id}/images', [CourseImageController::class, 'uploadImage']);
Route::delete('/images/{id}', [CourseImageController::class, 'deleteImage']);
// 時段管理
Route::get('/schedules', [ScheduleController::class, 'index']);
Route::post('/schedules', [ScheduleController::class, 'store']);
Route::put('/schedules/{id}', [ScheduleController::class, 'update']);
Route::delete('/schedules/{id}', [ScheduleController::class, 'destroy']);
// 預約管理
Route::get('/bookings', [ProviderBookingController::class, 'index']);
Route::put('/bookings/{id}/confirm', [ProviderBookingController::class, 'confirm']);
Route::put('/bookings/{id}/reject', [ProviderBookingController::class, 'reject']);
Route::put('/bookings/{id}/cancel', [ProviderBookingController::class, 'cancel']);
Route::put('/bookings/{id}/complete', [ProviderBookingController::class, 'complete']);
});
// 管理員登入(帳號建立僅限主機端 php artisan app:create-admin,不提供公開註冊)
Route::middleware('throttle:3,1')->post('/admin/login', [AuthController::class, 'loginAdmin']);
// 管理員專屬 API(需登入)
Route::middleware(['auth:sanctum', 'admin'])->prefix('admin')->group(function () {
// 管理員登出
Route::post('/logout', [AuthController::class, 'logoutAdmin']);
// Token Refresh
Route::post('/refresh', [AuthController::class, 'refreshAdmin']);
// 取得管理員個人資料
Route::get('/profile', [AuthController::class, 'adminProfile']);
// 更新管理員個人資料
Route::put('/profile', [AuthController::class, 'updateAdminProfile']);
// 修改密碼
Route::put('/change-password', [AuthController::class, 'changeAdminPassword']);
// 查詢會員資料
Route::get('/check-member/{id}', [AuthController::class, 'checkMember']);
// 查詢服務提供者資料
Route::get('/check-provider/{id}', [AuthController::class, 'checkProvider']);
// 統計數據
Route::get('/stats', [AdminStatsController::class, 'index']);
// 用戶管理
Route::get('/members', [AdminUserController::class, 'members']);
Route::get('/members/{id}', [AdminUserController::class, 'member']);
Route::put('/members/{id}/toggle-active', [AdminUserController::class, 'toggleMemberActive']);
Route::get('/providers', [AdminUserController::class, 'providers']);
Route::get('/providers/{id}', [AdminUserController::class, 'provider']);
Route::put('/providers/{id}/toggle-active', [AdminUserController::class, 'toggleProviderActive']);
// 教練審核(toggle-verified 已由審核狀態機取代)
Route::get('/verifications', [\App\Http\Controllers\API\AdminVerificationController::class, 'index']);
Route::put('/verifications/{userId}/approve', [\App\Http\Controllers\API\AdminVerificationController::class, 'approve']);
Route::put('/verifications/{userId}/reject', [\App\Http\Controllers\API\AdminVerificationController::class, 'reject']);
// 課程管理
Route::get('/offers', [AdminOfferController::class, 'index']);
Route::delete('/offers/{id}', [AdminOfferController::class, 'destroy']);
// 預約管理
Route::get('/bookings', [AdminBookingController::class, 'index']);
Route::put('/bookings/{id}/complete', [AdminBookingController::class, 'complete']);
// 評價管理
Route::get('/reviews', [AdminReviewController::class, 'index']);
Route::delete('/reviews/{id}', [AdminReviewController::class, 'destroy']);
});
// 通知(Member + Provider 共用)
Route::middleware('auth:sanctum')->prefix('notifications')->group(function () {
Route::get('/', [NotificationController::class, 'index']);
Route::get('/unread-count', [NotificationController::class, 'unreadCount']);
Route::patch('/read-all', [NotificationController::class, 'markAllRead']);
Route::patch('/{id}/read', [NotificationController::class, 'markRead']);
Route::delete('/{id}', [NotificationController::class, 'destroy']);
});
// 即時訊息(Member + Provider 共用,依 booking 參與方驗證)
Route::middleware('auth:sanctum')->group(function () {
// unread-counts 必須在 {booking} 之前,否則會被 route model binding 吃掉
Route::get('/bookings/messages/unread-counts', [BookingMessageController::class, 'unreadCounts']);
Route::get('/bookings/{booking}/messages', [BookingMessageController::class, 'index']);
Route::post('/bookings/{booking}/messages', [BookingMessageController::class, 'store']);
Route::post('/bookings/{booking}/messages/read', [BookingMessageController::class, 'markRead']);
});
// 需要認證的通用路由
Route::middleware('auth:sanctum')->group(function () {
Route::post('/logout', [AuthController::class, 'logout']);
Route::get('/user', [AuthController::class, 'user']);
});