syncEvents(); $songsCount = $this->syncSongs(); $summary = [ 'services_count' => $eventsSummary['services_count'], 'songs_count' => $eventsSummary['songs_count'], 'matched_songs_count' => $eventsSummary['matched_songs_count'], 'unmatched_songs_count' => $eventsSummary['unmatched_songs_count'], 'catalog_songs_count' => $songsCount, ]; $this->writeSyncLog('success', $summary, null, $startedAt, Carbon::now()); return $summary; } catch (Throwable $exception) { $summary = [ 'services_count' => 0, 'songs_count' => 0, 'matched_songs_count' => 0, 'unmatched_songs_count' => 0, 'catalog_songs_count' => 0, ]; $this->writeSyncLog('error', $summary, $exception->getMessage(), $startedAt, Carbon::now()); throw $exception; } } public function syncEvents(): array { $events = $this->fetchEvents(); $summary = [ 'services_count' => 0, 'songs_count' => 0, 'matched_songs_count' => 0, 'unmatched_songs_count' => 0, ]; foreach ($events as $event) { $eventId = (int) ($event->getId() ?? 0); if ($eventId === 0) { continue; } $serviceRoles = $this->extractServiceRoles($this->getEventServices($eventId)); $service = $this->upsertService($event, $serviceRoles); $songSummary = $this->syncServiceAgendaSongs((int) $service->id, $eventId); $summary['services_count']++; $summary['songs_count'] += $songSummary['songs_count']; $summary['matched_songs_count'] += $songSummary['matched_songs_count']; $summary['unmatched_songs_count'] += $songSummary['unmatched_songs_count']; } return $summary; } public function syncSongs(): int { $songs = $this->fetchSongs(); $count = 0; foreach ($songs as $song) { $ccliId = $this->normalizeCcli($song->getCcli() ?? null); if ($ccliId === null) { continue; } DB::table('songs')->updateOrInsert( ['ccli_id' => $ccliId], [ 'title' => (string) ($song->getName() ?? ''), 'updated_at' => Carbon::now(), 'created_at' => Carbon::now(), ] ); $count++; } return $count; } public function syncAgenda(int $eventId): mixed { $fetcher = $this->agendaFetcher ?? function (int $id): mixed { $this->configureApi(); return EventAgendaRequest::fromEvent($id)->get(); }; return $fetcher($eventId); } public function getEventServices(int $eventId): array { $fetcher = $this->eventServiceFetcher ?? function (int $id): array { $this->configureApi(); $event = EventRequest::find($id); return $event?->getEventServices() ?? []; }; return $fetcher($eventId); } private function fetchEvents(): array { $fetcher = $this->eventFetcher ?? function (): array { $this->configureApi(); return EventRequest::where('from', Carbon::now()->toDateString())->get(); }; return $fetcher(); } private function fetchSongs(): array { $fetcher = $this->songFetcher ?? function (): array { $this->configureApi(); return SongRequest::all(); }; return $fetcher(); } private function configureApi(): void { if ($this->apiConfigured) { return; } $apiUrl = (string) Config::get('services.churchtools.url', ''); $apiToken = (string) Config::get('services.churchtools.api_token', ''); if ($apiUrl !== '') { CTConfig::setApiUrl(rtrim($apiUrl, '/')); } CTConfig::setApiKey($apiToken); $this->apiConfigured = true; } private function upsertService(mixed $event, array $serviceRoles): object { $ctsEventId = (string) $event->getId(); DB::table('services')->updateOrInsert( ['cts_event_id' => $ctsEventId], [ 'title' => (string) ($event->getName() ?? ''), 'date' => Carbon::parse((string) $event->getStartDate())->toDateString(), 'preacher_name' => $serviceRoles['preacher'], 'beamer_tech_name' => $serviceRoles['beamer_technician'], 'last_synced_at' => Carbon::now(), 'cts_data' => json_encode($this->extractRawData($event), JSON_THROW_ON_ERROR), 'updated_at' => Carbon::now(), 'created_at' => Carbon::now(), ] ); return DB::table('services') ->where('cts_event_id', $ctsEventId) ->firstOrFail(); } private function syncServiceAgendaSongs(int $serviceId, int $eventId): array { $agenda = $this->syncAgenda($eventId); $agendaSongs = method_exists($agenda, 'getSongs') ? $agenda->getSongs() : []; $summary = [ 'songs_count' => 0, 'matched_songs_count' => 0, 'unmatched_songs_count' => 0, ]; $songMatchingService = app(SongMatchingService::class); foreach ($agendaSongs as $index => $song) { $ctsSongId = (string) ($song->getId() ?? ''); if ($ctsSongId === '') { continue; } $ccliId = $this->normalizeCcli($song->getCcli() ?? null); DB::table('service_songs')->updateOrInsert( [ 'service_id' => $serviceId, 'order' => $index + 1, ], [ 'cts_song_name' => (string) ($song->getName() ?? ''), 'cts_ccli_id' => $ccliId, 'updated_at' => Carbon::now(), 'created_at' => Carbon::now(), ] ); $serviceSong = \App\Models\ServiceSong::where('service_id', $serviceId) ->where('order', $index + 1) ->first(); $matched = false; if ($serviceSong !== null && $serviceSong->cts_ccli_id && ! $serviceSong->song_id) { $matched = $songMatchingService->autoMatch($serviceSong); } elseif ($serviceSong !== null && $serviceSong->song_id) { $matched = true; } $summary['songs_count']++; if ($matched) { $summary['matched_songs_count']++; } else { $summary['unmatched_songs_count']++; } } return $summary; } private function extractServiceRoles(array $eventServices): array { $roles = [ 'preacher' => null, 'beamer_technician' => null, ]; foreach ($eventServices as $eventService) { $serviceName = Str::lower((string) ($eventService->getName() ?? '')); $personName = $this->extractPersonName($eventService); if ($personName === null) { continue; } if ($roles['preacher'] === null && (str_contains($serviceName, 'predigt') || str_contains($serviceName, 'preach'))) { $roles['preacher'] = $personName; } if ($roles['beamer_technician'] === null && str_contains($serviceName, 'beamer')) { $roles['beamer_technician'] = $personName; } } return $roles; } private function extractPersonName(mixed $eventService): ?string { if (! method_exists($eventService, 'getPerson')) { return null; } $person = $eventService->getPerson(); if ($person === null) { return null; } if (method_exists($person, 'getName')) { $name = trim((string) $person->getName()); if ($name !== '') { return $name; } } $firstName = method_exists($person, 'getFirstName') ? trim((string) $person->getFirstName()) : ''; $lastName = method_exists($person, 'getLastName') ? trim((string) $person->getLastName()) : ''; $fullName = trim($firstName . ' ' . $lastName); return $fullName === '' ? null : $fullName; } private function extractRawData(mixed $event): array { if (method_exists($event, 'extractData')) { return $event->extractData(); } return [ 'id' => $event->getId() ?? null, 'title' => $event->getName() ?? null, 'startDate' => $event->getStartDate() ?? null, 'note' => $event->getNote() ?? null, ]; } private function normalizeCcli(?string $ccli): ?string { if ($ccli === null) { return null; } $value = trim($ccli); return $value === '' ? null : $value; } private function writeSyncLog(string $status, array $summary, ?string $message, mixed $startedAt, mixed $finishedAt): void { DB::table('cts_sync_log')->insert([ 'status' => $status, 'synced_at' => $finishedAt, 'events_count' => $summary['services_count'] ?? 0, 'songs_count' => $summary['songs_count'] ?? 0, 'error' => $message, 'created_at' => Carbon::now(), 'updated_at' => Carbon::now(), ]); } }