From cd078ca6437caad55dd6ab7aa91c67509eb346f0 Mon Sep 17 00:00:00 2001 From: john Date: Wed, 21 May 2025 00:19:49 +0200 Subject: [PATCH] fix injectionses --- .../Controllers/Posts/PostsController.cs | 12 ++++- Femto.Api/CurrentUserContext.cs | 22 ++++++++- Femto.Api/Program.cs | 17 ++++--- Femto.Api/appsettings.json | 4 +- Femto.Common/ICurrentUserContext.cs | 5 +- .../ScopeBinding.cs | 17 ++++--- Femto.Modules.Auth/Application/AuthStartup.cs | 46 +++++++++++++------ Femto.Modules.Blog/Application/BlogModule.cs | 16 ++----- Femto.Modules.Blog/Application/BlogStartup.cs | 24 +++++++--- .../Commands/DeletePost/DeletePostCommand.cs | 5 ++ Femto.Modules.Blog/Application/IBlogModule.cs | 6 +-- 11 files changed, 119 insertions(+), 55 deletions(-) rename {Femto.Modules.Auth/Application => Femto.Common}/ScopeBinding.cs (50%) create mode 100644 Femto.Modules.Blog/Application/Commands/DeletePost/DeletePostCommand.cs diff --git a/Femto.Api/Controllers/Posts/PostsController.cs b/Femto.Api/Controllers/Posts/PostsController.cs index 9eeeafc..ce2d75f 100644 --- a/Femto.Api/Controllers/Posts/PostsController.cs +++ b/Femto.Api/Controllers/Posts/PostsController.cs @@ -2,6 +2,7 @@ using Femto.Api.Controllers.Posts.Dto; using Femto.Common; using Femto.Modules.Blog.Application; using Femto.Modules.Blog.Application.Commands.CreatePost; +using Femto.Modules.Blog.Application.Commands.DeletePost; using Femto.Modules.Blog.Application.Queries.GetPosts; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -18,7 +19,7 @@ public class PostsController(IBlogModule blogModule, ICurrentUserContext current CancellationToken cancellationToken ) { - var res = await blogModule.PostQuery( + var res = await blogModule.Query( new GetPostsQuery(currentUserContext.CurrentUser?.Id) { From = searchParams.From, @@ -48,7 +49,7 @@ public class PostsController(IBlogModule blogModule, ICurrentUserContext current CancellationToken cancellationToken ) { - var guid = await blogModule.PostCommand( + var guid = await blogModule.Command( new CreatePostCommand( req.AuthorId, req.Content, @@ -70,4 +71,11 @@ public class PostsController(IBlogModule blogModule, ICurrentUserContext current return new CreatePostResponse(guid); } + + [HttpDelete("{postId}")] + [Authorize] + public async Task DeletePost(Guid postId, CancellationToken cancellationToken) + { + await blogModule.Command(new DeletePostCommand(postId, currentUserContext.CurrentUser.Id), cancellationToken); + } } diff --git a/Femto.Api/CurrentUserContext.cs b/Femto.Api/CurrentUserContext.cs index 1754dbe..01d0d5c 100644 --- a/Femto.Api/CurrentUserContext.cs +++ b/Femto.Api/CurrentUserContext.cs @@ -4,5 +4,23 @@ namespace Femto.Api; internal class CurrentUserContext : ICurrentUserContext { - public CurrentUser? CurrentUser { get; set; } -} \ No newline at end of file + private CurrentUser? _currentUser; + + public CurrentUser? tryGetUserCurrentUser() => this._currentUser; + + public bool HasUser => this._currentUser is not null; + + public CurrentUser CurrentUser + { + get + { + if (_currentUser is null) + throw new InvalidOperationException( + "don't access current user if not authenticated" + ); + + return _currentUser; + } + set => _currentUser = value; + } +} diff --git a/Femto.Api/Program.cs b/Femto.Api/Program.cs index 469d3e0..08a40b8 100644 --- a/Femto.Api/Program.cs +++ b/Femto.Api/Program.cs @@ -21,11 +21,16 @@ var builder = WebApplication.CreateBuilder(args); builder.Services.AddOpenApi(); -builder.Logging.ClearProviders(); -builder.Logging.AddConsole(); -builder.Logging.AddDebug(); -builder.Logging.SetMinimumLevel(LogLevel.Information); +var loggerFactory = LoggerFactory.Create(b => +{ + b.SetMinimumLevel(LogLevel.Information) + .AddConfiguration(builder.Configuration.GetSection("Logging")) + .AddConsole() + .AddDebug(); +}); +builder.Services.AddSingleton(loggerFactory); +builder.Services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); var connectionString = builder.Configuration.GetConnectionString("Database"); if (connectionString is null) @@ -39,9 +44,9 @@ if (blobStorageRoot is null) var eventBus = new EventBus(Channel.CreateUnbounded()); builder.Services.AddHostedService(_ => eventBus); -builder.Services.InitializeBlogModule(connectionString, eventBus); +builder.Services.InitializeBlogModule(connectionString, eventBus, loggerFactory); builder.Services.InitializeMediaModule(connectionString, blobStorageRoot); -builder.Services.InitializeAuthenticationModule(connectionString, eventBus); +builder.Services.InitializeAuthenticationModule(connectionString, eventBus, loggerFactory); builder.Services.AddScoped(); builder.Services.AddScoped(s => s.GetRequiredService()); diff --git a/Femto.Api/appsettings.json b/Femto.Api/appsettings.json index 51120b2..5c949c0 100644 --- a/Femto.Api/appsettings.json +++ b/Femto.Api/appsettings.json @@ -3,7 +3,9 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Femto": "Debug", + "Microsoft.AspNetCore": "Warning", + "Microsoft.EntityFrameworkCore": "Warning" } }, "AllowedHosts": "*" diff --git a/Femto.Common/ICurrentUserContext.cs b/Femto.Common/ICurrentUserContext.cs index a7233e0..0cb0207 100644 --- a/Femto.Common/ICurrentUserContext.cs +++ b/Femto.Common/ICurrentUserContext.cs @@ -2,7 +2,10 @@ namespace Femto.Common; public interface ICurrentUserContext { - CurrentUser? CurrentUser { get; } + CurrentUser? tryGetUserCurrentUser(); + + bool HasUser { get; } + CurrentUser CurrentUser { get; } } public record CurrentUser(Guid Id, string Username, string SessionId); diff --git a/Femto.Modules.Auth/Application/ScopeBinding.cs b/Femto.Common/ScopeBinding.cs similarity index 50% rename from Femto.Modules.Auth/Application/ScopeBinding.cs rename to Femto.Common/ScopeBinding.cs index 4a6419f..c78408e 100644 --- a/Femto.Modules.Auth/Application/ScopeBinding.cs +++ b/Femto.Common/ScopeBinding.cs @@ -1,16 +1,21 @@ using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; -namespace Femto.Modules.Auth.Application; - +namespace Femto.Common; /// /// We use this to bind a scope to the request scope in the composition root /// Any scoped services provided by this subcontainer should be accessed via a ScopeBinding injected in the host /// /// -public class ScopeBinding(IServiceScope scope) : IDisposable +public class ScopeBinding(IServiceScope scope) : IDisposable + where T : notnull { - public T GetService() where T : notnull => scope.ServiceProvider.GetRequiredService(); + public T GetService() { + return scope.ServiceProvider.GetRequiredService(); + } - public void Dispose() => scope.Dispose(); -} \ No newline at end of file + public void Dispose() { + scope.Dispose(); + } +} diff --git a/Femto.Modules.Auth/Application/AuthStartup.cs b/Femto.Modules.Auth/Application/AuthStartup.cs index 195aa23..b9e6132 100644 --- a/Femto.Modules.Auth/Application/AuthStartup.cs +++ b/Femto.Modules.Auth/Application/AuthStartup.cs @@ -1,3 +1,4 @@ +using Femto.Common; using Femto.Common.Infrastructure; using Femto.Common.Infrastructure.DbConnection; using Femto.Common.Infrastructure.Outbox; @@ -15,37 +16,52 @@ namespace Femto.Modules.Auth.Application; public static class AuthStartup { - public static void InitializeAuthenticationModule(this IServiceCollection rootContainer, - string connectionString, IEventBus eventBus) + public static void InitializeAuthenticationModule( + this IServiceCollection rootContainer, + string connectionString, + IEventBus eventBus, + ILoggerFactory loggerFactory + ) { var hostBuilder = Host.CreateDefaultBuilder(); - hostBuilder.ConfigureServices(services => ConfigureServices(services, connectionString, eventBus)); + hostBuilder.ConfigureServices(services => + ConfigureServices(services, connectionString, eventBus, loggerFactory) + ); var host = hostBuilder.Build(); - - rootContainer.AddScoped(_ => new ScopeBinding(host.Services.CreateScope())); - - rootContainer.AddScoped(services => - services.GetRequiredService().GetService()); - + + rootContainer.AddScoped(_ => new ScopeBinding(host.Services.CreateScope())); + rootContainer.AddScoped(services => + services.GetRequiredService>().GetService() + ); + rootContainer.AddHostedService(services => new AuthApplication(host)); - eventBus.Subscribe((evt, cancellationToken) => EventSubscriber(evt, host.Services, cancellationToken)); + eventBus.Subscribe( + (evt, cancellationToken) => EventSubscriber(evt, host.Services, cancellationToken) + ); } - private static void ConfigureServices(IServiceCollection services, string connectionString, IEventPublisher publisher) + 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(); - var loggerFactory = LoggerFactory.Create(b => { }); builder.UseLoggerFactory(loggerFactory); // #if DEBUG // builder.EnableSensitiveDataLogging(); // #endif }); + services.AddSingleton(loggerFactory); + services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); + services.AddQuartzHostedService(options => { options.WaitForJobsToComplete = true; @@ -58,10 +74,10 @@ public static class AuthStartup services.ConfigureDomainServices(); services.AddSingleton(publisher); - + services.AddScoped(); } - + private static async Task EventSubscriber( IEvent evt, IServiceProvider provider, diff --git a/Femto.Modules.Blog/Application/BlogModule.cs b/Femto.Modules.Blog/Application/BlogModule.cs index d96228c..c293ceb 100644 --- a/Femto.Modules.Blog/Application/BlogModule.cs +++ b/Femto.Modules.Blog/Application/BlogModule.cs @@ -1,37 +1,29 @@ using Femto.Common.Domain; using MediatR; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; namespace Femto.Modules.Blog.Application; -internal class BlogModule(IHost host) : IBlogModule +internal class BlogModule(IMediator mediator) : IBlogModule { - public async Task PostCommand(ICommand command, CancellationToken cancellationToken = default) + public async Task Command(ICommand command, CancellationToken cancellationToken = default) { - using var scope = host.Services.CreateScope(); - var mediator = scope.ServiceProvider.GetRequiredService(); await mediator.Send(command, cancellationToken); } - public async Task PostCommand( + public async Task Command( ICommand command, CancellationToken cancellationToken = default ) { - using var scope = host.Services.CreateScope(); - var mediator = scope.ServiceProvider.GetRequiredService(); var response = await mediator.Send(command, cancellationToken); return response; } - public async Task PostQuery( + public async Task Query( IQuery query, CancellationToken cancellationToken = default ) { - using var scope = host.Services.CreateScope(); - var mediator = scope.ServiceProvider.GetRequiredService(); var response = await mediator.Send(query, cancellationToken); return response; } diff --git a/Femto.Modules.Blog/Application/BlogStartup.cs b/Femto.Modules.Blog/Application/BlogStartup.cs index 000e9bd..b134f4c 100644 --- a/Femto.Modules.Blog/Application/BlogStartup.cs +++ b/Femto.Modules.Blog/Application/BlogStartup.cs @@ -1,4 +1,5 @@ using System.Runtime.CompilerServices; +using Femto.Common; using Femto.Common.Infrastructure; using Femto.Common.Infrastructure.DbConnection; using Femto.Common.Infrastructure.Outbox; @@ -20,20 +21,24 @@ public static class BlogStartup public static void InitializeBlogModule( this IServiceCollection rootContainer, string connectionString, - IEventBus bus + IEventBus bus, + ILoggerFactory loggerFactory ) { var hostBuilder = Host.CreateDefaultBuilder(); hostBuilder.ConfigureServices(services => - ConfigureServices(services, connectionString, bus) + ConfigureServices(services, connectionString, bus, loggerFactory) ); var host = hostBuilder.Build(); - rootContainer.AddHostedService(services => new BlogApplication(host)); + rootContainer.AddHostedService(_ => new BlogApplication(host)); - rootContainer.AddScoped(_ => new BlogModule(host)); + rootContainer.AddScoped(_ => new ScopeBinding(host.Services.CreateScope())); + rootContainer.AddScoped(services => + services.GetRequiredService>().GetService() + ); bus.Subscribe( (evt, cancellationToken) => EventSubscriber(evt, host.Services, cancellationToken) @@ -43,7 +48,8 @@ public static class BlogStartup private static void ConfigureServices( this IServiceCollection services, string connectionString, - IEventPublisher publisher + IEventPublisher publisher, + ILoggerFactory loggerFactory ) { services.AddTransient(_ => new DbConnectionFactory(connectionString)); @@ -58,10 +64,12 @@ public static class BlogStartup } ); builder.UseSnakeCaseNamingConvention(); - var loggerFactory = LoggerFactory.Create(b => { }); builder.UseLoggerFactory(loggerFactory); }); - + + services.AddSingleton(loggerFactory); + services.AddSingleton(typeof(ILogger<>), typeof(Logger<>)); + services.AddOutbox(); services.AddMediatR(c => @@ -71,6 +79,8 @@ public static class BlogStartup services.ConfigureDomainServices(); + services.AddScoped(); + services.AddSingleton(publisher); } diff --git a/Femto.Modules.Blog/Application/Commands/DeletePost/DeletePostCommand.cs b/Femto.Modules.Blog/Application/Commands/DeletePost/DeletePostCommand.cs new file mode 100644 index 0000000..099ed49 --- /dev/null +++ b/Femto.Modules.Blog/Application/Commands/DeletePost/DeletePostCommand.cs @@ -0,0 +1,5 @@ +using Femto.Common.Domain; + +namespace Femto.Modules.Blog.Application.Commands.DeletePost; + +public record DeletePostCommand(Guid PostId, Guid InitiatingUserId) : ICommand; \ No newline at end of file diff --git a/Femto.Modules.Blog/Application/IBlogModule.cs b/Femto.Modules.Blog/Application/IBlogModule.cs index 941e1e2..083b184 100644 --- a/Femto.Modules.Blog/Application/IBlogModule.cs +++ b/Femto.Modules.Blog/Application/IBlogModule.cs @@ -4,14 +4,14 @@ namespace Femto.Modules.Blog.Application; public interface IBlogModule { - Task PostCommand(ICommand command, CancellationToken cancellationToken = default); + Task Command(ICommand command, CancellationToken cancellationToken = default); - Task PostCommand( + Task Command( ICommand command, CancellationToken cancellationToken = default ); - Task PostQuery( + Task Query( IQuery query, CancellationToken cancellationToken = default );