diff --git a/app/Http/Controllers/ApiLogController.php b/app/Http/Controllers/ApiLogController.php index 05b140d..0a9c367 100644 --- a/app/Http/Controllers/ApiLogController.php +++ b/app/Http/Controllers/ApiLogController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Models\ApiRequestLog; +use Illuminate\Http\JsonResponse; use Inertia\Inertia; use Inertia\Response; @@ -39,4 +40,12 @@ public function index(): Response ], ]); } + + public function responseBody(ApiRequestLog $log): JsonResponse + { + return response()->json([ + 'response_body' => $log->response_body, + 'request_context' => $log->request_context, + ]); + } } diff --git a/app/Models/ApiRequestLog.php b/app/Models/ApiRequestLog.php index 3e93803..b451d8f 100644 --- a/app/Models/ApiRequestLog.php +++ b/app/Models/ApiRequestLog.php @@ -17,6 +17,7 @@ class ApiRequestLog extends Model 'status', 'request_context', 'response_summary', + 'response_body', 'error_message', 'duration_ms', 'sync_log_id', diff --git a/app/Services/ChurchToolsService.php b/app/Services/ChurchToolsService.php index 0a8fd7f..948c113 100644 --- a/app/Services/ChurchToolsService.php +++ b/app/Services/ChurchToolsService.php @@ -11,6 +11,7 @@ use Illuminate\Support\Carbon; use Illuminate\Support\Facades\Config; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; use Throwable; @@ -72,20 +73,29 @@ public function syncEvents(): array ]; foreach ($events as $event) { - $eventId = (int) ($event->getId() ?? 0); - if ($eventId === 0) { + try { + $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']; + } catch (\Throwable $e) { + Log::warning('Sync-Fehler für Event', [ + 'event_id' => $event->getId() ?? 'unknown', + 'error' => $e->getMessage(), + ]); + 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; @@ -156,9 +166,18 @@ private function fetchEvents(): array $fetcher = $this->eventFetcher ?? function (): array { $this->configureApi(); - return EventRequest::where('from', Carbon::now()->toDateString()) + $futureEvents = EventRequest::where('from', Carbon::now()->toDateString()) ->where('to', Carbon::now()->addMonths(3)->toDateString()) ->get(); + + $pastEvents = EventRequest::where('from', Carbon::now()->subMonths(3)->toDateString()) + ->where('to', Carbon::now()->subDay()->toDateString()) + ->get(); + + $future = array_slice($futureEvents, 0, 10); + $past = array_slice(array_reverse($pastEvents), 0, 10); + + return array_merge($past, $future); }; return $this->logApiCall('fetchEvents', 'events', fn (): array => $fetcher()); @@ -189,6 +208,7 @@ private function logApiCall(string $method, string $endpoint, Closure $operation 'status' => 'success', 'request_context' => $context, 'response_summary' => $this->summarizeResponse($result), + 'response_body' => $this->serializeResponseBody($result), 'duration_ms' => $duration, ]); @@ -230,6 +250,21 @@ private function summarizeResponse(mixed $result): ?string return null; } + private function serializeResponseBody(mixed $result): ?string + { + if ($result === null) { + return null; + } + + try { + $json = json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR); + + return mb_strlen($json) > 512000 ? mb_substr($json, 0, 512000) . "\n... (abgeschnitten)" : $json; + } catch (\JsonException) { + return null; + } + } + private function configureApi(): void { if ($this->apiConfigured) { diff --git a/database/migrations/2026_03_02_121522_add_response_body_to_api_request_logs_table.php b/database/migrations/2026_03_02_121522_add_response_body_to_api_request_logs_table.php new file mode 100644 index 0000000..84dfcfa --- /dev/null +++ b/database/migrations/2026_03_02_121522_add_response_body_to_api_request_logs_table.php @@ -0,0 +1,27 @@ +longText('response_body')->nullable()->after('response_summary'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('api_request_logs', function (Blueprint $table) { + $table->dropColumn('response_body'); + }); + } +}; diff --git a/resources/js/Pages/ApiLogs/Index.vue b/resources/js/Pages/ApiLogs/Index.vue index 0d1f322..cc8bc2a 100644 --- a/resources/js/Pages/ApiLogs/Index.vue +++ b/resources/js/Pages/ApiLogs/Index.vue @@ -66,9 +66,37 @@ function statusText(logStatus) { } const expandedId = ref(null) +const detailData = ref({}) +const loadingDetail = ref(null) -function toggleExpanded(id) { - expandedId.value = expandedId.value === id ? null : id +async function toggleExpanded(id) { + if (expandedId.value === id) { + expandedId.value = null + return + } + + expandedId.value = id + + if (!detailData.value[id]) { + loadingDetail.value = id + + try { + const response = await fetch(route('api-logs.response-body', id), { + headers: { + Accept: 'application/json', + 'X-Requested-With': 'XMLHttpRequest', + }, + }) + + if (response.ok) { + detailData.value[id] = await response.json() + } + } catch (e) { + console.error('Detail load error:', e) + } finally { + loadingDetail.value = null + } + } } @@ -148,18 +176,35 @@ function toggleExpanded(id) {
{{ JSON.stringify(log.request_context, null, 2) }}
- Kein Kontext verfügbar
+ +{{ JSON.stringify(detailData[log.id].request_context, null, 2) }}
+ Kein Kontext verfügbar
{{ log.response_summary }}
-Keine Zusammenfassung verfügbar
+ +{{ detailData[log.id].response_body }}
+ Keine Antwort-Daten verfügbar