İlk temizlik tamamlandı bir önceki projeden
This commit is contained in:
@@ -0,0 +1,699 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* NAKS Multi-Module Synchronization Service
|
||||
*
|
||||
* Synchronizes NAKS data across multiple project sites for various modules:
|
||||
* - NAKS Technology (naks_certificates)
|
||||
* - NAKS Welder (naks_welders)
|
||||
* - NAKS Consumables (naks_consumables)
|
||||
* - NAKS Expert (register_of_experts)
|
||||
* - NAKS Equipment (welding_equipment)
|
||||
*
|
||||
* Each site runs this service to pull data from other sites.
|
||||
*/
|
||||
class NaksSyncService
|
||||
{
|
||||
protected string $basePdfPath = 'documents/003_Welding_Database';
|
||||
protected int $timeout = 30;
|
||||
protected int $batchSize = 100;
|
||||
|
||||
protected ?string $adminEmail = null;
|
||||
protected ?string $adminPassword = null;
|
||||
protected ?string $currentSiteUrl = null;
|
||||
|
||||
/**
|
||||
* Module configurations
|
||||
* Each module defines its table, unique keys, and PDF folder
|
||||
*/
|
||||
protected array $modules = [
|
||||
'technology' => [
|
||||
'name' => 'NAKS Technology',
|
||||
'table' => 'naks_certificates',
|
||||
'unique_keys' => ['short_number', 'certificate_no'],
|
||||
'pdf_folder' => '0000_Naks Technology',
|
||||
'download_field' => 'download',
|
||||
],
|
||||
'welder' => [
|
||||
'name' => 'NAKS Welder',
|
||||
'table' => 'naks_welders',
|
||||
'unique_keys' => ['naks_certificate_no', 'welder_id'],
|
||||
'pdf_folder' => '0003_Naks Welder',
|
||||
'download_field' => 'download',
|
||||
],
|
||||
'consumables' => [
|
||||
'name' => 'NAKS Consumables',
|
||||
'table' => 'naks_consumables',
|
||||
'unique_keys' => ['naks_certificate_no', 'batch_number'],
|
||||
'pdf_folder' => '0002_Naks Consumables',
|
||||
'download_field' => 'download',
|
||||
],
|
||||
'expert' => [
|
||||
'name' => 'NAKS Expert',
|
||||
'table' => 'register_of_experts',
|
||||
'unique_keys' => ['certificate_no'],
|
||||
'pdf_folder' => '0004_Naks Expert',
|
||||
'download_field' => 'download',
|
||||
],
|
||||
'equipment' => [
|
||||
'name' => 'NAKS Equipment',
|
||||
'table' => 'welding_equipment',
|
||||
'unique_keys' => ['attestation'],
|
||||
'pdf_folder' => '0001_Naks Equipment',
|
||||
'download_field' => 'download',
|
||||
],
|
||||
];
|
||||
|
||||
protected array $stats = [
|
||||
'total_projects' => 0,
|
||||
'successful_projects' => 0,
|
||||
'failed_projects' => [],
|
||||
'modules' => [],
|
||||
'errors' => [],
|
||||
];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->adminEmail = env('SYNC_ADMIN_EMAIL');
|
||||
$this->adminPassword = env('SYNC_ADMIN_PASSWORD');
|
||||
$this->currentSiteUrl = rtrim(config('app.url'), '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available modules
|
||||
*/
|
||||
public function getModules(): array
|
||||
{
|
||||
return $this->modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get module configuration by key
|
||||
*/
|
||||
public function getModuleConfig(string $moduleKey): ?array
|
||||
{
|
||||
return $this->modules[$moduleKey] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the full synchronization process
|
||||
*
|
||||
* @param string $moduleFilter Module to sync ('all' or specific module key)
|
||||
* @param string|null $specificProject Filter to sync only a specific project
|
||||
* @param bool $dryRun If true, don't make any changes
|
||||
* @param bool $force If true, sync all records (ignore last_synced_id)
|
||||
* @return array Statistics about the sync operation
|
||||
*/
|
||||
public function sync(
|
||||
string $moduleFilter = 'all',
|
||||
?string $specificProject = null,
|
||||
bool $dryRun = false,
|
||||
bool $force = false
|
||||
): array {
|
||||
Log::info("🔄 NAKS Multi-Module Sync Started", [
|
||||
'module_filter' => $moduleFilter,
|
||||
'specific_project' => $specificProject,
|
||||
'dry_run' => $dryRun,
|
||||
'force' => $force,
|
||||
]);
|
||||
|
||||
if (!$this->adminEmail || !$this->adminPassword) {
|
||||
$error = 'SYNC_ADMIN_EMAIL or SYNC_ADMIN_PASSWORD not configured in .env';
|
||||
Log::error($error);
|
||||
$this->stats['errors'][] = $error;
|
||||
return $this->stats;
|
||||
}
|
||||
|
||||
// Determine which modules to sync
|
||||
$modulesToSync = $this->getModulesToSync($moduleFilter);
|
||||
|
||||
if (empty($modulesToSync)) {
|
||||
$error = "Invalid module: {$moduleFilter}. Available: " . implode(', ', array_keys($this->modules));
|
||||
Log::error($error);
|
||||
$this->stats['errors'][] = $error;
|
||||
return $this->stats;
|
||||
}
|
||||
|
||||
// Initialize module stats
|
||||
foreach ($modulesToSync as $moduleKey => $moduleConfig) {
|
||||
$this->stats['modules'][$moduleKey] = [
|
||||
'name' => $moduleConfig['name'],
|
||||
'total_synced' => 0,
|
||||
'total_inserted' => 0,
|
||||
'total_updated' => 0,
|
||||
'total_skipped' => 0,
|
||||
'total_pdfs_downloaded' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
$projects = $this->getProjectUrls();
|
||||
$this->stats['total_projects'] = count($projects);
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$projectUrl = rtrim($project['url'], '/');
|
||||
$projectName = $project['project_name'] ?? $projectUrl;
|
||||
|
||||
// Skip if filtering by specific project
|
||||
if ($specificProject && !$this->matchesProject($projectUrl, $projectName, $specificProject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip current site (don't sync from ourselves)
|
||||
if ($this->isSameSite($projectUrl)) {
|
||||
Log::debug("Skipping current site: {$projectUrl}");
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->syncFromProject($projectUrl, $projectName, $modulesToSync, $dryRun, $force);
|
||||
$this->stats['successful_projects']++;
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Failed to sync from project: {$projectName}", [
|
||||
'url' => $projectUrl,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
$this->stats['failed_projects'][] = [
|
||||
'name' => $projectName,
|
||||
'url' => $projectUrl,
|
||||
'error' => $e->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error("NAKS Sync failed: " . $e->getMessage());
|
||||
$this->stats['errors'][] = $e->getMessage();
|
||||
}
|
||||
|
||||
Log::info("🔄 NAKS Multi-Module Sync Completed", $this->stats);
|
||||
|
||||
return $this->stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get modules to sync based on filter
|
||||
*/
|
||||
protected function getModulesToSync(string $filter): array
|
||||
{
|
||||
if ($filter === 'all') {
|
||||
return $this->modules;
|
||||
}
|
||||
|
||||
if (isset($this->modules[$filter])) {
|
||||
return [$filter => $this->modules[$filter]];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of project URLs from the API
|
||||
*/
|
||||
public function getProjectUrls(): array
|
||||
{
|
||||
$response = Http::timeout($this->timeout)
|
||||
->get($this->currentSiteUrl . '/api/project-app-urls');
|
||||
|
||||
if (!$response->successful()) {
|
||||
throw new \Exception("Failed to fetch project URLs: " . $response->status());
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
|
||||
if (!isset($data['data']) || !is_array($data['data'])) {
|
||||
throw new \Exception("Invalid project URLs response format");
|
||||
}
|
||||
|
||||
return $data['data'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticate with a remote site and get bearer token
|
||||
*/
|
||||
public function authenticate(string $baseUrl): ?string
|
||||
{
|
||||
try {
|
||||
$response = Http::timeout($this->timeout)
|
||||
->post(rtrim($baseUrl, '/') . '/api/login', [
|
||||
'email' => $this->adminEmail,
|
||||
'password' => $this->adminPassword,
|
||||
]);
|
||||
|
||||
if (!$response->successful()) {
|
||||
Log::warning("Authentication failed for {$baseUrl}", [
|
||||
'status' => $response->status(),
|
||||
]);
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
|
||||
return $data['data']['access_token'] ?? null;
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Authentication error for {$baseUrl}: " . $e->getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch records from a remote site for a specific module
|
||||
*/
|
||||
public function fetchRecords(string $baseUrl, string $token, string $tableName, int $afterId = 0, int $take = 100): array
|
||||
{
|
||||
$url = rtrim($baseUrl, '/') . '/api/' . $tableName . '/read';
|
||||
|
||||
$params = [
|
||||
'take' => $take,
|
||||
'sort' => json_encode([['selector' => 'id', 'desc' => false]]),
|
||||
];
|
||||
|
||||
if ($afterId > 0) {
|
||||
$params['filter'] = json_encode(['id', '>', $afterId]);
|
||||
}
|
||||
|
||||
$response = Http::timeout($this->timeout)
|
||||
->withToken($token)
|
||||
->get($url, $params);
|
||||
|
||||
if (!$response->successful()) {
|
||||
throw new \Exception("Failed to fetch records from {$tableName}: " . $response->status());
|
||||
}
|
||||
|
||||
$data = $response->json();
|
||||
|
||||
return $data['data'] ?? [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync all modules from a specific project
|
||||
*/
|
||||
protected function syncFromProject(
|
||||
string $projectUrl,
|
||||
string $projectName,
|
||||
array $modulesToSync,
|
||||
bool $dryRun,
|
||||
bool $force
|
||||
): void {
|
||||
Log::info("Syncing from project: {$projectName}", ['url' => $projectUrl]);
|
||||
|
||||
// Authenticate once for all modules
|
||||
$token = $this->authenticate($projectUrl);
|
||||
if (!$token) {
|
||||
throw new \Exception("Authentication failed");
|
||||
}
|
||||
|
||||
// Sync each module
|
||||
foreach ($modulesToSync as $moduleKey => $moduleConfig) {
|
||||
try {
|
||||
$this->syncModuleFromProject(
|
||||
$projectUrl,
|
||||
$projectName,
|
||||
$token,
|
||||
$moduleKey,
|
||||
$moduleConfig,
|
||||
$dryRun,
|
||||
$force
|
||||
);
|
||||
} catch (\Exception $e) {
|
||||
Log::error("Failed to sync module {$moduleKey} from {$projectName}: " . $e->getMessage());
|
||||
$this->stats['errors'][] = "Module {$moduleKey} from {$projectName}: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a specific module from a project
|
||||
*/
|
||||
protected function syncModuleFromProject(
|
||||
string $projectUrl,
|
||||
string $projectName,
|
||||
string $token,
|
||||
string $moduleKey,
|
||||
array $moduleConfig,
|
||||
bool $dryRun,
|
||||
bool $force
|
||||
): void {
|
||||
$tableName = $moduleConfig['table'];
|
||||
|
||||
Log::info("Syncing module: {$moduleConfig['name']} from {$projectName}");
|
||||
|
||||
// Get last synced ID
|
||||
$lastSyncedId = $force ? 0 : $this->getLastSyncedId($tableName, $projectUrl);
|
||||
Log::debug("Last synced ID for {$tableName} from {$projectName}: {$lastSyncedId}");
|
||||
|
||||
$moduleStats = [
|
||||
'synced' => 0,
|
||||
'inserted' => 0,
|
||||
'updated' => 0,
|
||||
'skipped' => 0,
|
||||
'pdfs' => 0,
|
||||
];
|
||||
|
||||
$maxId = $lastSyncedId;
|
||||
$hasMore = true;
|
||||
|
||||
while ($hasMore) {
|
||||
// Fetch batch of records
|
||||
$records = $this->fetchRecords($projectUrl, $token, $tableName, $maxId, $this->batchSize);
|
||||
|
||||
if (empty($records)) {
|
||||
$hasMore = false;
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($records as $record) {
|
||||
$recordId = $record['id'] ?? 0;
|
||||
$maxId = max($maxId, $recordId);
|
||||
|
||||
try {
|
||||
$result = $this->syncRecord($record, $moduleConfig, $projectUrl, $projectName, $dryRun);
|
||||
|
||||
$moduleStats['synced']++;
|
||||
|
||||
switch ($result['action']) {
|
||||
case 'inserted':
|
||||
$moduleStats['inserted']++;
|
||||
break;
|
||||
case 'updated':
|
||||
$moduleStats['updated']++;
|
||||
break;
|
||||
case 'skipped':
|
||||
$moduleStats['skipped']++;
|
||||
break;
|
||||
}
|
||||
|
||||
// Download PDF if needed
|
||||
$downloadField = $moduleConfig['download_field'] ?? 'download';
|
||||
if ($result['action'] !== 'skipped' && !$dryRun && !empty($record[$downloadField])) {
|
||||
if ($this->downloadPdf($projectUrl, $record[$downloadField], $moduleConfig['pdf_folder'])) {
|
||||
$moduleStats['pdfs']++;
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::warning("Failed to sync record", [
|
||||
'module' => $moduleKey,
|
||||
'id' => $recordId,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// If we got less than batch size, we're done
|
||||
if (count($records) < $this->batchSize) {
|
||||
$hasMore = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Update sync state
|
||||
if (!$dryRun && $maxId > $lastSyncedId) {
|
||||
$this->updateSyncState($tableName, $projectUrl, $maxId, $moduleStats['synced']);
|
||||
}
|
||||
|
||||
// Update global stats
|
||||
$this->stats['modules'][$moduleKey]['total_synced'] += $moduleStats['synced'];
|
||||
$this->stats['modules'][$moduleKey]['total_inserted'] += $moduleStats['inserted'];
|
||||
$this->stats['modules'][$moduleKey]['total_updated'] += $moduleStats['updated'];
|
||||
$this->stats['modules'][$moduleKey]['total_skipped'] += $moduleStats['skipped'];
|
||||
$this->stats['modules'][$moduleKey]['total_pdfs_downloaded'] += $moduleStats['pdfs'];
|
||||
|
||||
Log::info("Completed sync of {$moduleConfig['name']} from {$projectName}", $moduleStats);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a single record
|
||||
*
|
||||
* @param string $projectName The project name where the record is synced from (stored in source_project column)
|
||||
* @return array ['action' => 'inserted'|'updated'|'skipped', 'local_id' => int|null]
|
||||
*/
|
||||
public function syncRecord(array $record, array $moduleConfig, string $sourceUrl, string $projectName, bool $dryRun = false): array
|
||||
{
|
||||
$tableName = $moduleConfig['table'];
|
||||
$uniqueKeys = $moduleConfig['unique_keys'];
|
||||
|
||||
// Build unique key conditions
|
||||
$conditions = [];
|
||||
foreach ($uniqueKeys as $key) {
|
||||
$value = $record[$key] ?? null;
|
||||
if ($value === null || $value === '') {
|
||||
return ['action' => 'skipped', 'local_id' => null, 'reason' => "missing_unique_key: {$key}"];
|
||||
}
|
||||
$conditions[$key] = $value;
|
||||
}
|
||||
|
||||
// Check if record exists locally
|
||||
$query = DB::table($tableName);
|
||||
foreach ($conditions as $field => $value) {
|
||||
$query->where($field, $value);
|
||||
}
|
||||
$existing = $query->first();
|
||||
|
||||
// Prepare data for insert/update
|
||||
$data = $this->prepareRecordData($record, $tableName);
|
||||
|
||||
if ($existing) {
|
||||
// Compare updated_at to decide if we should update
|
||||
$remoteUpdatedAt = isset($record['updated_at']) ? Carbon::parse($record['updated_at']) : null;
|
||||
$localUpdatedAt = isset($existing->updated_at) ? Carbon::parse($existing->updated_at) : null;
|
||||
|
||||
// Only update if remote is newer
|
||||
if ($remoteUpdatedAt && $localUpdatedAt && $remoteUpdatedAt <= $localUpdatedAt) {
|
||||
return ['action' => 'skipped', 'local_id' => $existing->id, 'reason' => 'local_is_newer'];
|
||||
}
|
||||
|
||||
if (!$dryRun) {
|
||||
// Add source_project to track where the record came from
|
||||
$data['source_project'] = $projectName;
|
||||
|
||||
DB::table($tableName)
|
||||
->where('id', $existing->id)
|
||||
->update($data);
|
||||
}
|
||||
|
||||
return ['action' => 'updated', 'local_id' => $existing->id];
|
||||
} else {
|
||||
if (!$dryRun) {
|
||||
// Add source_project to track where the record came from
|
||||
$data['source_project'] = $projectName;
|
||||
|
||||
$newId = DB::table($tableName)->insertGetId($data);
|
||||
return ['action' => 'inserted', 'local_id' => $newId];
|
||||
}
|
||||
|
||||
return ['action' => 'inserted', 'local_id' => null];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare record data for database insert/update
|
||||
*/
|
||||
protected function prepareRecordData(array $record, string $tableName): array
|
||||
{
|
||||
// Remove fields that should not be copied
|
||||
$excludeFields = ['id', 'uid'];
|
||||
|
||||
$data = [];
|
||||
$columns = \Schema::getColumnListing($tableName);
|
||||
|
||||
foreach ($record as $key => $value) {
|
||||
if (in_array($key, $excludeFields)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($key, $columns)) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
// Update timestamps
|
||||
$data['updated_at'] = now();
|
||||
|
||||
// If inserting, set created_at
|
||||
if (!isset($data['created_at'])) {
|
||||
$data['created_at'] = now();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download PDF file from remote site
|
||||
*/
|
||||
public function downloadPdf(string $baseUrl, string $remotePath, string $pdfFolder): bool
|
||||
{
|
||||
if (empty($remotePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract just the filename
|
||||
$filename = basename($remotePath);
|
||||
|
||||
// Local path
|
||||
$localPath = storage_path($this->basePdfPath . '/' . $pdfFolder . '/' . $filename);
|
||||
|
||||
// Skip if file already exists locally
|
||||
if (File::exists($localPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Construct download URL
|
||||
$downloadUrl = rtrim($baseUrl, '/') . '/' . ltrim($remotePath, '/');
|
||||
|
||||
$response = Http::timeout(60)->get($downloadUrl);
|
||||
|
||||
if (!$response->successful()) {
|
||||
Log::warning("Failed to download PDF: {$downloadUrl}", [
|
||||
'status' => $response->status(),
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
$directory = dirname($localPath);
|
||||
if (!File::isDirectory($directory)) {
|
||||
File::makeDirectory($directory, 0755, true);
|
||||
}
|
||||
|
||||
// Save file
|
||||
File::put($localPath, $response->body());
|
||||
|
||||
Log::debug("Downloaded PDF: {$filename} to {$pdfFolder}");
|
||||
return true;
|
||||
} catch (\Exception $e) {
|
||||
Log::warning("Error downloading PDF: " . $e->getMessage(), [
|
||||
'url' => $baseUrl,
|
||||
'path' => $remotePath,
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last synced ID for a table and source URL
|
||||
*/
|
||||
public function getLastSyncedId(string $tableName, string $sourceUrl): int
|
||||
{
|
||||
$state = DB::table('sync_states')
|
||||
->where('table_name', $tableName)
|
||||
->where('source_url', $sourceUrl)
|
||||
->first();
|
||||
|
||||
return $state ? (int) $state->last_synced_id : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the sync state for a table and source URL
|
||||
*/
|
||||
public function updateSyncState(string $tableName, string $sourceUrl, int $lastId, int $syncedCount = 0): void
|
||||
{
|
||||
$existing = DB::table('sync_states')
|
||||
->where('table_name', $tableName)
|
||||
->where('source_url', $sourceUrl)
|
||||
->first();
|
||||
|
||||
if ($existing) {
|
||||
DB::table('sync_states')
|
||||
->where('id', $existing->id)
|
||||
->update([
|
||||
'last_synced_id' => $lastId,
|
||||
'last_synced_at' => now(),
|
||||
'synced_count' => $existing->synced_count + $syncedCount,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
} else {
|
||||
DB::table('sync_states')->insert([
|
||||
'table_name' => $tableName,
|
||||
'source_url' => $sourceUrl,
|
||||
'last_synced_id' => $lastId,
|
||||
'last_synced_at' => now(),
|
||||
'synced_count' => $syncedCount,
|
||||
'created_at' => now(),
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a URL matches the current site
|
||||
*/
|
||||
protected function isSameSite(string $url): bool
|
||||
{
|
||||
$normalizedUrl = $this->normalizeUrl($url);
|
||||
$normalizedCurrent = $this->normalizeUrl($this->currentSiteUrl);
|
||||
|
||||
return $normalizedUrl === $normalizedCurrent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize URL for comparison
|
||||
*/
|
||||
protected function normalizeUrl(string $url): string
|
||||
{
|
||||
$parsed = parse_url($url);
|
||||
$host = $parsed['host'] ?? '';
|
||||
|
||||
// Remove common prefixes/suffixes
|
||||
$host = str_replace(['www.', '/admin'], '', $host);
|
||||
|
||||
return strtolower($host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a project matches the filter
|
||||
*/
|
||||
protected function matchesProject(string $url, string $name, string $filter): bool
|
||||
{
|
||||
$filter = strtolower($filter);
|
||||
|
||||
return str_contains(strtolower($url), $filter) ||
|
||||
str_contains(strtolower($name), $filter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sync statistics
|
||||
*/
|
||||
public function getStats(): array
|
||||
{
|
||||
return $this->stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset sync state for a specific table/source or all
|
||||
*/
|
||||
public function resetSyncState(?string $tableName = null, ?string $sourceUrl = null): int
|
||||
{
|
||||
$query = DB::table('sync_states');
|
||||
|
||||
if ($tableName) {
|
||||
$query->where('table_name', $tableName);
|
||||
}
|
||||
|
||||
if ($sourceUrl) {
|
||||
$query->where('source_url', $sourceUrl);
|
||||
}
|
||||
|
||||
return $query->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all sync states, optionally filtered by table
|
||||
*/
|
||||
public function getSyncStates(?string $tableName = null): array
|
||||
{
|
||||
$query = DB::table('sync_states');
|
||||
|
||||
if ($tableName) {
|
||||
$query->where('table_name', $tableName);
|
||||
}
|
||||
|
||||
return $query->orderBy('table_name')->orderBy('source_url')->get()->toArray();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user