From 125d4bf8d822cc6ad52ba957c7c4da1b684eed2a Mon Sep 17 00:00:00 2001 From: RD-Hank Date: Mon, 12 May 2025 01:22:09 +0800 Subject: [PATCH] =?UTF-8?q?fix:coach=E7=9B=B8=E9=97=9C=E6=94=B9provider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/API/AuthController.php | 211 ++++++++++-------- app/Models/AdminProfile.php | 16 ++ app/Models/CoachProfile.php | 17 ++ app/Models/MemberProfile.php | 26 ++- app/Models/ProviderProfile.php | 74 ++++++ app/Models/User.php | 56 +++-- ..._154921_create_provider_profiles_table.php | 47 ++++ ...rename_coach_member_to_provider_member.php | 31 +++ public/admin-coach-test.html | 90 +++++--- routes/api.php | 30 +-- 10 files changed, 433 insertions(+), 165 deletions(-) create mode 100644 app/Models/ProviderProfile.php create mode 100644 database/migrations/2025_05_11_154921_create_provider_profiles_table.php create mode 100644 database/migrations/2025_05_11_160627_rename_coach_member_to_provider_member.php diff --git a/app/Http/Controllers/API/AuthController.php b/app/Http/Controllers/API/AuthController.php index 8dc14ca..6ac6f48 100644 --- a/app/Http/Controllers/API/AuthController.php +++ b/app/Http/Controllers/API/AuthController.php @@ -5,18 +5,19 @@ namespace App\Http\Controllers\API; use App\Http\Controllers\Controller; use App\Models\User; use App\Models\AdminProfile; -use App\Models\CoachProfile; +use App\Models\ProviderProfile; use App\Models\MemberProfile; use Illuminate\Http\Request; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rules\Password; + class AuthController extends Controller { // 科定規範角色 private const ROLE_MEMBER = 'member'; - private const ROLE_COACH = 'coach'; + private const ROLE_PROVIDER = 'provider'; private const ROLE_ADMIN = 'admin'; /** @@ -65,13 +66,13 @@ class AuthController extends Controller if ($role === self::ROLE_MEMBER) { $targetUser->load('memberProfile'); } else { - $targetUser->load('coachProfile'); + $targetUser->load('providerProfile'); } return response()->json([ 'status' => true, 'data' => $userData, - 'profile' => $role === self::ROLE_MEMBER ? $targetUser->memberProfile : $targetUser->coachProfile, + 'profile' => $role === self::ROLE_MEMBER ? $targetUser->memberProfile : $targetUser->providerProfile, ]); } @@ -144,7 +145,7 @@ class AuthController extends Controller // 撤銷目前的 token $request->user()->currentAccessToken()->delete(); - $roleText = $role === self::ROLE_MEMBER ? '會員' : ($role === self::ROLE_COACH ? '教練' : '管理員'); + $roleText = $role === self::ROLE_MEMBER ? '會員' : ($role === self::ROLE_PROVIDER ? '服務提供者' : '管理員'); return response()->json([ 'status' => true, @@ -197,8 +198,8 @@ class AuthController extends Controller // 根據角色加載對應的資料 if ($user->isAdmin()) { $user->load('adminProfile'); - } elseif ($user->isCoach()) { - $user->load('coachProfile'); + } elseif ($user->isProvider()) { + $user->load('providerProfile'); } elseif ($user->isMember()) { $user->load('memberProfile'); } @@ -209,9 +210,9 @@ class AuthController extends Controller ]); } - /** - * 會員註冊 - */ +/** + * 會員註冊 + */ public function registerMember(Request $request) { // 驗證請求數據 @@ -262,9 +263,9 @@ class AuthController extends Controller ], 201); } - /** - * 會員登入 - */ +/** + * 會員登入 + */ public function loginMember(Request $request) { // 驗證請求數據 @@ -319,9 +320,9 @@ class AuthController extends Controller ]); } - /** - * 會員登出 - */ +/** + * 會員登出 + */ public function logoutMember(Request $request) { // 確保只有會員可以使用這個方法 @@ -342,9 +343,9 @@ class AuthController extends Controller ]); } - /** - * 取得會員個人資料 - */ +/** + * 取得會員個人資料 + */ public function memberProfile(Request $request) { $user = auth()->user(); @@ -365,9 +366,9 @@ class AuthController extends Controller ]); } - /** - * 更新會員個人資料 - */ +/** + * 更新會員個人資料 + */ public function updateMemberProfile(Request $request) { $user = auth()->user(); @@ -416,9 +417,9 @@ class AuthController extends Controller ]); } - /** - * 修改會員密碼 - */ +/** + * 修改會員密碼 + */ public function changeMemberPassword(Request $request) { $user = auth()->user(); @@ -462,10 +463,10 @@ class AuthController extends Controller ]); } - /** - * 教練註冊 - */ - public function registerCoach(Request $request) +/** + * 服務提供者註冊 + */ + public function registerProvider(Request $request) { // 驗證請求數據 $validator = Validator::make($request->all(), [ @@ -473,8 +474,13 @@ class AuthController extends Controller 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6|confirmed', 'phone' => 'nullable|string|max:20', - 'bio' => 'nullable|string', - 'expertise' => 'nullable|string|max:100', + 'business_name' => 'required|string|max:255', + 'description' => 'nullable|string', + 'contact_person' => 'nullable|string|max:100', + 'contact_phone' => 'nullable|string|max:20', + 'contact_email' => 'nullable|string|email|max:255', + 'address' => 'nullable|string|max:255', + 'business_hours' => 'nullable|string|max:100', ]); if ($validator->fails()) { @@ -491,22 +497,27 @@ class AuthController extends Controller 'email' => $request->email, 'password' => Hash::make($request->password), 'phone' => $request->phone, - 'role' => 'coach', // 強制為教練角色 + 'role' => 'provider', // 強制為服務提供者角色 ]); - // 創建教練資料 - CoachProfile::create([ + // 創建服務提供者資料 + ProviderProfile::create([ 'user_id' => $user->id, - 'bio' => $request->bio, - 'expertise' => $request->expertise, + 'business_name' => $request->business_name, + 'description' => $request->description ?? null, + 'contact_person' => $request->contact_person ?? null, + 'contact_phone' => $request->contact_phone ?? null, + 'contact_email' => $request->contact_email ?? null, + 'address' => $request->address ?? null, + 'business_hours' => $request->business_hours ?? null, ]); // 創建 API token $token = $user->createToken('auth_token')->plainTextToken; - + return response()->json([ 'status' => true, - 'message' => '教練註冊成功', + 'message' => '服務提供者註冊成功', 'data' => [ 'user' => $user, 'token' => $token, @@ -515,10 +526,10 @@ class AuthController extends Controller ], 201); } - /** - * 教練登入 - */ - public function loginCoach(Request $request) +/** + * 服務提供者登入 + */ + public function loginProvider(Request $request) { // 驗證請求數據 $validator = Validator::make($request->all(), [ @@ -536,7 +547,7 @@ class AuthController extends Controller // 檢查用戶是否存在 $user = User::where('email', $request->email) - ->where('role', 'coach') // 只驗證教練 + ->where('role', 'provider') // 只驗證服務提供者 ->first(); // 檢查密碼 @@ -558,8 +569,8 @@ class AuthController extends Controller // 創建 API token $token = $user->createToken('auth_token')->plainTextToken; - // 加載教練資料 - $user->load('coachProfile'); + // 加載服務提供者資料 + $user->load('providerProfile'); return response()->json([ 'status' => true, @@ -572,14 +583,14 @@ class AuthController extends Controller ]); } - /** - * 教練登出 - */ - public function logoutCoach(Request $request) +/** + * 服務提供者登出 + */ + public function logoutProvider(Request $request) { - // 確保只有教練可以使用這個方法 + // 確保只有服務提供者可以使用這個方法 $user = $request->user(); - if ($user->role !== 'coach') { + if ($user->role !== 'provider') { return response()->json([ 'status' => false, 'message' => '無權限存取' @@ -591,26 +602,26 @@ class AuthController extends Controller return response()->json([ 'status' => true, - 'message' => '教練登出成功' + 'message' => '服務提供者登出成功' ]); } - /** - * 取得教練個人資料 - */ - public function coachProfile(Request $request) +/** + * 取得服務提供者資料 + */ + public function providerProfile(Request $request) { $user = auth()->user(); - // 確保只有教練可以使用這個方法 - if ($user->role !== 'coach') { + // 確保只有服務提供者可以使用這個方法 + if ($user->role !== 'provider') { return response()->json([ 'status' => false, 'message' => '無權限存取' ], 403); } - // 加載教練資料 - $user->load('coachProfile'); + // 加載服務提供者資料 + $user->load('providerProfile'); return response()->json([ 'status' => true, @@ -618,14 +629,14 @@ class AuthController extends Controller ]); } - /** - * 更新教練個人資料 - */ - public function updateCoachProfile(Request $request) +/** + * 更新服務提供者資料 + */ + public function updateProviderProfile(Request $request) { $user = auth()->user(); - // 確保只有教練可以使用這個方法 - if ($user->role !== 'coach') { + // 確保只有服務提供者可以使用這個方法 + if ($user->role !== 'provider') { return response()->json([ 'status' => false, 'message' => '無權限存取' @@ -637,8 +648,13 @@ class AuthController extends Controller 'name' => 'nullable|string|max:255', 'email' => 'nullable|string|email|max:255|unique:users,email,' . $user->id, 'phone' => 'nullable|string|max:20', - 'bio' => 'nullable|string', - 'expertise' => 'nullable|string|max:100', + 'business_name' => 'nullable|string|max:255', + 'description' => 'nullable|string', + 'contact_person' => 'nullable|string|max:100', + 'contact_phone' => 'nullable|string|max:20', + 'contact_email' => 'nullable|string|email|max:255', + 'address' => 'nullable|string|max:255', + 'business_hours' => 'nullable|string|max:100', ]); if ($validator->fails()) { @@ -661,36 +677,51 @@ class AuthController extends Controller } $user->save(); - // 更新教練資料 - if ($request->has('bio') || $request->has('expertise')) { - $coachProfile = $user->coachProfile; - if ($request->has('bio')) { - $coachProfile->bio = $request->bio; - } - if ($request->has('expertise')) { - $coachProfile->expertise = $request->expertise; - } - $coachProfile->save(); + // 更新服務提供者資料 + $providerProfile = $user->providerProfile; + + if ($request->has('business_name')) { + $providerProfile->business_name = $request->business_name; } + if ($request->has('description')) { + $providerProfile->description = $request->description; + } + if ($request->has('contact_person')) { + $providerProfile->contact_person = $request->contact_person; + } + if ($request->has('contact_phone')) { + $providerProfile->contact_phone = $request->contact_phone; + } + if ($request->has('contact_email')) { + $providerProfile->contact_email = $request->contact_email; + } + if ($request->has('address')) { + $providerProfile->address = $request->address; + } + if ($request->has('business_hours')) { + $providerProfile->business_hours = $request->business_hours; + } + + $providerProfile->save(); - // 加載教練資料 - $user->load('coachProfile'); + // 加載服務提供者資料 + $user->load('providerProfile'); return response()->json([ 'status' => true, - 'message' => '教練資料已更新', + 'message' => '服務提供者資料已更新', 'data' => $user, ]); } - /** - * 修改教練密碼 - */ - public function changeCoachPassword(Request $request) +/** + * 修改服務提供者密碼 + */ + public function changeProviderPassword(Request $request) { $user = auth()->user(); - // 確保只有教練可以使用這個方法 - if ($user->role !== 'coach') { + // 確保只有服務提供者可以使用這個方法 + if ($user->role !== 'provider') { return response()->json([ 'status' => false, 'message' => '無權限存取' @@ -1006,11 +1037,11 @@ class AuthController extends Controller } /** - * 查詢教練資料 + * 查詢服務提供者資料 * 只有管理員可以使用這個方法 */ - public function checkCoach(int $id) + public function checkProvider(int $id) { - return $this->checkUser(self::ROLE_COACH, $id); + return $this->checkUser(self::ROLE_PROVIDER, $id); } } \ No newline at end of file diff --git a/app/Models/AdminProfile.php b/app/Models/AdminProfile.php index 8164bdc..6e6874d 100644 --- a/app/Models/AdminProfile.php +++ b/app/Models/AdminProfile.php @@ -5,6 +5,22 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +/** + * @OA\Schema( + * schema="AdminProfile", + * title="管理員個人資料", + * description="管理員的詳細個人資料", + * @OA\Property(property="id", type="integer", format="int64", example=1, description="資料ID"), + * @OA\Property(property="user_id", type="integer", format="int64", example=1, description="關聯的使用者ID"), + * @OA\Property(property="position", type="string", example="系統管理員", description="職位"), + * @OA\Property(property="department", type="string", example="IT部門", description="部門"), + * @OA\Property(property="permissions", type="array", description="權限列表", + * @OA\Items(type="string", example="manage_users") + * ), + * @OA\Property(property="created_at", type="string", format="date-time", example="2023-01-01T00:00:00.000000Z", description="創建時間"), + * @OA\Property(property="updated_at", type="string", format="date-time", example="2023-01-01T00:00:00.000000Z", description="更新時間") + * ) + */ class AdminProfile extends Model { use HasFactory; diff --git a/app/Models/CoachProfile.php b/app/Models/CoachProfile.php index bb8a10f..37fb2a7 100644 --- a/app/Models/CoachProfile.php +++ b/app/Models/CoachProfile.php @@ -4,6 +4,23 @@ namespace App\Models; use Illuminate\Database\Eloquent\Model; +/** + * @OA\Schema( + * schema="CoachProfile", + * title="教練個人資料", + * description="教練的詳細個人資料", + * @OA\Property(property="id", type="integer", format="int64", example=1, description="資料ID"), + * @OA\Property(property="user_id", type="integer", format="int64", example=1, description="關聯的使用者ID"), + * @OA\Property(property="bio", type="string", example="專業潛水教練,擁有10年教學經驗", description="個人簡介"), + * @OA\Property(property="expertise", type="string", example="自由潛水,水肺潛水", description="專長領域"), + * @OA\Property(property="certification", type="string", example="PADI專業潛水教練", description="證照資訊"), + * @OA\Property(property="experience", type="string", example="10年教學經驗,帶領超過500名學員", description="教學經驗"), + * @OA\Property(property="rating", type="number", format="float", example=4.8, description="評分"), + * @OA\Property(property="availability", type="string", example="週一至週五 09:00-18:00", description="可授課時間"), + * @OA\Property(property="created_at", type="string", format="date-time", example="2023-01-01T00:00:00.000000Z", description="創建時間"), + * @OA\Property(property="updated_at", type="string", format="date-time", example="2023-01-01T00:00:00.000000Z", description="更新時間") + * ) + */ class CoachProfile extends Model { /** diff --git a/app/Models/MemberProfile.php b/app/Models/MemberProfile.php index 2a4809c..7f1691a 100644 --- a/app/Models/MemberProfile.php +++ b/app/Models/MemberProfile.php @@ -5,6 +5,22 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +/** + * @OA\Schema( + * schema="MemberProfile", + * title="會員個人資料", + * description="會員的詳細個人資料", + * @OA\Property(property="id", type="integer", format="int64", example=1, description="資料ID"), + * @OA\Property(property="user_id", type="integer", format="int64", example=1, description="關聯的使用者ID"), + * @OA\Property(property="birthday", type="string", format="date", example="1990-01-01", description="生日"), + * @OA\Property(property="gender", type="string", enum={"male", "female", "other"}, example="male", description="性別"), + * @OA\Property(property="address", type="string", example="台北市信義區某街123號", description="地址"), + * @OA\Property(property="emergency_contact", type="string", example="王大明", description="緊急聯絡人"), + * @OA\Property(property="emergency_phone", type="string", example="0987654321", description="緊急聯絡電話"), + * @OA\Property(property="created_at", type="string", format="date-time", example="2023-01-01T00:00:00.000000Z", description="創建時間"), + * @OA\Property(property="updated_at", type="string", format="date-time", example="2023-01-01T00:00:00.000000Z", description="更新時間") + * ) + */ class MemberProfile extends Model { use HasFactory; @@ -67,17 +83,17 @@ class MemberProfile extends Model } /** - * 獲取會員的教練 + * 獲取會員的服務提供者 */ - public function coaches() + public function providers() { return $this->hasManyThrough( - CoachProfile::class, - 'coach_member', + ProviderProfile::class, + 'provider_member', 'member_id', 'user_id', 'user_id', - 'coach_id' + 'provider_id' ); } diff --git a/app/Models/ProviderProfile.php b/app/Models/ProviderProfile.php new file mode 100644 index 0000000..036e55e --- /dev/null +++ b/app/Models/ProviderProfile.php @@ -0,0 +1,74 @@ + + */ + protected $fillable = [ + 'user_id', + 'business_name', + 'business_license', + 'description', + 'contact_person', + 'contact_phone', + 'contact_email', + 'address', + 'dive_sites', + 'services', + 'certifications', + 'facilities', + 'business_hours', + 'is_verified', + 'rating', + 'website', + 'social_media', + 'logo_url', + 'banner_url', + 'is_active' + ]; + + /** + * 與用戶的關聯 + */ + public function user() + { + return $this->belongsTo(User::class); + } +} diff --git a/app/Models/User.php b/app/Models/User.php index f4eba94..971ee8e 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -11,6 +11,24 @@ use Laravel\Sanctum\HasApiTokens; * User 使用者模型 * * 對應 users 資料表,並提供角色判斷、關聯資料取得等功能。 + * + * @OA\Schema( + * schema="User", + * title="使用者", + * description="使用者資料模型", + * @OA\Property(property="id", type="integer", format="int64", example=1, description="使用者ID"), + * @OA\Property(property="name", type="string", example="王小明", description="使用者姓名"), + * @OA\Property(property="email", type="string", format="email", example="user@example.com", description="電子郵件"), + * @OA\Property(property="phone", type="string", example="0912345678", description="電話號碼"), + * @OA\Property(property="role", type="string", enum={"member", "provider", "admin"}, example="member", description="角色"), + * @OA\Property(property="is_active", type="boolean", example=true, description="是否啟用"), + * @OA\Property(property="email_verified_at", type="string", format="date-time", example="2023-01-01T00:00:00.000000Z", description="電子郵件驗證時間"), + * @OA\Property(property="created_at", type="string", format="date-time", example="2023-01-01T00:00:00.000000Z", description="創建時間"), + * @OA\Property(property="updated_at", type="string", format="date-time", example="2023-01-01T00:00:00.000000Z", description="更新時間"), + * @OA\Property(property="memberProfile", type="object", ref="#/components/schemas/MemberProfile", description="會員詳細資料"), + * @OA\Property(property="providerProfile", type="object", ref="#/components/schemas/ProviderProfile", description="服務提供者詳細資料"), + * @OA\Property(property="adminProfile", type="object", ref="#/components/schemas/AdminProfile", description="管理員詳細資料") + * ) */ class User extends Authenticatable { @@ -75,15 +93,15 @@ class User extends Authenticatable } /** - * 判斷用戶是否為教練 + * 判斷用戶是否為服務提供者 */ /** - * 判斷用戶是否為教練 + * 判斷用戶是否為服務提供者 * @return bool */ - public function isCoach() + public function isProvider() { - return $this->role === 'coach'; + return $this->role === 'provider'; } /** @@ -111,15 +129,15 @@ class User extends Authenticatable } /** - * 獲取用戶的教練資料 + * 獲取用戶的服務提供者資料 */ /** - * 取得用戶的教練詳細資料(關聯 coach_profiles) + * 取得用戶的服務提供者詳細資料(關聯 provider_profiles) * @return \Illuminate\Database\Eloquent\Relations\HasOne */ - public function coachProfile() + public function providerProfile() { - return $this->hasOne(CoachProfile::class); + return $this->hasOne(ProviderProfile::class); } /** @@ -145,45 +163,45 @@ class User extends Authenticatable { if ($this->isAdmin()) { return $this->adminProfile; - } elseif ($this->isCoach()) { - return $this->coachProfile; + } elseif ($this->isProvider()) { + return $this->providerProfile; } else { return $this->memberProfile; } } /** - * 獲取教練的會員 (僅適用於教練角色) + * 獲取服務提供者的會員 (僅適用於服務提供者角色) */ /** - * 取得教練所帶的會員(僅教練角色適用) + * 取得服務提供者所帶的會員(僅服務提供者角色適用) * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|null */ public function members() { - if (!$this->isCoach()) { + if (!$this->isProvider()) { return null; } - return $this->belongsToMany(User::class, 'coach_member', 'coach_id', 'member_id') + return $this->belongsToMany(User::class, 'provider_member', 'provider_id', 'member_id') ->where('role', 'member'); } /** - * 獲取會員的教練 (僅適用於會員角色) + * 獲取會員的服務提供者 (僅適用於會員角色) */ /** - * 取得會員對應的教練(僅會員角色適用) + * 取得會員對應的服務提供者(僅會員角色適用) * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|null */ - public function coaches() + public function providers() { if (!$this->isMember()) { return null; } - return $this->belongsToMany(User::class, 'coach_member', 'member_id', 'coach_id') - ->where('role', 'coach'); + return $this->belongsToMany(User::class, 'provider_member', 'member_id', 'provider_id') + ->where('role', 'provider'); } /** diff --git a/database/migrations/2025_05_11_154921_create_provider_profiles_table.php b/database/migrations/2025_05_11_154921_create_provider_profiles_table.php new file mode 100644 index 0000000..a1ea4d0 --- /dev/null +++ b/database/migrations/2025_05_11_154921_create_provider_profiles_table.php @@ -0,0 +1,47 @@ +id(); + $table->foreignId('user_id')->constrained()->onDelete('cascade'); + $table->string('business_name')->nullable(); + $table->string('business_license')->nullable()->comment('營業執照號碼'); + $table->text('description')->nullable()->comment('業者描述'); + $table->string('contact_person')->nullable()->comment('聯絡人'); + $table->string('contact_phone')->nullable(); + $table->string('contact_email')->nullable(); + $table->string('address')->nullable()->comment('營業地址'); + $table->text('dive_sites')->nullable()->comment('提供的潛點'); + $table->text('services')->nullable()->comment('提供的服務,例如:訓練課程、裝備出租、潛導服務等'); + $table->text('certifications')->nullable()->comment('業者相關認證'); + $table->text('facilities')->nullable()->comment('設施,如壓縮空氣設備、沖洗區等'); + $table->string('business_hours')->nullable()->comment('營業時間'); + $table->boolean('is_verified')->default(false)->comment('是否通過平台驗證'); + $table->float('rating')->default(0)->comment('評分'); + $table->string('website')->nullable()->comment('官方網站'); + $table->string('social_media')->nullable()->comment('社群媒體連結'); + $table->string('logo_url')->nullable()->comment('業者標誌URL'); + $table->string('banner_url')->nullable()->comment('業者橫幅URL'); + $table->boolean('is_active')->default(true)->comment('是否啟用'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('provider_profiles'); + } +}; diff --git a/database/migrations/2025_05_11_160627_rename_coach_member_to_provider_member.php b/database/migrations/2025_05_11_160627_rename_coach_member_to_provider_member.php new file mode 100644 index 0000000..336ba81 --- /dev/null +++ b/database/migrations/2025_05_11_160627_rename_coach_member_to_provider_member.php @@ -0,0 +1,31 @@ +id(); // 關聯主鍵ID + $table->foreignId('provider_id')->constrained('users'); // 關聯服務提供者(users表) + $table->foreignId('member_id')->constrained('users'); // 關聯會員(users表) + $table->timestamps(); // 建立與更新時間 + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + // 刪除 provider_member 表 + Schema::dropIfExists('provider_member'); + } +}; diff --git a/public/admin-coach-test.html b/public/admin-coach-test.html index 6fd56e2..988e7f7 100644 --- a/public/admin-coach-test.html +++ b/public/admin-coach-test.html @@ -3,7 +3,7 @@ - CFDive平台 - 管理員與教練註冊測試 + CFDive平台 - 管理員與服務提供者註冊測試