validate([ 'page' => ['sometimes', 'integer', 'min:1'], 'per_page' => ['sometimes', 'integer', 'min:1', 'max:100'], 'status' => ['sometimes', 'nullable', 'string', 'max:32'], 'track' => ['sometimes', 'nullable', 'string', 'max:100'], 'keyword' => ['sometimes', 'nullable', 'string', 'max:100'], ]); $query = Application::query() ->where('competition_id', $competition->id) ->withCount(['files', 'reviewScores']); $status = trim((string) ($data['status'] ?? '')); if ($status !== '') { $query->where('status', $status); } $track = trim((string) ($data['track'] ?? '')); if ($track !== '') { $query->where('track', $track); } $keyword = trim((string) ($data['keyword'] ?? '')); if ($keyword !== '') { $like = '%'.str_replace(['%', '_'], ['\\%', '\\_'], $keyword).'%'; $query->where(function ($q) use ($like): void { $q->where('player_name', 'like', $like) ->orWhere('project_name', 'like', $like) ->orWhere('school', 'like', $like) ->orWhere('contact_mobile', 'like', $like) ->orWhere('company_name', 'like', $like); }); } $perPage = min((int) ($data['per_page'] ?? 15), 100); $paginator = $query ->orderByDesc('submitted_at') ->orderByDesc('id') ->paginate($perPage); $stats = $this->reviewStatsForApplicationIds( $paginator->getCollection()->pluck('id')->map(fn ($id) => (int) $id)->all() ); $trackTitles = $competition->tracks()->pluck('title', 'track_code'); $paginator->getCollection()->transform( fn (Application $app) => $this->toListRow($app, $trackTitles, $stats[(int) $app->id] ?? null) ); return response()->json($paginator); } public function show(Competition $competition, Application $application): JsonResponse { if ((int) $application->competition_id !== (int) $competition->id) { abort(404); } $application->loadMissing(['files', 'reviewScores.reviewer', 'user', 'signupChannel']); $trackTitles = $competition->tracks()->pluck('title', 'track_code'); $stats = $this->reviewStatsForApplicationIds([(int) $application->id]); return response()->json( $this->toDetail($application, $competition, $trackTitles, $stats[(int) $application->id] ?? null) ); } public function downloadFile(Competition $competition, Application $application, ApplicationFile $file): StreamedResponse { if ((int) $application->competition_id !== (int) $competition->id || (int) $file->application_id !== (int) $application->id) { abort(404); } return Storage::disk($file->disk)->download( $file->path, $file->clientDownloadName(), ['Cache-Control' => 'private, no-store'] ); } /** * @param list $ids * @return array */ private function reviewStatsForApplicationIds(array $ids): array { if (count($ids) === 0) { return []; } return ApplicationReviewScore::query() ->select([ 'application_id', DB::raw('COUNT(*) as submitted_review_count'), DB::raw('SUM(line_total) as team_sum'), DB::raw('AVG(line_total) as team_avg'), ]) ->whereIn('application_id', $ids) ->groupBy('application_id') ->get() ->mapWithKeys(fn ($row) => [ (int) $row->application_id => [ 'submitted_review_count' => (int) $row->submitted_review_count, 'team_sum' => $row->team_sum !== null ? (float) $row->team_sum : null, 'team_avg' => $row->team_avg !== null ? (float) $row->team_avg : null, ], ]) ->all(); } private function toListRow(Application $app, $trackTitles, ?array $stats): array { return [ 'id' => $app->id, 'status' => $app->status, 'project_name' => $app->project_name ?? '', 'player_name' => $app->player_name ?? '', 'school' => $app->school ?? '', 'contact_mobile' => $app->contact_mobile ?? '', 'entry_group' => $app->entry_group ?? '', 'track_code' => $app->track ?? '', 'track_title' => $trackTitles->get($app->track ?? '') ?? ($app->track ?? ''), 'submitted_at' => $app->submitted_at?->toIso8601String(), 'updated_at' => $app->updated_at?->toIso8601String(), 'files_count' => (int) ($app->files_count ?? 0), 'submitted_review_count' => (int) ($stats['submitted_review_count'] ?? 0), 'team_sum' => $stats['team_sum'] ?? null, 'team_avg' => $stats['team_avg'] ?? null, ]; } private function toDetail(Application $app, Competition $competition, $trackTitles, ?array $stats): array { $base = $this->toListRow($app, $trackTitles, $stats); return $base + [ 'competition' => [ 'id' => $competition->id, 'slug' => $competition->slug, 'name' => $competition->name, ], 'participant' => [ 'id' => $app->user?->id, 'mobile' => $app->user?->mobile, 'name' => $app->user?->name, 'email' => $app->user?->email, 'company' => $app->user?->company, ], 'contact_email' => $app->contact_email ?? '', 'degree' => $app->degree ?? '', 'company_name' => $app->company_name ?? '', 'location_country' => $app->location_country ?? '', 'location_province' => $app->location_province ?? '', 'location_city' => $app->location_city ?? '', 'oversea_country' => $app->oversea_country ?? '', 'intro' => $app->intro ?? '', 'promise_signed_at' => $app->promise_signed_at?->toIso8601String(), 'signup_channel' => $app->signupChannel ? [ 'id' => $app->signupChannel->id, 'channel_code' => $app->signupChannel->channel_code, 'channel_name' => $app->signupChannel->channel_name, ] : null, 'signup_channel_code' => $app->signup_channel_code, 'signup_channel_state' => $app->signup_channel_state, 'files' => $app->files->map(fn (ApplicationFile $file) => [ 'id' => $file->id, 'kind' => $file->kind, 'original_name' => $file->original_name, 'size' => $file->size, 'mime' => $file->mime, ])->values(), 'review_scores' => $app->reviewScores->map(fn (ApplicationReviewScore $score) => [ 'id' => $score->id, 'reviewer_id' => $score->reviewer_id, 'reviewer_name' => $score->reviewer?->name, 'line_total' => $score->line_total !== null ? (string) $score->line_total : null, 'updated_at' => $score->updated_at?->toIso8601String(), ])->values(), ]; } }