feat: 整合 Preline UI 3.x 與重寫 README 為 Docker 架構
- 新增 Preline UI 3.2.3 作為 UI 組件庫 - 更新 tailwind.config.js 整合 Preline - 更新 app.js 初始化 Preline - 完全重寫 README.md 以 Docker 容器化架構為核心 - 新增 Docker 常用指令大全 - 新增故障排除與生產部署指南 - 新增會員系統相關功能(會員、錢包、點數、會籍、禮物) - 新增社交登入測試功能
This commit is contained in:
260
app/Http/Controllers/Api/MemberController.php
Normal file
260
app/Http/Controllers/Api/MemberController.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Member;
|
||||
use App\Models\SocialAccount;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
|
||||
class MemberController extends Controller
|
||||
{
|
||||
/**
|
||||
* 會員註冊
|
||||
*/
|
||||
public function register(Request $request): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => ['required', 'string', 'max:255'],
|
||||
'email' => ['nullable', 'email', 'unique:members,email'],
|
||||
'phone' => ['nullable', 'string', 'unique:members,phone'],
|
||||
'password' => ['required', Password::min(6)],
|
||||
'birthday' => ['nullable', 'date'],
|
||||
'gender' => ['nullable', 'in:male,female,other'],
|
||||
], [
|
||||
'name.required' => '請輸入姓名',
|
||||
'email.unique' => '此 Email 已被註冊',
|
||||
'phone.unique' => '此手機號碼已被註冊',
|
||||
'password.required' => '請輸入密碼',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '驗證失敗',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
// 必須提供 email 或 phone 其中之一
|
||||
if (empty($request->email) && empty($request->phone)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '請提供 Email 或手機號碼',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$member = Member::create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'phone' => $request->phone,
|
||||
'password' => $request->password,
|
||||
'birthday' => $request->birthday,
|
||||
'gender' => $request->gender,
|
||||
]);
|
||||
|
||||
$token = $member->createToken('member-token')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '註冊成功',
|
||||
'data' => [
|
||||
'member' => $member,
|
||||
'token' => $token,
|
||||
],
|
||||
], 201);
|
||||
}
|
||||
|
||||
/**
|
||||
* 會員登入(Email/Phone + Password)
|
||||
*/
|
||||
public function login(Request $request): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'account' => ['required', 'string'],
|
||||
'password' => ['required', 'string'],
|
||||
], [
|
||||
'account.required' => '請輸入帳號',
|
||||
'password.required' => '請輸入密碼',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '驗證失敗',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
// 嘗試以 email 或 phone 查詢
|
||||
$member = Member::where('email', $request->account)
|
||||
->orWhere('phone', $request->account)
|
||||
->first();
|
||||
|
||||
if (!$member || !Hash::check($request->password, $member->password)) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '帳號或密碼錯誤',
|
||||
], 401);
|
||||
}
|
||||
|
||||
if (!$member->is_active) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '帳號已被停用',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$token = $member->createToken('member-token')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '登入成功',
|
||||
'data' => [
|
||||
'member' => $member,
|
||||
'token' => $token,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 社群登入
|
||||
*/
|
||||
public function socialLogin(Request $request): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'provider' => ['required', 'in:line,google,facebook'],
|
||||
'provider_id' => ['required', 'string'],
|
||||
'access_token' => ['nullable', 'string'],
|
||||
'name' => ['nullable', 'string'],
|
||||
'email' => ['nullable', 'email'],
|
||||
'avatar' => ['nullable', 'string'],
|
||||
], [
|
||||
'provider.required' => '請指定登入平台',
|
||||
'provider.in' => '不支援的登入平台',
|
||||
'provider_id.required' => '缺少社群用戶 ID',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '驗證失敗',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
// 查詢是否已綁定
|
||||
$socialAccount = SocialAccount::where('provider', $request->provider)
|
||||
->where('provider_id', $request->provider_id)
|
||||
->first();
|
||||
|
||||
if ($socialAccount) {
|
||||
// 已綁定,直接登入
|
||||
$member = $socialAccount->member;
|
||||
|
||||
// 更新 token
|
||||
$socialAccount->update([
|
||||
'access_token' => $request->access_token,
|
||||
]);
|
||||
} else {
|
||||
// 未綁定,建立新會員
|
||||
$member = Member::create([
|
||||
'name' => $request->name ?? '會員',
|
||||
'email' => $request->email,
|
||||
'avatar' => $request->avatar,
|
||||
'email_verified_at' => $request->email ? now() : null, // 社群登入自動驗證
|
||||
]);
|
||||
|
||||
// 綁定社群帳號
|
||||
$member->socialAccounts()->create([
|
||||
'provider' => $request->provider,
|
||||
'provider_id' => $request->provider_id,
|
||||
'access_token' => $request->access_token,
|
||||
'profile_data' => $request->only(['name', 'email', 'avatar']),
|
||||
]);
|
||||
}
|
||||
|
||||
if (!$member->is_active) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '帳號已被停用',
|
||||
], 403);
|
||||
}
|
||||
|
||||
$token = $member->createToken('member-token')->plainTextToken;
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '登入成功',
|
||||
'data' => [
|
||||
'member' => $member,
|
||||
'token' => $token,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得個人資料
|
||||
*/
|
||||
public function profile(Request $request): JsonResponse
|
||||
{
|
||||
$member = $request->user();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'data' => [
|
||||
'member' => $member->load('socialAccounts'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新個人資料
|
||||
*/
|
||||
public function updateProfile(Request $request): JsonResponse
|
||||
{
|
||||
$member = $request->user();
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'name' => ['nullable', 'string', 'max:255'],
|
||||
'birthday' => ['nullable', 'date'],
|
||||
'gender' => ['nullable', 'in:male,female,other'],
|
||||
'avatar' => ['nullable', 'string'],
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => '驗證失敗',
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$member->update($request->only(['name', 'birthday', 'gender', 'avatar']));
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '更新成功',
|
||||
'data' => [
|
||||
'member' => $member,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出
|
||||
*/
|
||||
public function logout(Request $request): JsonResponse
|
||||
{
|
||||
$request->user()->currentAccessToken()->delete();
|
||||
|
||||
return response()->json([
|
||||
'success' => true,
|
||||
'message' => '登出成功',
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user