From 73b506992cfc5f93fe898c56bc2e600a21a94ace Mon Sep 17 00:00:00 2001 From: Anatolii Grynchuk Date: Sat, 2 May 2026 00:46:49 +0300 Subject: [PATCH] feat: add EF migrations and design-time factory - Fix EmailEmailTemplateEntityConfiguration -> EmailTemplateEntityConfiguration - Rename tables to match domain: templates->email_templates, providers->email_channels, provider_usage->email_channel_usage - Add NotificationDbContextFactory for design-time migrations tooling - Add InitialCreate migration: email_templates, email_channels, email_channel_usage Ref: IT-628 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../EmailChannelEntityConfiguration.cs | 2 +- .../EmailChannelUsageEntityConfiguration.cs | 2 +- .../EmailTemplateEntityConfiguration.cs | 4 +- .../20260501214629_InitialCreate.Designer.cs | 211 ++++++++++++++++++ .../20260501214629_InitialCreate.cs | 102 +++++++++ .../NotificationDbContextModelSnapshot.cs | 208 +++++++++++++++++ .../NotificationDbContextFactory.cs | 16 ++ 7 files changed, 541 insertions(+), 4 deletions(-) create mode 100644 HrynCo.NotificationService.DAL.EF/Migrations/20260501214629_InitialCreate.Designer.cs create mode 100644 HrynCo.NotificationService.DAL.EF/Migrations/20260501214629_InitialCreate.cs create mode 100644 HrynCo.NotificationService.DAL.EF/Migrations/NotificationDbContextModelSnapshot.cs create mode 100644 HrynCo.NotificationService.DAL.EF/NotificationDbContextFactory.cs diff --git a/HrynCo.NotificationService.DAL.EF/Configurations/EmailChannelEntityConfiguration.cs b/HrynCo.NotificationService.DAL.EF/Configurations/EmailChannelEntityConfiguration.cs index 2fc3d58..0869846 100644 --- a/HrynCo.NotificationService.DAL.EF/Configurations/EmailChannelEntityConfiguration.cs +++ b/HrynCo.NotificationService.DAL.EF/Configurations/EmailChannelEntityConfiguration.cs @@ -8,7 +8,7 @@ internal class EmailChannelEntityConfiguration : IEntityTypeConfiguration builder) { - builder.ToTable("providers"); + builder.ToTable("email_channels"); builder.HasKey(x => x.Id); builder.Property(x => x.Id).HasColumnName("id"); diff --git a/HrynCo.NotificationService.DAL.EF/Configurations/EmailChannelUsageEntityConfiguration.cs b/HrynCo.NotificationService.DAL.EF/Configurations/EmailChannelUsageEntityConfiguration.cs index 9823d93..576fb7e 100644 --- a/HrynCo.NotificationService.DAL.EF/Configurations/EmailChannelUsageEntityConfiguration.cs +++ b/HrynCo.NotificationService.DAL.EF/Configurations/EmailChannelUsageEntityConfiguration.cs @@ -8,7 +8,7 @@ internal class EmailChannelUsageEntityConfiguration : IEntityTypeConfiguration builder) { - builder.ToTable("provider_usage"); + builder.ToTable("email_channel_usage"); builder.HasKey(x => x.Id); builder.Property(x => x.Id).HasColumnName("id"); diff --git a/HrynCo.NotificationService.DAL.EF/Configurations/EmailTemplateEntityConfiguration.cs b/HrynCo.NotificationService.DAL.EF/Configurations/EmailTemplateEntityConfiguration.cs index efb6fa9..7522d01 100644 --- a/HrynCo.NotificationService.DAL.EF/Configurations/EmailTemplateEntityConfiguration.cs +++ b/HrynCo.NotificationService.DAL.EF/Configurations/EmailTemplateEntityConfiguration.cs @@ -4,11 +4,11 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace HrynCo.NotificationService.DAL.EF.Configurations; -internal class EmailEmailTemplateEntityConfiguration : IEntityTypeConfiguration +internal class EmailTemplateEntityConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { - builder.ToTable("templates"); + builder.ToTable("email_templates"); builder.HasKey(x => x.Id); builder.Property(x => x.Id).HasColumnName("id"); diff --git a/HrynCo.NotificationService.DAL.EF/Migrations/20260501214629_InitialCreate.Designer.cs b/HrynCo.NotificationService.DAL.EF/Migrations/20260501214629_InitialCreate.Designer.cs new file mode 100644 index 0000000..8d3fbe1 --- /dev/null +++ b/HrynCo.NotificationService.DAL.EF/Migrations/20260501214629_InitialCreate.Designer.cs @@ -0,0 +1,211 @@ +// +using System; +using HrynCo.NotificationService.DAL.EF; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HrynCo.NotificationService.DAL.EF.Migrations +{ + [DbContext(typeof(NotificationDbContext))] + [Migration("20260501214629_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("HrynCo.NotificationService.DAL.EF.Entities.EmailChannelEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("DailyLimit") + .HasColumnType("integer") + .HasColumnName("daily_limit"); + + b.Property("EmailChannelType") + .HasColumnType("integer") + .HasColumnName("provider_type"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("is_active"); + + b.Property("MonthlyLimit") + .HasColumnType("integer") + .HasColumnName("monthly_limit"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("service_name"); + + b.Property("SettingsJson") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("settings"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated"); + + b.Property("WarnThresholdPercent") + .HasColumnType("integer") + .HasColumnName("warn_threshold_percent"); + + b.HasKey("Id"); + + b.HasIndex("ServiceName", "Priority"); + + b.ToTable("email_channels", (string)null); + }); + + modelBuilder.Entity("HrynCo.NotificationService.DAL.EF.Entities.EmailChannelUsageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Date") + .HasColumnType("date") + .HasColumnName("date"); + + b.Property("ProviderId") + .HasColumnType("uuid") + .HasColumnName("provider_id"); + + b.Property("SentCount") + .HasColumnType("integer") + .HasColumnName("sent_count"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "Date") + .IsUnique(); + + b.ToTable("email_channel_usage", (string)null); + }); + + modelBuilder.Entity("HrynCo.NotificationService.DAL.EF.Entities.EmailTemplateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("HtmlBody") + .IsRequired() + .HasColumnType("text") + .HasColumnName("html_body"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("key"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("language_code"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("service_name"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text") + .HasColumnName("subject"); + + b.Property("TextBody") + .IsRequired() + .HasColumnType("text") + .HasColumnName("text_body"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated"); + + b.HasKey("Id"); + + b.HasIndex("ServiceName", "Key", "LanguageCode") + .IsUnique(); + + b.ToTable("email_templates", (string)null); + }); + + modelBuilder.Entity("HrynCo.NotificationService.DAL.EF.Entities.EmailTemplateEntity", b => + { + b.OwnsMany("HrynCo.NotificationService.DAL.EF.Entities.EmailTemplateVariableData", "Variables", b1 => + { + b1.Property("EmailTemplateEntityId") + .HasColumnType("uuid"); + + b1.Property("__synthesizedOrdinal") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b1.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "name"); + + b1.Property("Required") + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "required"); + + b1.HasKey("EmailTemplateEntityId", "__synthesizedOrdinal"); + + b1.ToTable("email_templates"); + + b1.ToJson("variables"); + + b1.WithOwner() + .HasForeignKey("EmailTemplateEntityId"); + }); + + b.Navigation("Variables"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/HrynCo.NotificationService.DAL.EF/Migrations/20260501214629_InitialCreate.cs b/HrynCo.NotificationService.DAL.EF/Migrations/20260501214629_InitialCreate.cs new file mode 100644 index 0000000..8618cf7 --- /dev/null +++ b/HrynCo.NotificationService.DAL.EF/Migrations/20260501214629_InitialCreate.cs @@ -0,0 +1,102 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace HrynCo.NotificationService.DAL.EF.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "email_channel_usage", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + provider_id = table.Column(type: "uuid", nullable: false), + date = table.Column(type: "date", nullable: false), + sent_count = table.Column(type: "integer", nullable: false), + created = table.Column(type: "timestamp with time zone", nullable: false), + updated = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_email_channel_usage", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "email_channels", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + service_name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + priority = table.Column(type: "integer", nullable: false), + provider_type = table.Column(type: "integer", nullable: false), + settings = table.Column(type: "jsonb", nullable: false), + daily_limit = table.Column(type: "integer", nullable: true), + monthly_limit = table.Column(type: "integer", nullable: true), + warn_threshold_percent = table.Column(type: "integer", nullable: false), + is_active = table.Column(type: "boolean", nullable: false), + created = table.Column(type: "timestamp with time zone", nullable: false), + updated = table.Column(type: "timestamp with time zone", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_email_channels", x => x.id); + }); + + migrationBuilder.CreateTable( + name: "email_templates", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + service_name = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + key = table.Column(type: "character varying(100)", maxLength: 100, nullable: false), + language_code = table.Column(type: "character varying(10)", maxLength: 10, nullable: false), + subject = table.Column(type: "text", nullable: false), + html_body = table.Column(type: "text", nullable: false), + text_body = table.Column(type: "text", nullable: false), + created = table.Column(type: "timestamp with time zone", nullable: false), + updated = table.Column(type: "timestamp with time zone", nullable: true), + variables = table.Column(type: "jsonb", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_email_templates", x => x.id); + }); + + migrationBuilder.CreateIndex( + name: "IX_email_channel_usage_provider_id_date", + table: "email_channel_usage", + columns: new[] { "provider_id", "date" }, + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_email_channels_service_name_priority", + table: "email_channels", + columns: new[] { "service_name", "priority" }); + + migrationBuilder.CreateIndex( + name: "IX_email_templates_service_name_key_language_code", + table: "email_templates", + columns: new[] { "service_name", "key", "language_code" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "email_channel_usage"); + + migrationBuilder.DropTable( + name: "email_channels"); + + migrationBuilder.DropTable( + name: "email_templates"); + } + } +} diff --git a/HrynCo.NotificationService.DAL.EF/Migrations/NotificationDbContextModelSnapshot.cs b/HrynCo.NotificationService.DAL.EF/Migrations/NotificationDbContextModelSnapshot.cs new file mode 100644 index 0000000..4266c16 --- /dev/null +++ b/HrynCo.NotificationService.DAL.EF/Migrations/NotificationDbContextModelSnapshot.cs @@ -0,0 +1,208 @@ +// +using System; +using HrynCo.NotificationService.DAL.EF; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace HrynCo.NotificationService.DAL.EF.Migrations +{ + [DbContext(typeof(NotificationDbContext))] + partial class NotificationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.5") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("HrynCo.NotificationService.DAL.EF.Entities.EmailChannelEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("DailyLimit") + .HasColumnType("integer") + .HasColumnName("daily_limit"); + + b.Property("EmailChannelType") + .HasColumnType("integer") + .HasColumnName("provider_type"); + + b.Property("IsActive") + .HasColumnType("boolean") + .HasColumnName("is_active"); + + b.Property("MonthlyLimit") + .HasColumnType("integer") + .HasColumnName("monthly_limit"); + + b.Property("Priority") + .HasColumnType("integer") + .HasColumnName("priority"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("service_name"); + + b.Property("SettingsJson") + .IsRequired() + .HasColumnType("jsonb") + .HasColumnName("settings"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated"); + + b.Property("WarnThresholdPercent") + .HasColumnType("integer") + .HasColumnName("warn_threshold_percent"); + + b.HasKey("Id"); + + b.HasIndex("ServiceName", "Priority"); + + b.ToTable("email_channels", (string)null); + }); + + modelBuilder.Entity("HrynCo.NotificationService.DAL.EF.Entities.EmailChannelUsageEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("Date") + .HasColumnType("date") + .HasColumnName("date"); + + b.Property("ProviderId") + .HasColumnType("uuid") + .HasColumnName("provider_id"); + + b.Property("SentCount") + .HasColumnType("integer") + .HasColumnName("sent_count"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated"); + + b.HasKey("Id"); + + b.HasIndex("ProviderId", "Date") + .IsUnique(); + + b.ToTable("email_channel_usage", (string)null); + }); + + modelBuilder.Entity("HrynCo.NotificationService.DAL.EF.Entities.EmailTemplateEntity", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Created") + .HasColumnType("timestamp with time zone") + .HasColumnName("created"); + + b.Property("HtmlBody") + .IsRequired() + .HasColumnType("text") + .HasColumnName("html_body"); + + b.Property("Key") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("key"); + + b.Property("LanguageCode") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("character varying(10)") + .HasColumnName("language_code"); + + b.Property("ServiceName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("character varying(100)") + .HasColumnName("service_name"); + + b.Property("Subject") + .IsRequired() + .HasColumnType("text") + .HasColumnName("subject"); + + b.Property("TextBody") + .IsRequired() + .HasColumnType("text") + .HasColumnName("text_body"); + + b.Property("Updated") + .HasColumnType("timestamp with time zone") + .HasColumnName("updated"); + + b.HasKey("Id"); + + b.HasIndex("ServiceName", "Key", "LanguageCode") + .IsUnique(); + + b.ToTable("email_templates", (string)null); + }); + + modelBuilder.Entity("HrynCo.NotificationService.DAL.EF.Entities.EmailTemplateEntity", b => + { + b.OwnsMany("HrynCo.NotificationService.DAL.EF.Entities.EmailTemplateVariableData", "Variables", b1 => + { + b1.Property("EmailTemplateEntityId") + .HasColumnType("uuid"); + + b1.Property("__synthesizedOrdinal") + .ValueGeneratedOnAdd() + .HasColumnType("integer"); + + b1.Property("Name") + .IsRequired() + .HasColumnType("text") + .HasAnnotation("Relational:JsonPropertyName", "name"); + + b1.Property("Required") + .HasColumnType("boolean") + .HasAnnotation("Relational:JsonPropertyName", "required"); + + b1.HasKey("EmailTemplateEntityId", "__synthesizedOrdinal"); + + b1.ToTable("email_templates"); + + b1.ToJson("variables"); + + b1.WithOwner() + .HasForeignKey("EmailTemplateEntityId"); + }); + + b.Navigation("Variables"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/HrynCo.NotificationService.DAL.EF/NotificationDbContextFactory.cs b/HrynCo.NotificationService.DAL.EF/NotificationDbContextFactory.cs new file mode 100644 index 0000000..57a7f5e --- /dev/null +++ b/HrynCo.NotificationService.DAL.EF/NotificationDbContextFactory.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace HrynCo.NotificationService.DAL.EF; + +internal sealed class NotificationDbContextFactory : IDesignTimeDbContextFactory +{ + public NotificationDbContext CreateDbContext(string[] args) + { + var options = new DbContextOptionsBuilder() + .UseNpgsql("Host=localhost;Port=5432;Database=notification_service;Username=postgres;Password=postgres") + .Options; + + return new NotificationDbContext(options); + } +} \ No newline at end of file