diff --git a/Components/ProfileSaver.razor b/Components/ProfileSaver.razor
index 52142ad..2699635 100644
--- a/Components/ProfileSaver.razor
+++ b/Components/ProfileSaver.razor
@@ -1,4 +1,6 @@
@* Componente per salvare la configurazione corrente come profilo *@
+@using System.IO
+
+
+
+
@if (fieldMappings.Any())
{
@@ -1124,23 +1143,6 @@
}
-
-@if (isDatabaseConnected && isRestConnected && fieldMappings.Any())
-{
-
-}
-
e.Name == profile.DestinationEndpoint);
- if (entity != null)
+ var sourceCredential = await CredentialService.GetDatabaseCredentialAsync(profile.SourceCredentialId.Value);
+ if (sourceCredential != null)
{
- await SelectRestEntity(entity);
+ selectedDatabaseCredential = sourceCredential.Name;
+ Logger.LogInformation("Credenziale database selezionata: {Credential}", selectedDatabaseCredential);
+
+ // Force UI update for credential selection
+ StateHasChanged();
+ await Task.Delay(200);
+
+ // Connetti al database
+ Logger.LogInformation("Iniziando connessione database...");
+ if (!string.IsNullOrEmpty(profile.SourceSchema))
+ {
+ Logger.LogInformation("Connessione con schema specifico: {Schema}", profile.SourceSchema);
+ await ConnectToDatabaseWithSchema(profile.SourceSchema);
+ }
+ else
+ {
+ Logger.LogInformation("Connessione senza schema specifico");
+ await ConnectToDatabase();
+ }
+
+ Logger.LogInformation("Stato dopo connessione database - Connected: {Connected}, Tables: {TableCount}",
+ isDatabaseConnected, availableTableNames.Count);
+
+ // Seleziona la tabella se specificata e se la connessione è riuscita
+ if (!string.IsNullOrEmpty(profile.SourceTable) && isDatabaseConnected)
+ {
+ Logger.LogInformation("Selezione tabella: {Table}", profile.SourceTable);
+ await SelectTable(profile.SourceTable);
+ Logger.LogInformation("Tabella selezionata: {SelectedTable}, Schema caricato: {SchemaLoaded}",
+ selectedTable, databaseTables.ContainsKey(profile.SourceTable));
+ }
+ else
+ {
+ Logger.LogWarning("Impossibile selezionare tabella - Table: {Table}, Connected: {Connected}",
+ profile.SourceTable, isDatabaseConnected);
+ }
}
else
{
- Logger.LogWarning("Entità REST con endpoint {Endpoint} non trovata", profile.DestinationEndpoint);
+ Logger.LogWarning("Credenziale database con ID {CredentialId} non trovata", profile.SourceCredentialId);
}
}
- }
- }
-
- // Applica mapping dei campi se disponibile
- if (!string.IsNullOrEmpty(profile.FieldMappingJson))
- {
- try
- {
- var service = new DataCouplerProfileService(null!); // Temporaneo per deserializzazione
- var mappings = service.DeserializeFieldMappings(profile.FieldMappingJson);
-
- // Applica i mapping
- fieldMappings.Clear();
- keyFields.Clear();
-
- foreach (var mapping in mappings)
+ else if (profile.SourceType == "file")
{
- fieldMappings[mapping.SourceField] = mapping.DestinationField;
- if (mapping.IsKey)
+ // Per i file, non possiamo ricreare il file caricato, ma possiamo impostare le informazioni
+ if (!string.IsNullOrEmpty(profile.SourceFilePath))
{
- keyFields.Add(mapping.DestinationField);
+ selectedFileName = Path.GetFileName(profile.SourceFilePath);
+ Logger.LogInformation("Informazioni file impostate: {FileName}", selectedFileName);
}
}
-
- Logger.LogInformation("Applicati {MappingCount} mapping dei campi dal profilo", mappings.Count);
}
- catch (Exception ex)
+ else
{
- Logger.LogWarning(ex, "Errore nel caricamento dei mapping dei campi dal profilo");
+ Logger.LogInformation("Nessuna credenziale sorgente da configurare");
+ }
+
+ // Small delay to let source configuration settle
+ await Task.Delay(300);
+
+ // Step 3: Configura e connetti la destinazione
+ if (profile.DestinationCredentialId.HasValue)
+ {
+ Logger.LogInformation("Step 3 - Configurazione destinazione con ID credenziale: {CredentialId}", profile.DestinationCredentialId);
+
+ var destinationCredential = await CredentialService.GetRestApiCredentialAsync(profile.DestinationCredentialId.Value);
+ if (destinationCredential != null)
+ {
+ selectedRestCredential = destinationCredential.Name;
+ Logger.LogInformation("Credenziale REST selezionata: {Credential}", selectedRestCredential);
+
+ // Force UI update for REST credential selection
+ StateHasChanged();
+ await Task.Delay(200);
+
+ // Connetti al servizio REST
+ Logger.LogInformation("Iniziando connessione REST...");
+ await ConnectToRestApi();
+
+ Logger.LogInformation("Stato dopo connessione REST - Connected: {Connected}, Entities: {EntityCount}",
+ isRestConnected, restEntities.Count);
+
+ // Seleziona l'entità REST se la connessione è riuscita
+ if (!string.IsNullOrEmpty(profile.DestinationEndpoint) && isRestConnected)
+ {
+ var entity = restEntities.FirstOrDefault(e => e.Name == profile.DestinationEndpoint);
+ if (entity != null)
+ {
+ Logger.LogInformation("Selezione entità REST: {Entity}", entity.Name);
+ await SelectRestEntity(entity);
+ Logger.LogInformation("Entità REST selezionata: {SelectedEntity}, Dettagli caricati: {DetailsLoaded}",
+ selectedRestEntity?.Name, restEntityDetails != null);
+ }
+ else
+ {
+ Logger.LogWarning("Entità REST non trovata: {Endpoint} - Entities disponibili: {Entities}",
+ profile.DestinationEndpoint, string.Join(", ", restEntities.Select(e => e.Name)));
+ }
+ }
+ else
+ {
+ Logger.LogWarning("Impossibile selezionare entità REST - Endpoint: {Endpoint}, Connected: {Connected}",
+ profile.DestinationEndpoint, isRestConnected);
+ }
+ }
+ else
+ {
+ Logger.LogWarning("Credenziale REST con ID {CredentialId} non trovata", profile.DestinationCredentialId);
+ }
+ }
+ else
+ {
+ Logger.LogInformation("Nessuna credenziale destinazione da configurare");
}
- }
- StateHasChanged();
+ // Small delay to let destination configuration settle
+ await Task.Delay(300);
+
+ // Step 4: Applica mapping dei campi se disponibile
+ if (!string.IsNullOrEmpty(profile.FieldMappingJson))
+ {
+ Logger.LogInformation("Step 4 - Applicazione mapping campi...");
+ try
+ {
+ var service = new DataCouplerProfileService(null!);
+ var mappings = service.DeserializeFieldMappings(profile.FieldMappingJson);
+
+ Logger.LogInformation("Mappings deserializzati: {Count}", mappings.Count);
+
+ // Applica i mapping
+ fieldMappings.Clear();
+ keyFields.Clear();
+
+ foreach (var mapping in mappings)
+ {
+ fieldMappings[mapping.SourceField] = mapping.DestinationField;
+ if (mapping.IsKey)
+ {
+ keyFields.Add(mapping.DestinationField);
+ }
+ Logger.LogInformation("Mapping applicato: {Source} -> {Destination} (IsKey: {IsKey})",
+ mapping.SourceField, mapping.DestinationField, mapping.IsKey);
+ }
+
+ Logger.LogInformation("Mappings applicati - Totale: {MappingCount}, Chiavi: {KeyCount}",
+ fieldMappings.Count, keyFields.Count);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogWarning(ex, "Errore nel caricamento dei mapping dei campi dal profilo");
+ }
+ }
+ else
+ {
+ Logger.LogInformation("Nessun mapping campi da applicare");
+ }
+
+ // Step 5: Applica configurazione chiave sorgente
+ if (!string.IsNullOrEmpty(profile.SourceKeyField))
+ {
+ sourceKeyField = profile.SourceKeyField;
+ Logger.LogInformation("Step 5 - Chiave sorgente applicata: {SourceKey}", sourceKeyField);
+ }
+ else
+ {
+ Logger.LogInformation("Nessuna chiave sorgente da applicare");
+ }
+
+ // Step 6: Applica configurazione associazioni record
+ useRecordAssociations = profile.UseRecordAssociations;
+ Logger.LogInformation("Step 6 - Associazioni record configurate: {UseAssociations}", useRecordAssociations);
+
+ Logger.LogInformation("=== FINE APPLICAZIONE PROFILO ===");
+ Logger.LogInformation("Stato finale - Source: {SourceType}, DatabaseConnected: {DatabaseConnected}, RestConnected: {RestConnected}, Mappings: {MappingCount}",
+ selectedSourceType, isDatabaseConnected, isRestConnected, fieldMappings.Count);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Errore nell'applicazione della configurazione del profilo {ProfileName}", profile.Name);
+ await JSRuntime.InvokeVoidAsync("alert", $"Errore nel caricamento del profilo: {ex.Message}");
+ }
+ finally
+ {
+ // Force final UI update
+ StateHasChanged();
+ Logger.LogInformation("Aggiornamento finale UI completato");
+ }
}
private async Task OnProfileSaved(DataCouplerProfileDto profileDto)
{
try
{
+ Logger.LogInformation("Tentativo di salvataggio profilo: {ProfileName}", profileDto.Name);
+
var profileService = new DataCouplerProfileService(null!); // Usa il service di conversione
var profile = profileService.FromDto(profileDto, "System"); // TODO: Usa utente corrente
- await ProfileService.SaveProfileAsync(profile);
- await LoadProfiles(); // Ricarica la lista
+ // Controlla se esiste già un profilo con lo stesso nome (inclusi quelli inattivi)
+ Logger.LogInformation("Controllo esistenza profilo con nome: {ProfileName}", profileDto.Name);
+ var existingProfile = await ProfileService.GetProfileByNameIncludingInactiveAsync(profileDto.Name);
- await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' salvato con successo!");
+ if (existingProfile != null)
+ {
+ Logger.LogInformation("Trovato profilo esistente con ID: {ProfileId}, IsActive: {IsActive}",
+ existingProfile.Id, existingProfile.IsActive);
+
+ if (!existingProfile.IsActive)
+ {
+ // Il profilo esiste ma è inattivo - riattivalo e aggiornalo
+ Logger.LogInformation("Riattivazione del profilo inattivo: {ProfileName}", profileDto.Name);
+ profile.Id = existingProfile.Id;
+ profile.IsActive = true;
+
+ // Aggiorna direttamente il profilo esistente invece di creare un nuovo oggetto
+ existingProfile.Description = profile.Description;
+ existingProfile.SourceType = profile.SourceType;
+ existingProfile.SourceCredentialId = profile.SourceCredentialId;
+ existingProfile.SourceSchema = profile.SourceSchema;
+ existingProfile.SourceTable = profile.SourceTable;
+ existingProfile.SourceFilePath = profile.SourceFilePath;
+ existingProfile.DestinationType = profile.DestinationType;
+ existingProfile.DestinationCredentialId = profile.DestinationCredentialId;
+ existingProfile.DestinationSchema = profile.DestinationSchema;
+ existingProfile.DestinationTable = profile.DestinationTable;
+ existingProfile.DestinationEndpoint = profile.DestinationEndpoint;
+ existingProfile.FieldMappingJson = profile.FieldMappingJson;
+ existingProfile.SourceKeyField = profile.SourceKeyField;
+ existingProfile.UseRecordAssociations = profile.UseRecordAssociations;
+ existingProfile.IsActive = true;
+
+ await ProfileService.UpdateProfileAsync(existingProfile);
+ await LoadProfiles();
+
+ await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' riattivato e aggiornato con successo!");
+ return;
+ }
+
+ // Il profilo esiste ed è attivo - chiedi conferma per sovrascrittura
+ var confirmOverwrite = await JSRuntime.InvokeAsync("confirm",
+ $"Esiste già un profilo attivo con il nome '{profileDto.Name}'. Vuoi sovrascriverlo?");
+
+ if (confirmOverwrite)
+ {
+ Logger.LogInformation("Utente ha confermato la sovrascrittura del profilo: {ProfileName}", profileDto.Name);
+
+ // Aggiorna il profilo esistente direttamente
+ existingProfile.Description = profile.Description;
+ existingProfile.SourceType = profile.SourceType;
+ existingProfile.SourceCredentialId = profile.SourceCredentialId;
+ existingProfile.SourceSchema = profile.SourceSchema;
+ existingProfile.SourceTable = profile.SourceTable;
+ existingProfile.SourceFilePath = profile.SourceFilePath;
+ existingProfile.DestinationType = profile.DestinationType;
+ existingProfile.DestinationCredentialId = profile.DestinationCredentialId;
+ existingProfile.DestinationSchema = profile.DestinationSchema;
+ existingProfile.DestinationTable = profile.DestinationTable;
+ existingProfile.DestinationEndpoint = profile.DestinationEndpoint;
+ existingProfile.FieldMappingJson = profile.FieldMappingJson;
+ existingProfile.SourceKeyField = profile.SourceKeyField;
+ existingProfile.UseRecordAssociations = profile.UseRecordAssociations;
+
+ await ProfileService.UpdateProfileAsync(existingProfile);
+ await LoadProfiles(); // Ricarica la lista
+
+ await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' aggiornato con successo!");
+ }
+ else
+ {
+ Logger.LogInformation("Utente ha annullato la sovrascrittura del profilo: {ProfileName}", profileDto.Name);
+
+ // Proponi di creare con un nome unico
+ var useUniqueName = await JSRuntime.InvokeAsync("confirm",
+ "Vuoi salvare il profilo con un nome unico automatico (es. 'Nome Profilo (1)')?");
+
+ if (useUniqueName)
+ {
+ var uniqueName = await GenerateUniqueProfileName(profileDto.Name);
+ profile.Name = uniqueName;
+
+ try
+ {
+ await ProfileService.SaveProfileAsync(profile);
+ await LoadProfiles();
+
+ await JSRuntime.InvokeVoidAsync("alert", $"Profilo salvato con nome '{uniqueName}'!");
+ }
+ catch (Exception uniqueEx)
+ {
+ Logger.LogError(uniqueEx, "Errore durante il salvataggio del profilo con nome unico: {UniqueName}", uniqueName);
+
+ // Gestisci l'errore di unique constraint anche per il nome unico
+ if (uniqueEx.Message.Contains("UNIQUE constraint failed"))
+ {
+ await JSRuntime.InvokeVoidAsync("alert",
+ $"Errore: Non è stato possibile generare un nome unico per il profilo. " +
+ "Prova a ricaricare la pagina e riprova.");
+ }
+ else
+ {
+ await JSRuntime.InvokeVoidAsync("alert", $"Errore nel salvataggio del profilo: {uniqueEx.Message}");
+ }
+ }
+ }
+ // Altrimenti, non salvare nulla
+ return;
+ }
+ }
+ else
+ {
+ Logger.LogInformation("Nessun profilo esistente trovato, creazione nuovo profilo: {ProfileName}", profileDto.Name);
+
+ // Crea un nuovo profilo
+ try
+ {
+ await ProfileService.SaveProfileAsync(profile);
+ await LoadProfiles(); // Ricarica la lista
+
+ await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' salvato con successo!");
+ }
+ catch (Exception saveEx)
+ {
+ Logger.LogError(saveEx, "Errore durante il salvataggio del nuovo profilo: {ProfileName}", profileDto.Name);
+
+ // Possibile race condition - riprova con controllo duplicato
+ if (saveEx.Message.Contains("UNIQUE constraint failed"))
+ {
+ Logger.LogWarning("Race condition rilevata durante il salvataggio, gestione del duplicato...");
+
+ // Chiedi se vuole sovrascrivere o creare nome unico
+ var handleDuplicate = await JSRuntime.InvokeAsync("confirm",
+ $"Un profilo con il nome '{profileDto.Name}' è stato creato nel frattempo. " +
+ "Vuoi sovrascriverlo? (Clicca 'Annulla' per salvare con un nome unico)");
+
+ if (handleDuplicate)
+ {
+ // Trova il profilo e aggiornalo
+ var duplicateProfile = await ProfileService.GetProfileByNameIncludingInactiveAsync(profileDto.Name);
+ if (duplicateProfile != null)
+ {
+ profile.Id = duplicateProfile.Id;
+ await ProfileService.UpdateProfileAsync(profile);
+ await LoadProfiles();
+
+ await JSRuntime.InvokeVoidAsync("alert", $"Profilo '{profileDto.Name}' aggiornato con successo!");
+ }
+ else
+ {
+ await JSRuntime.InvokeVoidAsync("alert", "Errore: Il profilo duplicato non è stato trovato.");
+ }
+ }
+ else
+ {
+ // Crea con nome unico
+ var uniqueName = await GenerateUniqueProfileName(profileDto.Name);
+ profile.Name = uniqueName;
+
+ await ProfileService.SaveProfileAsync(profile);
+ await LoadProfiles();
+
+ await JSRuntime.InvokeVoidAsync("alert", $"Profilo salvato con nome '{uniqueName}'!");
+ }
+ }
+ else
+ {
+ throw; // Rilancia eccezioni non gestite
+ }
+ }
+ }
}
catch (Exception ex)
{
- Logger.LogError(ex, "Errore nel salvataggio del profilo");
- await JSRuntime.InvokeVoidAsync("alert", $"Errore nel salvataggio del profilo: {ex.Message}");
+ Logger.LogError(ex, "Errore generale nel salvataggio del profilo: {ProfileName}", profileDto.Name);
+
+ // Gestione generica degli errori
+ if (ex.Message.Contains("UNIQUE constraint failed"))
+ {
+ await JSRuntime.InvokeVoidAsync("alert",
+ $"Errore: Esiste già un profilo con il nome '{profileDto.Name}'. " +
+ "Questo può accadere se ci sono stati problemi di sincronizzazione. " +
+ "Prova a ricaricare la pagina e riprova.");
+ }
+ else
+ {
+ await JSRuntime.InvokeVoidAsync("alert", $"Errore nel salvataggio del profilo: {ex.Message}");
+ }
}
}
@@ -930,7 +1237,7 @@ public partial class DataCoupler : ComponentBase
{
isConnectingRest = false;
}
- } private async void SelectTable(string tableName)
+ } private async Task SelectTable(string tableName)
{
selectedTable = tableName;
@@ -1005,7 +1312,9 @@ public partial class DataCoupler : ComponentBase
}
StateHasChanged();
- } private async Task SelectRestEntity(RestEntitySummary entity)
+ }
+
+ private async Task SelectRestEntity(RestEntitySummary entity)
{
selectedRestEntity = entity;
@@ -2015,6 +2324,7 @@ public partial class DataCoupler : ComponentBase
///
/// Verifica se una query è una SELECT query sicura
+
///
private bool IsSelectQuery(string query)
{
@@ -2262,20 +2572,44 @@ public partial class DataCoupler : ComponentBase
///
/// Ottiene l'ID della credenziale sorgente corrente
///
- private int? GetCurrentSourceCredentialId()
+ private async Task GetCurrentSourceCredentialIdAsync()
{
- // TODO: Implementare logica per ottenere l'ID dalla credenziale selezionata
- // Per ora ritorniamo null dato che i DTO non hanno ID
+ if (selectedSourceType == "database" && !string.IsNullOrEmpty(selectedDatabaseCredential))
+ {
+ try
+ {
+ // Usa il nuovo metodo per ottenere direttamente l'ID della credenziale
+ return await CredentialService.GetCredentialIdByNameAsync(selectedDatabaseCredential, CredentialManager.Models.CredentialType.Database);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Errore nell'ottenere l'ID della credenziale database: {CredentialName}", selectedDatabaseCredential);
+ return null;
+ }
+ }
+
return null;
}
///
/// Ottiene l'ID della credenziale destinazione corrente
///
- private int? GetCurrentDestinationCredentialId()
+ private async Task GetCurrentDestinationCredentialIdAsync()
{
- // TODO: Implementare logica per ottenere l'ID dalla credenziale selezionata
- // Per ora ritorniamo null dato che i DTO non hanno ID
+ if (!string.IsNullOrEmpty(selectedRestCredential))
+ {
+ try
+ {
+ // Usa il nuovo metodo per ottenere direttamente l'ID della credenziale
+ return await CredentialService.GetCredentialIdByNameAsync(selectedRestCredential, CredentialManager.Models.CredentialType.RestApi);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogError(ex, "Errore nell'ottenere l'ID della credenziale REST: {CredentialName}", selectedRestCredential);
+ return null;
+ }
+ }
+
return null;
}
@@ -2400,7 +2734,7 @@ public partial class DataCoupler : ComponentBase
catch (Exception ex)
{
Logger.LogError(ex, "Errore nella connessione con lo schema selezionato");
- databaseErrorMessage = $"Errore nella connessione con schema {selectedSchema}: {ex.Message}";
+ databaseErrorMessage = $"Errore nella connessione al database {selectedSchema}: {ex.Message}";
}
StateHasChanged();
@@ -2778,8 +3112,9 @@ public partial class DataCoupler : ComponentBase
"id", "ID", "Id",
"_id", "_ID", "_Id",
"key", "KEY", "Key",
- "code", "CODE", "Code",
- "number", "NUMBER", "Number"
+ "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
@@ -2912,5 +3247,18 @@ public partial class DataCoupler : ComponentBase
}
}
+ private async Task GenerateUniqueProfileName(string baseName)
+ {
+ var uniqueName = baseName;
+ var counter = 1;
+
+ while (await ProfileService.GetProfileByNameIncludingInactiveAsync(uniqueName) != null)
+ {
+ uniqueName = $"{baseName} ({counter})";
+ counter++;
+ }
+
+ return uniqueName;
+ }
}
diff --git a/Data_Coupler/Pages/DataCoupler_temp.cs b/Data_Coupler/Pages/DataCoupler_temp.cs
new file mode 100644
index 0000000..e69de29
diff --git a/Data_Coupler/wwwroot/data/credentials.db b/Data_Coupler/wwwroot/data/credentials.db
index 98fd888..cbd53c8 100644
Binary files a/Data_Coupler/wwwroot/data/credentials.db and b/Data_Coupler/wwwroot/data/credentials.db differ
diff --git a/Data_Coupler/wwwroot/data/credentials.db-shm b/Data_Coupler/wwwroot/data/credentials.db-shm
index 47db41f..a814b92 100644
Binary files a/Data_Coupler/wwwroot/data/credentials.db-shm and b/Data_Coupler/wwwroot/data/credentials.db-shm differ
diff --git a/Data_Coupler/wwwroot/data/credentials.db-wal b/Data_Coupler/wwwroot/data/credentials.db-wal
index 8e955bd..7c12581 100644
Binary files a/Data_Coupler/wwwroot/data/credentials.db-wal and b/Data_Coupler/wwwroot/data/credentials.db-wal differ