- Fix probundle exports missing images (double slides/ prefix in storage path) - Replace manual ZipArchive with PresentationBundle + ProBundleWriter from parser plugin - Add per-agenda-item download route and buttons for songs and slide items - Remove text layer from image-only slides in .pro generation - Fix image conversion: upscale small images, black bars on 2 sides max (contain) - Add upload warnings for non-16:9 and sub-1920x1080 images (German, non-blocking) - Update SlideFactory and all tests to use slides/ prefix in stored_filename - Add 11 new tests (agenda download, image conversion, upload warnings)
681 lines
24 KiB
PHP
681 lines
24 KiB
PHP
<?php
|
|
|
|
use App\Services\ChurchToolsService;
|
|
use Illuminate\Database\Schema\Blueprint;
|
|
use Illuminate\Support\Carbon;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Schema;
|
|
|
|
beforeEach(function () {
|
|
ensureSyncTables();
|
|
});
|
|
|
|
test('cts:sync synchronisiert services, agenda songs und schreibt sync log', function () {
|
|
Carbon::setTestNow('2026-03-01 09:00:00');
|
|
|
|
$localSongId = DB::table('songs')->insertGetId([
|
|
'title' => 'Way Maker',
|
|
'ccli_id' => '7115744',
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(
|
|
id: 100,
|
|
title: 'Gottesdienst Sonntag',
|
|
startDate: '2026-03-08T10:00:00+00:00',
|
|
note: 'Probe',
|
|
eventServices: [
|
|
new FakeEventService('Predigt', new FakePerson('Max', 'Mustermann')),
|
|
new FakeEventService('Beamer', new FakePerson('Lisa', 'Technik')),
|
|
],
|
|
),
|
|
],
|
|
songFetcher: fn () => [new FakeSong(id: 1, title: 'Way Maker', ccli: '7115744')],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeAgendaItem(id: 1, position: '1', title: 'Begrüssung', type: 'Default'),
|
|
new FakeAgendaItem(id: 2, position: '2', title: 'Way Maker', type: 'Song', song: new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744')),
|
|
new FakeAgendaItem(id: 3, position: '3', title: 'Unbekannt', type: 'Song', song: new FakeSong(id: 5002, title: 'Unbekannt', ccli: '9999999')),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [
|
|
new FakeEventService('Predigt', new FakePerson('Max', 'Mustermann')),
|
|
new FakeEventService('Beamer', new FakePerson('Lisa', 'Technik')),
|
|
],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
expect(Artisan::output())->toContain('Daten wurden aktualisiert');
|
|
|
|
$service = DB::table('services')->where('cts_event_id', '100')->first();
|
|
expect($service)->not->toBeNull();
|
|
expect($service->title)->toBe('Gottesdienst Sonntag');
|
|
expect($service->preacher_name)->toBe('Max Mustermann');
|
|
expect($service->beamer_tech_name)->toBe('Lisa Technik');
|
|
|
|
$matchedSong = DB::table('service_songs')->where('order', 2)->first();
|
|
expect($matchedSong)->not->toBeNull();
|
|
expect((int) $matchedSong->song_id)->toBe($localSongId);
|
|
expect($matchedSong->matched_at)->not->toBeNull();
|
|
|
|
$unmatchedSong = DB::table('service_songs')->where('order', 3)->first();
|
|
expect($unmatchedSong)->not->toBeNull();
|
|
expect($unmatchedSong->song_id)->toBeNull();
|
|
expect($unmatchedSong->cts_ccli_id)->toBe('9999999');
|
|
|
|
$syncLog = DB::table('cts_sync_log')->latest('id')->first();
|
|
expect($syncLog)->not->toBeNull();
|
|
expect($syncLog->status)->toBe('success');
|
|
expect((int) $syncLog->events_count)->toBe(1);
|
|
expect((int) $syncLog->songs_count)->toBe(2);
|
|
});
|
|
|
|
test('sync speichert alle agenda items (songs und nicht-songs) in service_agenda_items', function () {
|
|
Carbon::setTestNow('2026-03-01 09:00:00');
|
|
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(id: 200, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
|
],
|
|
songFetcher: fn () => [],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeAgendaItem(id: 10, position: '1', title: 'Begrüssung', type: 'Default'),
|
|
new FakeAgendaItem(id: 11, position: '2', title: 'Lobpreis', type: 'Song', song: new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744')),
|
|
new FakeAgendaItem(id: 12, position: '3', title: 'Predigt', type: 'Default', note: 'Zum Thema Liebe'),
|
|
new FakeAgendaItem(id: 13, position: '4', title: 'Abschluss', type: 'Song', song: new FakeSong(id: 5002, title: 'Amazing Grace', ccli: '1234567')),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
$service = DB::table('services')->where('cts_event_id', '200')->first();
|
|
|
|
$agendaItems = DB::table('service_agenda_items')
|
|
->where('service_id', $service->id)
|
|
->orderBy('sort_order')
|
|
->get();
|
|
|
|
expect($agendaItems)->toHaveCount(4);
|
|
expect($agendaItems[0]->title)->toBe('Begrüssung');
|
|
expect($agendaItems[0]->type)->toBe('Default');
|
|
expect((int) $agendaItems[0]->sort_order)->toBe(1);
|
|
expect($agendaItems[1]->title)->toBe('Lobpreis');
|
|
expect($agendaItems[1]->type)->toBe('Song');
|
|
expect((int) $agendaItems[1]->sort_order)->toBe(2);
|
|
expect($agendaItems[2]->title)->toBe('Predigt');
|
|
expect($agendaItems[2]->note)->toBe('Zum Thema Liebe');
|
|
expect($agendaItems[3]->title)->toBe('Abschluss');
|
|
});
|
|
|
|
test('song items erstellen service_song UND service_agenda_item mit korrekter service_song_id', function () {
|
|
Carbon::setTestNow('2026-03-01 09:00:00');
|
|
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(id: 300, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
|
],
|
|
songFetcher: fn () => [],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeAgendaItem(id: 20, position: '1', title: 'Lobpreis', type: 'Song', song: new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744')),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
$service = DB::table('services')->where('cts_event_id', '300')->first();
|
|
|
|
$serviceSong = DB::table('service_songs')->where('service_id', $service->id)->first();
|
|
expect($serviceSong)->not->toBeNull();
|
|
expect($serviceSong->cts_song_name)->toBe('Way Maker');
|
|
|
|
$agendaItem = DB::table('service_agenda_items')->where('service_id', $service->id)->first();
|
|
expect($agendaItem)->not->toBeNull();
|
|
expect($agendaItem->title)->toBe('Lobpreis');
|
|
expect((int) $agendaItem->service_song_id)->toBe((int) $serviceSong->id);
|
|
});
|
|
|
|
test('nicht-song items erstellen keine service_song eintraege', function () {
|
|
Carbon::setTestNow('2026-03-01 09:00:00');
|
|
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(id: 400, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
|
],
|
|
songFetcher: fn () => [],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeAgendaItem(id: 30, position: '1', title: 'Begrüssung', type: 'Default'),
|
|
new FakeAgendaItem(id: 31, position: '2', title: 'Predigt', type: 'Default'),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
$service = DB::table('services')->where('cts_event_id', '400')->first();
|
|
|
|
$agendaItems = DB::table('service_agenda_items')->where('service_id', $service->id)->get();
|
|
expect($agendaItems)->toHaveCount(2);
|
|
|
|
$serviceSongs = DB::table('service_songs')->where('service_id', $service->id)->get();
|
|
expect($serviceSongs)->toHaveCount(0);
|
|
});
|
|
|
|
test('is_before_event items werden uebersprungen', function () {
|
|
Carbon::setTestNow('2026-03-01 09:00:00');
|
|
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(id: 500, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
|
],
|
|
songFetcher: fn () => [],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeAgendaItem(id: 40, position: '0', title: 'Soundcheck', type: 'Default', isBeforeEvent: true),
|
|
new FakeAgendaItem(id: 41, position: '1', title: 'Begrüssung', type: 'Default'),
|
|
new FakeAgendaItem(id: 42, position: '2', title: 'Probe', type: 'Default', isBeforeEvent: true),
|
|
new FakeAgendaItem(id: 43, position: '3', title: 'Lobpreis', type: 'Song', song: new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744')),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
$service = DB::table('services')->where('cts_event_id', '500')->first();
|
|
|
|
$agendaItems = DB::table('service_agenda_items')->where('service_id', $service->id)->get();
|
|
expect($agendaItems)->toHaveCount(2);
|
|
|
|
$titles = $agendaItems->pluck('title')->toArray();
|
|
expect($titles)->not->toContain('Soundcheck');
|
|
expect($titles)->not->toContain('Probe');
|
|
expect($titles)->toContain('Begrüssung');
|
|
expect($titles)->toContain('Lobpreis');
|
|
});
|
|
|
|
test('verwaiste agenda items werden bei re-sync entfernt', function () {
|
|
Carbon::setTestNow('2026-03-01 09:00:00');
|
|
|
|
// First sync with 3 items
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(id: 600, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
|
],
|
|
songFetcher: fn () => [],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeAgendaItem(id: 50, position: '1', title: 'Begrüssung', type: 'Default'),
|
|
new FakeAgendaItem(id: 51, position: '2', title: 'Lobpreis', type: 'Song', song: new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744')),
|
|
new FakeAgendaItem(id: 52, position: '3', title: 'Predigt', type: 'Default'),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
$service = DB::table('services')->where('cts_event_id', '600')->first();
|
|
expect(DB::table('service_agenda_items')->where('service_id', $service->id)->count())->toBe(3);
|
|
|
|
// Re-sync with only 1 item
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(id: 600, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
|
],
|
|
songFetcher: fn () => [],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeAgendaItem(id: 50, position: '1', title: 'Begrüssung', type: 'Default'),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
$agendaItems = DB::table('service_agenda_items')->where('service_id', $service->id)->get();
|
|
expect($agendaItems)->toHaveCount(1);
|
|
expect($agendaItems[0]->title)->toBe('Begrüssung');
|
|
});
|
|
|
|
test('slides werden erhalten (FK genullt) wenn verwaiste agenda items entfernt werden', function () {
|
|
Carbon::setTestNow('2026-03-01 09:00:00');
|
|
|
|
// First sync with 2 items
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(id: 700, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
|
],
|
|
songFetcher: fn () => [],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeAgendaItem(id: 60, position: '1', title: 'Begrüssung', type: 'Default'),
|
|
new FakeAgendaItem(id: 61, position: '2', title: 'Predigt', type: 'Default'),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
$service = DB::table('services')->where('cts_event_id', '700')->first();
|
|
$agendaItem = DB::table('service_agenda_items')
|
|
->where('service_id', $service->id)
|
|
->where('sort_order', 2)
|
|
->first();
|
|
|
|
// Create a slide linked to the second agenda item
|
|
$slideId = DB::table('slides')->insertGetId([
|
|
'service_id' => $service->id,
|
|
'service_agenda_item_id' => $agendaItem->id,
|
|
'type' => 'sermon',
|
|
'original_filename' => 'predigt.jpg',
|
|
'stored_filename' => 'slides/predigt_stored.jpg',
|
|
'thumbnail_filename' => 'predigt_thumb.jpg',
|
|
'uploaded_at' => now(),
|
|
'sort_order' => 1,
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
]);
|
|
|
|
// Re-sync with only 1 item (second item removed)
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(id: 700, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
|
],
|
|
songFetcher: fn () => [],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeAgendaItem(id: 60, position: '1', title: 'Begrüssung', type: 'Default'),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
// Slide still exists but FK is null
|
|
$slide = DB::table('slides')->where('id', $slideId)->first();
|
|
expect($slide)->not->toBeNull();
|
|
expect($slide->service_agenda_item_id)->toBeNull();
|
|
|
|
// Orphaned agenda item is gone
|
|
expect(DB::table('service_agenda_items')->where('service_id', $service->id)->count())->toBe(1);
|
|
});
|
|
|
|
test('sync summary enthaelt agenda_items_count', function () {
|
|
Carbon::setTestNow('2026-03-01 09:00:00');
|
|
|
|
$service = new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(id: 800, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
|
],
|
|
songFetcher: fn () => [],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeAgendaItem(id: 70, position: '1', title: 'Begrüssung', type: 'Default'),
|
|
new FakeAgendaItem(id: 71, position: '2', title: 'Lobpreis', type: 'Song', song: new FakeSong(id: 5001, title: 'Way Maker', ccli: '7115744')),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [],
|
|
);
|
|
|
|
$summary = $service->sync();
|
|
|
|
expect($summary)->toHaveKey('agenda_items_count');
|
|
expect($summary['agenda_items_count'])->toBe(2);
|
|
expect($summary['songs_count'])->toBe(1);
|
|
expect($summary['services_count'])->toBe(1);
|
|
});
|
|
|
|
test('event ohne agenda setzt has_agenda auf false und ueberspringt agenda sync', function () {
|
|
Carbon::setTestNow('2026-03-01 09:00:00');
|
|
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(id: 893, title: 'Service ohne Agenda', startDate: '2026-03-08T10:00:00+00:00'),
|
|
],
|
|
songFetcher: fn () => [],
|
|
agendaFetcher: fn (int $eventId) => throw new CTRequestException(
|
|
"Agenda for event [{$eventId}] not found."
|
|
),
|
|
eventServiceFetcher: fn (int $eventId) => [],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
$service = DB::table('services')->where('cts_event_id', '893')->first();
|
|
expect($service)->not->toBeNull();
|
|
expect($service->title)->toBe('Service ohne Agenda');
|
|
expect((bool) $service->has_agenda)->toBeFalse();
|
|
|
|
$agendaItems = DB::table('service_agenda_items')->where('service_id', $service->id)->get();
|
|
expect($agendaItems)->toHaveCount(0);
|
|
|
|
$serviceSongs = DB::table('service_songs')->where('service_id', $service->id)->get();
|
|
expect($serviceSongs)->toHaveCount(0);
|
|
|
|
$syncLog = DB::table('cts_sync_log')->latest('id')->first();
|
|
expect($syncLog)->not->toBeNull();
|
|
expect($syncLog->status)->toBe('success');
|
|
});
|
|
|
|
test('event mit agenda setzt has_agenda auf true', function () {
|
|
Carbon::setTestNow('2026-03-01 09:00:00');
|
|
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(id: 894, title: 'Service mit Agenda', startDate: '2026-03-08T10:00:00+00:00'),
|
|
],
|
|
songFetcher: fn () => [],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeAgendaItem(id: 90, position: '1', title: 'Begrüssung', type: 'Default'),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
$service = DB::table('services')->where('cts_event_id', '894')->first();
|
|
expect($service)->not->toBeNull();
|
|
expect((bool) $service->has_agenda)->toBeTrue();
|
|
|
|
$agendaItems = DB::table('service_agenda_items')->where('service_id', $service->id)->get();
|
|
expect($agendaItems)->toHaveCount(1);
|
|
});
|
|
|
|
test('responsible feld wird als json gespeichert', function () {
|
|
Carbon::setTestNow('2026-03-01 09:00:00');
|
|
|
|
app()->instance(ChurchToolsService::class, new ChurchToolsService(
|
|
eventFetcher: fn () => [
|
|
new FakeEvent(id: 900, title: 'Service', startDate: '2026-03-08T10:00:00+00:00'),
|
|
],
|
|
songFetcher: fn () => [],
|
|
agendaFetcher: fn () => new FakeAgenda([
|
|
new FakeAgendaItem(
|
|
id: 80,
|
|
position: '1',
|
|
title: 'Begrüssung',
|
|
type: 'Default',
|
|
responsible: ['name' => 'Max Mustermann'],
|
|
),
|
|
]),
|
|
eventServiceFetcher: fn (int $eventId) => [],
|
|
));
|
|
|
|
Artisan::call('cts:sync');
|
|
|
|
$service = DB::table('services')->where('cts_event_id', '900')->first();
|
|
$agendaItem = DB::table('service_agenda_items')->where('service_id', $service->id)->first();
|
|
|
|
$responsible = json_decode($agendaItem->responsible, true);
|
|
expect($responsible)->toBe(['name' => 'Max Mustermann']);
|
|
});
|
|
|
|
function ensureSyncTables(): void
|
|
{
|
|
if (! Schema::hasTable('services')) {
|
|
Schema::create('services', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('cts_event_id')->unique();
|
|
$table->string('title');
|
|
$table->date('date')->nullable();
|
|
$table->string('preacher_name')->nullable();
|
|
$table->string('beamer_tech_name')->nullable();
|
|
$table->timestamp('last_synced_at')->nullable();
|
|
$table->json('cts_data')->nullable();
|
|
$table->boolean('has_agenda')->default(false);
|
|
$table->timestamps();
|
|
});
|
|
}
|
|
|
|
if (! Schema::hasTable('songs')) {
|
|
Schema::create('songs', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->string('title');
|
|
$table->string('ccli_id')->nullable()->unique();
|
|
$table->string('cts_song_id')->nullable()->index();
|
|
$table->timestamps();
|
|
});
|
|
}
|
|
|
|
if (! Schema::hasTable('service_songs')) {
|
|
Schema::create('service_songs', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->foreignId('service_id')->constrained('services')->cascadeOnDelete();
|
|
$table->foreignId('song_id')->nullable()->constrained('songs')->nullOnDelete();
|
|
$table->string('cts_song_name');
|
|
$table->string('cts_ccli_id')->nullable();
|
|
$table->string('cts_song_id')->nullable();
|
|
$table->unsignedInteger('order')->default(0);
|
|
$table->timestamp('matched_at')->nullable();
|
|
$table->timestamps();
|
|
$table->unique(['service_id', 'order']);
|
|
});
|
|
}
|
|
|
|
if (! Schema::hasTable('service_agenda_items')) {
|
|
Schema::create('service_agenda_items', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->foreignId('service_id')->constrained('services')->cascadeOnDelete();
|
|
$table->string('cts_agenda_item_id')->nullable()->index();
|
|
$table->string('position');
|
|
$table->string('title');
|
|
$table->string('type');
|
|
$table->text('note')->nullable();
|
|
$table->string('duration')->nullable();
|
|
$table->string('start')->nullable();
|
|
$table->boolean('is_before_event')->default(false);
|
|
$table->json('responsible')->nullable();
|
|
$table->foreignId('service_song_id')->nullable()->constrained('service_songs')->nullOnDelete();
|
|
$table->unsignedInteger('sort_order');
|
|
$table->timestamps();
|
|
$table->unique(['service_id', 'sort_order']);
|
|
});
|
|
}
|
|
|
|
if (! Schema::hasTable('slides')) {
|
|
Schema::create('slides', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->enum('type', ['information', 'moderation', 'sermon']);
|
|
$table->foreignId('service_id')->nullable()->constrained()->nullOnDelete();
|
|
$table->foreignId('service_agenda_item_id')->nullable()->constrained('service_agenda_items')->nullOnDelete();
|
|
$table->string('original_filename');
|
|
$table->string('stored_filename');
|
|
$table->string('thumbnail_filename');
|
|
$table->date('expire_date')->nullable();
|
|
$table->string('uploader_name')->nullable();
|
|
$table->timestamp('uploaded_at');
|
|
$table->unsignedInteger('sort_order')->default(0);
|
|
$table->softDeletes();
|
|
$table->timestamps();
|
|
});
|
|
}
|
|
|
|
if (! Schema::hasTable('cts_sync_log')) {
|
|
Schema::create('cts_sync_log', function (Blueprint $table) {
|
|
$table->id();
|
|
$table->timestamp('synced_at')->nullable();
|
|
$table->unsignedInteger('events_count')->default(0);
|
|
$table->unsignedInteger('songs_count')->default(0);
|
|
$table->string('status');
|
|
$table->text('error')->nullable();
|
|
$table->timestamps();
|
|
});
|
|
}
|
|
}
|
|
|
|
final class FakeEvent
|
|
{
|
|
public function __construct(
|
|
private readonly int $id,
|
|
private readonly string $title,
|
|
private readonly string $startDate,
|
|
private readonly ?string $note = null,
|
|
private readonly array $eventServices = [],
|
|
) {}
|
|
|
|
public function getId(): string
|
|
{
|
|
return (string) $this->id;
|
|
}
|
|
|
|
public function getName(): string
|
|
{
|
|
return $this->title;
|
|
}
|
|
|
|
public function getStartDate(): string
|
|
{
|
|
return $this->startDate;
|
|
}
|
|
|
|
public function getNote(): ?string
|
|
{
|
|
return $this->note;
|
|
}
|
|
|
|
public function getEventServices(): array
|
|
{
|
|
return $this->eventServices;
|
|
}
|
|
}
|
|
|
|
final class FakeEventService
|
|
{
|
|
public function __construct(
|
|
private readonly string $name,
|
|
private readonly ?FakePerson $person,
|
|
) {}
|
|
|
|
public function getName(): string
|
|
{
|
|
return $this->name;
|
|
}
|
|
|
|
public function getPerson(): ?FakePerson
|
|
{
|
|
return $this->person;
|
|
}
|
|
}
|
|
|
|
final class FakePerson
|
|
{
|
|
public function __construct(
|
|
private readonly string $firstName,
|
|
private readonly string $lastName,
|
|
) {}
|
|
|
|
public function getFirstName(): string
|
|
{
|
|
return $this->firstName;
|
|
}
|
|
|
|
public function getLastName(): string
|
|
{
|
|
return $this->lastName;
|
|
}
|
|
}
|
|
|
|
final class FakeAgenda
|
|
{
|
|
public function __construct(private readonly array $items) {}
|
|
|
|
public function getItems(): array
|
|
{
|
|
return $this->items;
|
|
}
|
|
|
|
public function getSongs(): array
|
|
{
|
|
return array_values(array_filter(
|
|
array_map(fn ($item) => $item->getSong(), $this->items),
|
|
fn ($song) => $song !== null,
|
|
));
|
|
}
|
|
}
|
|
|
|
final class FakeAgendaItem
|
|
{
|
|
public function __construct(
|
|
private readonly int $id,
|
|
private readonly string $position,
|
|
private readonly string $title,
|
|
private readonly string $type,
|
|
private readonly ?FakeSong $song = null,
|
|
private readonly ?string $note = null,
|
|
private readonly ?string $duration = null,
|
|
private readonly ?string $start = null,
|
|
private readonly bool $isBeforeEvent = false,
|
|
private readonly ?array $responsible = null,
|
|
) {}
|
|
|
|
public function getId(): string
|
|
{
|
|
return (string) $this->id;
|
|
}
|
|
|
|
public function getPosition(): string
|
|
{
|
|
return $this->position;
|
|
}
|
|
|
|
public function getTitle(): string
|
|
{
|
|
return $this->title;
|
|
}
|
|
|
|
public function getType(): string
|
|
{
|
|
return $this->type;
|
|
}
|
|
|
|
public function getSong(): ?FakeSong
|
|
{
|
|
return $this->song;
|
|
}
|
|
|
|
public function getNote(): ?string
|
|
{
|
|
return $this->note;
|
|
}
|
|
|
|
public function getDuration(): ?string
|
|
{
|
|
return $this->duration;
|
|
}
|
|
|
|
public function getStart(): ?string
|
|
{
|
|
return $this->start;
|
|
}
|
|
|
|
public function getIsBeforeEvent(): bool
|
|
{
|
|
return $this->isBeforeEvent;
|
|
}
|
|
|
|
public function getResponsible(): ?array
|
|
{
|
|
return $this->responsible;
|
|
}
|
|
}
|
|
|
|
final class FakeSong
|
|
{
|
|
public function __construct(
|
|
private readonly int $id,
|
|
private readonly string $title,
|
|
private readonly ?string $ccli,
|
|
) {}
|
|
|
|
public function getId(): string
|
|
{
|
|
return (string) $this->id;
|
|
}
|
|
|
|
public function getName(): string
|
|
{
|
|
return $this->title;
|
|
}
|
|
|
|
public function getCcli(): ?string
|
|
{
|
|
return $this->ccli;
|
|
}
|
|
}
|