using Femto.Common; using Femto.Common.Infrastructure; using Femto.Common.Infrastructure.DbConnection; using Femto.Common.Infrastructure.Outbox; using Femto.Common.Integration; using Femto.Modules.Auth.Application.Services; using Femto.Modules.Auth.Data; using Femto.Modules.Auth.Infrastructure; using MediatR; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Quartz; namespace Femto.Modules.Auth.Application; public static class AuthStartup { public static void InitializeAuthenticationModule( this IServiceCollection rootContainer, string connectionString, IEventBus eventBus, ILoggerFactory loggerFactory ) { var hostBuilder = Host.CreateDefaultBuilder(); hostBuilder.ConfigureServices(services => ConfigureServices(services, connectionString, eventBus, loggerFactory) ); var host = hostBuilder.Build(); rootContainer.AddKeyedScoped( "AuthServiceScope", (s, o) => { var scope = host.Services.CreateScope(); return new ScopeBinding(scope); } ); rootContainer.ExposeScopedService(); rootContainer.AddHostedService(services => new AuthApplication(host)); eventBus.Subscribe( (evt, cancellationToken) => EventSubscriber(evt, host.Services, cancellationToken) ); } private static void ConfigureServices( IServiceCollection services, string connectionString, IEventPublisher publisher, ILoggerFactory loggerFactory ) { services.AddTransient(_ => new DbConnectionFactory(connectionString)); services.AddDbContext(builder => { builder.UseNpgsql(connectionString); builder.UseSnakeCaseNamingConvention(); builder.UseLoggerFactory(loggerFactory); // #if DEBUG // builder.EnableSensitiveDataLogging(); // #endif }); services.AddSingleton(loggerFactory); services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); services.AddQuartzHostedService(options => { options.WaitForJobsToComplete = true; }); // #endif services.AddOutbox(); services.AddMediatR(c => c.RegisterServicesFromAssembly(typeof(AuthStartup).Assembly)); services.ConfigureDomainServices(); services.AddSingleton(publisher); services.AddSingleton(); services.AddScoped( typeof(IPipelineBehavior<,>), typeof(SaveChangesPipelineBehaviour<,>) ); services.AddScoped(); } private static async Task EventSubscriber( IEvent evt, IServiceProvider provider, CancellationToken cancellationToken ) { using var scope = provider.CreateScope(); var context = scope.ServiceProvider.GetRequiredService(); var logger = scope.ServiceProvider.GetRequiredService>(); var publisher = scope.ServiceProvider.GetRequiredService(); IEventHandler? handler = evt switch { _ => null, }; if (handler is null) return; await handler.Handle(evt, cancellationToken); if (context.ChangeTracker.HasChanges()) { await context.EmitDomainEvents(logger, publisher, cancellationToken); await context.SaveChangesAsync(cancellationToken); } } } internal static class AuthServiceCollectionExtensions { public static void ExposeScopedService(this IServiceCollection container) where T : class { container.AddScoped(services => services.GetRequiredKeyedService("AuthServiceScope").GetService() ); } }