query('channel', 'free'); if (! $this->authorizeUpdateAccess($request, $channel)) { return response()->json([ 'success' => false, 'error' => 'Accesso non autorizzato al canale update richiesto', ], 403); } $manifestPath = rtrim((string) config('distribution.storage_root'), '/') . '/' . $channel . '/manifest.json'; if (! is_file($manifestPath)) { return response()->json([ 'success' => false, 'error' => 'Manifest non trovato per il canale richiesto', ], 404); } $json = json_decode((string) file_get_contents($manifestPath), true); if (! is_array($json)) { return response()->json([ 'success' => false, 'error' => 'Manifest non valido', ], 500); } return response()->json($json); } public function updatesPackage(Request $request): BinaryFileResponse | JsonResponse { $channel = (string) $request->query('channel', 'free'); $file = basename((string) $request->query('file', '')); if ($file === '') { return response()->json([ 'success' => false, 'error' => 'Parametro file mancante', ], 422); } if (! $this->authorizeUpdateAccess($request, $channel)) { return response()->json([ 'success' => false, 'error' => 'Accesso non autorizzato al pacchetto update', ], 403); } $packagePath = rtrim((string) config('distribution.storage_root'), '/') . '/' . $channel . '/' . $file; if (! is_file($packagePath)) { return response()->json([ 'success' => false, 'error' => 'Pacchetto non trovato', ], 404); } return response()->download($packagePath, $file, [ 'Content-Type' => 'application/zip', ]); } private function authorizeUpdateAccess(Request $request, string $channel): bool { if ($channel === 'free') { return true; } if ($channel !== 'licensed') { return false; } $code = trim((string) $request->query('code', '')); $auth = trim((string) $request->query('auth', '')); if ($code === '' || $auth === '') { return false; } $pairsRaw = trim((string) config('distribution.licensed_pairs', '')); if ($pairsRaw === '') { return false; } $pairs = array_filter(array_map('trim', explode(',', $pairsRaw))); foreach ($pairs as $pair) { [$cfgCode, $cfgAuth] = array_pad(explode(':', $pair, 2), 2, ''); if (hash_equals(trim($cfgCode), $code) && hash_equals(trim($cfgAuth), $auth)) { return true; } } return false; } /** * Health check del server per verifiche distribuzione */ public function health(): JsonResponse { try { $health = [ 'status' => 'ok', 'timestamp' => now()->toISOString(), 'version' => config('app.version', '1.0.0'), 'server' => config('app.url'), 'database' => [ 'connection' => config('database.default'), 'status' => 'ok', ], 'storage' => [ 'disk' => config('filesystems.default'), 'available' => true, ], 'administrators_count' => Amministratore::count(), 'features' => [ 'multi_database' => true, 'distribution' => true, 'migration' => true, 'backup' => true, ], ]; // Test connessione database try { DB::connection()->getPdo(); } catch (\Exception $e) { $health['database']['status'] = 'error'; $health['database']['error'] = $e->getMessage(); $health['status'] = 'degraded'; } // Test storage try { Storage::disk()->exists('.'); } catch (\Exception $e) { $health['storage']['available'] = false; $health['storage']['error'] = $e->getMessage(); $health['status'] = 'degraded'; } return response()->json($health); } catch (\Exception $e) { return response()->json([ 'status' => 'error', 'error' => $e->getMessage(), 'timestamp' => now()->toISOString(), ], 500); } } /** * Importa archivio amministratore da altro server */ public function importAdministrator(Request $request): JsonResponse { try { $request->validate([ 'codice_amministratore' => 'required|string|size:8', 'source_server' => 'required|url', 'migration_token' => 'required|string', 'archive' => 'required|file|mimes:zip|max:1048576', // Max 1GB ]); $codiceAmministratore = $request->input('codice_amministratore'); $sourceServer = $request->input('source_server'); $migrationToken = $request->input('migration_token'); Log::info("Inizio importazione amministratore {$codiceAmministratore} da {$sourceServer}"); // Verifica che l'amministratore non esista già if (Amministratore::where('codice_amministratore', $codiceAmministratore)->exists()) { return response()->json([ 'success' => false, 'error' => 'Amministratore già presente su questo server', ], 409); } // Salva archivio temporaneo $archive = $request->file('archive'); $tempPath = storage_path("app/temp/import_{$codiceAmministratore}_" . time() . ".zip"); if (! is_dir(dirname($tempPath))) { mkdir(dirname($tempPath), 0755, true); } $archive->move(dirname($tempPath), basename($tempPath)); // Estrae archivio $extractPath = storage_path("app/amministratori/{$codiceAmministratore}"); $zip = new \ZipArchive(); if ($zip->open($tempPath) !== true) { throw new \Exception('Impossibile aprire archivio ZIP'); } $zip->extractTo($extractPath); $zip->close(); // Rimuove file temporaneo unlink($tempPath); Log::info("Archivio amministratore {$codiceAmministratore} estratto in {$extractPath}"); return response()->json([ 'success' => true, 'message' => 'Archivio importato con successo', 'transfer_id' => uniqid('transfer_'), 'extracted_to' => $extractPath, 'imported_at' => now()->toISOString(), ]); } catch (\Exception $e) { Log::error("Errore importazione amministratore: " . $e->getMessage()); return response()->json([ 'success' => false, 'error' => $e->getMessage(), ], 500); } } /** * Attiva amministratore dopo importazione */ public function activateAdministrator(Request $request): JsonResponse { try { $request->validate([ 'codice_amministratore' => 'required|string|size:8', 'activation_token' => 'required|string', ]); $codiceAmministratore = $request->input('codice_amministratore'); Log::info("Attivazione amministratore {$codiceAmministratore}"); // Verifica che l'archivio sia stato importato $archivePath = storage_path("app/amministratori/{$codiceAmministratore}"); if (! is_dir($archivePath)) { throw new \Exception('Archivio amministratore non trovato. Importare prima l\'archivio.'); } // Cerca metadata $metadataPath = storage_path("app/migrations/metadata_{$codiceAmministratore}.json"); if (! file_exists($metadataPath)) { throw new \Exception('Metadata migrazione non trovati'); } $metadata = json_decode(file_get_contents($metadataPath), true); // Crea record amministratore nel database $amministratore = Amministratore::create([ 'nome' => $metadata['amministratore']['nome'], 'cognome' => $metadata['amministratore']['cognome'], 'denominazione_studio' => $metadata['amministratore']['denominazione_studio'], 'codice_amministratore' => $codiceAmministratore, 'user_id' => 1, // TODO: gestire user associato 'database_attivo' => $metadata['amministratore']['database_name'], 'cartella_dati' => "amministratori/{$codiceAmministratore}", 'stato_sincronizzazione' => 'attivo', 'attivo' => true, ]); // Ripristina database se presente $backupFiles = glob($archivePath . '/backup/database/*.sql'); if (! empty($backupFiles)) { $latestBackup = end($backupFiles); $this->restoreDatabase($amministratore, $latestBackup); } Log::info("Amministratore {$codiceAmministratore} attivato con successo"); return response()->json([ 'success' => true, 'message' => 'Amministratore attivato con successo', 'administrator_id' => $amministratore->id, 'database_restored' => ! empty($backupFiles), 'activated_at' => now()->toISOString(), ]); } catch (\Exception $e) { Log::error("Errore attivazione amministratore: " . $e->getMessage()); return response()->json([ 'success' => false, 'error' => $e->getMessage(), ], 500); } } /** * Sincronizza dati amministratore */ public function syncAdministrator(Request $request): JsonResponse { try { $request->validate([ 'codice_amministratore' => 'required|string|size:8', 'last_sync' => 'nullable|date', 'sync_token' => 'required|string', ]); $codiceAmministratore = $request->input('codice_amministratore'); $lastSync = $request->input('last_sync'); $amministratore = Amministratore::where('codice_amministratore', $codiceAmministratore)->first(); if (! $amministratore) { return response()->json([ 'success' => false, 'error' => 'Amministratore non trovato', ], 404); } // Simula sincronizzazione (da implementare logica specifica) $changes = 0; if ($lastSync) { // Conta modifiche dalla data di ultima sincronizzazione // TODO: implementare logica di tracking modifiche } return response()->json([ 'success' => true, 'changes' => $changes, 'last_sync' => $amministratore->updated_at, 'synced_at' => now()->toISOString(), ]); } catch (\Exception $e) { return response()->json([ 'success' => false, 'error' => $e->getMessage(), ], 500); } } /** * Esegue backup amministratore */ public function backupAdministrator(Request $request): JsonResponse { try { $request->validate([ 'codice_amministratore' => 'required|string|size:8', 'backup_token' => 'required|string', ]); $codiceAmministratore = $request->input('codice_amministratore'); $amministratore = Amministratore::where('codice_amministratore', $codiceAmministratore)->first(); if (! $amministratore) { return response()->json([ 'success' => false, 'error' => 'Amministratore non trovato', ], 404); } $backupResult = $amministratore->createDatabaseBackup(); if ($backupResult['success']) { $amministratore->update(['ultimo_backup' => now()]); } return response()->json($backupResult); } catch (\Exception $e) { return response()->json([ 'success' => false, 'error' => $e->getMessage(), ], 500); } } /** * Ottiene informazioni routing per amministratore */ public function getAdministratorRouting(string $codice): JsonResponse { try { $routingInfo = DistributionService::getAdministratorAccessUrl($codice); return response()->json($routingInfo); } catch (\Exception $e) { return response()->json([ 'success' => false, 'error' => $e->getMessage(), ], 500); } } /** * Ripristina database da backup */ private function restoreDatabase(Amministratore $amministratore, string $backupPath): bool { try { $dbName = $amministratore->getDatabaseName(); // Crea database se non esiste DB::statement("CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); // Ripristina da backup $command = sprintf( 'mysql -h %s -u %s -p%s %s < %s', env('DB_HOST', '127.0.0.1'), env('DB_USERNAME'), env('DB_PASSWORD'), escapeshellarg($dbName), escapeshellarg($backupPath) ); exec($command, $output, $returnCode); return $returnCode === 0; } catch (\Exception $e) { Log::error("Errore ripristino database: " . $e->getMessage()); return false; } } }