Files

188 lines
6.5 KiB
PHP
Raw Permalink Normal View History

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