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:
2025-10-02 01:12:39 +02:00
parent b76a6760fb
commit d042863a56
71 changed files with 17860 additions and 144 deletions
@@ -11,6 +11,8 @@ public class CredentialDbContext : DbContext
public DbSet<CredentialEntity> Credentials { get; set; }
public DbSet<KeyAssociation> KeyAssociations { get; set; }
public DbSet<DataCouplerProfile> DataCouplerProfiles { get; set; }
public DbSet<ProfileSchedule> ProfileSchedules { get; set; }
public DbSet<ScheduleExecutionHistory> ScheduleExecutionHistories { get; set; }
public CredentialDbContext(DbContextOptions<CredentialDbContext> options) : base(options)
{
@@ -217,5 +219,62 @@ public class CredentialDbContext : DbContext
.HasForeignKey(e => e.DestinationCredentialId)
.OnDelete(DeleteBehavior.SetNull);
});
// Configurazione della tabella ScheduleExecutionHistories
modelBuilder.Entity<ScheduleExecutionHistory>(entity =>
{
entity.ToTable("ScheduleExecutionHistories");
entity.HasKey(e => e.Id);
entity.Property(e => e.ProfileName)
.IsRequired()
.HasMaxLength(200);
entity.Property(e => e.Status)
.IsRequired()
.HasMaxLength(20);
entity.Property(e => e.Message)
.HasMaxLength(2000);
entity.Property(e => e.ErrorDetails)
.HasMaxLength(5000);
entity.Property(e => e.TriggerType)
.IsRequired()
.HasMaxLength(20);
entity.Property(e => e.TriggeredBy)
.HasMaxLength(100);
entity.Property(e => e.SourceType)
.HasMaxLength(50);
entity.Property(e => e.DestinationType)
.HasMaxLength(50);
entity.Property(e => e.SourceInfo)
.HasMaxLength(500);
entity.Property(e => e.DestinationInfo)
.HasMaxLength(500);
entity.Property(e => e.AdditionalInfo)
.HasMaxLength(2000);
// Indici
entity.HasIndex(e => e.ScheduleId);
entity.HasIndex(e => e.ProfileId);
entity.HasIndex(e => e.Status);
entity.HasIndex(e => e.StartTime);
entity.HasIndex(e => e.TriggerType);
// Relazione con ProfileSchedule
entity.HasOne(e => e.Schedule)
.WithMany()
.HasForeignKey(e => e.ScheduleId)
.OnDelete(DeleteBehavior.Cascade);
});
}
}
@@ -0,0 +1,443 @@
// <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("20250924155833_AddProfileSchedules")]
partial class AddProfileSchedules
{
/// <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>("SourceCustomQuery")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<string>("SourceDatabaseName")
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SourceFilePath")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("SourceKeyField")
.HasMaxLength(200)
.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.Property<bool>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AdditionalInfo")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Data_Hash")
.HasMaxLength(64)
.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.ProfileSchedule", 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<TimeOnly?>("DailyTime")
.HasColumnType("TEXT");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<DateTime?>("ExecuteOnce")
.HasColumnType("TEXT");
b.Property<int>("ExecutionCount")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0);
b.Property<bool>("IsActive")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(true);
b.Property<DateTime?>("LastExecution")
.HasColumnType("TEXT");
b.Property<string>("LastExecutionResult")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<bool>("LastExecutionSuccess")
.HasColumnType("INTEGER");
b.Property<int?>("MonthlyDay")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<DateTime?>("NextExecution")
.HasColumnType("TEXT");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER");
b.Property<string>("ScheduleType")
.IsRequired()
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<string>("WeeklyDays")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("IsActive");
b.HasIndex("Name")
.IsUnique();
b.HasIndex("NextExecution");
b.HasIndex("ProfileId");
b.HasIndex("ScheduleType");
b.ToTable("ProfileSchedules", (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");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,83 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CredentialManager.Migrations
{
/// <inheritdoc />
public partial class AddProfileSchedules : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "ProfileSchedules",
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),
ProfileId = table.Column<int>(type: "INTEGER", nullable: false),
ScheduleType = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
ExecuteOnce = table.Column<DateTime>(type: "TEXT", nullable: true),
DailyTime = table.Column<TimeOnly>(type: "TEXT", nullable: true),
WeeklyDays = table.Column<string>(type: "TEXT", maxLength: 50, nullable: true),
MonthlyDay = table.Column<int>(type: "INTEGER", nullable: true),
IsActive = table.Column<bool>(type: "INTEGER", nullable: false, defaultValue: true),
LastExecution = table.Column<DateTime>(type: "TEXT", nullable: true),
NextExecution = table.Column<DateTime>(type: "TEXT", nullable: true),
LastExecutionResult = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
LastExecutionSuccess = table.Column<bool>(type: "INTEGER", nullable: false),
ExecutionCount = table.Column<int>(type: "INTEGER", nullable: false, defaultValue: 0),
CreatedBy = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ProfileSchedules", x => x.Id);
table.ForeignKey(
name: "FK_ProfileSchedules_DataCouplerProfiles_ProfileId",
column: x => x.ProfileId,
principalTable: "DataCouplerProfiles",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ProfileSchedules_IsActive",
table: "ProfileSchedules",
column: "IsActive");
migrationBuilder.CreateIndex(
name: "IX_ProfileSchedules_Name",
table: "ProfileSchedules",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ProfileSchedules_NextExecution",
table: "ProfileSchedules",
column: "NextExecution");
migrationBuilder.CreateIndex(
name: "IX_ProfileSchedules_ProfileId",
table: "ProfileSchedules",
column: "ProfileId");
migrationBuilder.CreateIndex(
name: "IX_ProfileSchedules_ScheduleType",
table: "ProfileSchedules",
column: "ScheduleType");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ProfileSchedules");
}
}
}
@@ -0,0 +1,436 @@
// <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("20250924161239_AddProfileSchedule")]
partial class AddProfileSchedule
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.6");
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>("SourceCustomQuery")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<string>("SourceDatabaseName")
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SourceFilePath")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("SourceKeyField")
.HasMaxLength(200)
.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.Property<bool>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AdditionalInfo")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Data_Hash")
.HasMaxLength(64)
.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.ProfileSchedule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("DailyTime")
.HasMaxLength(10)
.HasColumnType("TEXT");
b.Property<int?>("DayOfMonth")
.HasColumnType("INTEGER");
b.Property<int?>("DayOfWeek")
.HasColumnType("INTEGER");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<int>("ExecutionCount")
.HasColumnType("INTEGER");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("LastExecutionMessage")
.HasMaxLength(1000)
.HasColumnType("TEXT");
b.Property<int?>("LastExecutionRecordCount")
.HasColumnType("INTEGER");
b.Property<string>("LastExecutionStatus")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<DateTime?>("LastExecutionTime")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<DateTime?>("NextExecutionTime")
.HasColumnType("TEXT");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER");
b.Property<string>("ScheduleType")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<DateTime?>("ScheduledDateTime")
.HasColumnType("TEXT");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ProfileId");
b.ToTable("ProfileSchedules");
});
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");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,225 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CredentialManager.Migrations
{
/// <inheritdoc />
public partial class AddProfileSchedule : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_ProfileSchedules_IsActive",
table: "ProfileSchedules");
migrationBuilder.DropIndex(
name: "IX_ProfileSchedules_Name",
table: "ProfileSchedules");
migrationBuilder.DropIndex(
name: "IX_ProfileSchedules_NextExecution",
table: "ProfileSchedules");
migrationBuilder.DropIndex(
name: "IX_ProfileSchedules_ScheduleType",
table: "ProfileSchedules");
migrationBuilder.DropColumn(
name: "LastExecutionResult",
table: "ProfileSchedules");
migrationBuilder.DropColumn(
name: "WeeklyDays",
table: "ProfileSchedules");
migrationBuilder.RenameColumn(
name: "NextExecution",
table: "ProfileSchedules",
newName: "ScheduledDateTime");
migrationBuilder.RenameColumn(
name: "MonthlyDay",
table: "ProfileSchedules",
newName: "LastExecutionRecordCount");
migrationBuilder.RenameColumn(
name: "LastExecutionSuccess",
table: "ProfileSchedules",
newName: "IsEnabled");
migrationBuilder.RenameColumn(
name: "LastExecution",
table: "ProfileSchedules",
newName: "NextExecutionTime");
migrationBuilder.RenameColumn(
name: "ExecuteOnce",
table: "ProfileSchedules",
newName: "LastExecutionTime");
migrationBuilder.AlterColumn<bool>(
name: "IsActive",
table: "ProfileSchedules",
type: "INTEGER",
nullable: false,
oldClrType: typeof(bool),
oldType: "INTEGER",
oldDefaultValue: true);
migrationBuilder.AlterColumn<int>(
name: "ExecutionCount",
table: "ProfileSchedules",
type: "INTEGER",
nullable: false,
oldClrType: typeof(int),
oldType: "INTEGER",
oldDefaultValue: 0);
migrationBuilder.AlterColumn<DateTime>(
name: "CreatedAt",
table: "ProfileSchedules",
type: "TEXT",
nullable: false,
oldClrType: typeof(DateTime),
oldType: "TEXT",
oldDefaultValueSql: "CURRENT_TIMESTAMP");
migrationBuilder.AddColumn<int>(
name: "DayOfMonth",
table: "ProfileSchedules",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<int>(
name: "DayOfWeek",
table: "ProfileSchedules",
type: "INTEGER",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "LastExecutionMessage",
table: "ProfileSchedules",
type: "TEXT",
maxLength: 1000,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "LastExecutionStatus",
table: "ProfileSchedules",
type: "TEXT",
maxLength: 20,
nullable: false,
defaultValue: "");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "DayOfMonth",
table: "ProfileSchedules");
migrationBuilder.DropColumn(
name: "DayOfWeek",
table: "ProfileSchedules");
migrationBuilder.DropColumn(
name: "LastExecutionMessage",
table: "ProfileSchedules");
migrationBuilder.DropColumn(
name: "LastExecutionStatus",
table: "ProfileSchedules");
migrationBuilder.RenameColumn(
name: "ScheduledDateTime",
table: "ProfileSchedules",
newName: "NextExecution");
migrationBuilder.RenameColumn(
name: "NextExecutionTime",
table: "ProfileSchedules",
newName: "LastExecution");
migrationBuilder.RenameColumn(
name: "LastExecutionTime",
table: "ProfileSchedules",
newName: "ExecuteOnce");
migrationBuilder.RenameColumn(
name: "LastExecutionRecordCount",
table: "ProfileSchedules",
newName: "MonthlyDay");
migrationBuilder.RenameColumn(
name: "IsEnabled",
table: "ProfileSchedules",
newName: "LastExecutionSuccess");
migrationBuilder.AlterColumn<bool>(
name: "IsActive",
table: "ProfileSchedules",
type: "INTEGER",
nullable: false,
defaultValue: true,
oldClrType: typeof(bool),
oldType: "INTEGER");
migrationBuilder.AlterColumn<int>(
name: "ExecutionCount",
table: "ProfileSchedules",
type: "INTEGER",
nullable: false,
defaultValue: 0,
oldClrType: typeof(int),
oldType: "INTEGER");
migrationBuilder.AlterColumn<DateTime>(
name: "CreatedAt",
table: "ProfileSchedules",
type: "TEXT",
nullable: false,
defaultValueSql: "CURRENT_TIMESTAMP",
oldClrType: typeof(DateTime),
oldType: "TEXT");
migrationBuilder.AddColumn<string>(
name: "LastExecutionResult",
table: "ProfileSchedules",
type: "TEXT",
maxLength: 500,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "WeeklyDays",
table: "ProfileSchedules",
type: "TEXT",
maxLength: 50,
nullable: true);
migrationBuilder.CreateIndex(
name: "IX_ProfileSchedules_IsActive",
table: "ProfileSchedules",
column: "IsActive");
migrationBuilder.CreateIndex(
name: "IX_ProfileSchedules_Name",
table: "ProfileSchedules",
column: "Name",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_ProfileSchedules_NextExecution",
table: "ProfileSchedules",
column: "NextExecution");
migrationBuilder.CreateIndex(
name: "IX_ProfileSchedules_ScheduleType",
table: "ProfileSchedules",
column: "ScheduleType");
}
}
}
@@ -0,0 +1,544 @@
// <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("20250924173231_AddScheduleExecutionHistory")]
partial class AddScheduleExecutionHistory
{
/// <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>("SourceCustomQuery")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<string>("SourceDatabaseName")
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SourceFilePath")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("SourceKeyField")
.HasMaxLength(200)
.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.Property<bool>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AdditionalInfo")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Data_Hash")
.HasMaxLength(64)
.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.ProfileSchedule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("DailyTime")
.HasMaxLength(10)
.HasColumnType("TEXT");
b.Property<int?>("DayOfMonth")
.HasColumnType("INTEGER");
b.Property<int?>("DayOfWeek")
.HasColumnType("INTEGER");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("DestinationDatabaseOverride")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<int>("ExecutionCount")
.HasColumnType("INTEGER");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("LastExecutionMessage")
.HasMaxLength(1000)
.HasColumnType("TEXT");
b.Property<int?>("LastExecutionRecordCount")
.HasColumnType("INTEGER");
b.Property<string>("LastExecutionStatus")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<DateTime?>("LastExecutionTime")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<DateTime?>("NextExecutionTime")
.HasColumnType("TEXT");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER");
b.Property<string>("ScheduleType")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<DateTime?>("ScheduledDateTime")
.HasColumnType("TEXT");
b.Property<string>("SourceDatabaseOverride")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ProfileId");
b.ToTable("ProfileSchedules");
});
modelBuilder.Entity("CredentialManager.Models.ScheduleExecutionHistory", 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>("DestinationInfo")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("DestinationType")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<DateTime?>("EndTime")
.HasColumnType("TEXT");
b.Property<string>("ErrorDetails")
.HasMaxLength(5000)
.HasColumnType("TEXT");
b.Property<string>("Message")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER");
b.Property<string>("ProfileName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<int>("RecordsProcessed")
.HasColumnType("INTEGER");
b.Property<int?>("RecordsWithErrors")
.HasColumnType("INTEGER");
b.Property<int>("ScheduleId")
.HasColumnType("INTEGER");
b.Property<string>("SourceInfo")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("SourceType")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<DateTime>("StartTime")
.HasColumnType("TEXT");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("TriggerType")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("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
}
}
}
@@ -0,0 +1,105 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CredentialManager.Migrations
{
/// <inheritdoc />
public partial class AddScheduleExecutionHistory : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "DestinationDatabaseOverride",
table: "ProfileSchedules",
type: "TEXT",
maxLength: 100,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "SourceDatabaseOverride",
table: "ProfileSchedules",
type: "TEXT",
maxLength: 100,
nullable: true);
migrationBuilder.CreateTable(
name: "ScheduleExecutionHistories",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ScheduleId = table.Column<int>(type: "INTEGER", nullable: false),
ProfileId = table.Column<int>(type: "INTEGER", nullable: false),
ProfileName = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
StartTime = table.Column<DateTime>(type: "TEXT", nullable: false),
EndTime = table.Column<DateTime>(type: "TEXT", nullable: true),
Status = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false),
Message = table.Column<string>(type: "TEXT", maxLength: 2000, nullable: true),
RecordsProcessed = table.Column<int>(type: "INTEGER", nullable: false),
RecordsWithErrors = table.Column<int>(type: "INTEGER", nullable: true),
ErrorDetails = table.Column<string>(type: "TEXT", maxLength: 5000, nullable: true),
TriggerType = table.Column<string>(type: "TEXT", maxLength: 20, nullable: false),
TriggeredBy = table.Column<string>(type: "TEXT", maxLength: 100, nullable: true),
SourceType = table.Column<string>(type: "TEXT", maxLength: 50, nullable: true),
DestinationType = table.Column<string>(type: "TEXT", maxLength: 50, nullable: true),
SourceInfo = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
DestinationInfo = table.Column<string>(type: "TEXT", maxLength: 500, nullable: true),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false),
AdditionalInfo = table.Column<string>(type: "TEXT", maxLength: 2000, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ScheduleExecutionHistories", x => x.Id);
table.ForeignKey(
name: "FK_ScheduleExecutionHistories_ProfileSchedules_ScheduleId",
column: x => x.ScheduleId,
principalTable: "ProfileSchedules",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_ScheduleExecutionHistories_ProfileId",
table: "ScheduleExecutionHistories",
column: "ProfileId");
migrationBuilder.CreateIndex(
name: "IX_ScheduleExecutionHistories_ScheduleId",
table: "ScheduleExecutionHistories",
column: "ScheduleId");
migrationBuilder.CreateIndex(
name: "IX_ScheduleExecutionHistories_StartTime",
table: "ScheduleExecutionHistories",
column: "StartTime");
migrationBuilder.CreateIndex(
name: "IX_ScheduleExecutionHistories_Status",
table: "ScheduleExecutionHistories",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_ScheduleExecutionHistories_TriggerType",
table: "ScheduleExecutionHistories",
column: "TriggerType");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "ScheduleExecutionHistories");
migrationBuilder.DropColumn(
name: "DestinationDatabaseOverride",
table: "ProfileSchedules");
migrationBuilder.DropColumn(
name: "SourceDatabaseOverride",
table: "ProfileSchedules");
}
}
}
@@ -0,0 +1,551 @@
// <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("20251001224302_AddIntervalSchedulingFields")]
partial class AddIntervalSchedulingFields
{
/// <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>("SourceCustomQuery")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<string>("SourceDatabaseName")
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("SourceFilePath")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("SourceKeyField")
.HasMaxLength(200)
.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.Property<bool>("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<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AdditionalInfo")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("Data_Hash")
.HasMaxLength(64)
.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.ProfileSchedule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("DailyTime")
.HasMaxLength(10)
.HasColumnType("TEXT");
b.Property<int?>("DayOfMonth")
.HasColumnType("INTEGER");
b.Property<int?>("DayOfWeek")
.HasColumnType("INTEGER");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("DestinationDatabaseOverride")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<int>("ExecutionCount")
.HasColumnType("INTEGER");
b.Property<string>("IntervalUnit")
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<int?>("IntervalValue")
.HasColumnType("INTEGER");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("LastExecutionMessage")
.HasMaxLength(1000)
.HasColumnType("TEXT");
b.Property<int?>("LastExecutionRecordCount")
.HasColumnType("INTEGER");
b.Property<string>("LastExecutionStatus")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<DateTime?>("LastExecutionTime")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<DateTime?>("NextExecutionTime")
.HasColumnType("TEXT");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER");
b.Property<string>("ScheduleType")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<DateTime?>("ScheduledDateTime")
.HasColumnType("TEXT");
b.Property<string>("SourceDatabaseOverride")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ProfileId");
b.ToTable("ProfileSchedules");
});
modelBuilder.Entity("CredentialManager.Models.ScheduleExecutionHistory", 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>("DestinationInfo")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("DestinationType")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<DateTime?>("EndTime")
.HasColumnType("TEXT");
b.Property<string>("ErrorDetails")
.HasMaxLength(5000)
.HasColumnType("TEXT");
b.Property<string>("Message")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER");
b.Property<string>("ProfileName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<int>("RecordsProcessed")
.HasColumnType("INTEGER");
b.Property<int?>("RecordsWithErrors")
.HasColumnType("INTEGER");
b.Property<int>("ScheduleId")
.HasColumnType("INTEGER");
b.Property<string>("SourceInfo")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("SourceType")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<DateTime>("StartTime")
.HasColumnType("TEXT");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("TriggerType")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("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
}
}
}
@@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace CredentialManager.Migrations
{
/// <inheritdoc />
public partial class AddIntervalSchedulingFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "IntervalUnit",
table: "ProfileSchedules",
type: "TEXT",
maxLength: 20,
nullable: true);
migrationBuilder.AddColumn<int>(
name: "IntervalValue",
table: "ProfileSchedules",
type: "INTEGER",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "IntervalUnit",
table: "ProfileSchedules");
migrationBuilder.DropColumn(
name: "IntervalValue",
table: "ProfileSchedules");
}
}
}
@@ -320,6 +320,190 @@ namespace CredentialManager.Migrations
b.ToTable("KeyAssociations", (string)null);
});
modelBuilder.Entity("CredentialManager.Models.ProfileSchedule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("DailyTime")
.HasMaxLength(10)
.HasColumnType("TEXT");
b.Property<int?>("DayOfMonth")
.HasColumnType("INTEGER");
b.Property<int?>("DayOfWeek")
.HasColumnType("INTEGER");
b.Property<string>("Description")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("DestinationDatabaseOverride")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<int>("ExecutionCount")
.HasColumnType("INTEGER");
b.Property<string>("IntervalUnit")
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<int?>("IntervalValue")
.HasColumnType("INTEGER");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<bool>("IsEnabled")
.HasColumnType("INTEGER");
b.Property<string>("LastExecutionMessage")
.HasMaxLength(1000)
.HasColumnType("TEXT");
b.Property<int?>("LastExecutionRecordCount")
.HasColumnType("INTEGER");
b.Property<string>("LastExecutionStatus")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<DateTime?>("LastExecutionTime")
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<DateTime?>("NextExecutionTime")
.HasColumnType("TEXT");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER");
b.Property<string>("ScheduleType")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<DateTime?>("ScheduledDateTime")
.HasColumnType("TEXT");
b.Property<string>("SourceDatabaseOverride")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ProfileId");
b.ToTable("ProfileSchedules");
});
modelBuilder.Entity("CredentialManager.Models.ScheduleExecutionHistory", 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>("DestinationInfo")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("DestinationType")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<DateTime?>("EndTime")
.HasColumnType("TEXT");
b.Property<string>("ErrorDetails")
.HasMaxLength(5000)
.HasColumnType("TEXT");
b.Property<string>("Message")
.HasMaxLength(2000)
.HasColumnType("TEXT");
b.Property<int>("ProfileId")
.HasColumnType("INTEGER");
b.Property<string>("ProfileName")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<int>("RecordsProcessed")
.HasColumnType("INTEGER");
b.Property<int?>("RecordsWithErrors")
.HasColumnType("INTEGER");
b.Property<int>("ScheduleId")
.HasColumnType("INTEGER");
b.Property<string>("SourceInfo")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<string>("SourceType")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<DateTime>("StartTime")
.HasColumnType("TEXT");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("TriggerType")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("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")
@@ -336,6 +520,28 @@ namespace CredentialManager.Migrations
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
}
}
+227
View File
@@ -0,0 +1,227 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace CredentialManager.Models;
/// <summary>
/// Modello per la schedulazione dei profili Data Coupler
/// </summary>
public class ProfileSchedule
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; } = string.Empty;
[MaxLength(500)]
public string? Description { get; set; }
// Relazione con il profilo
[Required]
public int ProfileId { get; set; }
[ForeignKey(nameof(ProfileId))]
public virtual DataCouplerProfile Profile { get; set; } = null!;
// Configurazione scheduling
[Required]
public bool IsEnabled { get; set; } = true;
[Required]
[MaxLength(20)]
public string ScheduleType { get; set; } = string.Empty; // "once", "daily", "weekly", "monthly", "interval"
public DateTime? ScheduledDateTime { get; set; } // Per schedulazioni "once"
[MaxLength(10)]
public string? DailyTime { get; set; } // Format "HH:mm" per schedulazioni ricorrenti
public int? DayOfWeek { get; set; } // 0-6 per schedulazioni settimanali (0=Domenica)
public int? DayOfMonth { get; set; } // 1-31 per schedulazioni mensili
// Configurazione per schedulazioni a intervalli
public int? IntervalValue { get; set; } // Valore dell'intervallo (es. 5, 10, 30)
[MaxLength(20)]
public string? IntervalUnit { get; set; } // "seconds", "minutes", "hours", "days", "weeks", "months"
// Tracking delle esecuzioni
public DateTime? LastExecutionTime { get; set; }
public DateTime? NextExecutionTime { get; set; }
public int ExecutionCount { get; set; } = 0;
[MaxLength(20)]
public string LastExecutionStatus { get; set; } = string.Empty; // "success", "failed", "running"
[MaxLength(1000)]
public string? LastExecutionMessage { get; set; }
public int? LastExecutionRecordCount { get; set; }
// Configurazione override per database sources
[MaxLength(100)]
public string? SourceDatabaseOverride { get; set; }
[MaxLength(100)]
public string? DestinationDatabaseOverride { get; set; }
// Metadati
[MaxLength(100)]
public string? CreatedBy { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
public bool IsActive { get; set; } = true;
// Metodi helper per calcolare la prossima esecuzione
public DateTime? CalculateNextExecution()
{
if (!IsEnabled || !IsActive)
return null;
var now = DateTime.Now;
return ScheduleType switch
{
"once" => ScheduledDateTime > now ? ScheduledDateTime : null,
"daily" when !string.IsNullOrEmpty(DailyTime) => CalculateNextDaily(now),
"weekly" when DayOfWeek.HasValue && !string.IsNullOrEmpty(DailyTime) => CalculateNextWeekly(now),
"monthly" when DayOfMonth.HasValue && !string.IsNullOrEmpty(DailyTime) => CalculateNextMonthly(now),
"interval" when IntervalValue.HasValue && !string.IsNullOrEmpty(IntervalUnit) => CalculateNextInterval(now),
_ => null
};
}
public DateTime? CalculateNextExecutionFromLast()
{
if (!IsEnabled || !IsActive)
return null;
// Per intervalli, calcola dalla ultima esecuzione (o da ora se mai eseguito)
if (ScheduleType == "interval" && IntervalValue.HasValue && !string.IsNullOrEmpty(IntervalUnit))
{
var baseTime = LastExecutionTime ?? DateTime.Now;
return CalculateNextInterval(baseTime);
}
// Per altri tipi, usa CalculateNextExecution normale
return CalculateNextExecution();
}
private DateTime CalculateNextDaily(DateTime now)
{
var time = TimeSpan.Parse(DailyTime!);
var today = now.Date.Add(time);
return today > now ? today : today.AddDays(1);
}
private DateTime CalculateNextWeekly(DateTime now)
{
var time = TimeSpan.Parse(DailyTime!);
var targetDayOfWeek = (DayOfWeek)DayOfWeek!;
var daysUntilTarget = ((int)targetDayOfWeek - (int)now.DayOfWeek + 7) % 7;
if (daysUntilTarget == 0)
{
// È oggi, controlla se l'orario è già passato
var todayAtTime = now.Date.Add(time);
if (todayAtTime > now)
return todayAtTime;
else
daysUntilTarget = 7; // Prossima settimana
}
return now.Date.AddDays(daysUntilTarget).Add(time);
}
private DateTime CalculateNextMonthly(DateTime now)
{
var time = TimeSpan.Parse(DailyTime!);
var targetDay = DayOfMonth!.Value;
var thisMonth = new DateTime(now.Year, now.Month, Math.Min(targetDay, DateTime.DaysInMonth(now.Year, now.Month))).Add(time);
if (thisMonth > now)
return thisMonth;
// Prossimo mese
var nextMonth = now.AddMonths(1);
return new DateTime(nextMonth.Year, nextMonth.Month, Math.Min(targetDay, DateTime.DaysInMonth(nextMonth.Year, nextMonth.Month))).Add(time);
}
private DateTime CalculateNextInterval(DateTime baseTime)
{
if (!IntervalValue.HasValue || string.IsNullOrEmpty(IntervalUnit))
return baseTime;
return IntervalUnit.ToLower() switch
{
"seconds" => baseTime.AddSeconds(IntervalValue.Value),
"minutes" => baseTime.AddMinutes(IntervalValue.Value),
"hours" => baseTime.AddHours(IntervalValue.Value),
"days" => baseTime.AddDays(IntervalValue.Value),
"weeks" => baseTime.AddDays(IntervalValue.Value * 7),
"months" => baseTime.AddMonths(IntervalValue.Value),
_ => baseTime
};
}
/// <summary>
/// Ottiene una descrizione leggibile della schedulazione
/// </summary>
public string GetScheduleDescription()
{
return ScheduleType switch
{
"once" => $"Una volta il {ScheduledDateTime:dd/MM/yyyy HH:mm}",
"daily" => $"Ogni giorno alle {DailyTime}",
"weekly" => $"Ogni {GetDayOfWeekName(DayOfWeek)} alle {DailyTime}",
"monthly" => $"Il giorno {DayOfMonth} di ogni mese alle {DailyTime}",
"interval" => GetIntervalDescription(),
_ => "Non configurato"
};
}
private string GetIntervalDescription()
{
if (!IntervalValue.HasValue || string.IsNullOrEmpty(IntervalUnit))
return "Intervallo non configurato";
var unit = IntervalUnit.ToLower() switch
{
"seconds" => IntervalValue.Value == 1 ? "secondo" : "secondi",
"minutes" => IntervalValue.Value == 1 ? "minuto" : "minuti",
"hours" => IntervalValue.Value == 1 ? "ora" : "ore",
"days" => IntervalValue.Value == 1 ? "giorno" : "giorni",
"weeks" => IntervalValue.Value == 1 ? "settimana" : "settimane",
"months" => IntervalValue.Value == 1 ? "mese" : "mesi",
_ => IntervalUnit
};
return $"Ogni {IntervalValue} {unit}";
}
private string GetDayOfWeekName(int? dayOfWeek)
{
if (!dayOfWeek.HasValue)
return "sconosciuto";
return ((DayOfWeek)dayOfWeek.Value).ToString() switch
{
"Monday" => "Lunedì",
"Tuesday" => "Martedì",
"Wednesday" => "Mercoledì",
"Thursday" => "Giovedì",
"Friday" => "Venerdì",
"Saturday" => "Sabato",
"Sunday" => "Domenica",
_ => dayOfWeek.Value.ToString()
};
}
}
@@ -0,0 +1,101 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace CredentialManager.Models;
/// <summary>
/// Modello per lo storico delle esecuzioni delle schedulazioni
/// </summary>
public class ScheduleExecutionHistory
{
[Key]
public int Id { get; set; }
// Relazione con la schedulazione
[Required]
public int ScheduleId { get; set; }
[ForeignKey(nameof(ScheduleId))]
public virtual ProfileSchedule Schedule { get; set; } = null!;
// Relazione con il profilo (denormalizzato per storico)
[Required]
public int ProfileId { get; set; }
[Required]
[MaxLength(200)]
public string ProfileName { get; set; } = string.Empty;
// Informazioni dell'esecuzione
[Required]
public DateTime StartTime { get; set; }
public DateTime? EndTime { get; set; }
public TimeSpan? Duration => EndTime - StartTime;
[Required]
[MaxLength(20)]
public string Status { get; set; } = string.Empty; // "success", "failed", "running", "cancelled"
[MaxLength(2000)]
public string? Message { get; set; }
public int RecordsProcessed { get; set; } = 0;
public int? RecordsWithErrors { get; set; }
// Dettagli dell'errore se presente
[MaxLength(5000)]
public string? ErrorDetails { get; set; }
// Informazioni sul trigger
[Required]
[MaxLength(20)]
public string TriggerType { get; set; } = string.Empty; // "manual", "automatic"
[MaxLength(100)]
public string? TriggeredBy { get; set; }
// Configurazioni al momento dell'esecuzione (per storico)
[MaxLength(50)]
public string? SourceType { get; set; }
[MaxLength(50)]
public string? DestinationType { get; set; }
[MaxLength(500)]
public string? SourceInfo { get; set; } // Informazioni aggiuntive sulla sorgente utilizzata
[MaxLength(500)]
public string? DestinationInfo { get; set; } // Informazioni aggiuntive sulla destinazione utilizzata
// Metadati
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
[MaxLength(2000)]
public string? AdditionalInfo { get; set; } // JSON per informazioni aggiuntive
// Helper methods
public bool IsCompleted => Status == "success" || Status == "failed" || Status == "cancelled";
public bool IsSuccessful => Status == "success";
public string GetStatusDisplayText() => Status switch
{
"success" => "Completato con successo",
"failed" => "Fallito",
"running" => "In esecuzione",
"cancelled" => "Cancellato",
_ => "Sconosciuto"
};
public string GetStatusColorClass() => Status switch
{
"success" => "text-success",
"failed" => "text-danger",
"running" => "text-primary",
"cancelled" => "text-warning",
_ => "text-muted"
};
}
@@ -57,8 +57,21 @@ public class DatabaseInitializer : IDatabaseInitializer
_logger.LogInformation("Trovate {Count} migrazioni pendenti: {Migrations}",
pendingMigrations.Count(), string.Join(", ", pendingMigrations));
await _context.Database.MigrateAsync();
_logger.LogInformation("Migrazioni applicate con successo");
try
{
await _context.Database.MigrateAsync();
_logger.LogInformation("Migrazioni applicate con successo");
}
catch (InvalidOperationException ex) when (ex.Message.Contains("PendingModelChangesWarning"))
{
_logger.LogWarning("Rilevate modifiche al modello pendenti, procedo con la creazione delle tabelle mancanti...");
// Creiamo le tabelle mancanti manualmente
await CreateScheduleExecutionHistoriesTableAsync();
await VerifyAndAddMissingColumnsAsync();
_logger.LogInformation("Tabelle e colonne mancanti create con successo");
}
}
else
{
@@ -105,6 +118,32 @@ public class DatabaseInitializer : IDatabaseInitializer
{
_logger.LogWarning(ex, "Tabella DataCouplerProfiles non accessibile");
}
// Verifica se la tabella ProfileSchedules esiste
try
{
await _context.ProfileSchedules.CountAsync();
_logger.LogInformation("Tabella ProfileSchedules verificata con successo");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Tabella ProfileSchedules non accessibile");
}
// Verifica se la tabella ScheduleExecutionHistories esiste
try
{
await _context.ScheduleExecutionHistories.CountAsync();
_logger.LogInformation("Tabella ScheduleExecutionHistories verificata con successo");
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Tabella ScheduleExecutionHistories non accessibile, tentativo di creazione...");
await CreateScheduleExecutionHistoriesTableAsync();
}
// Verifica e aggiungi colonne mancanti per ProfileSchedules
await VerifyAndAddMissingColumnsAsync();
}
catch (Exception ex)
{
@@ -235,4 +274,105 @@ public class DatabaseInitializer : IDatabaseInitializer
throw;
}
}
private async Task CreateScheduleExecutionHistoriesTableAsync()
{
try
{
_logger.LogInformation("Creazione tabella ScheduleExecutionHistories...");
// Crea la tabella ScheduleExecutionHistories
await _context.Database.ExecuteSqlRawAsync(@"
CREATE TABLE IF NOT EXISTS ScheduleExecutionHistories (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
ScheduleId INTEGER NOT NULL,
ProfileId INTEGER NOT NULL,
ProfileName TEXT NOT NULL,
StartTime DATETIME NOT NULL,
EndTime DATETIME,
Status TEXT NOT NULL,
Message TEXT,
RecordsProcessed INTEGER DEFAULT 0,
RecordsWithErrors INTEGER,
ErrorDetails TEXT,
TriggerType TEXT NOT NULL,
TriggeredBy TEXT,
SourceType TEXT,
DestinationType TEXT,
SourceInfo TEXT,
DestinationInfo TEXT,
AdditionalInfo TEXT,
CreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (ScheduleId) REFERENCES ProfileSchedules (Id) ON DELETE CASCADE
)");
// Crea gli indici
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_ScheduleId
ON ScheduleExecutionHistories (ScheduleId)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_ProfileId
ON ScheduleExecutionHistories (ProfileId)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_Status
ON ScheduleExecutionHistories (Status)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_StartTime
ON ScheduleExecutionHistories (StartTime)");
await _context.Database.ExecuteSqlRawAsync(@"
CREATE INDEX IF NOT EXISTS IDX_ScheduleExecutionHistories_TriggerType
ON ScheduleExecutionHistories (TriggerType)");
_logger.LogInformation("Tabella ScheduleExecutionHistories creata con successo");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante la creazione della tabella ScheduleExecutionHistories");
_logger.LogWarning("Continuazione normale - la tabella potrebbe già esistere");
}
}
private async Task VerifyAndAddMissingColumnsAsync()
{
try
{
_logger.LogInformation("Verifica colonne mancanti per ProfileSchedules...");
// Verifica se le colonne di override database esistono
try
{
await _context.Database.ExecuteSqlRawAsync(
"SELECT SourceDatabaseOverride FROM ProfileSchedules LIMIT 1");
}
catch
{
_logger.LogInformation("Aggiunta colonna SourceDatabaseOverride...");
await _context.Database.ExecuteSqlRawAsync(
"ALTER TABLE ProfileSchedules ADD COLUMN SourceDatabaseOverride TEXT");
}
try
{
await _context.Database.ExecuteSqlRawAsync(
"SELECT DestinationDatabaseOverride FROM ProfileSchedules LIMIT 1");
}
catch
{
_logger.LogInformation("Aggiunta colonna DestinationDatabaseOverride...");
await _context.Database.ExecuteSqlRawAsync(
"ALTER TABLE ProfileSchedules ADD COLUMN DestinationDatabaseOverride TEXT");
}
_logger.LogInformation("Verifica colonne completata");
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore durante la verifica/aggiunta colonne");
_logger.LogWarning("Continuazione normale - le colonne potrebbero già esistere");
}
}
}
@@ -0,0 +1,27 @@
using CredentialManager.Models;
namespace CredentialManager.Services;
/// <summary>
/// Servizio per la gestione delle schedulazioni dei profili
/// </summary>
public interface IProfileScheduleService
{
Task<List<ProfileSchedule>> GetAllSchedulesAsync();
Task<ProfileSchedule?> GetScheduleByIdAsync(int id);
Task<ProfileSchedule> CreateScheduleAsync(ProfileSchedule schedule);
Task<ProfileSchedule> UpdateScheduleAsync(ProfileSchedule schedule);
Task<bool> DeleteScheduleAsync(int id);
Task<List<ProfileSchedule>> GetActiveSchedulesAsync();
Task<List<ProfileSchedule>> GetPendingExecutionsAsync();
Task<bool> UpdateExecutionStatusAsync(int scheduleId, string status, string? message = null, int? recordCount = null);
Task UpdateNextExecutionTimeAsync(int scheduleId);
Task<List<DataCouplerProfile>> GetAvailableProfilesAsync();
// Metodi per lo storico delle esecuzioni
Task<ScheduleExecutionHistory> CreateExecutionHistoryAsync(ScheduleExecutionHistory history);
Task<ScheduleExecutionHistory> UpdateExecutionHistoryAsync(ScheduleExecutionHistory history);
Task<List<ScheduleExecutionHistory>> GetExecutionHistoryAsync(int scheduleId, int? limit = null);
Task<List<ScheduleExecutionHistory>> GetRecentExecutionsAsync(int limit = 50);
Task<ScheduleExecutionHistory?> GetExecutionByIdAsync(int executionId);
}
@@ -0,0 +1,351 @@
using CredentialManager.Data;
using CredentialManager.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace CredentialManager.Services;
public class ProfileScheduleService : IProfileScheduleService
{
private readonly CredentialDbContext _context;
private readonly ILogger<ProfileScheduleService> _logger;
public ProfileScheduleService(CredentialDbContext context, ILogger<ProfileScheduleService> logger)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public async Task<List<ProfileSchedule>> GetAllSchedulesAsync()
{
try
{
return await _context.ProfileSchedules
.Include(s => s.Profile)
.OrderBy(s => s.Name)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero di tutte le schedulazioni");
throw;
}
}
public async Task<ProfileSchedule?> GetScheduleByIdAsync(int id)
{
try
{
return await _context.ProfileSchedules
.Include(s => s.Profile)
.FirstOrDefaultAsync(s => s.Id == id);
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero della schedulazione con ID {Id}", id);
throw;
}
}
public async Task<ProfileSchedule> CreateScheduleAsync(ProfileSchedule schedule)
{
try
{
schedule.CreatedAt = DateTime.UtcNow;
schedule.UpdatedAt = DateTime.UtcNow;
schedule.CreatedBy = Environment.UserName;
// Calcola la prossima esecuzione
schedule.NextExecutionTime = schedule.CalculateNextExecution();
_context.ProfileSchedules.Add(schedule);
await _context.SaveChangesAsync();
_logger.LogInformation("Schedulazione creata: {Name} per il profilo {ProfileId}", schedule.Name, schedule.ProfileId);
return schedule;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nella creazione della schedulazione {Name}", schedule.Name);
throw;
}
}
public async Task<ProfileSchedule> UpdateScheduleAsync(ProfileSchedule schedule)
{
try
{
var existingSchedule = await _context.ProfileSchedules.FindAsync(schedule.Id);
if (existingSchedule == null)
throw new InvalidOperationException($"Schedulazione con ID {schedule.Id} non trovata");
// Aggiorna i campi
existingSchedule.Name = schedule.Name;
existingSchedule.Description = schedule.Description;
existingSchedule.ProfileId = schedule.ProfileId;
existingSchedule.IsEnabled = schedule.IsEnabled;
existingSchedule.ScheduleType = schedule.ScheduleType;
existingSchedule.ScheduledDateTime = schedule.ScheduledDateTime;
existingSchedule.DailyTime = schedule.DailyTime;
existingSchedule.DayOfWeek = schedule.DayOfWeek;
existingSchedule.DayOfMonth = schedule.DayOfMonth;
existingSchedule.IntervalValue = schedule.IntervalValue;
existingSchedule.IntervalUnit = schedule.IntervalUnit;
existingSchedule.IsActive = schedule.IsActive;
existingSchedule.UpdatedAt = DateTime.UtcNow;
// Ricalcola la prossima esecuzione
existingSchedule.NextExecutionTime = existingSchedule.CalculateNextExecution();
await _context.SaveChangesAsync();
_logger.LogInformation("Schedulazione aggiornata: {Name}", existingSchedule.Name);
return existingSchedule;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'aggiornamento della schedulazione con ID {Id}", schedule.Id);
throw;
}
}
public async Task<bool> DeleteScheduleAsync(int id)
{
try
{
var schedule = await _context.ProfileSchedules.FindAsync(id);
if (schedule == null)
return false;
_context.ProfileSchedules.Remove(schedule);
await _context.SaveChangesAsync();
_logger.LogInformation("Schedulazione eliminata: {Name} (ID: {Id})", schedule.Name, id);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'eliminazione della schedulazione con ID {Id}", id);
throw;
}
}
public async Task<List<ProfileSchedule>> GetActiveSchedulesAsync()
{
try
{
return await _context.ProfileSchedules
.Include(s => s.Profile)
.Where(s => s.IsActive && s.IsEnabled)
.OrderBy(s => s.NextExecutionTime)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero delle schedulazioni attive");
throw;
}
}
public async Task<List<ProfileSchedule>> GetPendingExecutionsAsync()
{
try
{
var now = DateTime.Now;
return await _context.ProfileSchedules
.Include(s => s.Profile)
.Where(s => s.IsActive &&
s.IsEnabled &&
s.NextExecutionTime.HasValue &&
s.NextExecutionTime <= now &&
s.LastExecutionStatus != "running")
.OrderBy(s => s.NextExecutionTime)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero delle schedulazioni in attesa di esecuzione");
throw;
}
}
public async Task<bool> UpdateExecutionStatusAsync(int scheduleId, string status, string? message = null, int? recordCount = null)
{
try
{
var schedule = await _context.ProfileSchedules.FindAsync(scheduleId);
if (schedule == null)
return false;
schedule.LastExecutionStatus = status;
schedule.LastExecutionMessage = message;
schedule.LastExecutionTime = DateTime.Now;
if (recordCount.HasValue)
schedule.LastExecutionRecordCount = recordCount;
if (status == "success" || status == "failed")
{
schedule.ExecutionCount++;
// Calcola la prossima esecuzione solo se completata (successo o errore)
schedule.NextExecutionTime = schedule.CalculateNextExecution();
}
await _context.SaveChangesAsync();
_logger.LogInformation("Status esecuzione aggiornato per schedulazione {ScheduleId}: {Status}", scheduleId, status);
return true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'aggiornamento dello status per schedulazione {ScheduleId}", scheduleId);
throw;
}
}
public async Task UpdateNextExecutionTimeAsync(int scheduleId)
{
try
{
var schedule = await _context.ProfileSchedules.FindAsync(scheduleId);
if (schedule == null)
return;
// Per schedulazioni a intervallo, calcola dalla ultima esecuzione
if (schedule.ScheduleType == "interval")
{
schedule.NextExecutionTime = schedule.CalculateNextExecutionFromLast();
}
else
{
schedule.NextExecutionTime = schedule.CalculateNextExecution();
}
await _context.SaveChangesAsync();
_logger.LogDebug("Prossima esecuzione aggiornata per schedulazione {ScheduleId}: {NextExecution} (tipo: {Type})",
scheduleId, schedule.NextExecutionTime, schedule.ScheduleType);
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'aggiornamento della prossima esecuzione per schedulazione {ScheduleId}", scheduleId);
throw;
}
}
public async Task<List<DataCouplerProfile>> GetAvailableProfilesAsync()
{
try
{
return await _context.DataCouplerProfiles
.Where(p => p.IsActive)
.OrderBy(p => p.Name)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero dei profili disponibili");
throw;
}
}
// Implementazione metodi per lo storico delle esecuzioni
public async Task<ScheduleExecutionHistory> CreateExecutionHistoryAsync(ScheduleExecutionHistory history)
{
try
{
history.CreatedAt = DateTime.UtcNow;
_context.ScheduleExecutionHistories.Add(history);
await _context.SaveChangesAsync();
_logger.LogInformation("Storico esecuzione creato: ID {HistoryId} per schedulazione {ScheduleId}",
history.Id, history.ScheduleId);
return history;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nella creazione dello storico esecuzione per schedulazione {ScheduleId}",
history.ScheduleId);
throw;
}
}
public async Task<ScheduleExecutionHistory> UpdateExecutionHistoryAsync(ScheduleExecutionHistory history)
{
try
{
_context.ScheduleExecutionHistories.Update(history);
await _context.SaveChangesAsync();
_logger.LogDebug("Storico esecuzione aggiornato: ID {HistoryId}", history.Id);
return history;
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nell'aggiornamento dello storico esecuzione ID {HistoryId}", history.Id);
throw;
}
}
public async Task<List<ScheduleExecutionHistory>> GetExecutionHistoryAsync(int scheduleId, int? limit = null)
{
try
{
var query = _context.ScheduleExecutionHistories
.Where(h => h.ScheduleId == scheduleId)
.OrderByDescending(h => h.StartTime);
if (limit.HasValue)
{
query = (IOrderedQueryable<ScheduleExecutionHistory>)query.Take(limit.Value);
}
return await query.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero dello storico per schedulazione {ScheduleId}", scheduleId);
throw;
}
}
public async Task<List<ScheduleExecutionHistory>> GetRecentExecutionsAsync(int limit = 50)
{
try
{
return await _context.ScheduleExecutionHistories
.Include(h => h.Schedule)
.OrderByDescending(h => h.StartTime)
.Take(limit)
.ToListAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero delle esecuzioni recenti");
throw;
}
}
public async Task<ScheduleExecutionHistory?> GetExecutionByIdAsync(int executionId)
{
try
{
return await _context.ScheduleExecutionHistories
.Include(h => h.Schedule)
.FirstOrDefaultAsync(h => h.Id == executionId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Errore nel recupero dell'esecuzione ID {ExecutionId}", executionId);
throw;
}
}
}
Binary file not shown.