using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using CredentialManager.Data; namespace CredentialManager.Services; /// /// Interfaccia per l'inizializzazione del database /// public interface IDatabaseInitializer { Task InitializeAsync(); Task DatabaseExistsAsync(); } /// /// Servizio per l'inizializzazione del database SQLite /// public class DatabaseInitializer : IDatabaseInitializer { private readonly CredentialDbContext _context; private readonly ILogger _logger; public DatabaseInitializer(CredentialDbContext context, ILogger logger) { _context = context; _logger = logger; } public async Task InitializeAsync() { try { _logger.LogInformation("Inizializzazione database in corso..."); // Verifica se il database esiste var databaseExists = await _context.Database.CanConnectAsync(); if (!databaseExists) { _logger.LogInformation("Database non esistente, creazione in corso..."); // Applica tutte le migrazioni per creare il database con la struttura più recente await _context.Database.MigrateAsync(); _logger.LogInformation("Database creato con tutte le migrazioni applicate"); // Aggiungi dati di esempio se necessario await SeedInitialDataAsync(); } else { _logger.LogInformation("Database esistente trovato"); // Applica eventuali migrazioni pendenti var pendingMigrations = await _context.Database.GetPendingMigrationsAsync(); if (pendingMigrations.Any()) { _logger.LogInformation("Trovate {Count} migrazioni pendenti: {Migrations}", pendingMigrations.Count(), string.Join(", ", pendingMigrations)); try { await _context.Database.MigrateAsync(); _logger.LogInformation("Migrazioni applicate con successo"); } catch (InvalidOperationException ex) when (ex.Message.Contains("PendingModelChangesWarning")) { _logger.LogWarning("Rilevate modifiche al modello pendenti, procedo con la creazione delle tabelle mancanti..."); // Creiamo le tabelle mancanti manualmente await CreateScheduleExecutionHistoriesTableAsync(); await VerifyAndAddMissingColumnsAsync(); _logger.LogInformation("Tabelle e colonne mancanti create con successo"); } } else { _logger.LogInformation("Nessuna migrazione pendente"); } // Verifica che le tabelle esistano await VerifyTablesAsync(); } } catch (Exception ex) { _logger.LogError(ex, "Errore durante l'inizializzazione del database"); throw; } } private async Task VerifyTablesAsync() { try { // Verifica che la tabella principale Credentials esista await _context.Credentials.CountAsync(); _logger.LogInformation("Tabella Credentials verificata con successo"); // Verifica se la tabella KeyAssociations esiste try { await _context.KeyAssociations.CountAsync(); _logger.LogInformation("Tabella KeyAssociations verificata con successo"); } catch (Exception ex) { _logger.LogWarning(ex, "Tabella KeyAssociations non accessibile"); } // Verifica se la tabella DataCouplerProfiles esiste try { await _context.DataCouplerProfiles.CountAsync(); _logger.LogInformation("Tabella DataCouplerProfiles verificata con successo"); } catch (Exception ex) { _logger.LogWarning(ex, "Tabella DataCouplerProfiles non accessibile"); } // Verifica se la tabella ProfileSchedules esiste try { await _context.ProfileSchedules.CountAsync(); _logger.LogInformation("Tabella ProfileSchedules verificata con successo"); } catch (Exception ex) { _logger.LogWarning(ex, "Tabella ProfileSchedules non accessibile"); } // Verifica se la tabella ScheduleExecutionHistories esiste try { await _context.ScheduleExecutionHistories.CountAsync(); _logger.LogInformation("Tabella ScheduleExecutionHistories verificata con successo"); } catch (Exception ex) { _logger.LogWarning(ex, "Tabella ScheduleExecutionHistories non accessibile, tentativo di creazione..."); await CreateScheduleExecutionHistoriesTableAsync(); } // Verifica e aggiungi colonne mancanti per ProfileSchedules await VerifyAndAddMissingColumnsAsync(); } catch (Exception ex) { _logger.LogError(ex, "Errore nella verifica delle tabelle"); // Se ci sono problemi gravi, prova a riapplicare le migrazioni _logger.LogInformation("Tentativo di riapplicazione delle migrazioni..."); await _context.Database.MigrateAsync(); } } public async Task DatabaseExistsAsync() { try { return await _context.Database.CanConnectAsync(); } catch { return false; } } private async Task SeedInitialDataAsync() { try { // Verifica se ci sono già dati if (await _context.Credentials.AnyAsync()) { _logger.LogInformation("Dati esistenti trovati. Seeding saltato."); return; } _logger.LogInformation("Aggiunta dati di esempio..."); // Qui puoi aggiungere dati di esempio se necessario // Ad esempio: /* var sampleCredentials = new List { new CredentialEntity { Name = "Esempio Database", Type = "Database", Host = "localhost", Port = 1433, DatabaseName = "TestDB", Username = "testuser", CreatedAt = DateTime.UtcNow, CreatedBy = "System", IsActive = true } }; _context.Credentials.AddRange(sampleCredentials); await _context.SaveChangesAsync(); */ _logger.LogInformation("Dati di esempio aggiunti con successo"); } catch (Exception ex) { _logger.LogError(ex, "Errore durante il seeding dei dati iniziali"); throw; } } private async Task CreateKeyAssociationsTableAsync() { try { _logger.LogInformation("Creazione tabella KeyAssociations..."); // Elimina la vecchia tabella se esiste await _context.Database.ExecuteSqlRawAsync("DROP TABLE IF EXISTS RecordAssociations"); // Crea la nuova tabella await _context.Database.ExecuteSqlRawAsync(@" CREATE TABLE KeyAssociations ( Id INTEGER PRIMARY KEY AUTOINCREMENT, KeyValue TEXT NOT NULL, SourceKeyField TEXT NOT NULL, DestinationKeyField TEXT NOT NULL, DestinationEntity TEXT NOT NULL, DestinationId TEXT NOT NULL, RestCredentialName TEXT NOT NULL, CreatedAt TEXT NOT NULL DEFAULT (datetime('now')), UpdatedAt TEXT, LastVerifiedAt TEXT, IsActive INTEGER NOT NULL DEFAULT 1, SourcesInfo TEXT, AdditionalInfo TEXT )"); // Crea gli indici await _context.Database.ExecuteSqlRawAsync(@" CREATE INDEX IX_KeyAssociations_KeyValue ON KeyAssociations (KeyValue)"); await _context.Database.ExecuteSqlRawAsync(@" CREATE UNIQUE INDEX IX_KeyAssociations_Unique ON KeyAssociations (KeyValue, DestinationEntity, RestCredentialName)"); await _context.Database.ExecuteSqlRawAsync(@" CREATE INDEX IX_KeyAssociations_DestinationEntity ON KeyAssociations (DestinationEntity)"); await _context.Database.ExecuteSqlRawAsync(@" CREATE INDEX IX_KeyAssociations_RestCredentialName ON KeyAssociations (RestCredentialName)"); await _context.Database.ExecuteSqlRawAsync(@" CREATE INDEX IX_KeyAssociations_IsActive ON KeyAssociations (IsActive)"); await _context.Database.ExecuteSqlRawAsync(@" CREATE INDEX IX_KeyAssociations_CreatedAt ON KeyAssociations (CreatedAt)"); await _context.Database.ExecuteSqlRawAsync(@" CREATE INDEX IX_KeyAssociations_LastVerifiedAt ON KeyAssociations (LastVerifiedAt)"); _logger.LogInformation("Tabella KeyAssociations creata con successo"); } catch (Exception ex) { _logger.LogError(ex, "Errore nella creazione della tabella KeyAssociations"); throw; } } private async Task CreateScheduleExecutionHistoriesTableAsync() { try { _logger.LogInformation("Creazione tabella ScheduleExecutionHistories..."); // Crea la tabella ScheduleExecutionHistories await _context.Database.ExecuteSqlRawAsync(@" CREATE TABLE IF NOT EXISTS ScheduleExecutionHistories ( Id INTEGER PRIMARY KEY AUTOINCREMENT, ScheduleId INTEGER NOT NULL, ProfileId INTEGER NOT NULL, ProfileName TEXT NOT NULL, StartTime DATETIME NOT NULL, EndTime DATETIME, Status TEXT NOT NULL, Message TEXT, RecordsProcessed INTEGER DEFAULT 0, RecordsWithErrors INTEGER, ErrorDetails TEXT, TriggerType TEXT NOT NULL, TriggeredBy TEXT, SourceType TEXT, DestinationType TEXT, SourceInfo TEXT, DestinationInfo TEXT, AdditionalInfo TEXT, CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (ScheduleId) REFERENCES ProfileSchedules (Id) ON DELETE CASCADE )"); // Crea gli indici await _context.Database.ExecuteSqlRawAsync(@" CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_ScheduleId ON ScheduleExecutionHistories (ScheduleId)"); await _context.Database.ExecuteSqlRawAsync(@" CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_ProfileId ON ScheduleExecutionHistories (ProfileId)"); await _context.Database.ExecuteSqlRawAsync(@" CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_Status ON ScheduleExecutionHistories (Status)"); await _context.Database.ExecuteSqlRawAsync(@" CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_StartTime ON ScheduleExecutionHistories (StartTime)"); await _context.Database.ExecuteSqlRawAsync(@" CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_TriggerType ON ScheduleExecutionHistories (TriggerType)"); _logger.LogInformation("Tabella ScheduleExecutionHistories creata con successo"); } catch (Exception ex) { _logger.LogError(ex, "Errore durante la creazione della tabella ScheduleExecutionHistories"); _logger.LogWarning("Continuazione normale - la tabella potrebbe già esistere"); } } private async Task VerifyAndAddMissingColumnsAsync() { try { _logger.LogInformation("Verifica colonne mancanti per ProfileSchedules..."); // Verifica se le colonne di override database esistono try { await _context.Database.ExecuteSqlRawAsync( "SELECT SourceDatabaseOverride FROM ProfileSchedules LIMIT 1"); } catch { _logger.LogInformation("Aggiunta colonna SourceDatabaseOverride..."); await _context.Database.ExecuteSqlRawAsync( "ALTER TABLE ProfileSchedules ADD COLUMN SourceDatabaseOverride TEXT"); } try { await _context.Database.ExecuteSqlRawAsync( "SELECT DestinationDatabaseOverride FROM ProfileSchedules LIMIT 1"); } catch { _logger.LogInformation("Aggiunta colonna DestinationDatabaseOverride..."); await _context.Database.ExecuteSqlRawAsync( "ALTER TABLE ProfileSchedules ADD COLUMN DestinationDatabaseOverride TEXT"); } _logger.LogInformation("Verifica colonne completata"); } catch (Exception ex) { _logger.LogError(ex, "Errore durante la verifica/aggiunta colonne"); _logger.LogWarning("Continuazione normale - le colonne potrebbero già esistere"); } } }