pp-planer/.sisyphus/notepads/edit-page-restructure/learnings.md
2026-03-29 11:37:33 +02:00

4.9 KiB

Learnings — edit-page-restructure

2026-03-29 — Session start

Code Conventions

  • Migrations: anonymous class return new class extends Migration {; methods up(): void, down(): void
  • Models: $fillable array; casts() method (not $casts property); promoted properties in constructor
  • SoftDeletes: used on Song and Slide models; whereNull('deleted_at') in manual queries
  • PHP string concat: no spaces around . operator
  • Return types always present; union types for multiple returns
  • Relationships: typed return types (HasMany, BelongsTo)
  • Error messages: German Du-form; flash ->with('success', '...'); JSON 'message' key

Codebase Facts

  • service_songs table stores ONLY songs from CTS agenda (via getSongs())
  • EventAgendaItem.type actual values: UNKNOWN — Task 5 will discover
  • EventAgendaItem.getItems() returns ALL agenda items (not just songs)
  • isBeforeEvent=true items should be excluded from visible agenda
  • Settings stored as plain strings in settings table (key-value)
  • Settings controller uses MACRO_KEYS constant array for validation
  • SlideUploader sends type, service_id, expire_date in FormData
  • PlaylistExportService builds: info slides → matched songs → moderation → sermon (OLD ORDER)
  • Backward compat needed: old services without agenda items fall back to block-based export

Test Patterns

  • Pest v4 function-style: test('description', function() { ... })
  • DB reset: use RefreshDatabase; in class-based, or implicit in Pest
  • Inertia assertions: $response->assertInertia(fn ($page) => $page->component('...')->has('...'))
  • Mocked API: injectable closures in ChurchToolsService constructor
  • Storage: Storage::fake('public') in beforeEach for file tests
  • Carbon: Carbon::setTestNow(...) for deterministic dates

2026-03-29 — ServiceAgendaItem DB schema

Created

  • Migration: 2026_03_29_100001_create_service_agenda_items_table.php — contains all EventAgendaItem fields from CTS
  • Migration: 2026_03_29_100002_add_service_agenda_item_id_to_slides_table.php — links slides to agenda items
  • Model: ServiceAgendaItem with scopeVisible(), relationships (service, serviceSong, slides)
  • Factory: ServiceAgendaItemFactory with withSong() and nonSong() states
  • Tests: ServiceAgendaItemTest — 14 tests covering schema, relationships, cascades, scopes

Key Fields

  • cts_agenda_item_id (nullable, indexed): CTS API item ID for syncing
  • position (string): CTS agenda item position (e.g. "1", "2", "1.1")
  • type (string): CTS type value ("Song", "Default", "Header", etc.)
  • is_before_event (boolean): filters with scopeVisible() scope
  • responsible (json → array cast): responsible persons
  • service_song_id (nullable FK): links agenda item to song (nullOnDelete)
  • sort_order (unsignedInt): unique per service with unique(['service_id', 'sort_order'])

Relationships

  • service() BelongsTo Service (cascadeOnDelete)
  • serviceSong() BelongsTo ServiceSong (nullable, nullOnDelete)
  • slides() HasMany Slide (new FK added to slides table)

Test Patterns

  • Factory states: ->withSong(song) for song items, ->nonSong() for non-song
  • Scope: ServiceAgendaItem::visible() filters is_before_event=false
  • Migrations now always use anonymous class return new class extends Migration {}
  • Unique constraint throws QueryException when violated (tested)

Slide Model Update

  • Added service_agenda_item_id to fillable
  • Added serviceAgendaItem() BelongsTo relationship (nullOnDelete)

2026-03-29 — AgendaMatcherService

Implementation

  • Created AgendaMatcherService with 4 public methods for glob-style wildcard matching
  • Uses PHP's native fnmatch($pattern, $title, FNM_CASEFOLD) for * wildcard support and case-insensitive matching
  • Key methods:
    • matches(string $itemTitle, string $pattern): bool — single pattern match
    • matchesAny(string $itemTitle, array $patterns): bool — match against array
    • findFirstMatch(array $agendaItems, string $patterns): ?ServiceAgendaItem — comma-separated patterns, returns first match or null
    • filterBetween(array $items, ?string $startPattern, ?string $endPattern): array — filters items between boundaries (boundaries excluded)

Test Coverage

  • 17 tests in Pest function-style covering all methods
  • Patterns tested: exact match, suffix wildcard, prefix wildcard, both sides, case-insensitive, no-match
  • filterBetween edge cases: both boundaries, start-only, neither, no-matching-start, empty arrays
  • Pattern parsing: comma-separated patterns with whitespace trimming
  • All tests pass; Pint clean

Code Patterns

  • Service class structure matches SongMatchingService (constructor, public methods, no state)
  • Public method documentation via docstrings (necessary for API clarity)
  • Test organization via section headers (necessary for readability)
  • No regex used — just fnmatch() for simplicity and correctness