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.

279 lines
10 KiB

1 month ago
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\AudienceRegistration;
use App\Models\AudienceSession;
use App\Models\Competition;
1 month ago
use App\Models\CompetitionPortalLocale;
1 month ago
use App\Models\User;
use App\Services\AudienceRegistrationService;
use App\Support\BizDateTime;
1 month ago
use App\Support\PortalLocale;
use App\Support\SignupApplicationFieldRegistry;
use App\Support\SignupDisplay;
use App\Support\SignupOptionCatalog;
1 month ago
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class AudienceController extends Controller
{
public function me(Request $request): JsonResponse
{
/** @var User $user */
$user = $request->user();
1 month ago
$payload = [
1 month ago
'id' => $user->id,
'mobile' => $user->mobile,
'name' => $user->name,
1 month ago
];
$slug = $request->query('competition_slug');
if (is_string($slug) && $slug !== '') {
$competition = Competition::query()
->where('slug', $slug)
->where('published', true)
->first();
if ($competition) {
$payload['signup_locale'] = SignupDisplay::audienceLocale((int) $user->id, (int) $competition->id);
$payload['signup_locale_locked'] = AudienceRegistration::existsForUserCompetition(
(int) $user->id,
(int) $competition->id
);
}
}
return response()->json($payload);
1 month ago
}
public function sessions(Request $request): JsonResponse
{
$competition = $this->publishedCompetition($request);
/** @var User $user */
$user = $request->user();
$registeredSessionIds = AudienceRegistration::query()
->where('user_id', $user->id)
->where('competition_id', $competition->id)
->pluck('audience_session_id')
->map(fn ($id) => (int) $id)
->all();
$sessions = $competition->audienceSessions()
->withCount('registrations')
->where('enabled', true)
->get()
->map(fn (AudienceSession $session) => $this->sessionPayload(
$session,
in_array((int) $session->id, $registeredSessionIds, true)
));
return response()->json(['data' => $sessions]);
}
public function storeRegistration(Request $request, AudienceRegistrationService $service): JsonResponse
{
$competition = $this->publishedCompetition($request);
/** @var User $user */
$user = $request->user();
1 month ago
if ($request->exists('purpose')) {
$request->merge([
'purpose' => SignupOptionCatalog::normalize('purpose', (string) $request->input('purpose')),
]);
}
$purposes = SignupOptionCatalog::acceptedValues(SignupOptionCatalog::audiencePurposes(), 'purpose');
$competition->loadMissing('audienceFormSchema');
$schema = $competition->audienceFormSchema?->schema_json;
$schemaKeys = SignupApplicationFieldRegistry::keysFromSchemaJson(is_array($schema) ? $schema : null);
$companyRequired = $this->audienceSchemaFieldRequired(is_array($schema) ? $schema : null, 'company', true);
$rules = [
1 month ago
'audience_session_id' => ['required', 'integer', 'min:1'],
'name' => ['required', 'string', 'max:64'],
1 month ago
'company' => [$companyRequired ? 'required' : 'nullable', 'string', 'max:128'],
1 month ago
'job' => ['nullable', 'string', 'max:128'],
1 month ago
'purpose' => ['required', 'string', Rule::in($purposes)],
];
foreach ($schemaKeys as $key) {
if (isset($rules[$key]) || in_array($key, ['phone', 'audience_session_id'], true)) {
continue;
}
$rules[$key] = ['nullable', 'string', 'max:5000'];
}
$data = $request->validate($rules, [], [
1 month ago
'name' => '个人姓名',
'company' => '所属单位',
'job' => '个人职务',
'purpose' => '观赛目的',
'audience_session_id' => '赛事场次',
]);
$mobile = trim((string) $user->mobile);
if (! preg_match('/^1[3-9]\d{9}$/', $mobile)) {
throw ValidationException::withMessages([
'phone' => ['登录手机号无效,请重新登录'],
]);
}
$data['phone'] = $mobile;
1 month ago
$columnKeys = ['audience_session_id', 'name', 'company', 'job', 'purpose', 'phone'];
$answers = [];
foreach ($data as $key => $value) {
if (! in_array($key, $columnKeys, true)) {
$answers[$key] = $value;
}
}
$data['answers_json'] = $answers;
1 month ago
$registration = $service->register($user, $competition, $data);
$registration->load('session');
1 month ago
$hadPrior = AudienceRegistration::query()
->where('user_id', $user->id)
->where('competition_id', $competition->id)
->whereKeyNot($registration->id)
->exists();
if (! $hadPrior) {
CompetitionPortalLocale::remember(
(int) $user->id,
(int) $competition->id,
PortalLocale::PORTAL_AUDIENCE,
PortalLocale::fromRequest(
is_string($request->query('lang'))
? $request->query('lang')
: (is_string($request->input('lang')) ? $request->input('lang') : null)
)
);
}
return response()->json($this->registrationPayload($registration, $user), 201);
1 month ago
}
public function registrations(Request $request): JsonResponse
{
$competition = $this->publishedCompetition($request);
/** @var User $user */
$user = $request->user();
$rows = AudienceRegistration::query()
->with('session')
->where('user_id', $user->id)
->where('competition_id', $competition->id)
->orderByDesc('registered_at')
->orderByDesc('id')
->get()
1 month ago
->map(fn (AudienceRegistration $row) => $this->registrationPayload($row, $user));
1 month ago
return response()->json(['data' => $rows]);
}
/**
* @return array<string, mixed>
*/
private function sessionPayload(AudienceSession $session, bool $isRegistered): array
{
$status = $session->windowStatus();
$statusLabel = $isRegistered ? '已报名' : $session->statusLabel();
return [
'id' => $session->id,
'code' => $session->code,
'name' => $session->name,
1 month ago
'name_en' => $session->name_en,
1 month ago
'event_date_text' => $session->event_date_text,
1 month ago
'event_date_text_en' => $session->event_date_text_en,
1 month ago
'address' => $session->address,
1 month ago
'address_en' => $session->address_en,
1 month ago
'capacity' => (int) $session->capacity,
'registered_count' => $session->registeredCount(),
'remaining' => $session->remainingCount(),
'is_final' => (bool) $session->is_final,
'signup_start_at' => BizDateTime::toIso8601($session->signup_start_at),
'signup_end_at' => BizDateTime::toIso8601($session->signup_end_at),
'status' => $status,
'status_label' => $statusLabel,
'is_registered' => $isRegistered,
'is_available' => ! $isRegistered && $session->isAvailable(),
];
}
/**
* @return array<string, mixed>
*/
1 month ago
private function registrationPayload(AudienceRegistration $row, User $user): array
1 month ago
{
$session = $row->session;
1 month ago
$locale = SignupDisplay::audienceLocale((int) $user->id, (int) $row->competition_id);
$payload = [
1 month ago
'id' => $row->id,
'audience_session_id' => $row->audience_session_id,
1 month ago
'session_name' => $session ? SignupDisplay::sessionName($session, $locale) : null,
'session_name_en' => $session ? SignupDisplay::sessionName($session, PortalLocale::EN) : null,
'event_date_text' => $session ? SignupDisplay::sessionDateText($session, $locale) : null,
'event_date_text_en' => $session ? SignupDisplay::sessionDateText($session, PortalLocale::EN) : null,
'address' => $session ? SignupDisplay::sessionAddress($session, $locale) : null,
'address_en' => $session ? SignupDisplay::sessionAddress($session, PortalLocale::EN) : null,
1 month ago
'name' => $row->name,
'phone' => $row->phone,
'company' => $row->company,
'job' => $row->job,
'purpose' => $row->purpose,
1 month ago
'purpose_label' => SignupDisplay::purposeLabel((string) $row->purpose, $locale),
1 month ago
'ticket_code' => $row->ticket_code,
'registered_at' => BizDateTime::toIso8601($row->registered_at),
'checked_in_at' => BizDateTime::toIso8601($row->checked_in_at),
1 month ago
'signup_locale' => $locale,
1 month ago
];
1 month ago
$answers = is_array($row->answers_json) ? $row->answers_json : [];
foreach ($answers as $key => $value) {
if (! array_key_exists($key, $payload)) {
$payload[$key] = $value;
}
}
return $payload;
1 month ago
}
private function publishedCompetition(Request $request): Competition
{
$data = $request->validate([
'competition_slug' => ['required', 'string', 'max:64'],
]);
$competition = Competition::query()
->where('slug', $data['competition_slug'])
->where('published', true)
->first();
if ($competition === null) {
throw ValidationException::withMessages([
'competition_slug' => ['赛事不存在或未发布'],
]);
}
return $competition;
}
1 month ago
/**
* @param list<mixed>|null $schema
*/
private function audienceSchemaFieldRequired(?array $schema, string $key, bool $default): bool
{
if (! is_array($schema) || $schema === []) {
return $default;
}
foreach ($schema as $row) {
if (! is_array($row) || ($row['key'] ?? '') !== $key) {
continue;
}
return (bool) ($row['required'] ?? false);
}
return false;
}
1 month ago
}