diff --git a/CredentialManager/Migrations/20251019220512_AddMappedDestinationFieldToKeyAssociation.Designer.cs b/CredentialManager/Migrations/20251019220512_AddMappedDestinationFieldToKeyAssociation.Designer.cs new file mode 100644 index 0000000..631b8b9 --- /dev/null +++ b/CredentialManager/Migrations/20251019220512_AddMappedDestinationFieldToKeyAssociation.Designer.cs @@ -0,0 +1,555 @@ +// +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("20251019220512_AddMappedDestinationFieldToKeyAssociation")] + partial class AddMappedDestinationFieldToKeyAssociation + { + /// + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdditionalParameters") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CommandTimeout") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(30); + + b.Property("ConnectionString") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DatabaseName") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DatabaseType") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("EncryptedApiKey") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("EncryptedAuthToken") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("EncryptedPassword") + .HasColumnType("TEXT"); + + b.Property("Headers") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("Host") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("IgnoreSslErrors") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(false); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("RestServiceType") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("TimeoutSeconds") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(100); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT") + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + b.Property("CreatedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("DestinationCredentialId") + .HasColumnType("INTEGER"); + + b.Property("DestinationEndpoint") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("DestinationSchema") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("DestinationTable") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("DestinationType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("FieldMappingJson") + .HasMaxLength(4000) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("LastUsedAt") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SourceCredentialId") + .HasColumnType("INTEGER"); + + b.Property("SourceCustomQuery") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("SourceDatabaseName") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SourceFilePath") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("SourceKeyField") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SourceSchema") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SourceTable") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SourceType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("UseRecordAssociations") + .HasColumnType("INTEGER"); + + 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("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdditionalInfo") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("Data_Hash") + .HasMaxLength(64) + .HasColumnType("TEXT"); + + b.Property("DestinationEntity") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("DestinationId") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("DestinationKeyField") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("IsActive") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER") + .HasDefaultValue(true); + + b.Property("KeyValue") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("LastVerifiedAt") + .HasColumnType("TEXT"); + + b.Property("MappedDestinationField") + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RestCredentialName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("SourceKeyField") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("SourcesInfo") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("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.ProfileSchedule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("CreatedBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("DailyTime") + .HasMaxLength(10) + .HasColumnType("TEXT"); + + b.Property("DayOfMonth") + .HasColumnType("INTEGER"); + + b.Property("DayOfWeek") + .HasColumnType("INTEGER"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("DestinationDatabaseOverride") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("ExecutionCount") + .HasColumnType("INTEGER"); + + b.Property("IntervalUnit") + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("IntervalValue") + .HasColumnType("INTEGER"); + + b.Property("IsActive") + .HasColumnType("INTEGER"); + + b.Property("IsEnabled") + .HasColumnType("INTEGER"); + + b.Property("LastExecutionMessage") + .HasMaxLength(1000) + .HasColumnType("TEXT"); + + b.Property("LastExecutionRecordCount") + .HasColumnType("INTEGER"); + + b.Property("LastExecutionStatus") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("LastExecutionTime") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("NextExecutionTime") + .HasColumnType("TEXT"); + + b.Property("ProfileId") + .HasColumnType("INTEGER"); + + b.Property("ScheduleType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("ScheduledDateTime") + .HasColumnType("TEXT"); + + b.Property("SourceDatabaseOverride") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.Property("UpdatedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId"); + + b.ToTable("ProfileSchedules"); + }); + + modelBuilder.Entity("CredentialManager.Models.ScheduleExecutionHistory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdditionalInfo") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("CreatedAt") + .HasColumnType("TEXT"); + + b.Property("DestinationInfo") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("DestinationType") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("EndTime") + .HasColumnType("TEXT"); + + b.Property("ErrorDetails") + .HasMaxLength(5000) + .HasColumnType("TEXT"); + + b.Property("Message") + .HasMaxLength(2000) + .HasColumnType("TEXT"); + + b.Property("ProfileId") + .HasColumnType("INTEGER"); + + b.Property("ProfileName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("TEXT"); + + b.Property("RecordsProcessed") + .HasColumnType("INTEGER"); + + b.Property("RecordsWithErrors") + .HasColumnType("INTEGER"); + + b.Property("ScheduleId") + .HasColumnType("INTEGER"); + + b.Property("SourceInfo") + .HasMaxLength(500) + .HasColumnType("TEXT"); + + b.Property("SourceType") + .HasMaxLength(50) + .HasColumnType("TEXT"); + + b.Property("StartTime") + .HasColumnType("TEXT"); + + b.Property("Status") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TriggerType") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.Property("TriggeredBy") + .HasMaxLength(100) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("ProfileId"); + + b.HasIndex("ScheduleId"); + + b.HasIndex("StartTime"); + + b.HasIndex("Status"); + + b.HasIndex("TriggerType"); + + b.ToTable("ScheduleExecutionHistories", (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"); + }); + + modelBuilder.Entity("CredentialManager.Models.ProfileSchedule", b => + { + b.HasOne("CredentialManager.Models.DataCouplerProfile", "Profile") + .WithMany() + .HasForeignKey("ProfileId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Profile"); + }); + + modelBuilder.Entity("CredentialManager.Models.ScheduleExecutionHistory", b => + { + b.HasOne("CredentialManager.Models.ProfileSchedule", "Schedule") + .WithMany() + .HasForeignKey("ScheduleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Schedule"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/CredentialManager/Migrations/20251019220512_AddMappedDestinationFieldToKeyAssociation.cs b/CredentialManager/Migrations/20251019220512_AddMappedDestinationFieldToKeyAssociation.cs new file mode 100644 index 0000000..dc61d06 --- /dev/null +++ b/CredentialManager/Migrations/20251019220512_AddMappedDestinationFieldToKeyAssociation.cs @@ -0,0 +1,29 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CredentialManager.Migrations +{ + /// + public partial class AddMappedDestinationFieldToKeyAssociation : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "MappedDestinationField", + table: "KeyAssociations", + type: "TEXT", + maxLength: 200, + nullable: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "MappedDestinationField", + table: "KeyAssociations"); + } + } +} diff --git a/CredentialManager/Migrations/CredentialDbContextModelSnapshot.cs b/CredentialManager/Migrations/CredentialDbContextModelSnapshot.cs index 2685223..bd4495b 100644 --- a/CredentialManager/Migrations/CredentialDbContextModelSnapshot.cs +++ b/CredentialManager/Migrations/CredentialDbContextModelSnapshot.cs @@ -281,6 +281,10 @@ namespace CredentialManager.Migrations b.Property("LastVerifiedAt") .HasColumnType("TEXT"); + b.Property("MappedDestinationField") + .HasMaxLength(200) + .HasColumnType("TEXT"); + b.Property("RestCredentialName") .IsRequired() .HasMaxLength(100) diff --git a/CredentialManager/Models/KeyAssociation.cs b/CredentialManager/Models/KeyAssociation.cs index a3db5d4..8bc7e27 100644 --- a/CredentialManager/Models/KeyAssociation.cs +++ b/CredentialManager/Models/KeyAssociation.cs @@ -36,6 +36,14 @@ public class KeyAssociation [MaxLength(200)] public string DestinationKeyField { get; set; } = string.Empty; + /// + /// Nome del campo di destinazione mappato alla chiave sorgente + /// (es: se dalla sorgente mappo "CardCode" verso "cardcode__c" in Salesforce, questo campo conterrà "cardcode__c") + /// Questo è il campo personalizzato nella destinazione, mentre DestinationKeyField è tipicamente l'ID + /// + [MaxLength(200)] + public string? MappedDestinationField { get; set; } + /// /// Nome dell'entità di destinazione /// diff --git a/CredentialManager/Services/KeyAssociationService.cs b/CredentialManager/Services/KeyAssociationService.cs index e8404e3..353468b 100644 --- a/CredentialManager/Services/KeyAssociationService.cs +++ b/CredentialManager/Services/KeyAssociationService.cs @@ -165,6 +165,7 @@ public class KeyAssociationService : IKeyAssociationService var restCredentialName = association.RestCredentialName; var sourceKeyField = association.SourceKeyField; var destinationKeyField = association.DestinationKeyField; + var mappedDestinationField = association.MappedDestinationField; // AGGIUNTO var additionalInfo = association.AdditionalInfo; var dataHash = association.Data_Hash; var currentTime = DateTime.UtcNow; @@ -178,8 +179,8 @@ public class KeyAssociationService : IKeyAssociationService try { - _logger.LogDebug("PARALLEL: Tentativo salvataggio associazione - KeyValue: '{KeyValue}', DestinationEntity: '{DestinationEntity}', DestinationId: '{DestinationId}', RestCredentialName: '{RestCredentialName}'", - keyValue, destinationEntity, destinationId, restCredentialName); + _logger.LogDebug("PARALLEL: Tentativo salvataggio associazione - KeyValue: '{KeyValue}', DestinationEntity: '{DestinationEntity}', DestinationId: '{DestinationId}', RestCredentialName: '{RestCredentialName}', MappedField: '{MappedField}'", + keyValue, destinationEntity, destinationId, restCredentialName, mappedDestinationField ?? "NULL"); // Implementazione thread-safe usando upsert pattern con DbContext separato // Prima tenta di aggiornare un record esistente @@ -191,12 +192,13 @@ public class KeyAssociationService : IKeyAssociationService UpdatedAt = {3}, LastVerifiedAt = {4}, AdditionalInfo = {5}, - Data_Hash = {6} - WHERE KeyValue = {7} - AND DestinationEntity = {8} - AND RestCredentialName = {9} + Data_Hash = {6}, + MappedDestinationField = {7} + WHERE KeyValue = {8} + AND DestinationEntity = {9} + AND RestCredentialName = {10} AND IsActive = 1", - destinationId, sourceKeyField, destinationKeyField, currentTime, currentTime, additionalInfo ?? (object)DBNull.Value, dataHash ?? (object)DBNull.Value, + destinationId, sourceKeyField, destinationKeyField, currentTime, currentTime, additionalInfo ?? (object)DBNull.Value, dataHash ?? (object)DBNull.Value, mappedDestinationField ?? (object)DBNull.Value, keyValue, destinationEntity, restCredentialName); if (rowsAffected > 0) @@ -230,6 +232,7 @@ public class KeyAssociationService : IKeyAssociationService KeyValue = keyValue, SourceKeyField = sourceKeyField, DestinationKeyField = destinationKeyField, + MappedDestinationField = mappedDestinationField, // AGGIUNTO DestinationEntity = destinationEntity, DestinationId = destinationId, RestCredentialName = restCredentialName, @@ -243,8 +246,8 @@ public class KeyAssociationService : IKeyAssociationService parallelContext.KeyAssociations.Add(newAssociation); await parallelContext.SaveChangesAsync(); - _logger.LogDebug("PARALLEL: Nuova associazione creata: KeyValue={KeyValue} -> {DestinationEntity}/{DestinationId}", - keyValue, destinationEntity, destinationId); + _logger.LogDebug("PARALLEL: Nuova associazione creata: KeyValue={KeyValue} -> {DestinationEntity}/{DestinationId}, MappedField={MappedField}", + keyValue, destinationEntity, destinationId, mappedDestinationField ?? "NULL"); return newAssociation.Id; } diff --git a/CredentialManager/design_time_temp.db b/CredentialManager/design_time_temp.db index 87ce085..9beea66 100644 Binary files a/CredentialManager/design_time_temp.db and b/CredentialManager/design_time_temp.db differ diff --git a/Data_Coupler/BackgrounServices/BackgroundServices.cs b/Data_Coupler/BackgrounServices/BackgroundServices.cs deleted file mode 100644 index f44aeb6..0000000 --- a/Data_Coupler/BackgrounServices/BackgroundServices.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -namespace Data_Coupler.BackgrounServices; - -public class BackgroundServices : BackgroundService -{ - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - try - { - // Qui puoi inserire il codice che vuoi eseguire in background - // Ad esempio, puoi chiamare un metodo per eseguire operazioni periodiche - - // Simula un'attività di lunga durata - await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken); - } - catch (OperationCanceledException) - { - // Gestisci l'eccezione se il task viene cancellato - break; - } - catch (Exception ex) - { - // Gestisci altre eccezioni - Console.WriteLine($"Errore: {ex.Message}"); - } - } - } -} diff --git a/Data_Coupler/Pages/DataCoupler.razor b/Data_Coupler/Pages/DataCoupler.razor index 54233e8..51e6edc 100644 --- a/Data_Coupler/Pages/DataCoupler.razor +++ b/Data_Coupler/Pages/DataCoupler.razor @@ -1056,6 +1056,16 @@ } + + @* Messaggio di errore per trasferimento disabilitato *@ + @if (!IsTransferButtonEnabled() && !string.IsNullOrEmpty(GetTransferDisabledReason())) + { + + } + @if (!string.IsNullOrEmpty(transferMessage)) {