middleware('auth:sanctum')->only(['store', 'update', 'destroy']); } /** * Seznam soutěží (stránkovaný výstup). * Podporuje ?lang=cs / ?lang=en – name/description se vrací v daném jazyce. */ public function index(Request $request): JsonResponse { $perPage = (int) $request->get('per_page', 100); $onlyActive = (bool) $request->query('only_active', false); $includeTests = filter_var($request->query('include_tests', true), FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE); if ($includeTests === null) { $includeTests = true; } $lang = $request->query('lang'); if (! is_string($lang) || $lang === '') { $lang = app()->getLocale(); } $items = Contest::query() ->with([ 'rounds', 'parameters', 'bands', 'categories', 'powerCategories', 'ruleSet', ]) ->when($onlyActive, fn ($q) => $q->where('is_active', true)) ->when(! $includeTests, fn ($q) => $q->where('is_test', false)) ->orderByDesc('created_at') ->paginate($perPage); // přemapování na konkrétní jazyk (stejný princip jako NewsPostController@index) $items->getCollection()->transform(function (Contest $contest) use ($lang) { $data = $contest->toArray(); $data['name'] = $contest->getTranslation('name', $lang, true); $data['description'] = $contest->getTranslation('description', $lang, true); return $data; }); return response()->json($items); } /** * Vytvoření nové soutěže. * Autorizace přes ContestPolicy@create. */ public function store(Request $request): JsonResponse { $this->authorize('create', Contest::class); $data = $this->validateData($request); $relations = $this->validateRelations($request); if (! array_key_exists('rule_set_id', $data) || $data['rule_set_id'] === null) { $data['rule_set_id'] = $this->resolveDefaultRuleSetId(); } $contest = Contest::create($data); $this->syncRelations($contest, $relations); $contest->load([ 'rounds', 'parameters', 'bands', 'categories', 'powerCategories', 'ruleSet', ]); return response()->json($contest, 201); } /** * Detail soutěže. * Můžeš volat i s ?lang=cs pro konkrétní jazyk. */ public function show(Request $request, Contest $contest): JsonResponse { $lang = $request->query('lang'); if (! is_string($lang) || $lang === '') { $lang = app()->getLocale(); } $contest->load([ 'rounds', 'parameters', 'bands', 'categories', 'powerCategories', 'ruleSet', ]); $data = $contest->toArray(); $data['name'] = $contest->getTranslation('name', $lang, true); $data['description'] = $contest->getTranslation('description', $lang, true); return response()->json($data); } /** * Aktualizace soutěže (partial update). * Autorizace přes ContestPolicy@update. */ public function update(Request $request, Contest $contest): JsonResponse { $this->authorize('update', $contest); $data = $this->validateData($request, partial: true); $relations = $this->validateRelations($request); $contest->fill($data); $contest->save(); $this->syncRelations($contest, $relations); $contest->load([ 'rounds', 'parameters', 'bands', 'categories', 'powerCategories', 'ruleSet', ]); return response()->json($contest); } /** * Smazání soutěže. * Autorizace přes ContestPolicy@delete. */ public function destroy(Contest $contest): JsonResponse { $this->authorize('delete', $contest); $contest->delete(); return response()->json(null, 204); } /** * Validace dat pro store / update. * Stejný princip jako u NewsPost – string nebo array { locale: value }. */ protected function validateData(Request $request, bool $partial = false): array { $required = $partial ? 'sometimes' : 'required'; return $request->validate([ 'name' => [ $required, function (string $attribute, $value, \Closure $fail) { if (is_string($value)) { if (mb_strlen($value) > 255) { $fail('The '.$attribute.' may not be greater than 255 characters.'); } return; } if (is_array($value)) { foreach ($value as $locale => $text) { if (! is_string($text)) { $fail("The {$attribute}.{$locale} must be a string."); return; } if (mb_strlen($text) > 255) { $fail("The {$attribute}.{$locale} may not be greater than 255 characters."); return; } } return; } $fail('The '.$attribute.' must be a string or an array of translated strings.'); }, ], 'description' => [ 'sometimes', function (string $attribute, $value, \Closure $fail) { if ($value === null) { return; } if (is_string($value)) { // max length pokud chceš, nebo bez omezení return; } if (is_array($value)) { foreach ($value as $locale => $text) { if (! is_string($text)) { $fail("The {$attribute}.{$locale} must be a string."); return; } } return; } $fail('The '.$attribute.' must be null, a string, or an array of translated strings.'); }, ], 'url' => ['sometimes', 'nullable', 'email', 'max:255'], 'evaluator' => ['sometimes', 'nullable', 'string', 'max:255'], 'email' => ['sometimes', 'nullable', 'email', 'max:255'], 'email2' => ['sometimes', 'nullable', 'email', 'max:255'], 'is_mcr' => ['sometimes', 'boolean'], 'is_sixhr' => ['sometimes', 'boolean'], 'is_active' => ['sometimes', 'boolean'], 'start_time' => ['sometimes', 'date_format:H:i:s'], 'duration' => ['sometimes', 'integer', 'min:1'], 'logs_deadline_days' => ['sometimes', 'integer', 'min:0'], 'rule_set_id' => ['sometimes', 'nullable', 'integer', 'exists:evaluation_rule_sets,id'], ]); } protected function resolveDefaultRuleSetId(): ?int { return EvaluationRuleSet::where('code', 'default_vhf_compat')->value('id'); } /** * Validace ID navázaných entit (bands, categories, powerCategories). */ protected function validateRelations(Request $request): array { return $request->validate([ 'band_ids' => ['sometimes', 'array'], 'band_ids.*' => ['integer', 'exists:bands,id'], 'category_ids' => ['sometimes', 'array'], 'category_ids.*' => ['integer', 'exists:categories,id'], 'power_category_ids' => ['sometimes', 'array'], 'power_category_ids.*' => ['integer', 'exists:power_categories,id'], ]); } /** * Sync vazeb pro belongsToMany vztahy. */ protected function syncRelations(Contest $contest, array $relations): void { if (array_key_exists('band_ids', $relations)) { $contest->bands()->sync($relations['band_ids']); } if (array_key_exists('category_ids', $relations)) { $contest->categories()->sync($relations['category_ids']); } if (array_key_exists('power_category_ids', $relations)) { $contest->powerCategories()->sync($relations['power_category_ids']); } } }