feat: Aggiunto sistema completo di gestione profili per Data Coupler
- Creata nuova libreria Components con componenti Blazor riutilizzabili * ProfileSelector: dropdown per selezione profili salvati * ProfileSaver: componente per salvare configurazioni correnti come profili * ProfileManagement: modale per gestione profili salvati * ProfileQuickActions: bottoni azioni rapide per operazioni sui profili - Esteso CredentialManager con entità e servizi per DataCouplerProfile * Aggiunto modello DataCouplerProfile con configurazioni mapping e metadati * Implementata migrazione Entity Framework per memorizzazione profili * Creato DataCouplerProfileService per operazioni CRUD * Aggiunto CredentialDbContextFactory per operazioni database design-time - Migliorato componente principale DataCoupler con integrazione profili * Integrata funzionalità caricamento/salvataggio profili * Aggiunto selettore profili nella parte superiore dell'interfaccia * Mantenuta retrocompatibilità con funzionalità esistenti * Migliorata esperienza utente con gestione configurazioni salvate - Aggiornata struttura progetto e dipendenze * Aggiunto progetto Components alla soluzione * Aggiornati riferimenti progetti e import * Rimosso progetto obsoleto TestDatabaseFix Questo aggiornamento migliora significativamente il flusso di lavoro permettendo agli utenti di salvare, caricare e gestire configurazioni complete di accoppiamento dati come
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using CredentialManager.Data;
|
||||
using CredentialManager.Models;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace CredentialManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Implementazione del servizio per la gestione dei profili Data Coupler
|
||||
/// </summary>
|
||||
public class DataCouplerProfileService : IDataCouplerProfileService
|
||||
{
|
||||
private readonly CredentialDbContext _context;
|
||||
|
||||
public DataCouplerProfileService(CredentialDbContext context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene tutti i profili attivi
|
||||
/// </summary>
|
||||
public async Task<IEnumerable<DataCouplerProfile>> GetAllProfilesAsync()
|
||||
{
|
||||
return await _context.DataCouplerProfiles
|
||||
.Include(p => p.SourceCredential)
|
||||
.Include(p => p.DestinationCredential)
|
||||
.Where(p => p.IsActive)
|
||||
.OrderByDescending(p => p.LastUsedAt)
|
||||
.ThenByDescending(p => p.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene un profilo per ID
|
||||
/// </summary>
|
||||
public async Task<DataCouplerProfile?> GetProfileByIdAsync(int id)
|
||||
{
|
||||
return await _context.DataCouplerProfiles
|
||||
.Include(p => p.SourceCredential)
|
||||
.Include(p => p.DestinationCredential)
|
||||
.FirstOrDefaultAsync(p => p.Id == id && p.IsActive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene un profilo per nome
|
||||
/// </summary>
|
||||
public async Task<DataCouplerProfile?> GetProfileByNameAsync(string name)
|
||||
{
|
||||
return await _context.DataCouplerProfiles
|
||||
.Include(p => p.SourceCredential)
|
||||
.Include(p => p.DestinationCredential)
|
||||
.FirstOrDefaultAsync(p => p.Name == name && p.IsActive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Salva un nuovo profilo
|
||||
/// </summary>
|
||||
public async Task<DataCouplerProfile> SaveProfileAsync(DataCouplerProfile profile)
|
||||
{
|
||||
profile.CreatedAt = DateTime.UtcNow;
|
||||
profile.IsActive = true;
|
||||
|
||||
_context.DataCouplerProfiles.Add(profile);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna un profilo esistente
|
||||
/// </summary>
|
||||
public async Task<DataCouplerProfile> UpdateProfileAsync(DataCouplerProfile profile)
|
||||
{
|
||||
var existingProfile = await _context.DataCouplerProfiles
|
||||
.FirstOrDefaultAsync(p => p.Id == profile.Id);
|
||||
|
||||
if (existingProfile == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Profilo con ID {profile.Id} non trovato");
|
||||
}
|
||||
|
||||
// Aggiorna le proprietà
|
||||
existingProfile.Name = profile.Name;
|
||||
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;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
return existingProfile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elimina un profilo (soft delete)
|
||||
/// </summary>
|
||||
public async Task<bool> DeleteProfileAsync(int id)
|
||||
{
|
||||
var profile = await _context.DataCouplerProfiles
|
||||
.FirstOrDefaultAsync(p => p.Id == id);
|
||||
|
||||
if (profile == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
profile.IsActive = false;
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna la data di ultimo utilizzo di un profilo
|
||||
/// </summary>
|
||||
public async Task UpdateLastUsedAsync(int id)
|
||||
{
|
||||
var profile = await _context.DataCouplerProfiles
|
||||
.FirstOrDefaultAsync(p => p.Id == id);
|
||||
|
||||
if (profile != null)
|
||||
{
|
||||
profile.LastUsedAt = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifica se esiste un profilo con il nome specificato
|
||||
/// </summary>
|
||||
public async Task<bool> ProfileExistsAsync(string name, int? excludeId = null)
|
||||
{
|
||||
var query = _context.DataCouplerProfiles
|
||||
.Where(p => p.Name == name && p.IsActive);
|
||||
|
||||
if (excludeId.HasValue)
|
||||
{
|
||||
query = query.Where(p => p.Id != excludeId.Value);
|
||||
}
|
||||
|
||||
return await query.AnyAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializza la lista di mapping dei campi in JSON
|
||||
/// </summary>
|
||||
public string SerializeFieldMappings(List<FieldMappingDto>? mappings)
|
||||
{
|
||||
if (mappings == null || !mappings.Any())
|
||||
return string.Empty;
|
||||
|
||||
return JsonSerializer.Serialize(mappings, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deserializza il JSON dei mapping dei campi
|
||||
/// </summary>
|
||||
public List<FieldMappingDto> DeserializeFieldMappings(string? json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return new List<FieldMappingDto>();
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<List<FieldMappingDto>>(json, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||
}) ?? new List<FieldMappingDto>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return new List<FieldMappingDto>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converte un DataCouplerProfile in DTO
|
||||
/// </summary>
|
||||
public DataCouplerProfileDto ToDto(DataCouplerProfile profile)
|
||||
{
|
||||
return new DataCouplerProfileDto
|
||||
{
|
||||
Id = profile.Id,
|
||||
Name = profile.Name,
|
||||
Description = profile.Description,
|
||||
SourceType = profile.SourceType,
|
||||
SourceCredentialId = profile.SourceCredentialId,
|
||||
SourceSchema = profile.SourceSchema,
|
||||
SourceTable = profile.SourceTable,
|
||||
SourceFilePath = profile.SourceFilePath,
|
||||
DestinationType = profile.DestinationType,
|
||||
DestinationCredentialId = profile.DestinationCredentialId,
|
||||
DestinationSchema = profile.DestinationSchema,
|
||||
DestinationTable = profile.DestinationTable,
|
||||
DestinationEndpoint = profile.DestinationEndpoint,
|
||||
FieldMappings = DeserializeFieldMappings(profile.FieldMappingJson)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converte un DTO in DataCouplerProfile
|
||||
/// </summary>
|
||||
public DataCouplerProfile FromDto(DataCouplerProfileDto dto, string? createdBy = null)
|
||||
{
|
||||
return new DataCouplerProfile
|
||||
{
|
||||
Id = dto.Id ?? 0,
|
||||
Name = dto.Name,
|
||||
Description = dto.Description,
|
||||
SourceType = dto.SourceType,
|
||||
SourceCredentialId = dto.SourceCredentialId,
|
||||
SourceSchema = dto.SourceSchema,
|
||||
SourceTable = dto.SourceTable,
|
||||
SourceFilePath = dto.SourceFilePath,
|
||||
DestinationType = dto.DestinationType,
|
||||
DestinationCredentialId = dto.DestinationCredentialId,
|
||||
DestinationSchema = dto.DestinationSchema,
|
||||
DestinationTable = dto.DestinationTable,
|
||||
DestinationEndpoint = dto.DestinationEndpoint,
|
||||
FieldMappingJson = SerializeFieldMappings(dto.FieldMappings),
|
||||
CreatedBy = createdBy
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user