From 26b29d169e0f17d7eeb3610e7e3d71ffc4ff19ad Mon Sep 17 00:00:00 2001 From: Anatolii Grynchuk Date: Fri, 1 May 2026 19:50:33 +0300 Subject: [PATCH] feat: add EF Core layer with entities, configurations and DbContext - Directory.Packages.props and Directory.Build.props for central package management - TemplateEntity, ProviderEntity, ProviderUsageEntity (internal to DAL.EF) - TemplateEntityConfiguration: composite unique index (service_name, key, language_code), Variables as JSON column - ProviderEntityConfiguration: settings stored as jsonb, index on (service_name, priority) - ProviderUsageEntityConfiguration: composite unique index (provider_id, date) - All entities map Id column explicitly as 'id' (snake_case) - NotificationDbContext with ApplyConfigurationsFromAssembly Ref: IT-628 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Directory.Build.props | 5 ++ Directory.Packages.props | 33 +++++++++++ ...icationService.Api.IntegrationTests.csproj | 10 ++-- .../HrynCo.NotificationService.Api.csproj | 2 +- .../ProviderEntityConfiguration.cs | 39 +++++++++++++ .../ProviderUsageEntityConfiguration.cs | 25 +++++++++ .../TemplateEntityConfiguration.cs | 56 +++++++++++++++++++ .../Entities/ProviderEntity.cs | 24 ++++++++ .../Entities/ProviderUsageEntity.cs | 11 ++++ .../Entities/TemplateEntity.cs | 21 +++++++ .../HrynCo.NotificationService.DAL.EF.csproj | 10 ++++ .../NotificationDbContext.cs | 21 +++++++ ....NotificationService.Services.Tests.csproj | 10 ++-- .../HrynCo.NotificationService.Worker.csproj | 2 +- 14 files changed, 257 insertions(+), 12 deletions(-) create mode 100644 Directory.Build.props create mode 100644 Directory.Packages.props create mode 100644 HrynCo.NotificationService.DAL.EF/Configurations/ProviderEntityConfiguration.cs create mode 100644 HrynCo.NotificationService.DAL.EF/Configurations/ProviderUsageEntityConfiguration.cs create mode 100644 HrynCo.NotificationService.DAL.EF/Configurations/TemplateEntityConfiguration.cs create mode 100644 HrynCo.NotificationService.DAL.EF/Entities/ProviderEntity.cs create mode 100644 HrynCo.NotificationService.DAL.EF/Entities/ProviderUsageEntity.cs create mode 100644 HrynCo.NotificationService.DAL.EF/Entities/TemplateEntity.cs create mode 100644 HrynCo.NotificationService.DAL.EF/NotificationDbContext.cs diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..e42216a --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,5 @@ + + + true + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..4137eeb --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HrynCo.NotificationService.Api.IntegrationTests/HrynCo.NotificationService.Api.IntegrationTests.csproj b/HrynCo.NotificationService.Api.IntegrationTests/HrynCo.NotificationService.Api.IntegrationTests.csproj index 51b03d1..35be975 100644 --- a/HrynCo.NotificationService.Api.IntegrationTests/HrynCo.NotificationService.Api.IntegrationTests.csproj +++ b/HrynCo.NotificationService.Api.IntegrationTests/HrynCo.NotificationService.Api.IntegrationTests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -8,10 +8,10 @@ - - - - + + + + diff --git a/HrynCo.NotificationService.Api/HrynCo.NotificationService.Api.csproj b/HrynCo.NotificationService.Api/HrynCo.NotificationService.Api.csproj index af153da..ef39b79 100644 --- a/HrynCo.NotificationService.Api/HrynCo.NotificationService.Api.csproj +++ b/HrynCo.NotificationService.Api/HrynCo.NotificationService.Api.csproj @@ -7,7 +7,7 @@ - + diff --git a/HrynCo.NotificationService.DAL.EF/Configurations/ProviderEntityConfiguration.cs b/HrynCo.NotificationService.DAL.EF/Configurations/ProviderEntityConfiguration.cs new file mode 100644 index 0000000..4d584c9 --- /dev/null +++ b/HrynCo.NotificationService.DAL.EF/Configurations/ProviderEntityConfiguration.cs @@ -0,0 +1,39 @@ +using HrynCo.NotificationService.DAL.EF.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace HrynCo.NotificationService.DAL.EF.Configurations; + +internal class ProviderEntityConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("providers"); + + builder.HasKey(x => x.Id); + builder.Property(x => x.Id).HasColumnName("id"); + + builder.Property(x => x.ServiceName) + .HasColumnName("service_name") + .IsRequired() + .HasMaxLength(100); + + builder.HasIndex(x => new { x.ServiceName, x.Priority }); + + builder.Property(x => x.Priority).HasColumnName("priority"); + + builder.Property(x => x.ProviderType).HasColumnName("provider_type"); + + builder.Property(x => x.SettingsJson) + .HasColumnName("settings") + .HasColumnType("jsonb") + .IsRequired(); + + builder.Property(x => x.DailyLimit).HasColumnName("daily_limit"); + builder.Property(x => x.MonthlyLimit).HasColumnName("monthly_limit"); + builder.Property(x => x.WarnThresholdPercent).HasColumnName("warn_threshold_percent"); + builder.Property(x => x.IsActive).HasColumnName("is_active"); + builder.Property(x => x.Created).HasColumnName("created"); + builder.Property(x => x.Updated).HasColumnName("updated"); + } +} diff --git a/HrynCo.NotificationService.DAL.EF/Configurations/ProviderUsageEntityConfiguration.cs b/HrynCo.NotificationService.DAL.EF/Configurations/ProviderUsageEntityConfiguration.cs new file mode 100644 index 0000000..b2ac7ff --- /dev/null +++ b/HrynCo.NotificationService.DAL.EF/Configurations/ProviderUsageEntityConfiguration.cs @@ -0,0 +1,25 @@ +using HrynCo.NotificationService.DAL.EF.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace HrynCo.NotificationService.DAL.EF.Configurations; + +internal class ProviderUsageEntityConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("provider_usage"); + + builder.HasKey(x => x.Id); + builder.Property(x => x.Id).HasColumnName("id"); + + builder.Property(x => x.ProviderId).HasColumnName("provider_id"); + + builder.HasIndex(x => new { x.ProviderId, x.Date }).IsUnique(); + + builder.Property(x => x.Date).HasColumnName("date"); + builder.Property(x => x.SentCount).HasColumnName("sent_count"); + builder.Property(x => x.Created).HasColumnName("created"); + builder.Property(x => x.Updated).HasColumnName("updated"); + } +} diff --git a/HrynCo.NotificationService.DAL.EF/Configurations/TemplateEntityConfiguration.cs b/HrynCo.NotificationService.DAL.EF/Configurations/TemplateEntityConfiguration.cs new file mode 100644 index 0000000..ed49681 --- /dev/null +++ b/HrynCo.NotificationService.DAL.EF/Configurations/TemplateEntityConfiguration.cs @@ -0,0 +1,56 @@ +using HrynCo.NotificationService.DAL.EF.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace HrynCo.NotificationService.DAL.EF.Configurations; + +internal class TemplateEntityConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.ToTable("templates"); + + builder.HasKey(x => x.Id); + builder.Property(x => x.Id).HasColumnName("id"); + + builder.Property(x => x.ServiceName) + .HasColumnName("service_name") + .IsRequired() + .HasMaxLength(100); + + builder.Property(x => x.Key) + .HasColumnName("key") + .IsRequired() + .HasMaxLength(100); + + builder.Property(x => x.LanguageCode) + .HasColumnName("language_code") + .IsRequired() + .HasMaxLength(10); + + builder.HasIndex(x => new { x.ServiceName, x.Key, x.LanguageCode }) + .IsUnique(); + + builder.Property(x => x.Subject) + .HasColumnName("subject") + .IsRequired(); + + builder.Property(x => x.HtmlBody) + .HasColumnName("html_body") + .IsRequired(); + + builder.Property(x => x.TextBody) + .HasColumnName("text_body") + .IsRequired(); + + builder.Property(x => x.Created).HasColumnName("created"); + builder.Property(x => x.Updated).HasColumnName("updated"); + + builder.OwnsMany(x => x.Variables, v => + { + v.ToJson("variables"); + v.Property(x => x.Name).HasJsonPropertyName("name"); + v.Property(x => x.Required).HasJsonPropertyName("required"); + }); + } +} diff --git a/HrynCo.NotificationService.DAL.EF/Entities/ProviderEntity.cs b/HrynCo.NotificationService.DAL.EF/Entities/ProviderEntity.cs new file mode 100644 index 0000000..1101ef1 --- /dev/null +++ b/HrynCo.NotificationService.DAL.EF/Entities/ProviderEntity.cs @@ -0,0 +1,24 @@ +using HrynCo.NotificationService.DAL.Abstract.Providers; + +namespace HrynCo.NotificationService.DAL.EF.Entities; + +internal class ProviderEntity +{ + public Guid Id { get; set; } + public required string ServiceName { get; set; } + public int Priority { get; set; } + public ProviderType ProviderType { get; set; } + + /// + /// Provider-specific credentials and settings stored as JSONB. + /// Deserialized based on in the repository. + /// + public required string SettingsJson { get; set; } + + public int? DailyLimit { get; set; } + public int? MonthlyLimit { get; set; } + public int WarnThresholdPercent { get; set; } + public bool IsActive { get; set; } + public DateTimeOffset Created { get; set; } + public DateTimeOffset? Updated { get; set; } +} diff --git a/HrynCo.NotificationService.DAL.EF/Entities/ProviderUsageEntity.cs b/HrynCo.NotificationService.DAL.EF/Entities/ProviderUsageEntity.cs new file mode 100644 index 0000000..dbce792 --- /dev/null +++ b/HrynCo.NotificationService.DAL.EF/Entities/ProviderUsageEntity.cs @@ -0,0 +1,11 @@ +namespace HrynCo.NotificationService.DAL.EF.Entities; + +internal class ProviderUsageEntity +{ + public Guid Id { get; set; } + public Guid ProviderId { get; set; } + public DateOnly Date { get; set; } + public int SentCount { get; set; } + public DateTimeOffset Created { get; set; } + public DateTimeOffset? Updated { get; set; } +} diff --git a/HrynCo.NotificationService.DAL.EF/Entities/TemplateEntity.cs b/HrynCo.NotificationService.DAL.EF/Entities/TemplateEntity.cs new file mode 100644 index 0000000..ffa132e --- /dev/null +++ b/HrynCo.NotificationService.DAL.EF/Entities/TemplateEntity.cs @@ -0,0 +1,21 @@ +namespace HrynCo.NotificationService.DAL.EF.Entities; + +internal class TemplateEntity +{ + public Guid Id { get; set; } + public required string ServiceName { get; set; } + public required string Key { get; set; } + public required string LanguageCode { get; set; } + public required string Subject { get; set; } + public required string HtmlBody { get; set; } + public required string TextBody { get; set; } + public List Variables { get; set; } = []; + public DateTimeOffset Created { get; set; } + public DateTimeOffset? Updated { get; set; } +} + +internal class TemplateVariableData +{ + public required string Name { get; set; } + public bool Required { get; set; } +} diff --git a/HrynCo.NotificationService.DAL.EF/HrynCo.NotificationService.DAL.EF.csproj b/HrynCo.NotificationService.DAL.EF/HrynCo.NotificationService.DAL.EF.csproj index 8011d57..f073f56 100644 --- a/HrynCo.NotificationService.DAL.EF/HrynCo.NotificationService.DAL.EF.csproj +++ b/HrynCo.NotificationService.DAL.EF/HrynCo.NotificationService.DAL.EF.csproj @@ -4,6 +4,16 @@ + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + net10.0 enable diff --git a/HrynCo.NotificationService.DAL.EF/NotificationDbContext.cs b/HrynCo.NotificationService.DAL.EF/NotificationDbContext.cs new file mode 100644 index 0000000..2ff4c34 --- /dev/null +++ b/HrynCo.NotificationService.DAL.EF/NotificationDbContext.cs @@ -0,0 +1,21 @@ +using HrynCo.NotificationService.DAL.EF.Entities; +using Microsoft.EntityFrameworkCore; + +namespace HrynCo.NotificationService.DAL.EF; + +public class NotificationDbContext : DbContext +{ + public NotificationDbContext(DbContextOptions options) + : base(options) + { + } + + internal DbSet Templates => Set(); + internal DbSet Providers => Set(); + internal DbSet ProviderUsage => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfigurationsFromAssembly(typeof(NotificationDbContext).Assembly); + } +} diff --git a/HrynCo.NotificationService.Services.Tests/HrynCo.NotificationService.Services.Tests.csproj b/HrynCo.NotificationService.Services.Tests/HrynCo.NotificationService.Services.Tests.csproj index 90aa85b..69e51f0 100644 --- a/HrynCo.NotificationService.Services.Tests/HrynCo.NotificationService.Services.Tests.csproj +++ b/HrynCo.NotificationService.Services.Tests/HrynCo.NotificationService.Services.Tests.csproj @@ -1,4 +1,4 @@ - + net10.0 @@ -8,10 +8,10 @@ - - - - + + + + diff --git a/HrynCo.NotificationService.Worker/HrynCo.NotificationService.Worker.csproj b/HrynCo.NotificationService.Worker/HrynCo.NotificationService.Worker.csproj index e044c6a..8f82f22 100644 --- a/HrynCo.NotificationService.Worker/HrynCo.NotificationService.Worker.csproj +++ b/HrynCo.NotificationService.Worker/HrynCo.NotificationService.Worker.csproj @@ -8,7 +8,7 @@ - +