You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

276 lines
11 KiB

1 week ago
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Activity;
use App\Models\TicketGrabEvent;
use App\Models\TicketGrabEventVenue;
use App\Models\VerifyPortalCredential;
use App\Support\VerifyPortalCode;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash;
1 week ago
use Illuminate\Validation\Rules\Password;
1 week ago
class VerifyPortalManageController extends Controller
{
public function activityShow(Request $request, Activity $activity): JsonResponse
{
$this->ensureActivityVenueAdmin($request, $activity);
if ($activity->verify_portal_token === null || $activity->verify_portal_token === '') {
$activity->forceFill(['verify_portal_token' => (string) \Illuminate\Support\Str::uuid()])->save();
}
VerifyPortalCode::ensureForActivity($activity);
$activity->refresh();
$rows = VerifyPortalCredential::query()
->where('portal_kind', VerifyPortalCredential::KIND_ACTIVITY)
->where('portal_id', $activity->id)
->with('venue:id,name')
->orderBy('id')
->get()
->map(fn (VerifyPortalCredential $c) => $this->credentialToRow($c));
return response()->json([
'verify_portal_code' => $activity->verify_portal_code,
'credentials' => $rows,
]);
}
public function activityStore(Request $request, Activity $activity): JsonResponse
{
$this->ensureActivityVenueAdmin($request, $activity);
$data = $request->validate([
'username' => ['required', 'string', 'max:64'],
'password' => ['required', 'string', 'min:6', 'max:72'],
'note' => ['nullable', 'string', 'max:255'],
]);
$vid = (int) $activity->venue_id;
$exists = VerifyPortalCredential::query()
->where('portal_kind', VerifyPortalCredential::KIND_ACTIVITY)
->where('portal_id', $activity->id)
->where('venue_id', $vid)
->where('username', $data['username'])
->exists();
if ($exists) {
return response()->json(['message' => '该场馆下此用户名已存在'], 422);
}
$plain = (string) $data['password'];
$c = VerifyPortalCredential::query()->create([
'portal_kind' => VerifyPortalCredential::KIND_ACTIVITY,
'portal_id' => $activity->id,
'venue_id' => $vid,
'username' => $data['username'],
'password' => Hash::make($plain),
'password_plain_enc' => Crypt::encryptString($plain),
'note' => $data['note'] ?? null,
]);
return response()->json(['id' => $c->id], 201);
}
public function activityUpdate(Request $request, Activity $activity, VerifyPortalCredential $verifyPortalCredential): JsonResponse
{
$this->ensureActivityVenueAdmin($request, $activity);
$this->assertCredentialBelongsActivity($verifyPortalCredential, $activity);
$data = $request->validate([
'password' => ['sometimes', 'string', 'min:6', 'max:72'],
'note' => ['sometimes', 'nullable', 'string', 'max:255'],
]);
if (array_key_exists('password', $data)) {
$plain = (string) $data['password'];
$verifyPortalCredential->password = Hash::make($plain);
$verifyPortalCredential->password_plain_enc = Crypt::encryptString($plain);
}
if (array_key_exists('note', $data)) {
$verifyPortalCredential->note = $data['note'];
}
$verifyPortalCredential->save();
return response()->json(['message' => '已更新']);
}
public function activityDestroy(Request $request, Activity $activity, VerifyPortalCredential $verifyPortalCredential): JsonResponse
{
$this->ensureActivityVenueAdmin($request, $activity);
$this->assertCredentialBelongsActivity($verifyPortalCredential, $activity);
$verifyPortalCredential->tokens()->delete();
$verifyPortalCredential->delete();
return response()->json(['message' => '已删除']);
}
public function ticketGrabShow(Request $request, TicketGrabEvent $ticketGrabEvent): JsonResponse
{
$this->ensureTicketGrabAdmin($request, $ticketGrabEvent);
if ($ticketGrabEvent->verify_portal_token === null || $ticketGrabEvent->verify_portal_token === '') {
$ticketGrabEvent->forceFill(['verify_portal_token' => (string) \Illuminate\Support\Str::uuid()])->save();
}
VerifyPortalCode::ensureForTicketGrabEvent($ticketGrabEvent);
$ticketGrabEvent->refresh();
$rows = VerifyPortalCredential::query()
->where('portal_kind', VerifyPortalCredential::KIND_TICKET_GRAB)
->where('portal_id', $ticketGrabEvent->id)
->with('venue:id,name')
->orderBy('venue_id')
->orderBy('id')
->get()
->map(fn (VerifyPortalCredential $c) => $this->credentialToRow($c));
return response()->json([
'verify_portal_code' => $ticketGrabEvent->verify_portal_code,
'credentials' => $rows,
]);
}
public function ticketGrabStore(Request $request, TicketGrabEvent $ticketGrabEvent): JsonResponse
{
$this->ensureTicketGrabAdmin($request, $ticketGrabEvent);
$data = $request->validate([
'venue_id' => ['required', 'integer'],
'username' => ['required', 'string', 'max:64'],
1 week ago
'password' => ['required', 'string', 'max:72', Password::min(8)->mixedCase()->symbols()],
1 week ago
'note' => ['nullable', 'string', 'max:255'],
]);
$vid = (int) $data['venue_id'];
$pivotOk = TicketGrabEventVenue::query()
->where('ticket_grab_event_id', $ticketGrabEvent->id)
->where('venue_id', $vid)
->exists();
abort_unless($pivotOk, 422, '场馆未参与本抢票活动');
$exists = VerifyPortalCredential::query()
->where('portal_kind', VerifyPortalCredential::KIND_TICKET_GRAB)
->where('portal_id', $ticketGrabEvent->id)
->where('venue_id', $vid)
->where('username', $data['username'])
->exists();
if ($exists) {
return response()->json(['message' => '该场馆下此用户名已存在'], 422);
}
$plain = (string) $data['password'];
$c = VerifyPortalCredential::query()->create([
'portal_kind' => VerifyPortalCredential::KIND_TICKET_GRAB,
'portal_id' => $ticketGrabEvent->id,
'venue_id' => $vid,
'username' => $data['username'],
'password' => Hash::make($plain),
'password_plain_enc' => Crypt::encryptString($plain),
'note' => $data['note'] ?? null,
]);
return response()->json(['id' => $c->id], 201);
}
public function ticketGrabUpdate(
Request $request,
TicketGrabEvent $ticketGrabEvent,
VerifyPortalCredential $verifyPortalCredential,
): JsonResponse {
$this->ensureTicketGrabAdmin($request, $ticketGrabEvent);
$this->assertCredentialBelongsTicketGrab($verifyPortalCredential, $ticketGrabEvent);
$data = $request->validate([
1 week ago
'password' => ['sometimes', 'string', 'max:72', Password::min(8)->mixedCase()->symbols()],
1 week ago
'note' => ['sometimes', 'nullable', 'string', 'max:255'],
]);
if (array_key_exists('password', $data)) {
$plain = (string) $data['password'];
$verifyPortalCredential->password = Hash::make($plain);
$verifyPortalCredential->password_plain_enc = Crypt::encryptString($plain);
}
if (array_key_exists('note', $data)) {
$verifyPortalCredential->note = $data['note'];
}
$verifyPortalCredential->save();
return response()->json(['message' => '已更新']);
}
public function ticketGrabDestroy(
Request $request,
TicketGrabEvent $ticketGrabEvent,
VerifyPortalCredential $verifyPortalCredential,
): JsonResponse {
$this->ensureTicketGrabAdmin($request, $ticketGrabEvent);
$this->assertCredentialBelongsTicketGrab($verifyPortalCredential, $ticketGrabEvent);
$verifyPortalCredential->tokens()->delete();
$verifyPortalCredential->delete();
return response()->json(['message' => '已删除']);
}
/**
* @return array<string, mixed>
*/
private function credentialToRow(VerifyPortalCredential $c): array
{
$plain = null;
if ($c->password_plain_enc !== null && $c->password_plain_enc !== '') {
try {
$plain = Crypt::decryptString($c->password_plain_enc);
} catch (\Throwable) {
$plain = null;
}
}
return [
'id' => $c->id,
'venue_id' => $c->venue_id,
'venue_name' => $c->venue?->name,
'username' => $c->username,
'password_plain' => $plain,
'note' => $c->note,
'created_at' => $c->created_at?->toDateTimeString(),
];
}
private function ensureActivityVenueAdmin(Request $request, Activity $activity): void
{
$user = $request->user();
if ($user->isSuperAdmin()) {
return;
}
$allowed = $user->venues()->where('venues.id', $activity->venue_id)->exists();
abort_unless($allowed, 403, '仅可操作已绑定场馆');
}
private function ensureTicketGrabAdmin(Request $request, TicketGrabEvent $e): void
{
$pivots = TicketGrabEventVenue::query()
->where('ticket_grab_event_id', $e->id)
->pluck('venue_id')
->all();
if ($pivots === []) {
return;
}
$user = $request->user();
if ($user->isSuperAdmin()) {
return;
}
$allow = $user->venues()->pluck('venues.id');
foreach (array_unique(array_map('intval', $pivots)) as $id) {
if ($id > 0 && ! $allow->contains($id)) {
abort(403, '仅可操作已绑定场馆');
}
}
}
private function assertCredentialBelongsActivity(VerifyPortalCredential $c, Activity $activity): void
{
abort_unless(
$c->portal_kind === VerifyPortalCredential::KIND_ACTIVITY && (int) $c->portal_id === (int) $activity->id,
404
);
}
private function assertCredentialBelongsTicketGrab(VerifyPortalCredential $c, TicketGrabEvent $e): void
{
abort_unless(
$c->portal_kind === VerifyPortalCredential::KIND_TICKET_GRAB && (int) $c->portal_id === (int) $e->id,
404
);
}
}