feat: Implementazione completa sistema di estrazione e trasferimento dati con auto-selezione chiave primaria

- Refactoring logica connessione database per gestire database specificato/non specificato
- Implementazione selezione database tramite modale quando necessario
- Aggiunta discovery robusta di tabelle, colonne e preview dati
- Implementazione validazione completa query custom (solo SELECT, blocco SQL injection)
- Aggiunta preview paginata dati per query custom
- Implementazione mapping automatico dei campi
- Implementazione auto-selezione chiave primaria:
  * Per tabelle database: rilevamento automatico PK
  * Per query custom: ricerca pattern comuni (id, key, etc.)
  * Per file Excel/CSV: ricerca pattern italiani (codice, numero, etc.)
- Aggiunta logging dettagliato per debugging e tracciamento flusso
- Implementazione sistema associazioni con fallback robusto
- Miglioramento gestione errori e validazioni input utente
- Test funzionali completi e validazione end-to-end

Il sistema ora supporta completamente il flusso di estrazione dati da database/file
verso API REST con gestione automatica delle chiavi e mapping intelligente.
This commit is contained in:
Alessio Dal Santo
2025-07-02 16:00:29 +02:00
parent 77efe986a0
commit 373eae0a5a
+211 -6
View File
@@ -662,11 +662,18 @@ public partial class DataCoupler : ComponentBase
// Clear mappings when changing sheet
ClearAllMappings();
// For file sources, always require manual key selection
// For file sources, try auto-selection and then require manual key selection if not found
sourceKeyField = "";
suggestedPrimaryKey = "";
requiresManualKeySelection = true;
// AUTO-SELECT della chiave per i file
if (fileSheets.ContainsKey(sheetName))
{
var columns = fileSheets[sheetName].ToList();
TryAutoSelectKeyForFile(columns);
}
StateHasChanged();
}
@@ -968,26 +975,33 @@ public partial class DataCoupler : ComponentBase
if (!string.IsNullOrEmpty(primaryKey))
{
suggestedPrimaryKey = primaryKey;
// Suggest the primary key but don't auto-select it
Logger.LogInformation("Primary key detected for table {TableName}: {PrimaryKey}", tableName, 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;
Logger.LogInformation("No primary key found for table {TableName}, manual selection required", tableName);
sourceKeyField = "";
Logger.LogInformation("Nessuna chiave primaria trovata per la tabella {TableName}, selezione manuale richiesta", tableName);
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error detecting primary key for table {TableName}", tableName);
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();
@@ -2008,7 +2022,38 @@ public partial class DataCoupler : ComponentBase
return false;
var trimmedQuery = query.Trim();
return trimmedQuery.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase);
// Deve iniziare con SELECT
if (!trimmedQuery.StartsWith("SELECT", StringComparison.OrdinalIgnoreCase))
return false;
// Lista di parole chiave vietate per sicurezza
var forbiddenKeywords = new[]
{
"INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER", "TRUNCATE",
"EXEC", "EXECUTE", "sp_", "xp_", "BULK", "OPENROWSET", "OPENDATASOURCE"
};
var upperQuery = trimmedQuery.ToUpperInvariant();
// Verifica che non contenga parole chiave vietate
foreach (var keyword in forbiddenKeywords)
{
if (upperQuery.Contains(keyword))
{
Logger.LogWarning("Query rifiutata: contiene parola chiave vietata '{Keyword}'", keyword);
return false;
}
}
// Verifica che non contenga commenti SQL potenzialmente pericolosi
if (upperQuery.Contains("--") || upperQuery.Contains("/*"))
{
Logger.LogWarning("Query rifiutata: contiene commenti SQL non consentiti");
return false;
}
return true;
}
/// <summary>
@@ -2028,6 +2073,12 @@ public partial class DataCoupler : ComponentBase
cleanQuery = cleanQuery.Substring(0, cleanQuery.Length - 1).Trim();
}
// Rimuove caratteri di controllo pericolosi
cleanQuery = System.Text.RegularExpressions.Regex.Replace(cleanQuery, @"[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]", "");
// Normalizza spazi multipli
cleanQuery = System.Text.RegularExpressions.Regex.Replace(cleanQuery, @"\s+", " ");
return cleanQuery;
}
@@ -2112,6 +2163,9 @@ public partial class DataCoupler : ComponentBase
// 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
@@ -2706,6 +2760,157 @@ public partial class DataCoupler : ComponentBase
}
}
/// <summary>
/// Tenta di auto-selezionare una chiave per le query custom
/// </summary>
private void TryAutoSelectKeyForQuery(List<string> 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",
"number", "NUMBER", "Number"
};
// 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;
}
}
/// <summary>
/// Tenta di auto-selezionare una chiave per i file (Excel/CSV)
/// </summary>
private void TryAutoSelectKeyForFile(List<string> columns)
{
try
{
// Reset stato chiave
sourceKeyField = "";
suggestedPrimaryKey = "";
requiresManualKeySelection = true;
// Pattern comuni per identificare possibili chiavi primarie nei file
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) ||
c.Equals("codice", StringComparison.OrdinalIgnoreCase));
// 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
{
detectedKey = columns.FirstOrDefault(c =>
c.EndsWith(pattern, StringComparison.OrdinalIgnoreCase));
if (detectedKey != null) break;
}
}
// 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 file: {KeyField}", detectedKey);
}
else
{
Logger.LogInformation("Nessuna chiave rilevabile automaticamente per file, selezione manuale richiesta");
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Errore nell'auto-selezione della chiave per file");
sourceKeyField = "";
suggestedPrimaryKey = "";
requiresManualKeySelection = true;
}
}
}