|
|
|
|
<?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;
|
|
|
|
|
use Illuminate\Validation\Rules\Password;
|
|
|
|
|
|
|
|
|
|
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->ensureActivityVerifyCredentialMutation($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->ensureActivityVerifyCredentialMutation($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->ensureActivityVerifyCredentialMutation($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'],
|
|
|
|
|
'password' => ['required', 'string', 'max:72', Password::min(8)->mixedCase()->symbols()],
|
|
|
|
|
'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([
|
|
|
|
|
'password' => ['sometimes', 'string', 'max:72', Password::min(8)->mixedCase()->symbols()],
|
|
|
|
|
'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 ensureActivityVerifyCredentialMutation(Request $request, Activity $activity): void
|
|
|
|
|
{
|
|
|
|
|
$this->ensureActivityVenueAdmin($request, $activity);
|
|
|
|
|
if ($request->user()->role === 'venue_admin') {
|
|
|
|
|
abort(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
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|