feat: Implementata eliminazione cascata credenziali con modale di conferma
Aggiunta funzionalità completa per l'eliminazione sicura delle credenziali con rimozione automatica di tutti i dati associati. Modifiche principali: Backend: - Aggiunta interfaccia ICredentialService.DeleteCredentialCascadeAsync() - Implementato CredentialService.DeleteCredentialCascadeAsync() con gestione transazionale - Aggiornata IDataConnectionCredentialService con metodi cascade delete - Implementati wrapper in DataConnectionCredentialService Eliminazione cascata gestisce: - Execution histories delle schedulazioni - Profile schedules associate ai profili - Data Coupler profiles che usano le credenziali - Key associations per credenziali REST - Credenziale stessa Frontend (CredentialManagement.razor): - Aggiunto modale Bootstrap di conferma eliminazione con design danger - Messaggio di attenzione chiaro che elenca cosa verrà eliminato - Refactoring metodo DeleteCredential() per usare modale invece di confirm JS - Aggiunti metodi CloseDeleteConfirmModal() e ConfirmDeleteCredential() Sicurezza: - Eliminazione fisica (hard delete) con transazione database - Rollback automatico in caso di errore - Logging dettagliato di ogni operazione - Conferma esplicita dell'utente richiesta
This commit is contained in:
@@ -40,6 +40,10 @@ public interface ICredentialService
|
||||
Task<bool> DeleteCredentialAsync(string name);
|
||||
Task<List<string>> GetCredentialNamesAsync(CredentialType? type = null);
|
||||
|
||||
// Cascade delete operations
|
||||
Task<bool> DeleteCredentialCascadeAsync(int id);
|
||||
Task<bool> DeleteCredentialCascadeAsync(string name);
|
||||
|
||||
// Helper methods to get credential ID by name
|
||||
Task<int?> GetCredentialIdByNameAsync(string name, CredentialType type);
|
||||
}
|
||||
@@ -985,5 +989,133 @@ public class CredentialService : ICredentialService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elimina fisicamente una credenziale e tutti i dati associati (profili e schedulazioni) in modo cascata
|
||||
/// </summary>
|
||||
/// <param name="id">ID della credenziale da eliminare</param>
|
||||
/// <returns>True se l'eliminazione è riuscita, False altrimenti</returns>
|
||||
public async Task<bool> DeleteCredentialCascadeAsync(int id)
|
||||
{
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
var credential = await _context.Credentials.FindAsync(id);
|
||||
if (credential == null)
|
||||
{
|
||||
_logger.LogWarning("Tentativo di eliminare credenziale inesistente con ID: {Id}", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Inizio eliminazione cascata per credenziale: {Name} (ID: {Id})", credential.Name, credential.Id);
|
||||
|
||||
// 1. Trova tutti i profili che usano questa credenziale (come sorgente o destinazione)
|
||||
var profilesToDelete = await _context.DataCouplerProfiles
|
||||
.Where(p => p.SourceCredentialId == id || p.DestinationCredentialId == id)
|
||||
.ToListAsync();
|
||||
|
||||
_logger.LogInformation("Trovati {Count} profili associati alla credenziale {Name}", profilesToDelete.Count, credential.Name);
|
||||
|
||||
// 2. Per ogni profilo, elimina le schedulazioni associate
|
||||
foreach (var profile in profilesToDelete)
|
||||
{
|
||||
var schedulesToDelete = await _context.ProfileSchedules
|
||||
.Where(s => s.ProfileId == profile.Id)
|
||||
.ToListAsync();
|
||||
|
||||
if (schedulesToDelete.Any())
|
||||
{
|
||||
_logger.LogInformation("Eliminazione di {Count} schedulazioni per il profilo {ProfileName}",
|
||||
schedulesToDelete.Count, profile.Name);
|
||||
|
||||
// Elimina le execution histories delle schedulazioni
|
||||
foreach (var schedule in schedulesToDelete)
|
||||
{
|
||||
var histories = await _context.ScheduleExecutionHistories
|
||||
.Where(h => h.ScheduleId == schedule.Id)
|
||||
.ToListAsync();
|
||||
|
||||
if (histories.Any())
|
||||
{
|
||||
_context.ScheduleExecutionHistories.RemoveRange(histories);
|
||||
_logger.LogInformation("Eliminate {Count} execution histories per la schedulazione {ScheduleName}",
|
||||
histories.Count, schedule.Name);
|
||||
}
|
||||
}
|
||||
|
||||
_context.ProfileSchedules.RemoveRange(schedulesToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Elimina i profili
|
||||
if (profilesToDelete.Any())
|
||||
{
|
||||
_context.DataCouplerProfiles.RemoveRange(profilesToDelete);
|
||||
_logger.LogInformation("Eliminati {Count} profili associati alla credenziale {Name}",
|
||||
profilesToDelete.Count, credential.Name);
|
||||
}
|
||||
|
||||
// 4. Elimina le key associations associate (se presenti)
|
||||
var keyAssociations = await _context.KeyAssociations
|
||||
.Where(ka => ka.RestCredentialName == credential.Name)
|
||||
.ToListAsync();
|
||||
|
||||
if (keyAssociations.Any())
|
||||
{
|
||||
_context.KeyAssociations.RemoveRange(keyAssociations);
|
||||
_logger.LogInformation("Eliminate {Count} key associations per la credenziale {Name}",
|
||||
keyAssociations.Count, credential.Name);
|
||||
}
|
||||
|
||||
// 5. Infine, elimina la credenziale
|
||||
_context.Credentials.Remove(credential);
|
||||
|
||||
// Salva tutte le modifiche
|
||||
await _context.SaveChangesAsync();
|
||||
await transaction.CommitAsync();
|
||||
|
||||
_logger.LogInformation(
|
||||
"Credenziale {Name} (ID: {Id}) eliminata con successo insieme a {ProfileCount} profili, " +
|
||||
"{ScheduleCount} schedulazioni e {KeyAssociationCount} key associations",
|
||||
credential.Name, credential.Id, profilesToDelete.Count,
|
||||
profilesToDelete.Sum(p => _context.ProfileSchedules.Count(s => s.ProfileId == p.Id)),
|
||||
keyAssociations.Count);
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
_logger.LogError(ex, "Errore durante l'eliminazione cascata della credenziale con ID: {Id}", id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elimina fisicamente una credenziale e tutti i dati associati (profili e schedulazioni) in modo cascata
|
||||
/// </summary>
|
||||
/// <param name="name">Nome della credenziale da eliminare</param>
|
||||
/// <returns>True se l'eliminazione è riuscita, False altrimenti</returns>
|
||||
public async Task<bool> DeleteCredentialCascadeAsync(string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
var credential = await _context.Credentials
|
||||
.FirstOrDefaultAsync(c => c.Name == name);
|
||||
|
||||
if (credential == null)
|
||||
{
|
||||
_logger.LogWarning("Tentativo di eliminare credenziale inesistente: {Name}", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return await DeleteCredentialCascadeAsync(credential.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore durante l'eliminazione cascata della credenziale: {Name}", name);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user