validate([ 'label_name' => ['required', 'string', 'max:255'], 'color' => ['nullable', 'string', 'regex:/^#[0-9A-Fa-f]{6}$/'], 'slides' => ['array'], 'slides.*.text_content' => ['required', 'string'], 'slides.*.text_content_translated' => ['nullable', 'string'], ], $this->validationMessages()); $normalizedLabelName = CcliLabels::normalizeLabelName($data['label_name']); $responseSong = DB::transaction(function () use ($song, $data, $normalizedLabelName): Song { $label = Label::firstOrCreate( ['name' => $normalizedLabelName], ['color' => $data['color'] ?? self::DEFAULT_LABEL_COLOR], ); if ($song->sections()->where('label_id', $label->id)->exists()) { abort(response()->json([ 'message' => 'Dieser Abschnitt existiert bereits in diesem Lied.', ], 422)); } $section = $song->sections()->create([ 'label_id' => $label->id, 'order' => ((int) $song->sections()->max('order')) + 1, ]); $this->replaceSlides($section, $data['slides'] ?? []); $defaultArrangement = $song->arrangements()->firstOrCreate( ['is_default' => true], ['name' => 'Normal'], ); $defaultArrangement->arrangementSections()->create([ 'song_section_id' => $section->id, 'order' => ((int) $defaultArrangement->arrangementSections()->max('order')) + 1, ]); $this->recomputeHasTranslation($song); return $this->freshSong($song); }); return response()->json([ 'message' => 'Sektion wurde hinzugefügt.', 'data' => $this->songController->formatSongDetail($responseSong), ], 201); } public function update(Request $request, Song $song, SongSection $section): JsonResponse { if ((int) $section->song_id !== (int) $song->id) { return response()->json(['message' => 'Sektion nicht gefunden.'], 404); } $data = $request->validate([ 'slides' => ['required', 'array'], 'slides.*.text_content' => ['required', 'string'], 'slides.*.text_content_translated' => ['nullable', 'string'], 'order' => ['sometimes', 'integer'], ], $this->validationMessages()); $responseSong = DB::transaction(function () use ($song, $section, $data): Song { if (array_key_exists('order', $data)) { $section->update(['order' => $data['order']]); } $this->replaceSlides($section, $data['slides']); $this->recomputeHasTranslation($song); return $this->freshSong($song); }); return response()->json([ 'message' => 'Sektion wurde gespeichert.', 'data' => $this->songController->formatSongDetail($responseSong), ]); } public function destroy(Song $song, SongSection $section): JsonResponse { if ((int) $section->song_id !== (int) $song->id) { return response()->json(['message' => 'Sektion nicht gefunden.'], 404); } $responseSong = DB::transaction(function () use ($song, $section): Song { SongArrangementSection::query() ->where('song_section_id', $section->id) ->whereHas('arrangement', fn ($query) => $query->where('song_id', $song->id)) ->delete(); $section->slides()->delete(); $section->delete(); $this->recomputeHasTranslation($song); return $this->freshSong($song); }); return response()->json([ 'message' => 'Sektion wurde gelöscht.', 'data' => $this->songController->formatSongDetail($responseSong), ]); } private function replaceSlides(SongSection $section, array $slides): void { $section->slides()->delete(); foreach (array_values($slides) as $index => $slide) { $section->slides()->create([ 'order' => $index + 1, 'text_content' => $slide['text_content'], 'text_content_translated' => $slide['text_content_translated'] ?? null, ]); } } private function recomputeHasTranslation(Song $song): void { $hasTranslation = $song->sections() ->whereHas('slides', fn ($query) => $query ->whereNotNull('text_content_translated') ->where('text_content_translated', '!=', '')) ->exists(); $song->update(['has_translation' => $hasTranslation]); } private function freshSong(Song $song): Song { return $song->fresh(['arrangements.arrangementSections.section.slides', 'arrangements.arrangementSections.section.label']); } private function validationMessages(): array { return [ 'label_name.required' => 'Bitte gib einen Namen für die Sektion ein.', 'label_name.string' => 'Der Sektionsname muss ein Text sein.', 'label_name.max' => 'Der Sektionsname darf höchstens 255 Zeichen lang sein.', 'color.regex' => 'Bitte gib eine gültige Hex-Farbe an.', 'slides.required' => 'Bitte gib mindestens eine Folie an.', 'slides.array' => 'Die Folien müssen als Liste gesendet werden.', 'slides.*.text_content.required' => 'Bitte gib einen Text für jede Folie ein.', 'slides.*.text_content.string' => 'Der Folientext muss ein Text sein.', 'slides.*.text_content_translated.string' => 'Der übersetzte Folientext muss ein Text sein.', 'order.integer' => 'Die Reihenfolge muss eine Zahl sein.', ]; } }