netgescon-day0/app/Http/Controllers/Api/DistributionController.php

446 lines
15 KiB
PHP
Executable File

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Amministratore;
use App\Services\DistributionService;
use App\Services\TenantArchivePathService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class DistributionController extends Controller
{
public function updatesManifest(Request $request): JsonResponse
{
$channel = (string) $request->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 = app(TenantArchivePathService::class)->amministratoreAbsolutePath($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 = app(TenantArchivePathService::class)->amministratoreAbsolutePath($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' => app(TenantArchivePathService::class)->amministratoreRelativePath($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;
}
}
}