188 lines
6.5 KiB
PHP
188 lines
6.5 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Http\Controllers\Api\V1;
|
||
|
|
|
||
|
|
use App\Http\Controllers\Controller;
|
||
|
|
use App\Models\Deployment;
|
||
|
|
use App\Models\Order;
|
||
|
|
use App\Models\Plan;
|
||
|
|
use App\Models\ServiceTicket;
|
||
|
|
use App\Models\UserSession;
|
||
|
|
use App\Models\UserToolOpen;
|
||
|
|
use App\Services\AuthService;
|
||
|
|
use App\Services\MembershipService;
|
||
|
|
use App\Support\Presenter;
|
||
|
|
use Illuminate\Http\Request;
|
||
|
|
use Illuminate\Support\Facades\DB;
|
||
|
|
|
||
|
|
class AccountController extends Controller
|
||
|
|
{
|
||
|
|
public function __construct(
|
||
|
|
private readonly AuthService $auth,
|
||
|
|
private readonly MembershipService $membership,
|
||
|
|
) {}
|
||
|
|
|
||
|
|
public function overview(Request $request)
|
||
|
|
{
|
||
|
|
$user = $request->user();
|
||
|
|
$recent = UserToolOpen::query()->with('tool.tags')->where('user_id', $user->id)->orderByDesc('last_opened_at')->limit(8)->get()
|
||
|
|
->filter(fn ($row) => $row->tool && $row->tool->status === 'published')
|
||
|
|
->map(fn ($row) => Presenter::toolCard($row->tool))->values();
|
||
|
|
|
||
|
|
return $this->ok([
|
||
|
|
'user' => Presenter::user($user),
|
||
|
|
'recent_tools' => $recent,
|
||
|
|
'pending_orders' => Order::query()->where('user_id', $user->id)->where('status', 'pending')->count(),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function updateProfile(Request $request)
|
||
|
|
{
|
||
|
|
$data = $request->validate(['nickname' => ['required', 'string', 'min:2', 'max:40']]);
|
||
|
|
$request->user()->update(['nickname' => $data['nickname']]);
|
||
|
|
|
||
|
|
return $this->ok(Presenter::user($request->user()->refresh()), '资料已更新');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function updatePassword(Request $request)
|
||
|
|
{
|
||
|
|
$data = $request->validate([
|
||
|
|
'old_password' => ['nullable', 'string'],
|
||
|
|
'password' => ['required', 'string'],
|
||
|
|
]);
|
||
|
|
$this->auth->changePassword(
|
||
|
|
$request->user(),
|
||
|
|
isset($data['old_password']) ? $this->auth->decrypt($data['old_password']) : null,
|
||
|
|
$this->auth->decrypt($data['password'])
|
||
|
|
);
|
||
|
|
|
||
|
|
return $this->ok(null, '密码已更新,其他工具需要重新登录');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function bind(Request $request)
|
||
|
|
{
|
||
|
|
$data = $request->validate([
|
||
|
|
'channel' => ['required', 'in:sms,email'],
|
||
|
|
'account' => ['required', 'string'],
|
||
|
|
'code' => ['required', 'string'],
|
||
|
|
]);
|
||
|
|
$this->auth->bind($request->user(), $data['channel'], $data['account'], $data['code']);
|
||
|
|
|
||
|
|
return $this->ok(Presenter::user($request->user()->refresh()), '绑定成功');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function sessions(Request $request)
|
||
|
|
{
|
||
|
|
$current = $request->session()->getId();
|
||
|
|
$rows = UserSession::query()->where('user_id', $request->user()->id)->whereNull('revoked_at')->orderByDesc('last_seen_at')->get();
|
||
|
|
|
||
|
|
return $this->ok($rows->map(fn (UserSession $row) => [
|
||
|
|
'id' => $row->id,
|
||
|
|
'ip' => $row->ip,
|
||
|
|
'user_agent' => $row->user_agent,
|
||
|
|
'last_seen_at' => $row->last_seen_at?->toIso8601String(),
|
||
|
|
'current' => $row->session_id === $current,
|
||
|
|
])->values());
|
||
|
|
}
|
||
|
|
|
||
|
|
public function revokeSession(Request $request, UserSession $session)
|
||
|
|
{
|
||
|
|
$this->owns($request, $session);
|
||
|
|
$current = $session->session_id === $request->session()->getId();
|
||
|
|
$session->update(['revoked_at' => now()]);
|
||
|
|
DB::table('sessions')->where('id', $session->session_id)->delete();
|
||
|
|
if ($current) {
|
||
|
|
$this->auth->logout($request, false);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->ok(['logged_out' => $current], '会话已下线');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function revokeAll(Request $request)
|
||
|
|
{
|
||
|
|
$this->auth->logout($request, true);
|
||
|
|
|
||
|
|
return $this->ok(null, '已全部下线');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function membership(Request $request)
|
||
|
|
{
|
||
|
|
return $this->ok($this->membership->snapshot($request->user()));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function orders(Request $request)
|
||
|
|
{
|
||
|
|
$rows = Order::query()->where('user_id', $request->user()->id)->latest()->limit(50)->get();
|
||
|
|
|
||
|
|
return $this->ok($rows->map(fn (Order $order) => $this->orderPayload($order))->values());
|
||
|
|
}
|
||
|
|
|
||
|
|
public function storeOrder(Request $request)
|
||
|
|
{
|
||
|
|
$data = $request->validate(['plan_id' => ['required', 'integer']]);
|
||
|
|
$plan = Plan::query()->where('status', 'published')->findOrFail($data['plan_id']);
|
||
|
|
if ($plan->price_cents <= 0) {
|
||
|
|
return response()->json(['message' => '免费方案无需下单', 'code' => 'free_plan'], 422);
|
||
|
|
}
|
||
|
|
$order = Order::query()->create([
|
||
|
|
'order_no' => 'QZ'.now()->format('YmdHis').random_int(1000, 9999),
|
||
|
|
'user_id' => $request->user()->id,
|
||
|
|
'type' => 'subscription',
|
||
|
|
'status' => 'pending',
|
||
|
|
'amount_cents' => $plan->price_cents,
|
||
|
|
'title' => $plan->name,
|
||
|
|
]);
|
||
|
|
$order->items()->create([
|
||
|
|
'plan_id' => $plan->id,
|
||
|
|
'title' => $plan->name,
|
||
|
|
'amount_cents' => $plan->price_cents,
|
||
|
|
]);
|
||
|
|
|
||
|
|
return $this->ok($this->orderPayload($order), '订单已提交,等待确认收款');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function services(Request $request)
|
||
|
|
{
|
||
|
|
$userId = $request->user()->id;
|
||
|
|
|
||
|
|
return $this->ok([
|
||
|
|
'tickets' => ServiceTicket::query()->with('tool:id,name')->where('user_id', $userId)->latest()->get(),
|
||
|
|
'deployments' => Deployment::query()->with('tool:id,name')->where('user_id', $userId)->latest()->get(),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function storeTicket(Request $request)
|
||
|
|
{
|
||
|
|
$data = $request->validate([
|
||
|
|
'contact' => ['required', 'string', 'max:120'],
|
||
|
|
'requirement' => ['required', 'string', 'max:5000'],
|
||
|
|
'tool_id' => ['nullable', 'integer', 'exists:tools,id'],
|
||
|
|
]);
|
||
|
|
$ticket = ServiceTicket::query()->create($data + [
|
||
|
|
'user_id' => $request->user()->id,
|
||
|
|
'status' => 'pending',
|
||
|
|
]);
|
||
|
|
|
||
|
|
return $this->ok($ticket, '需求已提交');
|
||
|
|
}
|
||
|
|
|
||
|
|
private function owns(Request $request, UserSession $session): void
|
||
|
|
{
|
||
|
|
abort_unless($session->user_id === $request->user()->id && $session->revoked_at === null, 404);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function orderPayload(Order $order): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'id' => $order->id,
|
||
|
|
'order_no' => $order->order_no,
|
||
|
|
'type' => $order->type,
|
||
|
|
'status' => $order->status,
|
||
|
|
'amount_cents' => $order->amount_cents,
|
||
|
|
'title' => $order->title,
|
||
|
|
'paid_at' => $order->paid_at?->toIso8601String(),
|
||
|
|
'created_at' => $order->created_at?->toIso8601String(),
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|