init
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\API;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\User;
|
||||
use App\Models\MemberProfile;
|
||||
use App\Models\SocialAccount;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
use Laravel\Socialite\Facades\Socialite;
|
||||
|
||||
class SocialAuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* 重定向到 Google 登入頁面
|
||||
*/
|
||||
public function redirectToGoogle()
|
||||
{
|
||||
return Socialite::driver('google')
|
||||
->scopes(['openid', 'profile', 'email'])
|
||||
->with(['access_type' => 'offline', 'prompt' => 'consent']) // 這裡要求 prompt=consent 才能每次都獲取 refresh token
|
||||
->stateless()
|
||||
->redirect();
|
||||
}
|
||||
|
||||
/**
|
||||
* 處理 Google 回調
|
||||
*/
|
||||
public function handleGoogleCallback(Request $request)
|
||||
{
|
||||
try {
|
||||
// 獲取 Google 用戶資訊
|
||||
$googleUser = Socialite::driver('google')->stateless()->user();
|
||||
|
||||
// 查找相關的社交帳號
|
||||
$socialAccount = SocialAccount::where('provider', 'google')
|
||||
->where('provider_id', $googleUser->getId())
|
||||
->first();
|
||||
|
||||
if ($socialAccount) {
|
||||
// 已存在社交帳號,直接獲取用戶
|
||||
$user = $socialAccount->user;
|
||||
|
||||
// 如果用戶不是會員,拒絕登入
|
||||
if ($user->role !== 'member') {
|
||||
return response()->json([
|
||||
'status' => false,
|
||||
'message' => '只有會員可以使用 Google 登入'
|
||||
], 403);
|
||||
}
|
||||
} else {
|
||||
// 檢查是否有相同 email 的用戶
|
||||
$user = User::where('email', $googleUser->getEmail())->first();
|
||||
|
||||
if ($user) {
|
||||
// 已存在用戶,但沒有連結社交帳號
|
||||
// 檢查是否為會員
|
||||
if ($user->role !== 'member') {
|
||||
return response()->json([
|
||||
'status' => false,
|
||||
'message' => '只有會員可以使用 Google 登入'
|
||||
], 403);
|
||||
}
|
||||
} else {
|
||||
// 建立新用戶
|
||||
$user = User::create([
|
||||
'name' => $googleUser->getName(),
|
||||
'email' => $googleUser->getEmail(),
|
||||
'password' => Hash::make(Str::random(24)),
|
||||
'role' => 'member', // 強制為會員角色
|
||||
'is_active' => true,
|
||||
]);
|
||||
|
||||
// 建立會員資料
|
||||
try {
|
||||
MemberProfile::create([
|
||||
'user_id' => $user->id,
|
||||
// 可以選擇性地從 Google 獲取更多資訊
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
// 記錄錯誤,但不中斷整個登入流程
|
||||
\Log::error('Google 登入建立會員資料失敗: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// 建立社交帳號連結
|
||||
$socialAccountData = [
|
||||
'user_id' => $user->id,
|
||||
'provider' => 'google',
|
||||
'provider_id' => $googleUser->getId(),
|
||||
'provider_email' => $googleUser->getEmail(),
|
||||
'access_token' => $googleUser->token,
|
||||
'expires_in' => $googleUser->expiresIn ?? null,
|
||||
];
|
||||
|
||||
// 確保如果有 refreshToken 就正確地儲存
|
||||
if (!empty($googleUser->refreshToken)) {
|
||||
$socialAccountData['refresh_token'] = $googleUser->refreshToken;
|
||||
}
|
||||
|
||||
$socialAccount = SocialAccount::create($socialAccountData);
|
||||
}
|
||||
|
||||
// 生成 Sanctum token
|
||||
$token = $user->createToken('google-auth')->plainTextToken;
|
||||
|
||||
// 載入會員資料
|
||||
$user->load('memberProfile');
|
||||
|
||||
return response()->json([
|
||||
'status' => true,
|
||||
'message' => 'Google 登入成功',
|
||||
'data' => [
|
||||
'user' => $user,
|
||||
'token' => $token,
|
||||
'token_type' => 'Bearer',
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json([
|
||||
'status' => false,
|
||||
'message' => 'Google 登入失敗:' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class AdminProfile extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* 與模型關聯的資料表
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'admin_profiles';
|
||||
|
||||
/**
|
||||
* 可以被批量賦值的屬性
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'position',
|
||||
'department',
|
||||
'permissions',
|
||||
];
|
||||
|
||||
/**
|
||||
* 應該被轉換的屬性
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'permissions' => 'array',
|
||||
];
|
||||
|
||||
/**
|
||||
* 獲取擁有此管理員資料的用戶
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 檢查管理員是否擁有特定權限
|
||||
*
|
||||
* @param string $permission
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPermission($permission)
|
||||
{
|
||||
if (empty($this->permissions)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return in_array($permission, $this->permissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加權限給管理員
|
||||
*
|
||||
* @param string $permission
|
||||
* @return void
|
||||
*/
|
||||
public function addPermission($permission)
|
||||
{
|
||||
$permissions = $this->permissions ?? [];
|
||||
|
||||
if (!in_array($permission, $permissions)) {
|
||||
$permissions[] = $permission;
|
||||
$this->permissions = $permissions;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除管理員的權限
|
||||
*
|
||||
* @param string $permission
|
||||
* @return void
|
||||
*/
|
||||
public function removePermission($permission)
|
||||
{
|
||||
if (empty($this->permissions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$permissions = array_filter($this->permissions, function($p) use ($permission) {
|
||||
return $p !== $permission;
|
||||
});
|
||||
|
||||
$this->permissions = array_values($permissions);
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 設定多個權限
|
||||
*
|
||||
* @param array $permissions
|
||||
* @return void
|
||||
*/
|
||||
public function setPermissions(array $permissions)
|
||||
{
|
||||
$this->permissions = $permissions;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除所有權限
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearPermissions()
|
||||
{
|
||||
$this->permissions = [];
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取所有權限
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPermissions()
|
||||
{
|
||||
return $this->permissions ?? [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class CoachProfile extends Model
|
||||
{
|
||||
/**
|
||||
* 可以批量分配的屬性
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'bio',
|
||||
'expertise',
|
||||
'certification',
|
||||
'experience',
|
||||
'rating',
|
||||
'availability'
|
||||
];
|
||||
|
||||
/**
|
||||
* 與用戶的關聯
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MemberProfile extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* 與模型關聯的資料表
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $table = 'member_profiles';
|
||||
|
||||
/**
|
||||
* 可以被批量賦值的屬性
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id', // 這個欄位必須包含在這裡
|
||||
'birthday',
|
||||
'gender',
|
||||
'address',
|
||||
'emergency_contact',
|
||||
'emergency_phone',
|
||||
];
|
||||
|
||||
/**
|
||||
* 應該被轉換的屬性
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts = [
|
||||
'birthday' => 'date',
|
||||
];
|
||||
|
||||
/**
|
||||
* 獲取擁有此會員資料的用戶
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取會員的訂閱記錄
|
||||
*/
|
||||
public function subscriptions()
|
||||
{
|
||||
return $this->hasMany(Subscription::class, 'user_id', 'user_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取會員的活躍訂閱
|
||||
*/
|
||||
public function activeSubscription()
|
||||
{
|
||||
return $this->hasOne(Subscription::class, 'user_id', 'user_id')
|
||||
->where('status', 'active')
|
||||
->where('end_date', '>=', now())
|
||||
->latest();
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取會員的教練
|
||||
*/
|
||||
public function coaches()
|
||||
{
|
||||
return $this->hasManyThrough(
|
||||
CoachProfile::class,
|
||||
'coach_member',
|
||||
'member_id',
|
||||
'user_id',
|
||||
'user_id',
|
||||
'coach_id'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 檢查會員是否有活躍訂閱
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasActiveSubscription()
|
||||
{
|
||||
return $this->activeSubscription()->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得會員年齡
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getAge()
|
||||
{
|
||||
if (!$this->birthday) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return now()->diffInYears($this->birthday);
|
||||
}
|
||||
|
||||
/**
|
||||
* 設定會員生日
|
||||
*
|
||||
* @param string|null $date
|
||||
* @return void
|
||||
*/
|
||||
public function setBirthday($date)
|
||||
{
|
||||
$this->birthday = $date;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新會員緊急聯絡資訊
|
||||
*
|
||||
* @param string $contact
|
||||
* @param string $phone
|
||||
* @return void
|
||||
*/
|
||||
public function updateEmergencyContact($contact, $phone)
|
||||
{
|
||||
$this->emergency_contact = $contact;
|
||||
$this->emergency_phone = $phone;
|
||||
$this->save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Plan extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class SocialAccount extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'user_id',
|
||||
'provider',
|
||||
'provider_id',
|
||||
'provider_email',
|
||||
'access_token',
|
||||
'refresh_token',
|
||||
'expires_in',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'access_token',
|
||||
'refresh_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* 取得此社交帳號的使用者
|
||||
*/
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Subscription extends Model
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
/**
|
||||
* User 使用者模型
|
||||
*
|
||||
* 對應 users 資料表,並提供角色判斷、關聯資料取得等功能。
|
||||
*/
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
/**
|
||||
* 可批次賦值的欄位(對應 users 資料表)
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name', // 姓名
|
||||
'email', // 電子郵件
|
||||
'password', // 密碼
|
||||
'phone', // 電話
|
||||
'role', // 角色
|
||||
'is_active', // 是否啟用
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
/**
|
||||
* 隱藏於序列化時的欄位
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password', // 密碼
|
||||
'remember_token', // 記住我 token
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be cast.
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
/**
|
||||
* 欄位型別轉換
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected $casts = [
|
||||
'email_verified_at' => 'datetime', // 驗證時間
|
||||
'password' => 'hashed', // 密碼雜湊
|
||||
];
|
||||
|
||||
/**
|
||||
* 判斷用戶是否為管理員
|
||||
*/
|
||||
/**
|
||||
* 判斷用戶是否為管理員
|
||||
* @return bool
|
||||
*/
|
||||
public function isAdmin()
|
||||
{
|
||||
return $this->role === 'admin';
|
||||
}
|
||||
|
||||
/**
|
||||
* 判斷用戶是否為教練
|
||||
*/
|
||||
/**
|
||||
* 判斷用戶是否為教練
|
||||
* @return bool
|
||||
*/
|
||||
public function isCoach()
|
||||
{
|
||||
return $this->role === 'coach';
|
||||
}
|
||||
|
||||
/**
|
||||
* 判斷用戶是否為一般會員
|
||||
*/
|
||||
/**
|
||||
* 判斷用戶是否為一般會員
|
||||
* @return bool
|
||||
*/
|
||||
public function isMember()
|
||||
{
|
||||
return $this->role === 'member';
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取用戶的管理員資料
|
||||
*/
|
||||
/**
|
||||
* 取得用戶的管理員詳細資料(關聯 admin_profiles)
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function adminProfile()
|
||||
{
|
||||
return $this->hasOne(AdminProfile::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取用戶的教練資料
|
||||
*/
|
||||
/**
|
||||
* 取得用戶的教練詳細資料(關聯 coach_profiles)
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function coachProfile()
|
||||
{
|
||||
return $this->hasOne(CoachProfile::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取用戶的會員資料
|
||||
*/
|
||||
/**
|
||||
* 取得用戶的會員詳細資料(關聯 member_profiles)
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function memberProfile()
|
||||
{
|
||||
return $this->hasOne(MemberProfile::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取用戶的設定檔資料 (根據角色自動選擇)
|
||||
*/
|
||||
/**
|
||||
* 取得用戶的詳細資料(依角色自動選擇對應 profile)
|
||||
* @return mixed
|
||||
*/
|
||||
public function profile()
|
||||
{
|
||||
if ($this->isAdmin()) {
|
||||
return $this->adminProfile;
|
||||
} elseif ($this->isCoach()) {
|
||||
return $this->coachProfile;
|
||||
} else {
|
||||
return $this->memberProfile;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取教練的會員 (僅適用於教練角色)
|
||||
*/
|
||||
/**
|
||||
* 取得教練所帶的會員(僅教練角色適用)
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|null
|
||||
*/
|
||||
public function members()
|
||||
{
|
||||
if (!$this->isCoach()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->belongsToMany(User::class, 'coach_member', 'coach_id', 'member_id')
|
||||
->where('role', 'member');
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取會員的教練 (僅適用於會員角色)
|
||||
*/
|
||||
/**
|
||||
* 取得會員對應的教練(僅會員角色適用)
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany|null
|
||||
*/
|
||||
public function coaches()
|
||||
{
|
||||
if (!$this->isMember()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->belongsToMany(User::class, 'coach_member', 'member_id', 'coach_id')
|
||||
->where('role', 'coach');
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取用戶的訂閱
|
||||
*/
|
||||
/**
|
||||
* 取得用戶的所有訂閱紀錄
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
*/
|
||||
public function subscriptions()
|
||||
{
|
||||
return $this->hasMany(Subscription::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* 獲取用戶目前有效的訂閱
|
||||
*/
|
||||
/**
|
||||
* 取得用戶目前有效的訂閱(狀態為 active 且未過期)
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasOne
|
||||
*/
|
||||
public function activeSubscription()
|
||||
{
|
||||
return $this->hasOne(Subscription::class)
|
||||
->where('status', 'active')
|
||||
->where('end_date', '>=', now())
|
||||
->latest();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user