syncService = $syncService; } /** * Execute the console command. */ public function handle(): int { $module = $this->option('module') ?? 'all'; $project = $this->option('project'); $dryRun = (bool) $this->option('dry-run'); $force = (bool) $this->option('force'); $statusOnly = (bool) $this->option('status'); $reset = (bool) $this->option('reset'); $this->info('╔════════════════════════════════════════════════════════════╗'); $this->info('║ NAKS Multi-Module Cross-Site Synchronization ║'); $this->info('╚════════════════════════════════════════════════════════════╝'); $this->newLine(); // Show available modules $this->showAvailableModules(); // Handle status display if ($statusOnly) { return $this->showStatus($module); } // Handle reset if ($reset) { return $this->resetSyncState($module, $project); } // Validate module if ($module !== 'all' && !$this->syncService->getModuleConfig($module)) { $this->error("❌ Invalid module: {$module}"); $this->line("Available modules: all, technology, welder, consumables, expert, equipment"); return self::FAILURE; } // Show mode information if ($dryRun) { $this->warn('⚠️ DRY RUN MODE - No changes will be made'); $this->newLine(); } if ($force) { $this->warn('⚠️ FORCE MODE - Ignoring last sync ID, syncing all records'); $this->newLine(); } $this->info("📦 Module(s): " . ($module === 'all' ? 'All modules' : $module)); if ($project) { $this->info("📌 Project filter: {$project}"); } $this->newLine(); // Check credentials if (!env('SYNC_ADMIN_EMAIL') || !env('SYNC_ADMIN_PASSWORD')) { $this->error('❌ SYNC_ADMIN_EMAIL and SYNC_ADMIN_PASSWORD must be set in .env'); return self::FAILURE; } // Show available projects $this->showAvailableProjects(); // Run sync $this->info('🔄 Starting synchronization...'); $this->newLine(); $startTime = microtime(true); try { $stats = $this->syncService->sync($module, $project, $dryRun, $force); } catch (\Exception $e) { $this->error('❌ Sync failed: ' . $e->getMessage()); return self::FAILURE; } $duration = round(microtime(true) - $startTime, 2); // Display results $this->displayResults($stats, $duration, $dryRun); // Return appropriate exit code if (!empty($stats['errors']) || !empty($stats['failed_projects'])) { return self::FAILURE; } return self::SUCCESS; } /** * Show available modules */ protected function showAvailableModules(): void { $modules = $this->syncService->getModules(); $this->info('📦 Available Modules:'); $tableData = []; foreach ($modules as $key => $config) { $tableData[] = [ $key, $config['name'], $config['table'], implode(' + ', $config['unique_keys']), ]; } $this->table( ['Key', 'Name', 'Table', 'Unique Keys'], $tableData ); $this->newLine(); } /** * Show available projects */ protected function showAvailableProjects(): void { try { $projects = $this->syncService->getProjectUrls(); $this->info('📋 Available Projects:'); $this->table( ['Project Name', 'URL'], array_map(function ($p) { return [$p['project_name'] ?? 'Unknown', $p['url'] ?? '']; }, $projects) ); $this->newLine(); } catch (\Exception $e) { $this->warn('Could not fetch project list: ' . $e->getMessage()); } } /** * Display sync results */ protected function displayResults(array $stats, float $duration, bool $dryRun): void { $this->newLine(); $this->info('════════════════════════════════════════════════════════════'); $this->info(' SYNC RESULTS'); $this->info('════════════════════════════════════════════════════════════'); $this->newLine(); // Project summary $this->table( ['Metric', 'Value'], [ ['Total Projects', $stats['total_projects']], ['Successful Projects', $stats['successful_projects']], ['Failed Projects', count($stats['failed_projects'])], ['Duration', "{$duration}s"], ] ); // Module-specific results if (!empty($stats['modules'])) { $this->newLine(); $this->info('📦 Module Results:'); $moduleData = []; $totalSynced = 0; $totalInserted = 0; $totalUpdated = 0; $totalSkipped = 0; $totalPdfs = 0; foreach ($stats['modules'] as $key => $moduleStats) { $moduleData[] = [ $moduleStats['name'], $moduleStats['total_synced'], $moduleStats['total_inserted'], $moduleStats['total_updated'], $moduleStats['total_skipped'], $moduleStats['total_pdfs_downloaded'], ]; $totalSynced += $moduleStats['total_synced']; $totalInserted += $moduleStats['total_inserted']; $totalUpdated += $moduleStats['total_updated']; $totalSkipped += $moduleStats['total_skipped']; $totalPdfs += $moduleStats['total_pdfs_downloaded']; } // Add totals row $moduleData[] = [ '─────────', '─────', '────────', '───────', '───────', '────', ]; $moduleData[] = [ 'TOTAL', $totalSynced, $totalInserted, $totalUpdated, $totalSkipped, $totalPdfs, ]; $this->table( ['Module', 'Synced', 'Inserted', 'Updated', 'Skipped', 'PDFs'], $moduleData ); } // Show failed projects if (!empty($stats['failed_projects'])) { $this->newLine(); $this->error('❌ Failed Projects:'); foreach ($stats['failed_projects'] as $failed) { $this->line(" • {$failed['name']}: {$failed['error']}"); } } // Show errors if (!empty($stats['errors'])) { $this->newLine(); $this->error('❌ Errors:'); foreach ($stats['errors'] as $error) { $this->line(" • {$error}"); } } $this->newLine(); if ($dryRun) { $this->warn('ℹ️ This was a DRY RUN - no actual changes were made'); } else { $hasSyncedRecords = false; foreach ($stats['modules'] as $moduleStats) { if ($moduleStats['total_synced'] > 0) { $hasSyncedRecords = true; break; } } if ($hasSyncedRecords) { $this->info('✅ Synchronization completed successfully!'); } else { $this->info('ℹ️ No new records to sync'); } } } /** * Show sync status */ protected function showStatus(string $module): int { $this->info('📊 Sync Status:'); $this->newLine(); $tableName = null; if ($module !== 'all') { $config = $this->syncService->getModuleConfig($module); if ($config) { $tableName = $config['table']; } } $states = $this->syncService->getSyncStates($tableName); if (empty($states)) { $this->warn('No sync history found. Run "php artisan naks:sync" to start syncing.'); return self::SUCCESS; } $tableData = array_map(function ($state) { return [ $state->table_name ?? 'Unknown', $state->source_url ?? 'Unknown', $state->last_synced_id ?? 0, $state->synced_count ?? 0, $state->last_synced_at ?? 'Never', ]; }, $states); $this->table( ['Table', 'Source URL', 'Last ID', 'Total Synced', 'Last Sync'], $tableData ); return self::SUCCESS; } /** * Reset sync state */ protected function resetSyncState(string $module, ?string $project): int { $tableName = null; if ($module !== 'all') { $config = $this->syncService->getModuleConfig($module); if ($config) { $tableName = $config['table']; } else { $this->error("Invalid module: {$module}"); return self::FAILURE; } } $message = 'Reset sync state for '; $message .= $module === 'all' ? 'ALL modules' : "module: {$module}"; if ($project) { $message .= " and project: {$project}"; } $message .= '?'; if (!$this->confirm($message)) { $this->info('Operation cancelled.'); return self::SUCCESS; } $sourceUrl = null; if ($project) { // Find matching project URL try { $projects = $this->syncService->getProjectUrls(); foreach ($projects as $p) { if (str_contains(strtolower($p['url']), strtolower($project)) || str_contains(strtolower($p['project_name'] ?? ''), strtolower($project))) { $sourceUrl = $p['url']; break; } } if (!$sourceUrl) { $this->error("Project not found: {$project}"); return self::FAILURE; } } catch (\Exception $e) { $this->error('Could not fetch project list: ' . $e->getMessage()); return self::FAILURE; } } $deleted = $this->syncService->resetSyncState($tableName, $sourceUrl); $this->info("✅ Reset {$deleted} sync state record(s)"); return self::SUCCESS; } }