diff --git a/Data_Coupler/Extensions/DataCoupler/DatabaseMethod.cs b/Data_Coupler/Extensions/DataCoupler/DatabaseMethod.cs new file mode 100644 index 0000000..ff77da6 --- /dev/null +++ b/Data_Coupler/Extensions/DataCoupler/DatabaseMethod.cs @@ -0,0 +1,882 @@ +using System; +using System.Data; +using System.Text; +using CredentialManager.Models; +using CredentialManager.Services; +using DataConnection.Interfaces; +using DataConnection.REST.Interfaces; +using DataConnection.REST.Models; +using DataConnection.CredentialManagement.Interfaces; +using ExcelDataReader; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.JSInterop; +using Microsoft.Extensions.Logging; +using Data_Coupler.Services; +using Data_Coupler.Models; + +namespace Data_Coupler.Pages; + +public partial class DataCoupler : ComponentBase +{ + // ===== PROPRIETÀ DATABASE ===== + + // Stato delle credenziali database + protected List databaseCredentials = new(); + protected string selectedDatabaseCredential = ""; + + // Stato connessioni database + protected bool isConnectingDatabase = false; + protected bool isDatabaseConnected = false; + protected string databaseErrorMessage = ""; + + // Database discovery + protected List availableTableNames = new(); // Solo nomi delle tabelle + protected Dictionary> databaseTables = new(); // Schema dettagliato per tabelle caricate + protected string selectedTable = ""; + protected string databaseSearchTerm = ""; + + // Database selection - per gestire la selezione del database quando non specificato nella connection string + protected List availableDatabases = new(); + protected string selectedDatabase = ""; + protected bool showDatabaseSelectionModal = false; + protected bool isLoadingDatabases = false; + + // Database selection (schemas only) + protected List availableSchemas = new(); + protected string selectedSchema = ""; + protected bool showSchemaSelectionModal = false; + protected bool isLoadingSchemas = false; + + // Custom query functionality + protected bool useCustomQuery = false; + protected string customQuery = ""; + protected bool isValidatingQuery = false; + protected bool isQueryValid = false; + protected string queryValidationMessage = ""; + protected List> queryPreviewData = new(); + protected List queryColumns = new(); + protected bool showQueryPreview = false; + protected bool isLoadingPreview = false; + + // Gestione chiavi sorgente per database + protected string suggestedPrimaryKey = ""; // Campo PK suggerito per database + + // Servizi database + protected IDatabaseManager? currentDatabaseManager = null; + + // ===== METODI DATABASE ===== + + /// + /// Gestisce il cambio di credenziale database selezionata + /// + protected void OnDatabaseCredentialChanged(ChangeEventArgs e) + { + selectedDatabaseCredential = e.Value?.ToString() ?? ""; + ResetDatabaseState(); + } + + /// + /// Resetta lo stato del database + /// + protected void ResetDatabaseState() + { + isDatabaseConnected = false; + databaseTables.Clear(); + selectedTable = ""; + databaseSearchTerm = ""; + databaseErrorMessage = ""; + + // Reset database selection + availableDatabases.Clear(); + selectedDatabase = ""; + showDatabaseSelectionModal = false; + isLoadingDatabases = false; + + // Reset custom query state + useCustomQuery = false; + customQuery = ""; + isValidatingQuery = false; + isQueryValid = false; + queryValidationMessage = ""; + queryPreviewData.Clear(); + queryColumns.Clear(); + showQueryPreview = false; + isLoadingPreview = false; + + currentDatabaseManager?.Dispose(); + currentDatabaseManager = null; + + // Clear mappings when resetting database state + ClearAllMappings(); + } + + /// + /// Connette al database utilizzando le credenziali selezionate + /// + protected async Task ConnectToDatabase() + { + if (string.IsNullOrEmpty(selectedDatabaseCredential)) + return; + + isConnectingDatabase = true; + databaseErrorMessage = ""; + + try + { + // Trova la credenziale + var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); + if (credential == null) + { + databaseErrorMessage = "Credenziale database non trovata"; + return; + } + + // Test della connessione + var (success, message) = await CredentialService.TestDatabaseConnectionAsync(credential.Name); + if (!success) + { + databaseErrorMessage = $"Connessione fallita: {message}"; + return; + } + + // Crea il database manager usando il factory con le credenziali complete + Logger.LogInformation("Creando database manager per credenziale: {CredentialName}", selectedDatabaseCredential); + currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential); + Logger.LogInformation("Database manager creato con successo"); + + // Verifica se il database è specificato nella connection string + bool isDatabaseSpecified = await IsDatabaseSpecifiedInConnectionString(credential); + + if (isDatabaseSpecified) + { + Logger.LogInformation("Database specificato nella connection string. Procedendo con discovery tabelle."); + try + { + await LoadTablesFromConnectedDatabase(); + isDatabaseConnected = true; + Logger.LogInformation("Tabelle caricate con successo, database connesso"); + return; // Importante: usciamo qui se tutto va bene + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nel caricamento tabelle dal database specificato"); + databaseErrorMessage = $"Errore nel caricamento tabelle: {ex.Message}"; + return; + } + } + else + { + Logger.LogInformation("Database non specificato nella connection string. Caricando database disponibili."); + await LoadAvailableDatabases(); + + if (availableDatabases.Any()) + { + Logger.LogInformation("Trovati {DatabaseCount} database disponibili", availableDatabases.Count); + showDatabaseSelectionModal = true; + StateHasChanged(); + return; // Non procediamo fino alla selezione del database + } + else + { + databaseErrorMessage = "Nessun database disponibile trovato"; + return; + } + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nella connessione al database"); + databaseErrorMessage = $"Errore: {ex.Message}"; + } + finally + { + isConnectingDatabase = false; + StateHasChanged(); + } + } + + /// + /// Connette a un database specifico + /// + protected async Task ConnectToDatabaseWithSpecificDatabase(string databaseName) + { + if (string.IsNullOrEmpty(selectedDatabaseCredential)) + return; + + isConnectingDatabase = true; + databaseErrorMessage = ""; + + try + { + // Trova la credenziale + var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); + if (credential == null) + { + databaseErrorMessage = "Credenziale database non trovata"; + return; + } + + // Test della connessione + var (success, message) = await CredentialService.TestDatabaseConnectionAsync(credential.Name); + if (!success) + { + databaseErrorMessage = $"Connessione fallita: {message}"; + return; + } + + // Crea il database manager + Logger.LogInformation("Creando database manager per credenziale: {CredentialName}", selectedDatabaseCredential); + currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential); + Logger.LogInformation("Database manager creato con successo"); + if (currentDatabaseManager == null) + { + databaseErrorMessage = "Database manager non disponibile"; + return; + } + + Logger.LogInformation("Cambiando database a: {DatabaseName}", databaseName); + + // Usa il metodo dell'interfaccia per cambiare database + await currentDatabaseManager.ChangeDatabaseAsync(databaseName); + Logger.LogInformation("Database cambiato con successo a: {DatabaseName}", databaseName); + + // Carica le tabelle dal database selezionato + await LoadTablesFromConnectedDatabase(); + + isDatabaseConnected = true; + Logger.LogInformation("Connessione completata per database: {DatabaseName}", databaseName); + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nella connessione al database specifico: {DatabaseName}", databaseName); + databaseErrorMessage = $"Errore nella connessione al database {databaseName}: {ex.Message}"; + throw; + } + finally + { + isConnectingDatabase = false; + StateHasChanged(); + } + } + + /// + /// Seleziona una tabella database e carica il suo schema + /// + protected async Task SelectTable(string tableName) + { + selectedTable = tableName; + + // Clear custom query state when selecting a table + useCustomQuery = false; + customQuery = ""; + isQueryValid = false; + queryValidationMessage = ""; + queryPreviewData.Clear(); + queryColumns.Clear(); + showQueryPreview = false; + + // Clear mappings when changing table + ClearAllMappings(); + + // Reset key field logic + sourceKeyField = ""; + suggestedPrimaryKey = ""; + requiresManualKeySelection = false; + + // Carica i dettagli della tabella se non sono già stati caricati + if (!databaseTables.ContainsKey(tableName) && currentDatabaseManager != null) + { + try + { + var tableSchema = await currentDatabaseManager.GetTableSchemaAsync(tableName); + databaseTables[tableName] = tableSchema; + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nel caricamento dello schema della tabella {TableName}", tableName); + databaseErrorMessage = $"Errore nel caricamento della tabella: {ex.Message}"; + } + } + + // If it's a database source, try to detect the primary key + if (selectedSourceType == "database" && currentDatabaseManager != null) + { + try + { + var primaryKey = await currentDatabaseManager.GetPrimaryKeyFieldAsync(tableName); + if (!string.IsNullOrEmpty(primaryKey)) + { + suggestedPrimaryKey = primaryKey; + + // AUTO-SELECT: Imposta automaticamente il campo chiave se rilevato + sourceKeyField = primaryKey; + requiresManualKeySelection = false; + + Logger.LogInformation("Chiave primaria rilevata e auto-selezionata per la tabella {TableName}: {PrimaryKey}", tableName, primaryKey); + } + else + { + // No primary key found, require manual selection + requiresManualKeySelection = true; + sourceKeyField = ""; + Logger.LogInformation("Nessuna chiave primaria trovata per la tabella {TableName}, selezione manuale richiesta", tableName); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nel rilevamento della chiave primaria per la tabella {TableName}", tableName); + requiresManualKeySelection = true; + sourceKeyField = ""; + } + } + else + { + // For non-database sources, always require manual selection + requiresManualKeySelection = true; + sourceKeyField = ""; + } + + StateHasChanged(); + } + + /// + /// Ottiene le tabelle filtrate in base al termine di ricerca + /// + protected IEnumerable GetFilteredDatabaseTables() + { + if (string.IsNullOrEmpty(databaseSearchTerm)) + return availableTableNames; + + return availableTableNames.Where(table => + table.Contains(databaseSearchTerm, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Filtra le tabelle database in base al termine di ricerca + /// + protected async Task FilterDatabaseTables(ChangeEventArgs e) + { + databaseSearchTerm = e.Value?.ToString() ?? ""; + await InvokeAsync(StateHasChanged); + } + + /// + /// Pulisce il filtro di ricerca delle tabelle database + /// + protected async Task ClearDatabaseSearch() + { + databaseSearchTerm = ""; + await InvokeAsync(StateHasChanged); + } + + /// + /// Seleziona una colonna database per il mapping + /// + protected void SelectDbColumn(string columnName) + { + selectedDbColumn = columnName; + } + + /// + /// Verifica se il database è specificato nella connection string + /// + protected Task IsDatabaseSpecifiedInConnectionString(DatabaseCredential credential) + { + try + { + Logger.LogInformation("Verifica database specificato - Tipo: {DatabaseType}, DatabaseName: '{DatabaseName}', Connection: {ConnectionString}", + credential.DatabaseType, credential.DatabaseName, credential.ConnectionString?.Substring(0, Math.Min(100, credential.ConnectionString?.Length ?? 0))); + + // Prima verifica se c'è un database specificato nel campo DatabaseName della credenziale + if (!string.IsNullOrEmpty(credential.DatabaseName)) + { + Logger.LogInformation("Database specificato nel campo DatabaseName: '{DatabaseName}' - RESULT: TRUE", credential.DatabaseName); + return Task.FromResult(true); + } + + // Per SQL Server verifica se Initial Catalog o Database è specificato nella connection string + if (credential.DatabaseType == DatabaseType.SqlServer) + { + var connectionString = credential.ConnectionString ?? ""; + var hasInitialCatalog = connectionString.Contains("Initial Catalog=", StringComparison.OrdinalIgnoreCase); + var hasDatabase = connectionString.Contains("Database=", StringComparison.OrdinalIgnoreCase); + var result = hasInitialCatalog || hasDatabase; + + Logger.LogInformation("SQL Server - HasInitialCatalog: {HasInitialCatalog}, HasDatabase: {HasDatabase}, Result: {Result}", + hasInitialCatalog, hasDatabase, result); + + return Task.FromResult(result); + } + + // TODO: Implementare per altri tipi di database + // MySQL: Database= + // PostgreSQL: Database= + // Oracle: più complesso con SID/Service Name + + Logger.LogWarning("Verifica database specificato non implementata per tipo database: {DatabaseType}", credential.DatabaseType); + return Task.FromResult(true); // Default: assume database specificato per tipi non implementati + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nella verifica database specificato in connection string"); + return Task.FromResult(true); // Default: assume database specificato in caso di errore + } + } + + /// + /// Carica le tabelle dal database attualmente connesso + /// + protected async Task LoadTablesFromConnectedDatabase() + { + try + { + if (currentDatabaseManager == null) + { + databaseErrorMessage = "Database manager non disponibile"; + return; + } + + Logger.LogInformation("Caricando tabelle dal database connesso"); + var tableNames = await currentDatabaseManager.GetTableNamesAsync(); + availableTableNames = tableNames.ToList(); + + Logger.LogInformation("Caricate {Count} tabelle dal database", availableTableNames.Count); + + // Resetta i dettagli delle tabelle - verranno caricati solo quando selezionati + databaseTables.Clear(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nel caricamento delle tabelle dal database connesso"); + databaseErrorMessage = $"Errore nel caricamento tabelle: {ex.Message}"; + throw; + } + } + + /// + /// Carica la lista dei database disponibili dal server + /// + protected async Task LoadAvailableDatabases() + { + try + { + if (currentDatabaseManager == null) + { + databaseErrorMessage = "Database manager non disponibile"; + return; + } + + isLoadingDatabases = true; + Logger.LogInformation("Caricando database disponibili"); + + // Usa il metodo corretto dell'interfaccia IDatabaseManager + var allDatabases = await currentDatabaseManager.GetAvailableDatabasesAsync(); + Logger.LogInformation("Ottenuti {DatabaseCount} database dal server", allDatabases.Count); + + // Filtra i database di sistema + availableDatabases = FilterSystemDatabases(allDatabases).ToList(); + + Logger.LogInformation("Trovati {TotalDatabases} database, filtrati a {FilteredDatabases} (esclusi quelli di sistema)", + allDatabases.Count, availableDatabases.Count); + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nel caricamento dei database disponibili"); + databaseErrorMessage = $"Errore nel caricamento database: {ex.Message}"; + } + finally + { + isLoadingDatabases = false; + } + } + + /// + /// Tenta di auto-selezionare una chiave per le query custom + /// + protected void TryAutoSelectKeyForQuery(List columns) + { + try + { + // Reset stato chiave + sourceKeyField = ""; + suggestedPrimaryKey = ""; + requiresManualKeySelection = true; + + // Pattern comuni per identificare possibili chiavi primarie + var keyPatterns = new[] + { + "id", "ID", "Id", + "_id", "_ID", "_Id", + "key", "KEY", "Key", + "code", "CODE", "Code", "codice", "CODICE", "Codice", + "number", "NUMBER", "Number", "numero", "NUMERO", "Numero", + "index", "INDEX", "Index", "indice", "INDICE", "Indice" + }; + + // Cerca colonne che potrebbero essere chiavi primarie + string? detectedKey = null; + + // 1. Cerca esattamente "id", "ID", "Id" + detectedKey = columns.FirstOrDefault(c => + c.Equals("id", StringComparison.OrdinalIgnoreCase) || + c.Equals("ID", StringComparison.Ordinal) || + c.Equals("Id", StringComparison.Ordinal)); + + // 2. Se non trovato, cerca colonne che terminano con "id", "ID", "Id" + if (detectedKey == null) + { + detectedKey = columns.FirstOrDefault(c => + c.EndsWith("id", StringComparison.OrdinalIgnoreCase) || + c.EndsWith("ID", StringComparison.Ordinal) || + c.EndsWith("Id", StringComparison.Ordinal)); + } + + // 3. Se non trovato, cerca colonne che contengono pattern di chiave + if (detectedKey == null) + { + foreach (var pattern in keyPatterns) + { + detectedKey = columns.FirstOrDefault(c => + c.Contains(pattern, StringComparison.OrdinalIgnoreCase)); + if (detectedKey != null) break; + } + } + + // 4. Auto-seleziona se trovato + if (!string.IsNullOrEmpty(detectedKey)) + { + sourceKeyField = detectedKey; + suggestedPrimaryKey = detectedKey; + requiresManualKeySelection = false; + + Logger.LogInformation("Chiave auto-selezionata per query custom: {KeyField}", detectedKey); + } + else + { + Logger.LogInformation("Nessuna chiave rilevabile automaticamente per query custom, selezione manuale richiesta"); + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nell'auto-selezione della chiave per query custom"); + sourceKeyField = ""; + suggestedPrimaryKey = ""; + requiresManualKeySelection = true; + } + } + + /// + /// Valida una custom query SQL + /// + protected async Task ValidateCustomQuery() + { + if (string.IsNullOrWhiteSpace(customQuery) || currentDatabaseManager == null) + { + isQueryValid = false; + queryValidationMessage = "Query vuota o manager database non disponibile"; + return; + } + + isValidatingQuery = true; + + try + { + // Controllo di sicurezza: verifica che sia una SELECT + if (!IsSelectQuery(customQuery)) + { + isQueryValid = false; + queryValidationMessage = "Solo query SELECT sono permesse per sicurezza"; + return; + } + + var cleanQuery = CleanQuery(customQuery); + + // Trova la credenziale per determinare il tipo di database + var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); + if (credential == null) + { + isQueryValid = false; + queryValidationMessage = "Credenziale database non trovata"; + return; + } + + // Crea una query di test con sintassi appropriata per il tipo di database + var testQuery = CreateLimitedQuery(cleanQuery, credential.DatabaseType, 1); + + Logger.LogInformation("Validando query: {Query}", testQuery); + + // Prova a eseguire la query per validarla + var testResults = await currentDatabaseManager.ExecuteRawQueryAsync(testQuery); + + if (testResults != null && testResults.Any()) + { + var firstRow = testResults.First(); + queryColumns = firstRow.Keys.ToList(); + isQueryValid = true; + queryValidationMessage = $"Query valida - {queryColumns.Count} colonne rilevate"; + + // Clear mappings quando cambia la query + ClearAllMappings(); + + // AUTO-SELECT della chiave per query custom + TryAutoSelectKeyForQuery(queryColumns); + + Logger.LogInformation("Query validata con successo: {ColumnCount} colonne", queryColumns.Count); + } + else + { + isQueryValid = false; + queryValidationMessage = "La query non ha restituito risultati o colonne"; + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nella validazione della query"); + isQueryValid = false; + queryValidationMessage = $"Errore nella validazione: {ex.Message}"; + } + finally + { + isValidatingQuery = false; + StateHasChanged(); + } + } + + /// + /// Carica un'anteprima dei dati della query + /// + protected async Task LoadQueryPreview() + { + if (!isQueryValid || string.IsNullOrWhiteSpace(customQuery) || currentDatabaseManager == null) + return; + + isLoadingPreview = true; + + try + { + var cleanQuery = CleanQuery(customQuery); + + // Trova la credenziale per determinare il tipo di database + var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); + if (credential == null) + { + queryValidationMessage = "Credenziale database non trovata"; + return; + } + + // Crea una query di anteprima con sintassi appropriata per il tipo di database + var previewQuery = CreateLimitedQuery(cleanQuery, credential.DatabaseType, 10); + + Logger.LogInformation("Caricando anteprima con query: {Query}", previewQuery); + + var previewResults = await currentDatabaseManager.ExecuteRawQueryAsync(previewQuery); + queryPreviewData = previewResults.ToList(); + showQueryPreview = true; + + Logger.LogInformation("Caricata anteprima query con {RecordCount} record", queryPreviewData.Count); + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nel caricamento anteprima query"); + queryValidationMessage = $"Errore anteprima: {ex.Message}"; + } + finally + { + isLoadingPreview = false; + StateHasChanged(); + } + } + + /// + /// Crea una query limitata in base al tipo di database + /// + protected string CreateLimitedQuery(string baseQuery, DatabaseType databaseType, int limit) + { + return databaseType switch + { + DatabaseType.SqlServer => $"SELECT TOP {limit} * FROM ({baseQuery}) AS subquery", + DatabaseType.Oracle => $"SELECT * FROM ({baseQuery}) WHERE ROWNUM <= {limit}", + DatabaseType.MySql => $"{baseQuery} LIMIT {limit}", + DatabaseType.PostgreSql => $"{baseQuery} LIMIT {limit}", + DatabaseType.Sqlite => $"{baseQuery} LIMIT {limit}", + DatabaseType.DB2 => $"SELECT * FROM ({baseQuery}) FETCH FIRST {limit} ROWS ONLY", + DatabaseType.SapHana => $"{baseQuery} LIMIT {limit}", + _ => $"{baseQuery} LIMIT {limit}" // Default a LIMIT per database non riconosciuti + }; + } + + /// + /// Nasconde l'anteprima della query + /// + protected void HideQueryPreview() + { + showQueryPreview = false; + StateHasChanged(); + } + + /// + /// Tenta di caricare gli schemi con query diretta + /// + protected async Task TryLoadSchemasWithDirectQuery() + { + if (currentDatabaseManager == null) + return; + + try + { + // Query diverse per ogni tipo di database - focalizzate sui database/cataloghi + var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); + if (credential == null) return; + + string? schemaQuery = credential.DatabaseType switch + { + DatabaseType.SqlServer => "SELECT name FROM sys.databases WHERE name NOT IN ('master', 'tempdb', 'model', 'msdb') AND state = 0", + DatabaseType.PostgreSql => "SELECT datname FROM pg_database WHERE datistemplate = false AND datname NOT IN ('postgres', 'template0', 'template1')", + DatabaseType.MySql => "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')", + DatabaseType.Oracle => "SELECT DISTINCT OWNER FROM ALL_TABLES WHERE OWNER NOT IN ('SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN', 'ANONYMOUS', 'CTXSYS', 'EXFSYS', 'LBACSYS', 'MDSYS', 'MGMT_VIEW', 'OLAPSYS', 'OWBSYS', 'ORDDATA', 'ORDSYS', 'SI_INFORMTN_SCHEMA', 'WK_TEST', 'WKPROXY', 'WMSYS', 'XDB', 'APEX_040000', 'APEX_PUBLIC_USER', 'DIP', 'FLOWS_FILES', 'HR', 'IX', 'OE', 'PM', 'SCOTT', 'SH', 'BI')", + _ => null + }; + + if (!string.IsNullOrEmpty(schemaQuery)) + { + Logger.LogInformation("Eseguendo query per database/schemi: {Query}", schemaQuery); + var results = await currentDatabaseManager.ExecuteRawQueryAsync(schemaQuery); + + if (results != null && results.Any()) + { + var schemas = results.Select(row => + { + var firstValue = row.Values.FirstOrDefault(); + return firstValue?.ToString() ?? ""; + }) + .Where(schema => !string.IsNullOrEmpty(schema)) + .OrderBy(schema => schema) + .ToList(); + + if (schemas.Any()) + { + availableSchemas.AddRange(schemas); + Logger.LogInformation("Caricati {SchemaCount} database/schemi via query diretta per {DatabaseType}: {Schemas}", + schemas.Count, credential.DatabaseType, string.Join(", ", schemas)); + } + } + } + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Errore nel caricamento database/schemi via query diretta"); + } + } + + /// + /// Gestisce la selezione database quando la discovery restituisce dizionario vuoto + /// + protected async Task HandleDatabaseSelectionRequired() + { + isLoadingDatabases = true; + showDatabaseSelectionModal = true; + availableDatabases.Clear(); + selectedDatabase = ""; + try + { + if (currentDatabaseManager != null) + { + var dbs = await currentDatabaseManager.GetAvailableDatabasesAsync(); + availableDatabases = dbs ?? new List(); + } + } + catch (Exception ex) + { + databaseErrorMessage = $"Errore nel caricamento dei database: {ex.Message}"; + } + finally + { + isLoadingDatabases = false; + } + } + + /// + /// Gestisce la selezione di un database specifico + /// + protected async Task OnDatabaseSelected() + { + try + { + if (string.IsNullOrEmpty(selectedDatabase)) + { + databaseErrorMessage = "Nessun database selezionato"; + return; + } + + showDatabaseSelectionModal = false; + + Logger.LogInformation("Database selezionato: {DatabaseName}. Riconnessione in corso...", selectedDatabase); + + // Riconnessione al database selezionato + await ConnectToDatabaseWithSpecificDatabase(selectedDatabase); + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nella selezione del database: {DatabaseName}", selectedDatabase); + databaseErrorMessage = $"Errore nella connessione al database {selectedDatabase}: {ex.Message}"; + } + } + + private IEnumerable FilterSystemDatabases(List allDatabases) + { + // Trova la credenziale per determinare il tipo di database + var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); + if (credential == null) + { + Logger.LogWarning("Credenziale non trovata per filtraggio database di sistema"); + return allDatabases; // Restituisce tutti se non riesce a determinare il tipo + } + + var databaseType = credential.DatabaseType; + + // Filtri per SQL Server + if (databaseType == DatabaseType.SqlServer) + { + var sqlServerSystemDatabases = new HashSet(StringComparer.OrdinalIgnoreCase) + { + "master", "tempdb", "model", "msdb", "Resource", "mssqlsystemresource", + "ReportServer", "ReportServerTempDB", "SSISDB", "distribution" + }; + + return allDatabases.Where(db => !sqlServerSystemDatabases.Contains(db)); + } + + // TODO: Implementare filtri per altri tipi di database + if (databaseType == DatabaseType.MySql) + { + Logger.LogInformation("Filtro database di sistema MySQL - DA IMPLEMENTARE"); + return allDatabases; // Per ora restituisce tutti + } + + if (databaseType == DatabaseType.PostgreSql) + { + Logger.LogInformation("Filtro database di sistema PostgreSQL - DA IMPLEMENTARE"); + return allDatabases; // Per ora restituisce tutti + } + + if (databaseType == DatabaseType.Oracle) + { + Logger.LogInformation("Filtro database di sistema Oracle - DA IMPLEMENTARE"); + return allDatabases; // Per ora restituisce tutti + } + + Logger.LogWarning("Tipo database non riconosciuto per filtraggio: {DatabaseType}", databaseType); + return allDatabases; // Restituisce tutti per tipi non riconosciuti + } + + private void CancelDatabaseSelection() + { + showDatabaseSelectionModal = false; + selectedDatabase = ""; + databaseErrorMessage = "Selezione database annullata"; + Logger.LogInformation("Selezione database annullata dall'utente"); + } + + // ===== FINE METODI DATABASE ===== +} diff --git a/Data_Coupler/Extensions/DataCoupler/RESTMethod.cs b/Data_Coupler/Extensions/DataCoupler/RESTMethod.cs new file mode 100644 index 0000000..1985fc1 --- /dev/null +++ b/Data_Coupler/Extensions/DataCoupler/RESTMethod.cs @@ -0,0 +1,237 @@ +using System; +using System.ComponentModel; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.JSInterop; +using Microsoft.Extensions.Logging; +using CredentialManager.Models; +using DataConnection.REST.Interfaces; +using DataConnection.REST.Models; +using DataConnection.CredentialManagement.Interfaces; +using DataConnection.Interfaces; +using Data_Coupler.Services; +using Data_Coupler.Models; + +namespace Data_Coupler.Pages; + +public partial class DataCoupler : ComponentBase +{ + // ===== PROPRIETÀ REST ===== + + // Credenziali REST + protected List restApiCredentials = new(); + protected string selectedRestCredential = ""; + + // Stato connessioni REST + protected bool isConnectingRest = false; + protected bool isRestConnected = false; + protected string restErrorMessage = ""; + + // REST discovery + protected List restEntities = new(); + protected RestEntitySummary? selectedRestEntity = null; + protected RestEntityInfo? restEntityDetails = null; + protected string restSearchTerm = ""; + + // Proprietà di mapping REST + protected string selectedRestProperty = ""; + + // Servizi REST + protected IRestMetadataDiscovery? currentRestDiscovery = null; + protected IRestServiceClient? currentRestClient = null; + + // ===== METODI REST ===== + + /// + /// Carica le credenziali REST API + /// + protected async Task LoadRestCredentials() + { + try + { + restApiCredentials = await CredentialService.GetAllRestApiCredentialsAsync(); + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nel caricamento delle credenziali REST"); + throw; + } + } + + /// + /// Gestisce il cambio di credenziale REST + /// + private void OnRestCredentialChanged(ChangeEventArgs e) + { + var newCredential = e.Value?.ToString() ?? ""; + + // Clear the cache if we're switching to a different credential + if (!string.IsNullOrEmpty(selectedRestCredential) && selectedRestCredential != newCredential) + { + ConnectionFactory.ClearRestClientCache(selectedRestCredential); + Logger.LogInformation("Cleared REST client cache for credential: {CredentialName}", selectedRestCredential); + } + + selectedRestCredential = newCredential; + ResetRestState(); + } + + /// + /// Resetta lo stato REST + /// + protected void ResetRestState() + { + isRestConnected = false; + restEntities.Clear(); + selectedRestEntity = null; + restEntityDetails = null; + restSearchTerm = ""; + restErrorMessage = ""; + currentRestDiscovery = null; + currentRestClient = null; + + // Clear mappings when resetting REST state - handled by main class + // ClearAllMappings(); + } + + /// + /// Connette al servizio REST API + /// + protected async Task ConnectToRestApi() + { + if (string.IsNullOrEmpty(selectedRestCredential)) + return; + + isConnectingRest = true; + restErrorMessage = ""; + + try + { + // Trova la credenziale + var credential = restApiCredentials.FirstOrDefault(c => c.Name == selectedRestCredential); + if (credential == null) + { + restErrorMessage = "Credenziale REST API non trovata"; + return; + } + + // Test della connessione + var (success, message) = await CredentialService.TestRestApiConnectionAsync(credential.Name); + if (!success) + { + restErrorMessage = $"Connessione fallita: {message}"; + return; + } + + // Crea i client REST usando il factory con le credenziali complete + currentRestClient = await ConnectionFactory.CreateRestServiceClientAsync(selectedRestCredential); + currentRestDiscovery = await ConnectionFactory.CreateRestMetadataDiscoveryAsync(selectedRestCredential); + + Logger.LogInformation("Iniziando autenticazione per il servizio REST {ServiceType} con credenziale: {CredentialName}", credential.ServiceType, selectedRestCredential); + + // Autenticazione prima del discovery + var authResult = await currentRestClient.AuthenticateAsync(); + if (!authResult) + { + Logger.LogWarning("Autenticazione fallita per il servizio REST {ServiceType}", credential.ServiceType); + restErrorMessage = "Autenticazione fallita per il servizio REST"; + return; + } + + Logger.LogInformation("Autenticazione completata con successo per il servizio REST {ServiceType}", credential.ServiceType); + + // Discovery delle entità disponibili + Logger.LogInformation("Iniziando discovery delle entità REST..."); + var entities = await currentRestDiscovery.DiscoverEntitiesAsync(); + + restEntities = entities.Select(e => new RestEntitySummary + { + Name = e.Name, + Label = e.Name, // Use Name as Label since RestEntityInfo doesn't have Label + Description = "" // RestEntityInfo doesn't have Description + }).ToList(); + isRestConnected = true; + + Logger.LogInformation("Discovery completato: trovate {EntityCount} entità REST", restEntities.Count); + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nella connessione al servizio REST {ServiceType}", selectedRestCredential); + restErrorMessage = $"Errore nella connessione: {ex.Message}"; + } + finally + { + isConnectingRest = false; + StateHasChanged(); + } + } + + /// + /// Seleziona un'entità REST + /// + protected async Task SelectRestEntity(RestEntitySummary entity) + { + selectedRestEntity = entity; + + // Clear mappings when changing entity - handled by main class + // ClearAllMappings(); + + try + { + if (currentRestDiscovery != null) + { + // Discovery dei dettagli dell'entità + restEntityDetails = await currentRestDiscovery.DiscoverEntityDetailsAsync(entity.Name); + } + else + { + restErrorMessage = "Servizio di discovery REST non disponibile"; + return; + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Errore nel caricamento dettagli entità {EntityName}", entity.Name); + restErrorMessage = $"Errore nel caricamento dettagli entità: {ex.Message}"; + } + } + + /// + /// Filtra le entità REST in base al termine di ricerca + /// + private IEnumerable GetFilteredRestEntities() + { + if (string.IsNullOrEmpty(restSearchTerm)) + return restEntities; + + return restEntities.Where(entity => + entity.Name.Contains(restSearchTerm, StringComparison.OrdinalIgnoreCase) || + (!string.IsNullOrEmpty(entity.Label) && entity.Label.Contains(restSearchTerm, StringComparison.OrdinalIgnoreCase))); + } + + /// + /// Applica il filtro alle entità REST + /// + private async Task FilterRestEntities(ChangeEventArgs e) + { + restSearchTerm = e.Value?.ToString() ?? ""; + await InvokeAsync(StateHasChanged); + } + + /// + /// Pulisce la ricerca delle entità REST + /// + private async Task ClearRestSearch() + { + restSearchTerm = ""; + await InvokeAsync(StateHasChanged); + } + + /// + /// Seleziona una proprietà REST per il mapping + /// + protected void SelectRestProperty(string propertyName) + { + selectedRestProperty = propertyName; + } +} diff --git a/Data_Coupler/Models/DataCouplerModels.cs b/Data_Coupler/Models/DataCouplerModels.cs new file mode 100644 index 0000000..1027b0e --- /dev/null +++ b/Data_Coupler/Models/DataCouplerModels.cs @@ -0,0 +1,13 @@ +using System; + +namespace Data_Coupler.Models; + +// Classe per i risultati del trasferimento +public class TransferResult +{ + public int RecordNumber { get; set; } + public string Status { get; set; } = ""; // "success", "error", "updated", "duplicate" + public string Message { get; set; } = ""; + public string? EntityId { get; set; } + public Dictionary RecordData { get; set; } = new(); +} \ No newline at end of file diff --git a/Data_Coupler/Pages/DataCoupler.razor.cs b/Data_Coupler/Pages/DataCoupler.razor.cs index a1b34b3..831fa10 100644 --- a/Data_Coupler/Pages/DataCoupler.razor.cs +++ b/Data_Coupler/Pages/DataCoupler.razor.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Components.Forms; using Microsoft.JSInterop; using Microsoft.Extensions.Logging; using Data_Coupler.Services; +using Data_Coupler.Models; namespace Data_Coupler.Pages; @@ -25,65 +26,12 @@ public partial class DataCoupler : ComponentBase [Inject] public ILogger Logger { get; set; } = default!; [Inject] public IDataCouplerProfileService ProfileService { get; set; } = default!; - // Classe per i risultati del trasferimento - public class TransferResult - { - public int RecordNumber { get; set; } - public string Status { get; set; } = ""; // "success", "error", "updated", "duplicate" - public string Message { get; set; } = ""; - public string? EntityId { get; set; } - public Dictionary RecordData { get; set; } = new(); - } - - // Stato delle credenziali - private List databaseCredentials = new(); - private List restApiCredentials = new(); + // Selezione tipo fonte private string selectedSourceType = ""; - // Credenziali selezionate - private string selectedDatabaseCredential = ""; - private string selectedRestCredential = ""; - - // Stato connessioni - private bool isConnectingDatabase = false; - private bool isConnectingRest = false; - private bool isDatabaseConnected = false; - private bool isRestConnected = false; - - // Messaggi di errore - private string databaseErrorMessage = ""; - private string restErrorMessage = ""; - - // Database discovery - private List availableTableNames = new(); // Solo nomi delle tabelle - private Dictionary> databaseTables = new(); // Schema dettagliato per tabelle caricate - private string selectedTable = ""; - private string databaseSearchTerm = ""; - - // Database selection - per gestire la selezione del database quando non specificato nella connection string - private List availableDatabases = new(); - private string selectedDatabase = ""; - private bool showDatabaseSelectionModal = false; - private bool isLoadingDatabases = false; - - // Database selection (schemas only) - private List availableSchemas = new(); - private string selectedSchema = ""; - private bool showSchemaSelectionModal = false; - private bool isLoadingSchemas = false; - - // Custom query functionality - private bool useCustomQuery = false; - private string customQuery = ""; - private bool isValidatingQuery = false; - private bool isQueryValid = false; - private string queryValidationMessage = ""; - private List> queryPreviewData = new(); - private List queryColumns = new(); - private bool showQueryPreview = false; - private bool isLoadingPreview = false; // File handling + // File handling private string selectedFileName = ""; private bool isProcessingFile = false; private string fileErrorMessage = ""; @@ -97,20 +45,13 @@ public partial class DataCoupler : ComponentBase private int GetTotalPages(string sheetName) => fileData.ContainsKey(sheetName) ? (int)Math.Ceiling((double)fileData[sheetName].Count / pageSize) : 0; - // REST discovery - private List restEntities = new(); - private RestEntitySummary? selectedRestEntity = null; - private RestEntityInfo? restEntityDetails = null; - private string restSearchTerm = ""; // Mapping campi private Dictionary fieldMappings = new(); // DbColumn -> RestProperty private HashSet keyFields = new(); // REST properties marked as keys private string selectedDbColumn = ""; - private string selectedRestProperty = ""; // Gestione chiavi sorgente e associazioni private string sourceKeyField = ""; // Campo che identifica univocamente il record sorgente - private string suggestedPrimaryKey = ""; // Campo PK suggerito per database private bool requiresManualKeySelection = false; // Flag per indicare se è richiesta selezione manuale private bool useRecordAssociations = true; // Se utilizzare il sistema di associazioni @@ -121,11 +62,6 @@ public partial class DataCoupler : ComponentBase private List transferResults = new(); private bool showDetailedResults = false; - // Servizi - private IDatabaseManager? currentDatabaseManager = null; - private IRestMetadataDiscovery? currentRestDiscovery = null; - private IRestServiceClient? currentRestClient = null; - // Gestione Profili private List availableProfiles = new(); private bool isLoadingProfiles = false; @@ -140,7 +76,7 @@ public partial class DataCoupler : ComponentBase try { databaseCredentials = await CredentialService.GetAllDatabaseCredentialsAsync(); - restApiCredentials = await CredentialService.GetAllRestApiCredentialsAsync(); + await LoadRestCredentials(); // Carica le credenziali REST dalla classe parziale // Carica anche i profili disponibili await LoadProfiles(); } @@ -1085,372 +1021,14 @@ public partial class DataCoupler : ComponentBase StateHasChanged(); } } - private void OnDatabaseCredentialChanged(ChangeEventArgs e) - { - selectedDatabaseCredential = e.Value?.ToString() ?? ""; - ResetDatabaseState(); - } - private void OnRestCredentialChanged(ChangeEventArgs e) - { - var newCredential = e.Value?.ToString() ?? ""; - // Clear the cache if we're switching to a different credential - if (!string.IsNullOrEmpty(selectedRestCredential) && selectedRestCredential != newCredential) - { - ConnectionFactory.ClearRestClientCache(selectedRestCredential); - Logger.LogInformation("Cleared REST client cache for credential: {CredentialName}", selectedRestCredential); - } - selectedRestCredential = newCredential; - ResetRestState(); - } - private void ResetDatabaseState() - { - isDatabaseConnected = false; - databaseTables.Clear(); - selectedTable = ""; - databaseSearchTerm = ""; - databaseErrorMessage = ""; - // Reset database selection - availableDatabases.Clear(); - selectedDatabase = ""; - showDatabaseSelectionModal = false; - isLoadingDatabases = false; - // Reset custom query state - useCustomQuery = false; - customQuery = ""; - isValidatingQuery = false; - isQueryValid = false; - queryValidationMessage = ""; - queryPreviewData.Clear(); - queryColumns.Clear(); - showQueryPreview = false; - isLoadingPreview = false; - currentDatabaseManager?.Dispose(); - currentDatabaseManager = null; - // Clear mappings when resetting database state - ClearAllMappings(); - } - private void ResetRestState() - { - isRestConnected = false; - restEntities.Clear(); - selectedRestEntity = null; - restEntityDetails = null; - restSearchTerm = ""; - restErrorMessage = ""; - currentRestDiscovery = null; - currentRestClient = null; - // Clear mappings when resetting REST state - ClearAllMappings(); - } - private async Task ConnectToDatabase() - { - if (string.IsNullOrEmpty(selectedDatabaseCredential)) - return; - isConnectingDatabase = true; - databaseErrorMessage = ""; - - try - { - // Trova la credenziale - var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); - if (credential == null) - { - databaseErrorMessage = "Credenziale database non trovata"; - return; - } - - // Test della connessione - var (success, message) = await CredentialService.TestDatabaseConnectionAsync(credential.Name); - if (!success) - { - databaseErrorMessage = $"Connessione fallita: {message}"; - return; - } - - // Crea il database manager usando il factory con le credenziali complete - Logger.LogInformation("Creando database manager per credenziale: {CredentialName}", selectedDatabaseCredential); - currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential); - Logger.LogInformation("Database manager creato con successo"); - - // Verifica se il database è specificato nella connection string - bool isDatabaseSpecified = await IsDatabaseSpecifiedInConnectionString(credential); - - if (isDatabaseSpecified) - { - Logger.LogInformation("Database specificato nella connection string. Procedendo con discovery tabelle."); - try - { - await LoadTablesFromConnectedDatabase(); - isDatabaseConnected = true; - Logger.LogInformation("Tabelle caricate con successo, database connesso"); - return; // Importante: usciamo qui se tutto va bene - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nel caricamento tabelle dal database specificato"); - databaseErrorMessage = $"Errore nel caricamento tabelle: {ex.Message}"; - return; - } - } - else - { - Logger.LogInformation("Database non specificato nella connection string. Caricando database disponibili."); - await LoadAvailableDatabases(); - - if (availableDatabases.Any()) - { - Logger.LogInformation("Trovati {DatabaseCount} database disponibili", availableDatabases.Count); - showDatabaseSelectionModal = true; - StateHasChanged(); - return; // Non procediamo fino alla selezione del database - } - else - { - databaseErrorMessage = "Nessun database disponibile trovato"; - return; - } - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nella connessione al database"); - databaseErrorMessage = $"Errore: {ex.Message}"; - } - finally - { - isConnectingDatabase = false; - StateHasChanged(); - } - } - private async Task ConnectToRestApi() - { - if (string.IsNullOrEmpty(selectedRestCredential)) - return; - - isConnectingRest = true; - restErrorMessage = ""; - - try - { - // Trova la credenziale - var credential = restApiCredentials.FirstOrDefault(c => c.Name == selectedRestCredential); - if (credential == null) - { - restErrorMessage = "Credenziale REST API non trovata"; - return; - } - - // Test della connessione - var (success, message) = await CredentialService.TestRestApiConnectionAsync(credential.Name); - if (!success) - { - restErrorMessage = $"Connessione fallita: {message}"; - return; - } // Crea i client REST usando il factory con le credenziali complete - currentRestClient = await ConnectionFactory.CreateRestServiceClientAsync(selectedRestCredential); - currentRestDiscovery = await ConnectionFactory.CreateRestMetadataDiscoveryAsync(selectedRestCredential); Logger.LogInformation("Iniziando autenticazione per il servizio REST {ServiceType} con credenziale: {CredentialName}", credential.ServiceType, selectedRestCredential); - - // Autenticazione prima del discovery - var authResult = await currentRestClient.AuthenticateAsync(); - if (!authResult) - { - Logger.LogWarning("Autenticazione fallita per il servizio REST {ServiceType}", credential.ServiceType); - restErrorMessage = "Autenticazione fallita per il servizio REST"; - return; - } - - Logger.LogInformation("Autenticazione completata. Iniziando discovery delle entità REST per {ServiceType}", credential.ServiceType); - - // Discovery delle entità disponibili - restEntities = await currentRestDiscovery.DiscoverEntitySummariesAsync(); - - Logger.LogInformation("Discovery completato. Trovate {Count} entità", restEntities?.Count ?? 0); - - if (restEntities == null || !restEntities.Any()) - { - Logger.LogWarning("Nessuna entità trovata dal servizio REST"); - restErrorMessage = "Nessuna entità disponibile dal servizio REST"; - return; - } - - isRestConnected = true; - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nella connessione al servizio REST"); - restErrorMessage = $"Errore: {ex.Message}"; - } - finally - { - isConnectingRest = false; - } - } - private async Task SelectTable(string tableName) - { - selectedTable = tableName; - - // Clear custom query state when selecting a table - useCustomQuery = false; - customQuery = ""; - isQueryValid = false; - queryValidationMessage = ""; - queryPreviewData.Clear(); - queryColumns.Clear(); - showQueryPreview = false; - - // Clear mappings when changing table - ClearAllMappings(); - - // Reset key field logic - sourceKeyField = ""; - suggestedPrimaryKey = ""; - requiresManualKeySelection = false; - - // Carica i dettagli della tabella se non sono già stati caricati - if (!databaseTables.ContainsKey(tableName) && currentDatabaseManager != null) - { - try - { - var tableSchema = await currentDatabaseManager.GetTableSchemaAsync(tableName); - databaseTables[tableName] = tableSchema; - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nel caricamento dello schema della tabella {TableName}", tableName); - databaseErrorMessage = $"Errore nel caricamento della tabella: {ex.Message}"; - } - } - - // If it's a database source, try to detect the primary key - if (selectedSourceType == "database" && currentDatabaseManager != null) - { - try - { - var primaryKey = await currentDatabaseManager.GetPrimaryKeyFieldAsync(tableName); - if (!string.IsNullOrEmpty(primaryKey)) - { - suggestedPrimaryKey = primaryKey; - - // AUTO-SELECT: Imposta automaticamente il campo chiave se rilevato - sourceKeyField = primaryKey; - requiresManualKeySelection = false; - - Logger.LogInformation("Chiave primaria rilevata e auto-selezionata per la tabella {TableName}: {PrimaryKey}", tableName, primaryKey); - } - else - { - // No primary key found, require manual selection - requiresManualKeySelection = true; - sourceKeyField = ""; - Logger.LogInformation("Nessuna chiave primaria trovata per la tabella {TableName}, selezione manuale richiesta", tableName); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nel rilevamento della chiave primaria per la tabella {TableName}", tableName); - requiresManualKeySelection = true; - sourceKeyField = ""; - } - } - else - { - // For non-database sources, always require manual selection - requiresManualKeySelection = true; - sourceKeyField = ""; - } - - StateHasChanged(); - } - - private async Task SelectRestEntity(RestEntitySummary entity) - { - selectedRestEntity = entity; - - // Clear mappings when changing entity - ClearAllMappings(); - - try - { - if (currentRestDiscovery != null) - { - // Discovery dei dettagli dell'entità - restEntityDetails = await currentRestDiscovery.DiscoverEntityDetailsAsync(entity.Name); - } - else - { - restErrorMessage = "Servizio di discovery REST non disponibile"; - return; - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nel caricamento dettagli entità {EntityName}", entity.Name); - restErrorMessage = $"Errore nel caricamento dettagli entità: {ex.Message}"; - } - } - - // Metodi per la ricerca e il filtraggio - private IEnumerable GetFilteredDatabaseTables() - { - if (string.IsNullOrEmpty(databaseSearchTerm)) - return availableTableNames; - - return availableTableNames.Where(table => - table.Contains(databaseSearchTerm, StringComparison.OrdinalIgnoreCase)); - } - - private IEnumerable GetFilteredRestEntities() - { - if (string.IsNullOrEmpty(restSearchTerm)) - return restEntities; - - return restEntities.Where(entity => - entity.Name.Contains(restSearchTerm, StringComparison.OrdinalIgnoreCase) || - (!string.IsNullOrEmpty(entity.Label) && entity.Label.Contains(restSearchTerm, StringComparison.OrdinalIgnoreCase))); - } - - private async Task FilterDatabaseTables(ChangeEventArgs e) - { - databaseSearchTerm = e.Value?.ToString() ?? ""; - await InvokeAsync(StateHasChanged); - } - - private async Task FilterRestEntities(ChangeEventArgs e) - { - restSearchTerm = e.Value?.ToString() ?? ""; - await InvokeAsync(StateHasChanged); - } - - private async Task ClearDatabaseSearch() - { - databaseSearchTerm = ""; - await InvokeAsync(StateHasChanged); - } - - private async Task ClearRestSearch() - { - restSearchTerm = ""; - await InvokeAsync(StateHasChanged); - } - - // Metodi per il mapping dei campi - private void SelectDbColumn(string columnName) - { - selectedDbColumn = columnName; - } - - private void SelectRestProperty(string propertyName) - { - selectedRestProperty = propertyName; - } private void CreateMapping() { @@ -2483,154 +2061,13 @@ public partial class DataCoupler : ComponentBase StateHasChanged(); } - /// - /// Valida la query SQL custom - /// - private async Task ValidateCustomQuery() - { - if (string.IsNullOrWhiteSpace(customQuery) || currentDatabaseManager == null) - { - isQueryValid = false; - queryValidationMessage = "Query vuota o manager database non disponibile"; - return; - } - isValidatingQuery = true; - try - { - // Controllo di sicurezza: verifica che sia una SELECT - if (!IsSelectQuery(customQuery)) - { - isQueryValid = false; - queryValidationMessage = "Solo query SELECT sono permesse per sicurezza"; - return; - } - var cleanQuery = CleanQuery(customQuery); - // Trova la credenziale per determinare il tipo di database - var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); - if (credential == null) - { - isQueryValid = false; - queryValidationMessage = "Credenziale database non trovata"; - return; - } - // Crea una query di test con sintassi appropriata per il tipo di database - var testQuery = CreateLimitedQuery(cleanQuery, credential.DatabaseType, 1); - Logger.LogInformation("Validando query: {Query}", testQuery); - // Prova a eseguire la query per validarla - var testResults = await currentDatabaseManager.ExecuteRawQueryAsync(testQuery); - - if (testResults != null && testResults.Any()) - { - var firstRow = testResults.First(); - queryColumns = firstRow.Keys.ToList(); - isQueryValid = true; - queryValidationMessage = $"Query valida - {queryColumns.Count} colonne rilevate"; - - // Clear mappings quando cambia la query - ClearAllMappings(); - - // AUTO-SELECT della chiave per query custom - TryAutoSelectKeyForQuery(queryColumns); - - Logger.LogInformation("Query validata con successo: {ColumnCount} colonne", queryColumns.Count); - } - else - { - isQueryValid = false; - queryValidationMessage = "La query non ha restituito risultati o colonne"; - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nella validazione della query"); - isQueryValid = false; - queryValidationMessage = $"Errore nella validazione: {ex.Message}"; - } - finally - { - isValidatingQuery = false; - StateHasChanged(); - } - } - - /// - /// Carica un'anteprima dei dati della query - /// - private async Task LoadQueryPreview() - { - if (!isQueryValid || string.IsNullOrWhiteSpace(customQuery) || currentDatabaseManager == null) - return; - - isLoadingPreview = true; - - try - { - var cleanQuery = CleanQuery(customQuery); - - // Trova la credenziale per determinare il tipo di database - var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); - if (credential == null) - { - queryValidationMessage = "Credenziale database non trovata"; - return; - } - - // Crea una query di anteprima con sintassi appropriata per il tipo di database - var previewQuery = CreateLimitedQuery(cleanQuery, credential.DatabaseType, 10); - - Logger.LogInformation("Caricando anteprima con query: {Query}", previewQuery); - - var previewResults = await currentDatabaseManager.ExecuteRawQueryAsync(previewQuery); - queryPreviewData = previewResults.ToList(); - showQueryPreview = true; - - Logger.LogInformation("Caricata anteprima query con {RecordCount} record", queryPreviewData.Count); - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nel caricamento anteprima query"); - queryValidationMessage = $"Errore anteprima: {ex.Message}"; - } - finally - { - isLoadingPreview = false; - StateHasChanged(); - } - } - - /// - /// Crea una query limitata in base al tipo di database - /// - private string CreateLimitedQuery(string baseQuery, DatabaseType databaseType, int limit) - { - return databaseType switch - { - DatabaseType.SqlServer => $"SELECT TOP {limit} * FROM ({baseQuery}) AS subquery", - DatabaseType.Oracle => $"SELECT * FROM ({baseQuery}) WHERE ROWNUM <= {limit}", - DatabaseType.MySql => $"{baseQuery} LIMIT {limit}", - DatabaseType.PostgreSql => $"{baseQuery} LIMIT {limit}", - DatabaseType.Sqlite => $"{baseQuery} LIMIT {limit}", - DatabaseType.DB2 => $"SELECT * FROM ({baseQuery}) FETCH FIRST {limit} ROWS ONLY", - DatabaseType.SapHana => $"{baseQuery} LIMIT {limit}", - _ => $"{baseQuery} LIMIT {limit}" // Default a LIMIT per database non riconosciuti - }; - } - - /// - /// Nasconde l'anteprima della query - /// - private void HideQueryPreview() - { - showQueryPreview = false; - StateHasChanged(); - } /// /// Ottiene l'ID della credenziale sorgente corrente @@ -2860,407 +2297,33 @@ public partial class DataCoupler : ComponentBase } } - /// - /// Prova a caricare gli schemi con query SQL dirette - /// - private async Task TryLoadSchemasWithDirectQuery() + + + + + + + + + + + + + + private async Task GenerateUniqueProfileName(string baseName) { - if (currentDatabaseManager == null) - return; + var uniqueName = baseName; + var counter = 1; - try + while (await ProfileService.GetProfileByNameIncludingInactiveAsync(uniqueName) != null) { - // Query diverse per ogni tipo di database - focalizzate sui database/cataloghi - var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); - if (credential == null) return; - - string? schemaQuery = credential.DatabaseType switch - { - DatabaseType.SqlServer => "SELECT name FROM sys.databases WHERE name NOT IN ('master', 'tempdb', 'model', 'msdb') AND state = 0", - DatabaseType.PostgreSql => "SELECT datname FROM pg_database WHERE datistemplate = false AND datname NOT IN ('postgres', 'template0', 'template1')", - DatabaseType.MySql => "SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')", - DatabaseType.Oracle => "SELECT DISTINCT OWNER FROM ALL_TABLES WHERE OWNER NOT IN ('SYS', 'SYSTEM', 'DBSNMP', 'SYSMAN', 'OUTLN', 'ANONYMOUS', 'CTXSYS', 'EXFSYS', 'LBACSYS', 'MDSYS', 'MGMT_VIEW', 'OLAPSYS', 'OWBSYS', 'ORDDATA', 'ORDSYS', 'SI_INFORMTN_SCHEMA', 'WK_TEST', 'WKPROXY', 'WMSYS', 'XDB', 'APEX_040000', 'APEX_PUBLIC_USER', 'DIP', 'FLOWS_FILES', 'HR', 'IX', 'OE', 'PM', 'SCOTT', 'SH', 'BI')", - _ => null - }; - - if (!string.IsNullOrEmpty(schemaQuery)) - { - Logger.LogInformation("Eseguendo query per database/schemi: {Query}", schemaQuery); - var results = await currentDatabaseManager.ExecuteRawQueryAsync(schemaQuery); - - if (results != null && results.Any()) - { - var schemas = results.Select(row => - { - var firstValue = row.Values.FirstOrDefault(); - return firstValue?.ToString() ?? ""; - }) - .Where(schema => !string.IsNullOrEmpty(schema)) - .OrderBy(schema => schema) - .ToList(); - - if (schemas.Any()) - { - availableSchemas.AddRange(schemas); - Logger.LogInformation("Caricati {SchemaCount} database/schemi via query diretta per {DatabaseType}: {Schemas}", - schemas.Count, credential.DatabaseType, string.Join(", ", schemas)); - } - } - } - } - catch (Exception ex) - { - Logger.LogWarning(ex, "Errore nel caricamento database/schemi via query diretta"); + uniqueName = $"{baseName} ({counter})"; + counter++; } + + return uniqueName; } - // Gestione selezione database quando la discovery restituisce dizionario vuoto - private async Task HandleDatabaseSelectionRequired() - { - isLoadingDatabases = true; - showDatabaseSelectionModal = true; - availableDatabases.Clear(); - selectedDatabase = ""; - try - { - if (currentDatabaseManager != null) - { - var dbs = await currentDatabaseManager.GetAvailableDatabasesAsync(); - availableDatabases = dbs ?? new List(); - } - } - catch (Exception ex) - { - databaseErrorMessage = $"Errore nel caricamento dei database: {ex.Message}"; - } - finally - { - isLoadingDatabases = false; - } - } - - private async Task OnDatabaseSelected() - { - try - { - if (string.IsNullOrEmpty(selectedDatabase)) - { - databaseErrorMessage = "Nessun database selezionato"; - return; - } - - showDatabaseSelectionModal = false; - - Logger.LogInformation("Database selezionato: {DatabaseName}. Riconnessione in corso...", selectedDatabase); - - // Riconnessione al database selezionato - await ConnectToDatabaseWithSpecificDatabase(selectedDatabase); - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nella selezione del database: {DatabaseName}", selectedDatabase); - databaseErrorMessage = $"Errore nella connessione al database {selectedDatabase}: {ex.Message}"; - } - } - - private void CancelDatabaseSelection() - { - showDatabaseSelectionModal = false; - selectedDatabase = ""; - databaseErrorMessage = "Selezione database annullata"; - Logger.LogInformation("Selezione database annullata dall'utente"); - } - - // Metodi helper per la connessione database - private Task IsDatabaseSpecifiedInConnectionString(DatabaseCredential credential) - { - try - { - Logger.LogInformation("Verifica database specificato - Tipo: {DatabaseType}, DatabaseName: '{DatabaseName}', Connection: {ConnectionString}", - credential.DatabaseType, credential.DatabaseName, credential.ConnectionString?.Substring(0, Math.Min(100, credential.ConnectionString?.Length ?? 0))); - - // Prima verifica se c'è un database specificato nel campo DatabaseName della credenziale - if (!string.IsNullOrEmpty(credential.DatabaseName)) - { - Logger.LogInformation("Database specificato nel campo DatabaseName: '{DatabaseName}' - RESULT: TRUE", credential.DatabaseName); - return Task.FromResult(true); - } - - // Per SQL Server verifica se Initial Catalog o Database è specificato nella connection string - if (credential.DatabaseType == DatabaseType.SqlServer) - { - var connectionString = credential.ConnectionString ?? ""; - var hasInitialCatalog = connectionString.Contains("Initial Catalog=", StringComparison.OrdinalIgnoreCase); - var hasDatabase = connectionString.Contains("Database=", StringComparison.OrdinalIgnoreCase); - var result = hasInitialCatalog || hasDatabase; - - Logger.LogInformation("SQL Server - HasInitialCatalog: {HasInitialCatalog}, HasDatabase: {HasDatabase}, Result: {Result}", - hasInitialCatalog, hasDatabase, result); - - return Task.FromResult(result); - } - - // TODO: Implementare per altri tipi di database - // MySQL: Database= - // PostgreSQL: Database= - // Oracle: più complesso con SID/Service Name - - Logger.LogWarning("Verifica database specificato non implementata per tipo database: {DatabaseType}", credential.DatabaseType); - return Task.FromResult(true); // Default: assume database specificato per tipi non implementati - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nella verifica database specificato in connection string"); - return Task.FromResult(true); // Default: assume database specificato in caso di errore - } - } - - private async Task LoadTablesFromConnectedDatabase() - { - try - { - if (currentDatabaseManager == null) - { - databaseErrorMessage = "Database manager non disponibile"; - return; - } - - Logger.LogInformation("Caricando tabelle dal database connesso"); - var tableNames = await currentDatabaseManager.GetTableNamesAsync(); - availableTableNames = tableNames.ToList(); - - Logger.LogInformation("Caricate {Count} tabelle dal database", availableTableNames.Count); - - // Resetta i dettagli delle tabelle - verranno caricati solo quando selezionati - databaseTables.Clear(); - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nel caricamento delle tabelle dal database connesso"); - databaseErrorMessage = $"Errore nel caricamento tabelle: {ex.Message}"; - throw; - } - } - - private async Task LoadAvailableDatabases() - { - try - { - if (currentDatabaseManager == null) - { - databaseErrorMessage = "Database manager non disponibile"; - return; - } - - isLoadingDatabases = true; - Logger.LogInformation("Caricando database disponibili"); - - // Usa il metodo corretto dell'interfaccia IDatabaseManager - var allDatabases = await currentDatabaseManager.GetAvailableDatabasesAsync(); - Logger.LogInformation("Ottenuti {DatabaseCount} database dal server", allDatabases.Count); - - // Filtra i database di sistema - availableDatabases = FilterSystemDatabases(allDatabases).ToList(); - - Logger.LogInformation("Trovati {TotalDatabases} database, filtrati a {FilteredDatabases} (esclusi quelli di sistema)", - allDatabases.Count, availableDatabases.Count); - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nel caricamento dei database disponibili"); - databaseErrorMessage = $"Errore nel caricamento database: {ex.Message}"; - throw; - } - finally - { - isLoadingDatabases = false; - } - } - - private IEnumerable FilterSystemDatabases(List allDatabases) - { - // Trova la credenziale per determinare il tipo di database - var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); - if (credential == null) - { - Logger.LogWarning("Credenziale non trovata per filtraggio database di sistema"); - return allDatabases; // Restituisce tutti se non riesce a determinare il tipo - } - - var databaseType = credential.DatabaseType; - - // Filtri per SQL Server - if (databaseType == DatabaseType.SqlServer) - { - var sqlServerSystemDatabases = new HashSet(StringComparer.OrdinalIgnoreCase) - { - "master", "tempdb", "model", "msdb", "Resource", "mssqlsystemresource", - "ReportServer", "ReportServerTempDB", "SSISDB", "distribution" - }; - - return allDatabases.Where(db => !sqlServerSystemDatabases.Contains(db)); - } - - // TODO: Implementare filtri per altri tipi di database - if (databaseType == DatabaseType.MySql) - { - Logger.LogInformation("Filtro database di sistema MySQL - DA IMPLEMENTARE"); - return allDatabases; // Per ora restituisce tutti - } - - if (databaseType == DatabaseType.PostgreSql) - { - Logger.LogInformation("Filtro database di sistema PostgreSQL - DA IMPLEMENTARE"); - return allDatabases; // Per ora restituisce tutti - } - - if (databaseType == DatabaseType.Oracle) - { - Logger.LogInformation("Filtro database di sistema Oracle - DA IMPLEMENTARE"); - return allDatabases; // Per ora restituisce tutti - } - - Logger.LogWarning("Tipo database non riconosciuto per filtraggio: {DatabaseType}", databaseType); - return allDatabases; // Restituisce tutti per tipi non riconosciuti - } - - // Metodo per gestire la selezione di un database dal modale - - - private async Task ConnectToDatabaseWithSpecificDatabase(string databaseName) - { - if (string.IsNullOrEmpty(selectedDatabaseCredential)) - return; - - isConnectingDatabase = true; - databaseErrorMessage = ""; - - try - { - // Trova la credenziale - var credential = databaseCredentials.FirstOrDefault(c => c.Name == selectedDatabaseCredential); - if (credential == null) - { - databaseErrorMessage = "Credenziale database non trovata"; - return; - } - - // Test della connessione - var (success, message) = await CredentialService.TestDatabaseConnectionAsync(credential.Name); - if (!success) - { - databaseErrorMessage = $"Connessione fallita: {message}"; - return; - } - - // Crea il database manager - Logger.LogInformation("Creando database manager per credenziale: {CredentialName}", selectedDatabaseCredential); - currentDatabaseManager = await ConnectionFactory.CreateDatabaseManagerAsync(selectedDatabaseCredential); - Logger.LogInformation("Database manager creato con successo"); - if (currentDatabaseManager == null) - { - databaseErrorMessage = "Database manager non disponibile"; - return; - } - - Logger.LogInformation("Cambiando database a: {DatabaseName}", databaseName); - - // Usa il metodo dell'interfaccia per cambiare database - await currentDatabaseManager.ChangeDatabaseAsync(databaseName); - Logger.LogInformation("Database cambiato con successo a: {DatabaseName}", databaseName); - - // Carica le tabelle dal database selezionato - await LoadTablesFromConnectedDatabase(); - - isDatabaseConnected = true; - Logger.LogInformation("Connessione completata per database: {DatabaseName}", databaseName); - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nella connessione al database specifico: {DatabaseName}", databaseName); - databaseErrorMessage = $"Errore nella connessione al database {databaseName}: {ex.Message}"; - throw; - } - } - - /// - /// Tenta di auto-selezionare una chiave per le query custom - /// - private void TryAutoSelectKeyForQuery(List columns) - { - try - { - // Reset stato chiave - sourceKeyField = ""; - suggestedPrimaryKey = ""; - requiresManualKeySelection = true; - - // Pattern comuni per identificare possibili chiavi primarie - var keyPatterns = new[] - { - "id", "ID", "Id", - "_id", "_ID", "_Id", - "key", "KEY", "Key", - "code", "CODE", "Code", "codice", "CODICE", "Codice", - "number", "NUMBER", "Number", "numero", "NUMERO", "Numero", - "index", "INDEX", "Index", "indice", "INDICE", "Indice" - }; - - // Cerca colonne che potrebbero essere chiavi primarie - string? detectedKey = null; - - // 1. Cerca esattamente "id", "ID", "Id" - detectedKey = columns.FirstOrDefault(c => - c.Equals("id", StringComparison.OrdinalIgnoreCase) || - c.Equals("ID", StringComparison.Ordinal) || - c.Equals("Id", StringComparison.Ordinal)); - - // 2. Se non trovato, cerca colonne che terminano con "id", "ID", "Id" - if (detectedKey == null) - { - detectedKey = columns.FirstOrDefault(c => - c.EndsWith("id", StringComparison.OrdinalIgnoreCase) || - c.EndsWith("ID", StringComparison.Ordinal) || - c.EndsWith("Id", StringComparison.Ordinal)); - } - - // 3. Se non trovato, cerca colonne che contengono pattern di chiave - if (detectedKey == null) - { - foreach (var pattern in keyPatterns) - { - detectedKey = columns.FirstOrDefault(c => - c.Contains(pattern, StringComparison.OrdinalIgnoreCase)); - if (detectedKey != null) break; - } - } - - // 4. Auto-seleziona se trovato - if (!string.IsNullOrEmpty(detectedKey)) - { - sourceKeyField = detectedKey; - suggestedPrimaryKey = detectedKey; - requiresManualKeySelection = false; - - Logger.LogInformation("Chiave auto-selezionata per query custom: {KeyField}", detectedKey); - } - else - { - Logger.LogInformation("Nessuna chiave rilevabile automaticamente per query custom, selezione manuale richiesta"); - } - } - catch (Exception ex) - { - Logger.LogError(ex, "Errore nell'auto-selezione della chiave per query custom"); - sourceKeyField = ""; - suggestedPrimaryKey = ""; - requiresManualKeySelection = true; - } - } - - /// - /// Tenta di auto-selezionare una chiave per i file (Excel/CSV) - /// private void TryAutoSelectKeyForFile(List columns) { try @@ -3294,7 +2357,7 @@ public partial class DataCoupler : ComponentBase // 2. Se non trovato, cerca colonne che terminano con pattern comuni if (detectedKey == null) { - foreach (var pattern in keyPatterns.Take(6)) // Solo i primi pattern più comuni + foreach (var pattern in keyPatterns.Take(6)) { detectedKey = columns.FirstOrDefault(c => c.EndsWith(pattern, StringComparison.OrdinalIgnoreCase)); @@ -3313,13 +2376,12 @@ public partial class DataCoupler : ComponentBase } } - // 4. Auto-seleziona se trovato + // Auto-seleziona se trovato if (!string.IsNullOrEmpty(detectedKey)) { sourceKeyField = detectedKey; suggestedPrimaryKey = detectedKey; requiresManualKeySelection = false; - Logger.LogInformation("Chiave auto-selezionata per file: {KeyField}", detectedKey); } else @@ -3335,19 +2397,5 @@ public partial class DataCoupler : ComponentBase requiresManualKeySelection = true; } } - - private async Task GenerateUniqueProfileName(string baseName) - { - var uniqueName = baseName; - var counter = 1; - - while (await ProfileService.GetProfileByNameIncludingInactiveAsync(uniqueName) != null) - { - uniqueName = $"{baseName} ({counter})"; - counter++; - } - - return uniqueName; - } }