diff --git a/app/Http/Controllers/SlideController.php b/app/Http/Controllers/SlideController.php index aefdb3d..2195a06 100644 --- a/app/Http/Controllers/SlideController.php +++ b/app/Http/Controllers/SlideController.php @@ -21,13 +21,14 @@ public function store(Request $request, FileConversionService $conversionService { $validated = $request->validate([ 'file' => ['required', 'file', 'max:51200'], - 'type' => ['required', Rule::in(['information', 'moderation', 'sermon'])], + 'type' => ['required', Rule::in(['information', 'moderation', 'sermon', 'agenda_item'])], 'service_id' => ['nullable', 'exists:services,id'], + 'service_agenda_item_id' => ['nullable', 'integer', 'exists:service_agenda_items,id'], 'expire_date' => ['nullable', 'date'], ]); - // moderation and sermon slides require a service_id - if (in_array($validated['type'], ['moderation', 'sermon']) && empty($validated['service_id'])) { + // moderation, sermon, and agenda_item slides require a service_id + if (in_array($validated['type'], ['moderation', 'sermon', 'agenda_item']) && empty($validated['service_id'])) { return response()->json([ 'message' => 'Moderations- und Predigtfolien benötigen einen Gottesdienst.', 'errors' => ['service_id' => ['Gottesdienst ist erforderlich.']], @@ -49,21 +50,22 @@ public function store(Request $request, FileConversionService $conversionService $uploaderName = $request->user()?->name ?? 'Unbekannt'; $serviceId = $validated['service_id'] ?? null; + $serviceAgendaItemId = $validated['service_agenda_item_id'] ?? null; $type = $validated['type']; $expireDate = $validated['expire_date'] ?? null; // Handle PowerPoint files — dispatch async job if (in_array($extension, self::POWERPOINT_EXTENSIONS, true)) { - return $this->handlePowerPoint($file, $conversionService, $type, $serviceId, $uploaderName, $expireDate); + return $this->handlePowerPoint($file, $conversionService, $type, $serviceId, $uploaderName, $expireDate, $serviceAgendaItemId); } // Handle ZIP files — extract and process if ($extension === 'zip') { - return $this->handleZip($file, $conversionService, $type, $serviceId, $uploaderName, $expireDate); + return $this->handleZip($file, $conversionService, $type, $serviceId, $uploaderName, $expireDate, $serviceAgendaItemId); } // Handle images — convert synchronously - return $this->handleImage($file, $conversionService, $type, $serviceId, $uploaderName, $expireDate); + return $this->handleImage($file, $conversionService, $type, $serviceId, $uploaderName, $expireDate, $serviceAgendaItemId); } public function destroy(Slide $slide): JsonResponse @@ -79,7 +81,7 @@ public function destroy(Slide $slide): JsonResponse public function destroyBulk(Request $request): JsonResponse { $validated = $request->validate([ - 'type' => ['required', Rule::in(['information', 'moderation', 'sermon'])], + 'type' => ['required', Rule::in(['information', 'moderation', 'sermon', 'agenda_item'])], 'service_id' => ['nullable', 'exists:services,id'], ]); @@ -155,6 +157,7 @@ private function handleImage( ?int $serviceId, string $uploaderName, ?string $expireDate, + ?int $serviceAgendaItemId = null, ): JsonResponse { try { $result = $conversionService->convertImage($file); @@ -162,6 +165,7 @@ private function handleImage( $slide = Slide::create([ 'type' => $type, 'service_id' => $serviceId, + 'service_agenda_item_id' => $serviceAgendaItemId, 'original_filename' => $file->getClientOriginalName(), 'stored_filename' => $result['filename'], 'thumbnail_filename' => $result['thumbnail'], @@ -190,6 +194,7 @@ private function handlePowerPoint( ?int $serviceId, string $uploaderName, ?string $expireDate, + ?int $serviceAgendaItemId = null, ): JsonResponse { // Store file persistently so the job can access it $storedPath = $file->store('temp/ppt', 'local'); @@ -206,6 +211,7 @@ private function handlePowerPoint( 'meta' => [ 'type' => $type, 'service_id' => $serviceId, + 'service_agenda_item_id' => $serviceAgendaItemId, 'uploader_name' => $uploaderName, 'expire_date' => $expireDate, 'original_filename' => $file->getClientOriginalName(), @@ -226,6 +232,7 @@ private function handleZip( ?int $serviceId, string $uploaderName, ?string $expireDate, + ?int $serviceAgendaItemId = null, ): JsonResponse { try { $results = $conversionService->processZip($file); @@ -242,6 +249,7 @@ private function handleZip( $slides[] = Slide::create([ 'type' => $type, 'service_id' => $serviceId, + 'service_agenda_item_id' => $serviceAgendaItemId, 'original_filename' => $file->getClientOriginalName(), 'stored_filename' => $result['filename'], 'thumbnail_filename' => $result['thumbnail'], diff --git a/resources/js/Components/AgendaItemRow.vue b/resources/js/Components/AgendaItemRow.vue index 7ec4df6..ca80950 100644 --- a/resources/js/Components/AgendaItemRow.vue +++ b/resources/js/Components/AgendaItemRow.vue @@ -92,8 +92,9 @@ function onUploaded() {
['information', 'moderation', 'sermon'].includes(v), + validator: (v) => ['information', 'moderation', 'sermon', 'agenda_item'].includes(v), }, serviceId: { type: [Number, null], default: null, }, + agendaItemId: { + type: Number, + default: null, + }, showExpireDate: { type: Boolean, default: false, @@ -107,6 +111,10 @@ function uploadNextFile(index) { formData.append('service_id', props.serviceId) } + if (props.agendaItemId) { + formData.append('service_agenda_item_id', props.agendaItemId) + } + if (props.showExpireDate && expireDate.value) { formData.append('expire_date', expireDate.value) } diff --git a/tests/Feature/SlideControllerTest.php b/tests/Feature/SlideControllerTest.php index 906b7ce..b681b11 100644 --- a/tests/Feature/SlideControllerTest.php +++ b/tests/Feature/SlideControllerTest.php @@ -1,6 +1,7 @@ open($zipPath, ZipArchive::CREATE); $zip->addFile($imgPath, 'slide1.png'); $zip->close(); @@ -171,6 +172,51 @@ @rmdir($tempDir); }); +test('upload agenda_item slide with service_agenda_item_id links to agenda item', function () { + $service = Service::factory()->create(); + $agendaItem = ServiceAgendaItem::factory()->create(['service_id' => $service->id]); + $file = makePngUploadForSlide('agenda-slide.png', 800, 600); + + $response = $this->post(route('slides.store'), [ + 'file' => $file, + 'type' => 'agenda_item', + 'service_id' => $service->id, + 'service_agenda_item_id' => $agendaItem->id, + ]); + + $response->assertStatus(200); + $response->assertJson(['success' => true]); + + $slide = Slide::first(); + expect($slide)->not->toBeNull(); + expect($slide->type)->toBe('agenda_item'); + expect($slide->service_id)->toBe($service->id); + expect($slide->service_agenda_item_id)->toBe($agendaItem->id); +}); + +test('upload agenda_item slide without service_id fails', function () { + $file = makePngUploadForSlide('agenda-slide.png', 400, 300); + + $response = $this->post(route('slides.store'), [ + 'file' => $file, + 'type' => 'agenda_item', + ]); + + $response->assertStatus(422); +}); + +test('agenda_item type is accepted in validation', function () { + $file = makePngUploadForSlide('test.png', 400, 300); + + $response = $this->postJson(route('slides.store'), [ + 'file' => $file, + 'type' => 'agenda_item', + 'service_id' => Service::factory()->create()->id, + ]); + + $response->assertStatus(200); +}); + test('unauthenticated user cannot upload slides', function () { auth()->logout(); $file = makePngUploadForSlide('test.png', 400, 300);