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(), ]; } }