header('X-Syncio-Secret') !== config('services.syncio.secret')) { abort(403); } $data = $request->json()->all(); // $data['status'] 'success' | 'partial_success' | 'failed' // $data['tables']['success'] кол-во успешных // $data['errors'] массив ошибок $scheduleId = $data['schedule_id'] ?? ($data['schedule']['id'] ?? null); // Обрабатываем только расписания, заведённые в таблице (связанные с проектом) if (! ReplicationSchedule::active()->where('schedule_id', $scheduleId)->exists()) { return response()->noContent(); // 204, но игнорируем чужое расписание } // Сохраняем информацию о репликации из вебхука ReplicationLog::create([ 'status' => $data['status'] ?? null, 'schedule_id' => $scheduleId, 'tables_success' => (int) ($data['tables']['success'] ?? 0), 'tables_failed' => (int) ($data['tables']['failed'] ?? 0), 'errors' => $data['errors'] ?? null, 'payload' => $data, 'received_at' => now(), ]); // При успешной репликации обновляем материализованные представления if (($data['status'] ?? null) === 'success') { RefreshMaterializedViews::dispatch(); } return response()->noContent(); // 204 } }