feat: wire up Program.cs, DI extensions, Serilog, and appsettings

- ServiceCollectionExtensions in DAL.EF: AddNotificationDataAccess (DbContext, UoW, repositories)
- ServiceCollectionExtensions in Services: AddNotificationServices (MediatR + TransactionBehavior)
- Api/Program.cs: Serilog, OpenAPI, controllers, DI wiring
- Worker/Program.cs: Serilog, DI wiring
- appsettings.json: Serilog config with Console + Seq sinks, connection string
- appsettings.Development.json: Debug log level overrides

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Anatolii Grynchuk
2026-05-02 00:24:54 +03:00
parent 101bb908bd
commit 5cf5f888eb
12 changed files with 144 additions and 58 deletions
+1
View File
@@ -21,6 +21,7 @@
<!-- Serilog -->
<PackageVersion Include="Serilog.AspNetCore" Version="9.0.0" />
<PackageVersion Include="Serilog.Extensions.Hosting" Version="9.0.0" />
<PackageVersion Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageVersion Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageVersion Include="Serilog.Sinks.Seq" Version="9.0.0" />
+15 -30
View File
@@ -1,41 +1,26 @@
using HrynCo.NotificationService.DAL.EF;
using HrynCo.NotificationService.Services;
using Serilog;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Host.UseSerilog((context, lc) =>
lc.ReadFrom.Configuration(context.Configuration));
builder.Services.AddOpenApi();
builder.Services.AddControllers();
string connectionString = builder.Configuration.GetConnectionString("Default")
?? throw new InvalidOperationException("Connection string 'Default' is not configured.");
builder.Services.AddNotificationDataAccess(connectionString);
builder.Services.AddNotificationServices();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseHttpsRedirection();
var summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
app.MapGet("/weatherforecast", () =>
{
var forecast = Enumerable.Range(1, 5).Select(index =>
new WeatherForecast
(
DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
Random.Shared.Next(-20, 55),
summaries[Random.Shared.Next(summaries.Length)]
))
.ToArray();
return forecast;
})
.WithName("GetWeatherForecast");
app.MapControllers();
app.Run();
record WeatherForecast(DateOnly Date, int TemperatureC, string? Summary)
{
public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
}
@@ -1,8 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Information",
"Microsoft.EntityFrameworkCore": "Information",
"Microsoft.AspNetCore": "Information"
}
}
}
}
@@ -1,9 +1,26 @@
{
"Logging": {
"LogLevel": {
"ConnectionStrings": {
"Default": "Host=localhost;Port=5432;Database=notification_service;Username=postgres;Password=postgres"
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"Microsoft.EntityFrameworkCore": "Warning",
"Microsoft.AspNetCore": "Warning"
}
},
"WriteTo": [
{ "Name": "Console" },
{
"Name": "Seq",
"Args": {
"serverUrl": "http://localhost:5341"
}
}
],
"Enrich": [ "FromLogContext" ]
},
"AllowedHosts": "*"
}
@@ -2,11 +2,11 @@ using HrynCo.NotificationService.DAL.Abstract.Templates;
namespace HrynCo.NotificationService.DAL.Abstract.Repositories;
public interface IEmailEmailTemplateRepository
public interface IEmailTemplateRepository
{
Task<IReadOnlyList<EmailTemplate>> GetByServiceAsync(string serviceName, CancellationToken ct = default);
Task<EmailTemplate?> GetAsync(string serviceName, string key, string languageCode, CancellationToken ct = default);
Task AddAsync(EmailTemplate EmailTemplate, CancellationToken ct = default);
Task UpdateAsync(EmailTemplate EmailTemplate, CancellationToken ct = default);
Task DeleteAsync(EmailTemplate EmailTemplate, CancellationToken ct = default);
Task AddAsync(EmailTemplate template, CancellationToken ct = default);
Task UpdateAsync(EmailTemplate template, CancellationToken ct = default);
Task DeleteAsync(EmailTemplate template, CancellationToken ct = default);
}
@@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore;
namespace HrynCo.NotificationService.DAL.EF.Repositories;
internal sealed class EmailTemplateRepository : EfRepository<EmailTemplateEntity>, IEmailEmailTemplateRepository
internal sealed class EmailTemplateRepository : EfRepository<EmailTemplateEntity>, IEmailTemplateRepository
{
public EmailTemplateRepository(NotificationDbContext dbContext) : base(dbContext)
{
@@ -0,0 +1,26 @@
using HrynCo.NotificationService.DAL.Abstract;
using HrynCo.NotificationService.DAL.Abstract.Repositories;
using HrynCo.NotificationService.DAL.EF.Core;
using HrynCo.NotificationService.DAL.EF.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace HrynCo.NotificationService.DAL.EF;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddNotificationDataAccess(
this IServiceCollection services,
string connectionString)
{
services.AddDbContext<NotificationDbContext>(options =>
options.UseNpgsql(connectionString));
services.AddScoped<IUnitOfWork, UnitOfWork>();
services.AddScoped<IEmailTemplateRepository, EmailTemplateRepository>();
services.AddScoped<IEmailChannelRepository, EmailChannelRepository>();
services.AddScoped<IEmailChannelUsageRepository, EmailChannelUsageRepository>();
return services;
}
}
@@ -0,0 +1,19 @@
using HrynCo.NotificationService.Services.Behaviors;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
namespace HrynCo.NotificationService.Services;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddNotificationServices(this IServiceCollection services)
{
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(ServiceCollectionExtensions).Assembly);
cfg.AddBehavior(typeof(IPipelineBehavior<,>), typeof(TransactionBehavior<,>));
});
return services;
}
}
@@ -10,6 +10,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Serilog.Extensions.Hosting" />
<PackageReference Include="Serilog.Settings.Configuration" />
<PackageReference Include="Serilog.Sinks.Console" />
<PackageReference Include="Serilog.Sinks.Seq" />
</ItemGroup>
@@ -1,6 +1,18 @@
using HrynCo.NotificationService.DAL.EF;
using HrynCo.NotificationService.Services;
using HrynCo.NotificationService.Worker;
using Serilog;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSerilog(lc =>
lc.ReadFrom.Configuration(builder.Configuration));
string connectionString = builder.Configuration.GetConnectionString("Default")
?? throw new InvalidOperationException("Connection string 'Default' is not configured.");
builder.Services.AddNotificationDataAccess(connectionString);
builder.Services.AddNotificationServices();
builder.Services.AddHostedService<Worker>();
var host = builder.Build();
@@ -1,8 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
"Serilog": {
"MinimumLevel": {
"Default": "Debug",
"Override": {
"Microsoft": "Information",
"Microsoft.EntityFrameworkCore": "Information",
"Microsoft.AspNetCore": "Information"
}
}
}
}
@@ -1,8 +1,25 @@
{
"Logging": {
"LogLevel": {
"ConnectionStrings": {
"Default": "Host=localhost;Port=5432;Database=notification_service;Username=postgres;Password=postgres"
},
"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Microsoft.Hosting.Lifetime": "Information"
"Override": {
"Microsoft": "Warning",
"Microsoft.EntityFrameworkCore": "Warning",
"Microsoft.AspNetCore": "Warning"
}
},
"WriteTo": [
{ "Name": "Console" },
{
"Name": "Seq",
"Args": {
"serverUrl": "http://localhost:5341"
}
}
],
"Enrich": [ "FromLogContext" ]
}
}