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.

201 lines
7.9 KiB

<?php
namespace App\Http\Controllers\Api\Admin;
use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\ApplicationFile;
use App\Models\ApplicationReviewScore;
use App\Models\Competition;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\StreamedResponse;
class ApplicationController extends Controller
{
public function index(Request $request, Competition $competition): JsonResponse
{
$data = $request->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<int> $ids
* @return array<int, array{submitted_review_count: int, team_sum: float|null, team_avg: float|null}>
*/
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,
],
'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(),
];
}
}