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.

115 lines
3.7 KiB

1 month ago
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Api\Concerns\ResolvesParticipantApplication;
use App\Http\Controllers\Controller;
use App\Models\Application;
use App\Models\ApplicationFile;
3 weeks ago
use App\Support\SignupFormFileRules;
1 month ago
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
1 month ago
use Symfony\Component\HttpFoundation\StreamedResponse;
1 month ago
class ApplicationFileController extends Controller
{
use ResolvesParticipantApplication;
private function application(Request $request): Application
{
$app = $this->participantApplication($request);
$app->assertMayEditSignup('file');
return $app;
}
public function store(Request $request): JsonResponse
{
$maxKb = (int) config('contest.file_max_kb', 20480);
$data = $request->validate([
'kind' => ['required', 'string', Rule::in(['plan', 'supporting'])],
'file' => ['required', 'file', 'max:'.$maxKb],
]);
$app = $this->application($request);
3 weeks ago
$app->loadMissing('competition');
1 month ago
$uploaded = $data['file'];
$ext = strtolower($uploaded->getClientOriginalExtension());
3 weeks ago
$schemaRow = SignupFormFileRules::fileFieldRow($app->competition, $data['kind']);
$allowedExt = SignupFormFileRules::effectiveAllowedExtensions($schemaRow);
if (! in_array($ext, $allowedExt, true)) {
1 month ago
throw ValidationException::withMessages([
3 weeks ago
'file' => ['仅支持:'.implode('、', $allowedExt)],
1 month ago
]);
}
3 weeks ago
$maxFiles = SignupFormFileRules::maxCount($schemaRow);
if ($maxFiles !== null) {
$current = $app->files()->where('kind', $data['kind'])->count();
if ($current >= $maxFiles) {
throw ValidationException::withMessages([
'file' => ['最多可上传 '.$maxFiles.' 个文件'],
]);
}
}
1 month ago
$path = $uploaded->store("applications/{$app->id}", 'public');
$file = ApplicationFile::create([
'application_id' => $app->id,
'kind' => $data['kind'],
'disk' => 'public',
'path' => $path,
'original_name' => $uploaded->getClientOriginalName(),
'size' => $uploaded->getSize(),
'mime' => $uploaded->getClientMimeType(),
]);
return response()->json([
'id' => $file->id,
'kind' => $file->kind,
'original_name' => $file->original_name,
'size' => $file->size,
1 month ago
'url' => $file->participantPreviewSignedUrl(),
1 month ago
], 201);
}
1 month ago
/**
* 选手附件预览(签名 URLGET 无需 Bearer由 API 在 json 中下发短期有效链接)。
*/
public function downloadSigned(Request $request, ApplicationFile $file): StreamedResponse
{
if (! Storage::disk($file->disk)->exists($file->path)) {
abort(404);
}
return Storage::disk($file->disk)->response(
$file->path,
$file->clientDownloadName(),
[
'Cache-Control' => 'private, no-store',
],
$file->preferredStreamDisposition(),
);
}
1 month ago
public function destroy(Request $request, ApplicationFile $file): JsonResponse
{
if ($file->application->user_id !== $request->user()->id) {
abort(404);
}
$file->application->assertMayEditSignup('file');
Storage::disk($file->disk)->delete($file->path);
$file->delete();
return response()->json(['message' => 'deleted']);
}
}