pp-planer/.sisyphus/notepads/cts-presenter-app/learnings.md
Thorsten Bus d915f8cfc2 feat: Wave 2 - Service list, Song CRUD, Slide upload, Arrangements, Song matching, Translation
T8: Service List Page
- ServiceController with index, finalize, reopen actions
- Services/Index.vue with status indicators (songs mapped/arranged, slides uploaded)
- German UI with finalize/reopen toggle buttons
- Status aggregation via SQL subqueries for efficiency
- Tests: 3 passing (46 assertions)

T9: Song CRUD Backend
- SongController with full REST API (index, store, show, update, destroy)
- SongService for default groups/arrangements creation
- SongRequest validation (title required, ccli_id unique)
- Search by title and CCLI ID
- last_used_in_service accessor via service_songs join
- Tests: 20 passing (85 assertions)

T10: Slide Upload Component
- SlideController with store, destroy, updateExpireDate
- SlideUploader.vue with vue3-dropzone drag-and-drop
- SlideGrid.vue with thumbnail grid and inline expire date editing
- Multi-format support: images (sync), PPT (async job), ZIP (extract)
- Type validation: information (global), moderation/sermon (service-specific)
- Tests: 15 passing (37 assertions)

T11: Arrangement Configurator
- ArrangementController with store, clone, update, destroy
- ArrangementConfigurator.vue with vue-draggable-plus
- Drag-and-drop arrangement editor with colored group pills
- Clone from default or existing arrangement
- Color picker for group customization
- Prevent deletion of last arrangement
- Tests: 4 passing (17 assertions)

T12: Song Matching Service
- SongMatchingService with autoMatch, manualAssign, requestCreation, unassign
- ServiceSongController API endpoints for song assignment
- Auto-match by CCLI ID during CTS sync
- Manual assignment with searchable song select
- Email request for missing songs (MissingSongRequest mailable)
- Tests: 14 passing (33 assertions)

T13: Translation Service
- TranslationService with fetchFromUrl, importTranslation, removeTranslation
- TranslationController API endpoints
- URL scraping (best-effort HTTP fetch with strip_tags)
- Line-count distribution algorithm (match original slide line counts)
- Mark song as translated, remove translation
- Tests: 18 passing (18 assertions)

All tests passing: 103/103 (488 assertions)
Build: ✓ Vite production build successful
German UI: All user-facing text in German with 'Du' form
2026-03-01 19:55:37 +01:00

3.4 KiB

  • 2026-03-01: Fuer 1920x1080 Slide-Output ohne Upscaling funktioniert in Intervention Image v3 die Kombination aus schwarzer Canvas (create()->fill('000000')), scaleDown(width: 1920, height: 1080) und zentriertem place(...) stabil.
  • 2026-03-01: Bei Fake-Storage in Tests muessen Zielordner vor direktem Intervention-save() explizit erstellt werden (makeDirectory/mkdir), sonst wirft Intervention NotWritableException.
  • 2026-03-01: Fuer Testverifikation von Letterbox/Pillarbox sind farbige PNG-Testbilder sinnvoller als UploadedFile::fake()->image(...), weil Fake-Bilder sonst komplett schwarz sein koennen.
  • 2026-03-01: CTS-Sync laeuft stabil mit EventRequest::where("from", heute) + EventAgendaRequest::fromEvent(...)->get(), wenn Services per cts_event_id und Agenda-Songs per (service_id,order) upserted werden; CCLI-Matching bleibt strikt auf songs.ccli_id und setzt nur dann song_id/matched_at.
  • 2026-03-01: SongController CRUD nutzt auth:sanctum Middleware; actingAs() in Tests funktioniert damit problemlos (Sanctum unterstuetzt Session-Auth in Tests).
  • 2026-03-01: SQLite gibt date-Spalten als YYYY-MM-DD 00:00:00 zurueck statt YYYY-MM-DD — Accessor muss substr($date, 0, 10) nutzen fuer saubere Date-Only Werte.
  • 2026-03-01: Attribute::get() in Laravel 12 fuer berechnete Accessors statt altem get{Name}Attribute() Pattern. Snake_case last_used_in_service mapped automatisch auf lastUsedInService() Methode.
  • 2026-03-01: Default-Gruppen (Strophe 1=#3B82F6, Refrain=#10B981, Bridge=#F59E0B) und Default-Arrangement 'Normal' werden automatisch bei Song-Erstellung via SongService erzeugt.
  • 2026-03-01: Rule::unique('songs', 'ccli_id')->ignore($songId)->whereNull('deleted_at') stellt sicher, dass Soft-Deleted Songs die Unique-Constraint nicht blockieren.
  • 2026-03-01: bootstrap/app.php braucht explizit api: __DIR__.'/../routes/api.php' in withRouting() — ist nicht automatisch registriert in Laravel 12.
  • 2026-03-01: Service-Listenstatus laesst sich performant in einem Query aggregieren via withCount(...) fuer Song-Metriken plus addSelect-Subqueries fuer has_sermon_slides und datumsabhaengige info_slides_count (inkl. globaler information-Slides mit service_id = null).
  • 2026-03-01: TranslationService line-count distribution: iterate groups (by order) → slides (by order), for each slide count lines in text_content, then slice that many lines from the translated text array. array_slice + offset tracking works cleanly.
  • 2026-03-01: URL scraping is best-effort only: Http::timeout(10)->get($url) + strip_tags() + trim(). Return null on any failure — no exceptions bubble up. PHP 8.1+ allows catch (\Exception) without variable capture.
  • 2026-03-01: Translation routes: POST /api/translation/fetch-url (preview), POST /api/songs/{song}/translation/import (save), DELETE /api/songs/{song}/translation (remove). All under auth:sanctum middleware.
  • 2026-03-01: removeTranslation uses a two-step approach: collect slide IDs via SongSlide::whereIn('song_group_id', $song->groups()->pluck('id')) then bulk-update text_content_translated = null, avoiding N+1 queries.
  • 2026-03-01: Der Arrangement-Konfigurator bleibt stabil bei mehrfachen Gruppeninstanzen, wenn die Sequenz mit Vue-Keys im Muster ${group.id}-${index} gerendert und die Reihenfolge nach jedem Drag-End sofort per router.put(..., { preserveScroll: true }) gespeichert wird.