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:
Alessio Dal Santo
2025-10-08 15:54:54 +02:00
parent d042863a56
commit 960166be9f
5 changed files with 526 additions and 22 deletions
@@ -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
}