feat: Implementazione completa sistema schedulazione con intervalli personalizzati
- Aggiunto supporto schedulazione con intervalli flessibili (secondi/minuti/ore/giorni/settimane/mesi) - Esteso modello ProfileSchedule con campi IntervalValue e IntervalUnit - Ottimizzato ScheduledJobService per controlli ogni 30s con esecuzione parallela - Implementata interfaccia UI completa con anteprima real-time in italiano - Aggiunta migrazione database AddIntervalSchedulingFields - Implementati metodi calcolo NextExecutionTime per intervalli - Aggiunta gestione tracking anti-duplicati e cleanup automatico - Creata documentazione completa (6 file, 2500+ righe) Modifiche tecniche: - ProfileSchedule.cs: Nuovi campi e metodi CalculateNextInterval/GetScheduleDescription - ScheduledJobService.cs: Ridotto check interval a 30s, aggiunto parallel processing - ProfileScheduleService.cs: Supporto calcolo intervalli in UpdateNextExecutionTimeAsync - Scheduling.razor: Aggiunta sezione UI per configurazione intervalli - Scheduling.razor.cs: Implementato GetIntervalPreview() e gestione stato campi
This commit is contained in:
@@ -0,0 +1,733 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using CredentialManager.Data;
|
||||
using CredentialManager.Models;
|
||||
using CredentialManager.Services;
|
||||
using Data_Coupler.Models;
|
||||
|
||||
namespace Data_Coupler.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Interfaccia per il servizio di backup e ripristino
|
||||
/// </summary>
|
||||
public interface IBackupService
|
||||
{
|
||||
/// <summary>
|
||||
/// Esporta tutti i dati del sistema in un file di backup
|
||||
/// </summary>
|
||||
Task<BackupOperationResult> ExportBackupAsync(BackupOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Importa i dati da un file di backup
|
||||
/// </summary>
|
||||
Task<BackupOperationResult> ImportBackupAsync(string backupFilePath, RestoreOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Importa i dati da contenuto JSON
|
||||
/// </summary>
|
||||
Task<BackupOperationResult> ImportBackupFromJsonAsync(string jsonContent, RestoreOptions options);
|
||||
|
||||
/// <summary>
|
||||
/// Valida un file di backup
|
||||
/// </summary>
|
||||
Task<BackupOperationResult> ValidateBackupAsync(string backupFilePath);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene le informazioni su un backup senza importarlo
|
||||
/// </summary>
|
||||
Task<SystemBackupData?> GetBackupInfoAsync(string backupFilePath);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Servizio per la gestione dei backup e ripristini del sistema
|
||||
/// </summary>
|
||||
public class BackupService : IBackupService
|
||||
{
|
||||
private readonly CredentialDbContext _context;
|
||||
private readonly IDataCouplerProfileService _profileService;
|
||||
private readonly ICredentialService _credentialService;
|
||||
private readonly IKeyAssociationService _keyAssociationService;
|
||||
private readonly ILogger<BackupService> _logger;
|
||||
|
||||
public BackupService(
|
||||
CredentialDbContext context,
|
||||
IDataCouplerProfileService profileService,
|
||||
ICredentialService credentialService,
|
||||
IKeyAssociationService keyAssociationService,
|
||||
ILogger<BackupService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_profileService = profileService;
|
||||
_credentialService = credentialService;
|
||||
_keyAssociationService = keyAssociationService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Esporta tutti i dati del sistema
|
||||
/// </summary>
|
||||
public async Task<BackupOperationResult> ExportBackupAsync(BackupOptions options)
|
||||
{
|
||||
var result = new BackupOperationResult();
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Avvio backup con opzioni: {@Options}", options);
|
||||
|
||||
var backupData = new SystemBackupData
|
||||
{
|
||||
Metadata = new BackupMetadata
|
||||
{
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
CreatedBy = options.CreatedBy,
|
||||
Description = options.Description,
|
||||
ApplicationVersion = "1.0.0"
|
||||
}
|
||||
};
|
||||
|
||||
// Export Profiles
|
||||
if (options.IncludeProfiles)
|
||||
{
|
||||
_logger.LogDebug("Esportazione profili in corso...");
|
||||
backupData.Profiles = await ExportProfilesAsync(options.IncludeOnlyActiveRecords);
|
||||
result.ProcessedCounts.Profiles = backupData.Profiles.Count;
|
||||
_logger.LogDebug("Esportati {Count} profili", backupData.Profiles.Count);
|
||||
}
|
||||
|
||||
// Export Credentials (senza dati sensibili)
|
||||
if (options.IncludeCredentials)
|
||||
{
|
||||
_logger.LogDebug("Esportazione credenziali in corso...");
|
||||
backupData.Credentials = await ExportCredentialsAsync(options.IncludeOnlyActiveRecords);
|
||||
result.ProcessedCounts.Credentials = backupData.Credentials.Count;
|
||||
result.Warnings.Add("Le credenziali esportate non includono password, API keys o token per motivi di sicurezza");
|
||||
_logger.LogDebug("Esportate {Count} credenziali", backupData.Credentials.Count);
|
||||
}
|
||||
|
||||
// Export Key Associations
|
||||
if (options.IncludeKeyAssociations)
|
||||
{
|
||||
_logger.LogDebug("Esportazione associazioni chiavi in corso...");
|
||||
backupData.KeyAssociations = await ExportKeyAssociationsAsync(options.IncludeOnlyActiveRecords);
|
||||
result.ProcessedCounts.KeyAssociations = backupData.KeyAssociations.Count;
|
||||
_logger.LogDebug("Esportate {Count} associazioni", backupData.KeyAssociations.Count);
|
||||
}
|
||||
|
||||
// Export Profile Schedules
|
||||
if (options.IncludeProfileSchedules)
|
||||
{
|
||||
_logger.LogDebug("Esportazione schedule profili in corso...");
|
||||
backupData.ProfileSchedules = await ExportProfileSchedulesAsync(options.IncludeOnlyActiveRecords);
|
||||
result.ProcessedCounts.ProfileSchedules = backupData.ProfileSchedules.Count;
|
||||
_logger.LogDebug("Esportate {Count} schedule", backupData.ProfileSchedules.Count);
|
||||
}
|
||||
|
||||
// Aggiorna metadata con conteggi
|
||||
backupData.Metadata.RecordCounts = result.ProcessedCounts;
|
||||
|
||||
// Serializza e salva
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true,
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
var json = JsonSerializer.Serialize(backupData, jsonOptions);
|
||||
var fileName = $"data_coupler_backup_{DateTime.Now:yyyyMMdd_HHmmss}.json";
|
||||
var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
|
||||
var backupFolder = Path.Combine(documentsPath, "DataCoupler", "Backups");
|
||||
Directory.CreateDirectory(backupFolder);
|
||||
|
||||
var filePath = Path.Combine(backupFolder, fileName);
|
||||
await File.WriteAllTextAsync(filePath, json);
|
||||
|
||||
result.FilePath = filePath;
|
||||
result.Success = true;
|
||||
result.Message = $"Backup completato con successo. File salvato: {filePath}";
|
||||
|
||||
_logger.LogInformation("Backup completato: {FilePath}, Record totali: {Total}",
|
||||
filePath,
|
||||
result.ProcessedCounts.Profiles + result.ProcessedCounts.Credentials +
|
||||
result.ProcessedCounts.KeyAssociations + result.ProcessedCounts.ProfileSchedules);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Success = false;
|
||||
result.Message = $"Errore durante il backup: {ex.Message}";
|
||||
result.Errors.Add(ex.ToString());
|
||||
_logger.LogError(ex, "Errore durante l'esportazione del backup");
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
result.Duration = stopwatch.Elapsed;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Importa dati da file backup
|
||||
/// </summary>
|
||||
public async Task<BackupOperationResult> ImportBackupAsync(string backupFilePath, RestoreOptions options)
|
||||
{
|
||||
if (!File.Exists(backupFilePath))
|
||||
{
|
||||
return new BackupOperationResult
|
||||
{
|
||||
Success = false,
|
||||
Message = "File di backup non trovato"
|
||||
};
|
||||
}
|
||||
|
||||
var json = await File.ReadAllTextAsync(backupFilePath);
|
||||
return await ImportBackupFromJsonAsync(json, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Importa dati da contenuto JSON
|
||||
/// </summary>
|
||||
public async Task<BackupOperationResult> ImportBackupFromJsonAsync(string jsonContent, RestoreOptions options)
|
||||
{
|
||||
var result = new BackupOperationResult();
|
||||
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
|
||||
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Avvio import backup con opzioni: {@Options}", options);
|
||||
|
||||
// Parse JSON
|
||||
var jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
};
|
||||
|
||||
var backupData = JsonSerializer.Deserialize<SystemBackupData>(jsonContent, jsonOptions);
|
||||
if (backupData == null)
|
||||
{
|
||||
throw new InvalidOperationException("Impossibile deserializzare i dati del backup");
|
||||
}
|
||||
|
||||
_logger.LogInformation("Backup da importare: {Metadata}", backupData.Metadata);
|
||||
|
||||
// Crea backup automatico prima del restore se richiesto
|
||||
if (options.CreateBackupBeforeRestore)
|
||||
{
|
||||
_logger.LogInformation("Creazione backup di sicurezza pre-restore...");
|
||||
var preRestoreBackup = await ExportBackupAsync(new BackupOptions
|
||||
{
|
||||
Description = "Backup automatico pre-restore",
|
||||
CreatedBy = options.ImportedBy
|
||||
});
|
||||
|
||||
if (preRestoreBackup.Success)
|
||||
{
|
||||
result.Warnings.Add($"Backup di sicurezza creato: {preRestoreBackup.FilePath}");
|
||||
}
|
||||
}
|
||||
|
||||
using var transaction = await _context.Database.BeginTransactionAsync();
|
||||
try
|
||||
{
|
||||
// Import Profiles
|
||||
if (options.RestoreProfiles && backupData.Profiles.Any())
|
||||
{
|
||||
_logger.LogDebug("Importazione profili in corso...");
|
||||
var importedProfiles = await ImportProfilesAsync(backupData.Profiles, options);
|
||||
result.ProcessedCounts.Profiles = importedProfiles;
|
||||
_logger.LogDebug("Importati {Count} profili", importedProfiles);
|
||||
}
|
||||
|
||||
// Import Credentials (solo metadati, nessun dato sensibile)
|
||||
if (options.RestoreCredentials && backupData.Credentials.Any())
|
||||
{
|
||||
_logger.LogDebug("Importazione credenziali in corso...");
|
||||
var importedCredentials = await ImportCredentialsAsync(backupData.Credentials, options);
|
||||
result.ProcessedCounts.Credentials = importedCredentials;
|
||||
result.Warnings.Add("Le credenziali importate richiedono configurazione manuale di password/API keys");
|
||||
_logger.LogDebug("Importate {Count} credenziali", importedCredentials);
|
||||
}
|
||||
|
||||
// Import Key Associations
|
||||
if (options.RestoreKeyAssociations && backupData.KeyAssociations.Any())
|
||||
{
|
||||
_logger.LogDebug("Importazione associazioni chiavi in corso...");
|
||||
var importedAssociations = await ImportKeyAssociationsAsync(backupData.KeyAssociations, options);
|
||||
result.ProcessedCounts.KeyAssociations = importedAssociations;
|
||||
_logger.LogDebug("Importate {Count} associazioni", importedAssociations);
|
||||
}
|
||||
|
||||
// Import Profile Schedules
|
||||
if (options.RestoreProfileSchedules && backupData.ProfileSchedules.Any())
|
||||
{
|
||||
_logger.LogDebug("Importazione schedule profili in corso...");
|
||||
var importedSchedules = await ImportProfileSchedulesAsync(backupData.ProfileSchedules, options);
|
||||
result.ProcessedCounts.ProfileSchedules = importedSchedules;
|
||||
_logger.LogDebug("Importate {Count} schedule", importedSchedules);
|
||||
}
|
||||
|
||||
await transaction.CommitAsync();
|
||||
|
||||
result.Success = true;
|
||||
result.Message = "Import completato con successo";
|
||||
|
||||
_logger.LogInformation("Import completato con successo. Record totali: {Total}",
|
||||
result.ProcessedCounts.Profiles + result.ProcessedCounts.Credentials +
|
||||
result.ProcessedCounts.KeyAssociations + result.ProcessedCounts.ProfileSchedules);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await transaction.RollbackAsync();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Success = false;
|
||||
result.Message = $"Errore durante l'import: {ex.Message}";
|
||||
result.Errors.Add(ex.ToString());
|
||||
_logger.LogError(ex, "Errore durante l'importazione del backup");
|
||||
}
|
||||
finally
|
||||
{
|
||||
stopwatch.Stop();
|
||||
result.Duration = stopwatch.Elapsed;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Valida un file di backup
|
||||
/// </summary>
|
||||
public async Task<BackupOperationResult> ValidateBackupAsync(string backupFilePath)
|
||||
{
|
||||
var result = new BackupOperationResult();
|
||||
|
||||
try
|
||||
{
|
||||
if (!File.Exists(backupFilePath))
|
||||
{
|
||||
result.Errors.Add("File non trovato");
|
||||
return result;
|
||||
}
|
||||
|
||||
var json = await File.ReadAllTextAsync(backupFilePath);
|
||||
var backupData = JsonSerializer.Deserialize<SystemBackupData>(json);
|
||||
|
||||
if (backupData == null)
|
||||
{
|
||||
result.Errors.Add("Formato backup non valido");
|
||||
return result;
|
||||
}
|
||||
|
||||
// Validazioni
|
||||
if (backupData.Metadata == null)
|
||||
{
|
||||
result.Errors.Add("Metadata mancanti");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(backupData.Metadata?.Version))
|
||||
{
|
||||
result.Warnings.Add("Versione backup non specificata");
|
||||
}
|
||||
|
||||
result.ProcessedCounts.Profiles = backupData.Profiles?.Count ?? 0;
|
||||
result.ProcessedCounts.Credentials = backupData.Credentials?.Count ?? 0;
|
||||
result.ProcessedCounts.KeyAssociations = backupData.KeyAssociations?.Count ?? 0;
|
||||
result.ProcessedCounts.ProfileSchedules = backupData.ProfileSchedules?.Count ?? 0;
|
||||
|
||||
result.Success = result.Errors.Count == 0;
|
||||
result.Message = result.Success ? "Backup valido" : "Backup contiene errori";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Success = false;
|
||||
result.Message = $"Errore validazione: {ex.Message}";
|
||||
result.Errors.Add(ex.ToString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene informazioni backup senza importare
|
||||
/// </summary>
|
||||
public async Task<SystemBackupData?> GetBackupInfoAsync(string backupFilePath)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(backupFilePath))
|
||||
return null;
|
||||
|
||||
var json = await File.ReadAllTextAsync(backupFilePath);
|
||||
return JsonSerializer.Deserialize<SystemBackupData>(json);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Errore lettura info backup da {FilePath}", backupFilePath);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Private Export Methods
|
||||
|
||||
private async Task<List<DataCouplerProfileBackup>> ExportProfilesAsync(bool onlyActive)
|
||||
{
|
||||
var query = _context.DataCouplerProfiles
|
||||
.Include(p => p.SourceCredential)
|
||||
.Include(p => p.DestinationCredential)
|
||||
.AsQueryable();
|
||||
|
||||
if (onlyActive)
|
||||
query = query.Where(p => p.IsActive);
|
||||
|
||||
var profiles = await query.ToListAsync();
|
||||
|
||||
return profiles.Select(p => new DataCouplerProfileBackup
|
||||
{
|
||||
Id = p.Id,
|
||||
Name = p.Name,
|
||||
Description = p.Description,
|
||||
SourceType = p.SourceType,
|
||||
SourceCredentialName = p.SourceCredential?.Name,
|
||||
SourceDatabaseName = p.SourceDatabaseName,
|
||||
SourceSchema = p.SourceSchema,
|
||||
SourceTable = p.SourceTable,
|
||||
SourceCustomQuery = p.SourceCustomQuery,
|
||||
SourceFilePath = p.SourceFilePath,
|
||||
DestinationType = p.DestinationType,
|
||||
DestinationCredentialName = p.DestinationCredential?.Name,
|
||||
DestinationSchema = p.DestinationSchema,
|
||||
DestinationTable = p.DestinationTable,
|
||||
DestinationEndpoint = p.DestinationEndpoint,
|
||||
FieldMappings = !string.IsNullOrEmpty(p.FieldMappingJson) ?
|
||||
System.Text.Json.JsonSerializer.Deserialize<List<FieldMappingDto>>(p.FieldMappingJson) ?? new List<FieldMappingDto>() :
|
||||
new List<FieldMappingDto>(),
|
||||
SourceKeyField = p.SourceKeyField,
|
||||
UseRecordAssociations = p.UseRecordAssociations,
|
||||
CreatedBy = p.CreatedBy,
|
||||
CreatedAt = p.CreatedAt,
|
||||
LastUsedAt = p.LastUsedAt,
|
||||
IsActive = p.IsActive
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private async Task<List<CredentialBackup>> ExportCredentialsAsync(bool onlyActive)
|
||||
{
|
||||
var query = _context.Credentials.AsQueryable();
|
||||
|
||||
if (onlyActive)
|
||||
query = query.Where(c => c.IsActive);
|
||||
|
||||
var credentials = await query.ToListAsync();
|
||||
|
||||
return credentials.Select(c => new CredentialBackup
|
||||
{
|
||||
Id = c.Id,
|
||||
Name = c.Name,
|
||||
Type = c.Type,
|
||||
DatabaseType = c.DatabaseType,
|
||||
Host = c.Host,
|
||||
Port = c.Port,
|
||||
DatabaseName = c.DatabaseName,
|
||||
Username = c.Username,
|
||||
// Password, API Keys e Token NON inclusi per sicurezza
|
||||
CommandTimeout = c.CommandTimeout,
|
||||
TimeoutSeconds = c.TimeoutSeconds,
|
||||
IgnoreSslErrors = c.IgnoreSslErrors,
|
||||
RestServiceType = c.RestServiceType,
|
||||
Headers = c.Headers,
|
||||
AdditionalParameters = c.AdditionalParameters,
|
||||
CreatedAt = c.CreatedAt,
|
||||
UpdatedAt = c.UpdatedAt,
|
||||
CreatedBy = c.CreatedBy,
|
||||
IsActive = c.IsActive
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private async Task<List<KeyAssociationBackup>> ExportKeyAssociationsAsync(bool onlyActive)
|
||||
{
|
||||
var query = _context.KeyAssociations.AsQueryable();
|
||||
|
||||
if (onlyActive)
|
||||
query = query.Where(ka => ka.IsActive);
|
||||
|
||||
var associations = await query.ToListAsync();
|
||||
|
||||
return associations.Select(ka => new KeyAssociationBackup
|
||||
{
|
||||
Id = ka.Id,
|
||||
KeyValue = ka.KeyValue,
|
||||
SourceKeyField = ka.SourceKeyField,
|
||||
DestinationKeyField = ka.DestinationKeyField,
|
||||
DestinationEntity = ka.DestinationEntity,
|
||||
DestinationId = ka.DestinationId,
|
||||
RestCredentialName = ka.RestCredentialName,
|
||||
CreatedAt = ka.CreatedAt,
|
||||
UpdatedAt = ka.UpdatedAt,
|
||||
LastVerifiedAt = ka.LastVerifiedAt,
|
||||
IsActive = ka.IsActive,
|
||||
DataHash = ka.Data_Hash,
|
||||
SourcesInfo = ka.SourcesInfo,
|
||||
AdditionalInfo = ka.AdditionalInfo
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
private Task<List<ProfileScheduleBackup>> ExportProfileSchedulesAsync(bool onlyActive)
|
||||
{
|
||||
// Nota: Assumendo che esista una tabella ProfileSchedules
|
||||
// Se non esiste, questo metodo restituirà una lista vuota
|
||||
var schedules = new List<ProfileScheduleBackup>();
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: Implementare quando la tabella ProfileSchedules sarà disponibile
|
||||
// var query = _context.ProfileSchedules.AsQueryable();
|
||||
// if (onlyActive)
|
||||
// query = query.Where(ps => ps.IsActive);
|
||||
// var profileSchedules = await query.ToListAsync();
|
||||
// schedules = profileSchedules.Select(ps => new ProfileScheduleBackup { ... }).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Tabella ProfileSchedules non disponibile, saltando export");
|
||||
}
|
||||
|
||||
return Task.FromResult(schedules);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Import Methods
|
||||
|
||||
private async Task<int> ImportProfilesAsync(List<DataCouplerProfileBackup> profiles, RestoreOptions options)
|
||||
{
|
||||
int imported = 0;
|
||||
|
||||
foreach (var profileBackup in profiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existing = await _context.DataCouplerProfiles
|
||||
.FirstOrDefaultAsync(p => p.Name == profileBackup.Name);
|
||||
|
||||
if (existing != null && !options.OverwriteExisting)
|
||||
{
|
||||
_logger.LogDebug("Profilo {Name} già esistente, saltando", profileBackup.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
var profile = new DataCouplerProfile
|
||||
{
|
||||
Name = profileBackup.Name,
|
||||
Description = profileBackup.Description,
|
||||
SourceType = profileBackup.SourceType,
|
||||
SourceDatabaseName = profileBackup.SourceDatabaseName,
|
||||
SourceSchema = profileBackup.SourceSchema,
|
||||
SourceTable = profileBackup.SourceTable,
|
||||
SourceCustomQuery = profileBackup.SourceCustomQuery,
|
||||
SourceFilePath = profileBackup.SourceFilePath,
|
||||
DestinationType = profileBackup.DestinationType,
|
||||
DestinationSchema = profileBackup.DestinationSchema,
|
||||
DestinationTable = profileBackup.DestinationTable,
|
||||
DestinationEndpoint = profileBackup.DestinationEndpoint,
|
||||
FieldMappingJson = profileBackup.FieldMappings != null ?
|
||||
System.Text.Json.JsonSerializer.Serialize(profileBackup.FieldMappings) :
|
||||
string.Empty,
|
||||
SourceKeyField = profileBackup.SourceKeyField,
|
||||
UseRecordAssociations = profileBackup.UseRecordAssociations,
|
||||
CreatedBy = options.ImportedBy ?? profileBackup.CreatedBy,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
IsActive = profileBackup.IsActive
|
||||
};
|
||||
|
||||
// Risolvi credential IDs per nome se esistenti
|
||||
if (!string.IsNullOrEmpty(profileBackup.SourceCredentialName))
|
||||
{
|
||||
var sourceCred = await _context.Credentials
|
||||
.FirstOrDefaultAsync(c => c.Name == profileBackup.SourceCredentialName);
|
||||
profile.SourceCredentialId = sourceCred?.Id;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(profileBackup.DestinationCredentialName))
|
||||
{
|
||||
var destCred = await _context.Credentials
|
||||
.FirstOrDefaultAsync(c => c.Name == profileBackup.DestinationCredentialName);
|
||||
profile.DestinationCredentialId = destCred?.Id;
|
||||
}
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
// Update existing
|
||||
_context.Entry(existing).CurrentValues.SetValues(profile);
|
||||
existing.Id = profileBackup.Id; // Mantieni ID originale se sovrascriviamo
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new
|
||||
_context.DataCouplerProfiles.Add(profile);
|
||||
}
|
||||
|
||||
imported++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Errore importazione profilo {Name}", profileBackup.Name);
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return imported;
|
||||
}
|
||||
|
||||
private async Task<int> ImportCredentialsAsync(List<CredentialBackup> credentials, RestoreOptions options)
|
||||
{
|
||||
int imported = 0;
|
||||
|
||||
foreach (var credBackup in credentials)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existing = await _context.Credentials
|
||||
.FirstOrDefaultAsync(c => c.Name == credBackup.Name);
|
||||
|
||||
if (existing != null && !options.OverwriteExisting)
|
||||
{
|
||||
_logger.LogDebug("Credenziale {Name} già esistente, saltando", credBackup.Name);
|
||||
continue;
|
||||
}
|
||||
|
||||
var credential = new CredentialEntity
|
||||
{
|
||||
Name = credBackup.Name,
|
||||
Type = credBackup.Type,
|
||||
DatabaseType = credBackup.DatabaseType,
|
||||
Host = credBackup.Host,
|
||||
Port = credBackup.Port,
|
||||
DatabaseName = credBackup.DatabaseName,
|
||||
Username = credBackup.Username,
|
||||
// Password, API Keys e Token dovranno essere riconfigurati manualmente
|
||||
CommandTimeout = credBackup.CommandTimeout,
|
||||
TimeoutSeconds = credBackup.TimeoutSeconds,
|
||||
IgnoreSslErrors = credBackup.IgnoreSslErrors,
|
||||
RestServiceType = credBackup.RestServiceType,
|
||||
Headers = credBackup.Headers,
|
||||
AdditionalParameters = credBackup.AdditionalParameters,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
CreatedBy = options.ImportedBy ?? credBackup.CreatedBy,
|
||||
IsActive = credBackup.IsActive
|
||||
};
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
// Update existing (preserva password esistenti)
|
||||
var oldPassword = existing.EncryptedPassword;
|
||||
var oldApiKey = existing.EncryptedApiKey;
|
||||
var oldAuthToken = existing.EncryptedAuthToken;
|
||||
|
||||
_context.Entry(existing).CurrentValues.SetValues(credential);
|
||||
existing.Id = credBackup.Id;
|
||||
existing.EncryptedPassword = oldPassword;
|
||||
existing.EncryptedApiKey = oldApiKey;
|
||||
existing.EncryptedAuthToken = oldAuthToken;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new
|
||||
_context.Credentials.Add(credential);
|
||||
}
|
||||
|
||||
imported++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Errore importazione credenziale {Name}", credBackup.Name);
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return imported;
|
||||
}
|
||||
|
||||
private async Task<int> ImportKeyAssociationsAsync(List<KeyAssociationBackup> associations, RestoreOptions options)
|
||||
{
|
||||
int imported = 0;
|
||||
|
||||
foreach (var assocBackup in associations)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existing = await _context.KeyAssociations
|
||||
.FirstOrDefaultAsync(ka => ka.KeyValue == assocBackup.KeyValue &&
|
||||
ka.DestinationEntity == assocBackup.DestinationEntity &&
|
||||
ka.RestCredentialName == assocBackup.RestCredentialName);
|
||||
|
||||
if (existing != null && !options.OverwriteExisting)
|
||||
{
|
||||
_logger.LogDebug("Associazione {Key}-{Entity} già esistente, saltando",
|
||||
assocBackup.KeyValue, assocBackup.DestinationEntity);
|
||||
continue;
|
||||
}
|
||||
|
||||
var association = new KeyAssociation
|
||||
{
|
||||
KeyValue = assocBackup.KeyValue,
|
||||
SourceKeyField = assocBackup.SourceKeyField,
|
||||
DestinationKeyField = assocBackup.DestinationKeyField,
|
||||
DestinationEntity = assocBackup.DestinationEntity,
|
||||
DestinationId = assocBackup.DestinationId,
|
||||
RestCredentialName = assocBackup.RestCredentialName,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
IsActive = assocBackup.IsActive,
|
||||
Data_Hash = assocBackup.DataHash,
|
||||
SourcesInfo = assocBackup.SourcesInfo,
|
||||
AdditionalInfo = assocBackup.AdditionalInfo
|
||||
};
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
// Update existing
|
||||
_context.Entry(existing).CurrentValues.SetValues(association);
|
||||
existing.Id = assocBackup.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add new
|
||||
_context.KeyAssociations.Add(association);
|
||||
}
|
||||
|
||||
imported++;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Errore importazione associazione {Key}-{Entity}",
|
||||
assocBackup.KeyValue, assocBackup.DestinationEntity);
|
||||
}
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return imported;
|
||||
}
|
||||
|
||||
private Task<int> ImportProfileSchedulesAsync(List<ProfileScheduleBackup> schedules, RestoreOptions options)
|
||||
{
|
||||
int imported = 0;
|
||||
|
||||
try
|
||||
{
|
||||
// TODO: Implementare quando la tabella ProfileSchedules sarà disponibile
|
||||
_logger.LogInformation("Import ProfileSchedules saltato - tabella non ancora implementata");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Errore importazione schedule profili");
|
||||
}
|
||||
|
||||
return Task.FromResult(imported);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user