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:
@@ -40,6 +40,7 @@ public static class CredentialManagerConfiguration
|
||||
services.AddScoped<IEncryptionService, EncryptionService>();
|
||||
services.AddScoped<ICredentialService, CredentialService>();
|
||||
services.AddScoped<IDatabaseInitializer, DatabaseInitializer>();
|
||||
services.AddScoped<IDataCouplerProfileService, DataCouplerProfileService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ public class CredentialDbContext : DbContext
|
||||
{
|
||||
public DbSet<CredentialEntity> Credentials { get; set; }
|
||||
public DbSet<KeyAssociation> KeyAssociations { get; set; }
|
||||
public DbSet<DataCouplerProfile> DataCouplerProfiles { get; set; }
|
||||
|
||||
public CredentialDbContext(DbContextOptions<CredentialDbContext> options) : base(options)
|
||||
{
|
||||
@@ -141,5 +142,80 @@ public class CredentialDbContext : DbContext
|
||||
entity.HasIndex(e => e.CreatedAt);
|
||||
entity.HasIndex(e => e.LastVerifiedAt);
|
||||
});
|
||||
|
||||
// Configurazione della tabella DataCouplerProfiles
|
||||
modelBuilder.Entity<DataCouplerProfile>(entity =>
|
||||
{
|
||||
entity.ToTable("DataCouplerProfiles");
|
||||
|
||||
entity.HasKey(e => e.Id);
|
||||
|
||||
entity.Property(e => e.Name)
|
||||
.IsRequired()
|
||||
.HasMaxLength(100);
|
||||
|
||||
entity.Property(e => e.Description)
|
||||
.HasMaxLength(500);
|
||||
|
||||
entity.Property(e => e.SourceType)
|
||||
.IsRequired()
|
||||
.HasMaxLength(20);
|
||||
|
||||
entity.Property(e => e.SourceSchema)
|
||||
.HasMaxLength(200);
|
||||
|
||||
entity.Property(e => e.SourceTable)
|
||||
.HasMaxLength(200);
|
||||
|
||||
entity.Property(e => e.SourceFilePath)
|
||||
.HasMaxLength(500);
|
||||
|
||||
entity.Property(e => e.DestinationType)
|
||||
.IsRequired()
|
||||
.HasMaxLength(20);
|
||||
|
||||
entity.Property(e => e.DestinationSchema)
|
||||
.HasMaxLength(200);
|
||||
|
||||
entity.Property(e => e.DestinationTable)
|
||||
.HasMaxLength(200);
|
||||
|
||||
entity.Property(e => e.DestinationEndpoint)
|
||||
.HasMaxLength(500);
|
||||
|
||||
entity.Property(e => e.FieldMappingJson)
|
||||
.HasMaxLength(4000);
|
||||
|
||||
entity.Property(e => e.CreatedBy)
|
||||
.HasMaxLength(100);
|
||||
|
||||
// Valori di default
|
||||
entity.Property(e => e.CreatedAt)
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
entity.Property(e => e.IsActive)
|
||||
.HasDefaultValue(true);
|
||||
|
||||
// Indici
|
||||
entity.HasIndex(e => e.Name)
|
||||
.IsUnique();
|
||||
|
||||
entity.HasIndex(e => e.SourceType);
|
||||
entity.HasIndex(e => e.DestinationType);
|
||||
entity.HasIndex(e => e.IsActive);
|
||||
entity.HasIndex(e => e.CreatedAt);
|
||||
entity.HasIndex(e => e.LastUsedAt);
|
||||
|
||||
// Relazioni con le credenziali
|
||||
entity.HasOne(e => e.SourceCredential)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.SourceCredentialId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
entity.HasOne(e => e.DestinationCredential)
|
||||
.WithMany()
|
||||
.HasForeignKey(e => e.DestinationCredentialId)
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
|
||||
namespace CredentialManager.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Factory per creare il DbContext durante la fase di design (migrations)
|
||||
/// </summary>
|
||||
public class CredentialDbContextFactory : IDesignTimeDbContextFactory<CredentialDbContext>
|
||||
{
|
||||
public CredentialDbContext CreateDbContext(string[] args)
|
||||
{
|
||||
var optionsBuilder = new DbContextOptionsBuilder<CredentialDbContext>();
|
||||
|
||||
// Usa un database SQLite temporaneo per le migrations
|
||||
var connectionString = "Data Source=design_time_temp.db";
|
||||
optionsBuilder.UseSqlite(connectionString);
|
||||
|
||||
return new CredentialDbContext(optionsBuilder.Options);
|
||||
}
|
||||
}
|
||||
+326
@@ -0,0 +1,326 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using CredentialManager.Data;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CredentialManager.Migrations
|
||||
{
|
||||
[DbContext(typeof(CredentialDbContext))]
|
||||
[Migration("20250701203438_AddDataCouplerProfiles")]
|
||||
partial class AddDataCouplerProfiles
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
|
||||
|
||||
modelBuilder.Entity("CredentialManager.Models.CredentialEntity", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AdditionalParameters")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("CommandTimeout")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(30);
|
||||
|
||||
b.Property<string>("ConnectionString")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DatabaseName")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DatabaseType")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EncryptedApiKey")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EncryptedAuthToken")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("EncryptedPassword")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Headers")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Host")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IgnoreSslErrors")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(false);
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("Port")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("RestServiceType")
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int>("TimeoutSeconds")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(100);
|
||||
|
||||
b.Property<string>("Type")
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Username")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DatabaseType");
|
||||
|
||||
b.HasIndex("IsActive");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Type");
|
||||
|
||||
b.ToTable("Credentials", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("DestinationCredentialId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("DestinationEndpoint")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DestinationSchema")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DestinationTable")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DestinationType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FieldMappingJson")
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<DateTime?>("LastUsedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("SourceCredentialId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SourceFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SourceSchema")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SourceTable")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SourceType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("DestinationCredentialId");
|
||||
|
||||
b.HasIndex("DestinationType");
|
||||
|
||||
b.HasIndex("IsActive");
|
||||
|
||||
b.HasIndex("LastUsedAt");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("SourceCredentialId");
|
||||
|
||||
b.HasIndex("SourceType");
|
||||
|
||||
b.ToTable("DataCouplerProfiles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CredentialManager.Models.KeyAssociation", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("AdditionalInfo")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DestinationEntity")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DestinationId")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DestinationKeyField")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<string>("KeyValue")
|
||||
.IsRequired()
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("LastVerifiedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("RestCredentialName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SourceKeyField")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SourcesInfo")
|
||||
.HasMaxLength(2000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("DestinationEntity");
|
||||
|
||||
b.HasIndex("IsActive");
|
||||
|
||||
b.HasIndex("KeyValue")
|
||||
.HasDatabaseName("IX_KeyAssociations_KeyValue");
|
||||
|
||||
b.HasIndex("LastVerifiedAt");
|
||||
|
||||
b.HasIndex("RestCredentialName");
|
||||
|
||||
b.HasIndex("KeyValue", "DestinationEntity", "RestCredentialName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_KeyAssociations_Unique");
|
||||
|
||||
b.ToTable("KeyAssociations", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
|
||||
{
|
||||
b.HasOne("CredentialManager.Models.CredentialEntity", "DestinationCredential")
|
||||
.WithMany()
|
||||
.HasForeignKey("DestinationCredentialId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("CredentialManager.Models.CredentialEntity", "SourceCredential")
|
||||
.WithMany()
|
||||
.HasForeignKey("SourceCredentialId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("DestinationCredential");
|
||||
|
||||
b.Navigation("SourceCredential");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace CredentialManager.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddDataCouplerProfiles : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DataCouplerProfiles",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
Name = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||
Description = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
||||
SourceType = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false),
|
||||
SourceCredentialId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
SourceSchema = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||
SourceTable = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||
SourceFilePath = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
||||
DestinationType = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false),
|
||||
DestinationCredentialId = table.Column<int>(type: "INTEGER", nullable: true),
|
||||
DestinationSchema = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||
DestinationTable = table.Column<string>(type: "TEXT", maxLength: 200, nullable: true),
|
||||
DestinationEndpoint = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
|
||||
FieldMappingJson = table.Column<string>(type: "TEXT", maxLength: 4000, nullable: true),
|
||||
CreatedBy = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
|
||||
LastUsedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
IsActive = table.Column<bool>(type: "INTEGER", nullable: false, defaultValue: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DataCouplerProfiles", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_DataCouplerProfiles_Credentials_DestinationCredentialId",
|
||||
column: x => x.DestinationCredentialId,
|
||||
principalTable: "Credentials",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
table.ForeignKey(
|
||||
name: "FK_DataCouplerProfiles_Credentials_SourceCredentialId",
|
||||
column: x => x.SourceCredentialId,
|
||||
principalTable: "Credentials",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.SetNull);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DataCouplerProfiles_CreatedAt",
|
||||
table: "DataCouplerProfiles",
|
||||
column: "CreatedAt");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DataCouplerProfiles_DestinationCredentialId",
|
||||
table: "DataCouplerProfiles",
|
||||
column: "DestinationCredentialId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DataCouplerProfiles_DestinationType",
|
||||
table: "DataCouplerProfiles",
|
||||
column: "DestinationType");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DataCouplerProfiles_IsActive",
|
||||
table: "DataCouplerProfiles",
|
||||
column: "IsActive");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DataCouplerProfiles_LastUsedAt",
|
||||
table: "DataCouplerProfiles",
|
||||
column: "LastUsedAt");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DataCouplerProfiles_Name",
|
||||
table: "DataCouplerProfiles",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DataCouplerProfiles_SourceCredentialId",
|
||||
table: "DataCouplerProfiles",
|
||||
column: "SourceCredentialId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DataCouplerProfiles_SourceType",
|
||||
table: "DataCouplerProfiles",
|
||||
column: "SourceType");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "DataCouplerProfiles");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ namespace CredentialManager.Migrations
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.6");
|
||||
modelBuilder.HasAnnotation("ProductVersion", "9.0.0");
|
||||
|
||||
modelBuilder.Entity("CredentialManager.Models.CredentialEntity", b =>
|
||||
{
|
||||
@@ -123,6 +123,104 @@ namespace CredentialManager.Migrations
|
||||
b.ToTable("Credentials", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("TEXT")
|
||||
.HasDefaultValueSql("CURRENT_TIMESTAMP");
|
||||
|
||||
b.Property<string>("CreatedBy")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("DestinationCredentialId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("DestinationEndpoint")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DestinationSchema")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DestinationTable")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("DestinationType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("FieldMappingJson")
|
||||
.HasMaxLength(4000)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<bool>("IsActive")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("INTEGER")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<DateTime?>("LastUsedAt")
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<int?>("SourceCredentialId")
|
||||
.HasColumnType("INTEGER");
|
||||
|
||||
b.Property<string>("SourceFilePath")
|
||||
.HasMaxLength(500)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SourceSchema")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SourceTable")
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.Property<string>("SourceType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("TEXT");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CreatedAt");
|
||||
|
||||
b.HasIndex("DestinationCredentialId");
|
||||
|
||||
b.HasIndex("DestinationType");
|
||||
|
||||
b.HasIndex("IsActive");
|
||||
|
||||
b.HasIndex("LastUsedAt");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("SourceCredentialId");
|
||||
|
||||
b.HasIndex("SourceType");
|
||||
|
||||
b.ToTable("DataCouplerProfiles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CredentialManager.Models.KeyAssociation", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
@@ -202,6 +300,23 @@ namespace CredentialManager.Migrations
|
||||
|
||||
b.ToTable("KeyAssociations", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("CredentialManager.Models.DataCouplerProfile", b =>
|
||||
{
|
||||
b.HasOne("CredentialManager.Models.CredentialEntity", "DestinationCredential")
|
||||
.WithMany()
|
||||
.HasForeignKey("DestinationCredentialId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.HasOne("CredentialManager.Models.CredentialEntity", "SourceCredential")
|
||||
.WithMany()
|
||||
.HasForeignKey("SourceCredentialId")
|
||||
.OnDelete(DeleteBehavior.SetNull);
|
||||
|
||||
b.Navigation("DestinationCredential");
|
||||
|
||||
b.Navigation("SourceCredential");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace CredentialManager.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Modello per salvare le configurazioni dei profili di Data Coupler
|
||||
/// </summary>
|
||||
public class DataCouplerProfile
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(100)]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? Description { get; set; }
|
||||
|
||||
// Configurazione Fonte Dati
|
||||
[Required]
|
||||
[MaxLength(20)]
|
||||
public string SourceType { get; set; } = string.Empty; // "database" o "file"
|
||||
|
||||
public int? SourceCredentialId { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
public string? SourceSchema { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
public string? SourceTable { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? SourceFilePath { get; set; }
|
||||
|
||||
// Configurazione Destinazione
|
||||
[Required]
|
||||
[MaxLength(20)]
|
||||
public string DestinationType { get; set; } = string.Empty; // "database" o "rest"
|
||||
|
||||
public int? DestinationCredentialId { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
public string? DestinationSchema { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
public string? DestinationTable { get; set; }
|
||||
|
||||
[MaxLength(500)]
|
||||
public string? DestinationEndpoint { get; set; }
|
||||
|
||||
// Mapping dei campi salvato come JSON
|
||||
[MaxLength(4000)]
|
||||
public string? FieldMappingJson { get; set; }
|
||||
|
||||
// Metadati
|
||||
[MaxLength(100)]
|
||||
public string? CreatedBy { get; set; }
|
||||
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
public DateTime? LastUsedAt { get; set; }
|
||||
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
// Relazioni opzionali con le credenziali
|
||||
[ForeignKey(nameof(SourceCredentialId))]
|
||||
public virtual CredentialEntity? SourceCredential { get; set; }
|
||||
|
||||
[ForeignKey(nameof(DestinationCredentialId))]
|
||||
public virtual CredentialEntity? DestinationCredential { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
namespace CredentialManager.Models;
|
||||
|
||||
/// <summary>
|
||||
/// DTO per la creazione/aggiornamento di un profilo DataCoupler
|
||||
/// </summary>
|
||||
public class DataCouplerProfileDto
|
||||
{
|
||||
public int? Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
|
||||
// Informazioni sorgente
|
||||
public string SourceType { get; set; } = string.Empty;
|
||||
public int? SourceCredentialId { get; set; }
|
||||
public string? SourceSchema { get; set; }
|
||||
public string? SourceTable { get; set; }
|
||||
public string? SourceFilePath { get; set; }
|
||||
|
||||
// Informazioni destinazione
|
||||
public string DestinationType { get; set; } = string.Empty;
|
||||
public int? DestinationCredentialId { get; set; }
|
||||
public string? DestinationSchema { get; set; }
|
||||
public string? DestinationTable { get; set; }
|
||||
public string? DestinationEndpoint { get; set; }
|
||||
|
||||
// Mapping dei campi
|
||||
public List<FieldMappingDto>? FieldMappings { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO per il mapping dei campi
|
||||
/// </summary>
|
||||
public class FieldMappingDto
|
||||
{
|
||||
public string SourceField { get; set; } = string.Empty;
|
||||
public string DestinationField { get; set; } = string.Empty;
|
||||
public string? DataType { get; set; }
|
||||
public bool IsKey { get; set; }
|
||||
public bool IsRequired { get; set; }
|
||||
public string? DefaultValue { get; set; }
|
||||
public string? Transformation { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// DTO per la visualizzazione di un profilo nella lista
|
||||
/// </summary>
|
||||
public class DataCouplerProfileSummaryDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public string? Description { get; set; }
|
||||
public string SourceType { get; set; } = string.Empty;
|
||||
public string? SourceName { get; set; }
|
||||
public string DestinationType { get; set; } = string.Empty;
|
||||
public string? DestinationName { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? LastUsedAt { get; set; }
|
||||
public string? CreatedBy { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using CredentialManager.Models;
|
||||
|
||||
namespace CredentialManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Interfaccia per il servizio di gestione dei profili Data Coupler
|
||||
/// </summary>
|
||||
public interface IDataCouplerProfileService
|
||||
{
|
||||
/// <summary>
|
||||
/// Ottiene tutti i profili attivi
|
||||
/// </summary>
|
||||
Task<IEnumerable<DataCouplerProfile>> GetAllProfilesAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene un profilo per ID
|
||||
/// </summary>
|
||||
Task<DataCouplerProfile?> GetProfileByIdAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene un profilo per nome
|
||||
/// </summary>
|
||||
Task<DataCouplerProfile?> GetProfileByNameAsync(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Salva un nuovo profilo
|
||||
/// </summary>
|
||||
Task<DataCouplerProfile> SaveProfileAsync(DataCouplerProfile profile);
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna un profilo esistente
|
||||
/// </summary>
|
||||
Task<DataCouplerProfile> UpdateProfileAsync(DataCouplerProfile profile);
|
||||
|
||||
/// <summary>
|
||||
/// Elimina un profilo
|
||||
/// </summary>
|
||||
Task<bool> DeleteProfileAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna la data di ultimo utilizzo di un profilo
|
||||
/// </summary>
|
||||
Task UpdateLastUsedAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Verifica se esiste un profilo con il nome specificato
|
||||
/// </summary>
|
||||
Task<bool> ProfileExistsAsync(string name, int? excludeId = null);
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user