|
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
|
|
|
|
|
|
use App\Http\Controllers\Controller;
|
|
|
|
|
use App\Models\AudienceRegistration;
|
|
|
|
|
use App\Models\AudienceSession;
|
|
|
|
|
use App\Models\Competition;
|
|
|
|
|
use App\Models\CompetitionPortalLocale;
|
|
|
|
|
use App\Models\User;
|
|
|
|
|
use App\Services\AudienceRegistrationService;
|
|
|
|
|
use App\Support\BizDateTime;
|
|
|
|
|
use App\Support\PortalLocale;
|
|
|
|
|
use App\Support\SignupApplicationFieldRegistry;
|
|
|
|
|
use App\Support\SignupDisplay;
|
|
|
|
|
use App\Support\SignupOptionCatalog;
|
|
|
|
|
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();
|
|
|
|
|
$payload = [
|
|
|
|
|
'id' => $user->id,
|
|
|
|
|
'mobile' => $user->mobile,
|
|
|
|
|
'name' => $user->name,
|
|
|
|
|
];
|
|
|
|
|
$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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
|
|
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 = [
|
|
|
|
|
'audience_session_id' => ['required', 'integer', 'min:1'],
|
|
|
|
|
'name' => ['required', 'string', 'max:64'],
|
|
|
|
|
'company' => [$companyRequired ? 'required' : 'nullable', 'string', 'max:128'],
|
|
|
|
|
'job' => ['nullable', 'string', 'max:128'],
|
|
|
|
|
'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, [], [
|
|
|
|
|
'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;
|
|
|
|
|
$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;
|
|
|
|
|
|
|
|
|
|
$registration = $service->register($user, $competition, $data);
|
|
|
|
|
$registration->load('session');
|
|
|
|
|
|
|
|
|
|
$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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
->map(fn (AudienceRegistration $row) => $this->registrationPayload($row, $user));
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
'name_en' => $session->name_en,
|
|
|
|
|
'event_date_text' => $session->event_date_text,
|
|
|
|
|
'event_date_text_en' => $session->event_date_text_en,
|
|
|
|
|
'address' => $session->address,
|
|
|
|
|
'address_en' => $session->address_en,
|
|
|
|
|
'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>
|
|
|
|
|
*/
|
|
|
|
|
private function registrationPayload(AudienceRegistration $row, User $user): array
|
|
|
|
|
{
|
|
|
|
|
$session = $row->session;
|
|
|
|
|
$locale = SignupDisplay::audienceLocale((int) $user->id, (int) $row->competition_id);
|
|
|
|
|
$payload = [
|
|
|
|
|
'id' => $row->id,
|
|
|
|
|
'audience_session_id' => $row->audience_session_id,
|
|
|
|
|
'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,
|
|
|
|
|
'name' => $row->name,
|
|
|
|
|
'phone' => $row->phone,
|
|
|
|
|
'company' => $row->company,
|
|
|
|
|
'job' => $row->job,
|
|
|
|
|
'purpose' => $row->purpose,
|
|
|
|
|
'purpose_label' => SignupDisplay::purposeLabel((string) $row->purpose, $locale),
|
|
|
|
|
'ticket_code' => $row->ticket_code,
|
|
|
|
|
'registered_at' => BizDateTime::toIso8601($row->registered_at),
|
|
|
|
|
'checked_in_at' => BizDateTime::toIso8601($row->checked_in_at),
|
|
|
|
|
'signup_locale' => $locale,
|
|
|
|
|
];
|
|
|
|
|
$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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @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;
|
|
|
|
|
}
|
|
|
|
|
}
|