validate([ 'raw_text' => ['required', 'string', 'max:200000'], ]); try { $parsed = $this->parser->parse($validated['raw_text']); } catch (InvalidArgumentException $e) { return response()->json([ 'message' => $e->getMessage(), ], 422); } return response()->json([ 'title' => $parsed->title, 'author' => $parsed->author, 'ccliId' => $parsed->ccliId, 'year' => $parsed->year, 'copyrightText' => $parsed->copyrightText, 'sections' => array_map(fn ($s) => [ 'label' => $s->label, 'kind' => $s->kind, 'number' => $s->number, 'modifier' => $s->modifier, 'lines' => $s->lines, 'hasTranslation' => $s->linesTranslated !== null, ], $parsed->sections), ]); } /** * POST /api/songs/import-from-ccli-paste * Import a CCLI paste in one of 3 modes. */ public function importPaste(Request $request): JsonResponse { $validated = $request->validate([ 'raw_text' => ['required', 'string', 'max:200000'], 'mode' => ['required', Rule::in(['create', 'pair-with-song', 'assign-to-service-song'])], 'target_id' => ['required_unless:mode,create', 'nullable', 'integer'], 'source_url' => ['nullable', 'string', 'max:500'], ]); $rawText = $validated['raw_text']; $mode = $validated['mode']; $targetId = $validated['target_id'] ?? null; $sourceUrl = $validated['source_url'] ?? null; try { if ($mode === 'create') { $result = $this->importService->import($rawText, $sourceUrl); return response()->json([ 'song_id' => $result['song']->id, 'status' => $result['status'], 'warnings' => $result['warnings'], ], 201); } if ($mode === 'pair-with-song') { $localSong = Song::findOrFail($targetId); $result = $this->pairingService->pair($localSong, $rawText); session()->flash('ccli_prefilled', $result['distributed_text']); return response()->json([ 'song_id' => $localSong->id, 'mapping' => $result['mapping'], 'unmatched_labels' => $result['unmatched_labels'], 'redirect_to' => route('songs.translate', $localSong->id).'?prefilled=true', ]); } if ($mode === 'assign-to-service-song') { $result = $this->importService->import($rawText, $sourceUrl); $song = $result['song']; $serviceSong = ServiceSong::findOrFail($targetId); $this->matchingService->manualAssign($serviceSong, $song); return response()->json([ 'song_id' => $song->id, 'service_song_id' => $serviceSong->id, 'status' => $result['status'], ], 201); } } catch (DuplicateCcliSongException $e) { return response()->json([ 'message' => $e->getMessage(), 'existing_song_id' => $e->existingSongId, 'edit_url' => route('songs.index').'#song-'.$e->existingSongId, ], 409); } catch (InvalidArgumentException $e) { return response()->json(['message' => $e->getMessage()], 422); } catch (RuntimeException $e) { return response()->json(['message' => $e->getMessage()], 422); } return response()->json(['message' => 'Unbekannter Modus'], 422); } /** * GET /songs/import-from-ccli-paste * Render the Inertia page for bookmarklet redirect. */ public function showImportPage(Request $request): InertiaResponse { $prefill = $request->query('prefill'); $prefilledText = null; $prefilledMetadata = null; $prefillError = null; if ($prefill !== null && is_string($prefill)) { try { $decoded = base64_decode($prefill, strict: true); if ($decoded === false) { throw new InvalidArgumentException('Ungültige Kodierung'); } $payload = json_decode($decoded, associative: true, flags: JSON_THROW_ON_ERROR); if (! is_array($payload)) { throw new InvalidArgumentException('Ungültiges Payload-Format'); } $prefilledText = $payload['rawText'] ?? null; $prefilledMetadata = [ 'title' => $payload['title'] ?? null, 'author' => $payload['author'] ?? null, 'ccliId' => $payload['ccliId'] ?? null, 'sourceUrl' => $payload['sourceUrl'] ?? null, ]; } catch (Throwable) { $prefillError = 'Lesezeichen-Daten konnten nicht gelesen werden. Bitte den Liedtext manuell einfügen.'; } } return Inertia::render('Songs/ImportFromCcliPaste', [ 'prefilledText' => $prefilledText, 'prefilledMetadata' => $prefilledMetadata, 'prefillError' => $prefillError, ]); } }