fix injectionses

This commit is contained in:
john 2025-05-21 00:19:49 +02:00
parent b93115d787
commit cd078ca643
11 changed files with 119 additions and 55 deletions

View file

@ -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);
}
}

View file

@ -4,5 +4,23 @@ namespace Femto.Api;
internal class CurrentUserContext : ICurrentUserContext
{
public CurrentUser? CurrentUser { get; set; }
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;
}
}

View file

@ -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<IEvent>());
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<CurrentUserContext, CurrentUserContext>();
builder.Services.AddScoped<ICurrentUserContext>(s => s.GetRequiredService<CurrentUserContext>());

View file

@ -3,7 +3,9 @@
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
"Femto": "Debug",
"Microsoft.AspNetCore": "Warning",
"Microsoft.EntityFrameworkCore": "Warning"
}
},
"AllowedHosts": "*"

View file

@ -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);

View file

@ -1,16 +1,21 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace Femto.Modules.Auth.Application;
namespace Femto.Common;
/// <summary>
/// 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
/// </summary>
/// <param name="scope"></param>
public class ScopeBinding(IServiceScope scope) : IDisposable
public class ScopeBinding<T>(IServiceScope scope) : IDisposable
where T : notnull
{
public T GetService<T>() where T : notnull => scope.ServiceProvider.GetRequiredService<T>();
public T GetService() {
return scope.ServiceProvider.GetRequiredService<T>();
}
public void Dispose() => scope.Dispose();
public void Dispose() {
scope.Dispose();
}
}

View file

@ -1,3 +1,4 @@
using Femto.Common;
using Femto.Common.Infrastructure;
using Femto.Common.Infrastructure.DbConnection;
using Femto.Common.Infrastructure.Outbox;
@ -15,23 +16,36 @@ 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<IAuthModule>(services =>
services.GetRequiredService<ScopeBinding>().GetService<IAuthModule>());
rootContainer.AddScoped(_ => new ScopeBinding<IAuthModule>(host.Services.CreateScope()));
rootContainer.AddScoped(services =>
services.GetRequiredService<ScopeBinding<IAuthModule>>().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<IDbConnectionFactory>(_ => new DbConnectionFactory(connectionString));
@ -39,13 +53,15 @@ public static class AuthStartup
{
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;

View file

@ -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<IMediator>();
await mediator.Send(command, cancellationToken);
}
public async Task<TResponse> PostCommand<TResponse>(
public async Task<TResponse> Command<TResponse>(
ICommand<TResponse> command,
CancellationToken cancellationToken = default
)
{
using var scope = host.Services.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
var response = await mediator.Send(command, cancellationToken);
return response;
}
public async Task<TResponse> PostQuery<TResponse>(
public async Task<TResponse> Query<TResponse>(
IQuery<TResponse> query,
CancellationToken cancellationToken = default
)
{
using var scope = host.Services.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<IMediator>();
var response = await mediator.Send(query, cancellationToken);
return response;
}

View file

@ -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<IBlogModule>(_ => new BlogModule(host));
rootContainer.AddScoped(_ => new ScopeBinding<IBlogModule>(host.Services.CreateScope()));
rootContainer.AddScoped(services =>
services.GetRequiredService<ScopeBinding<IBlogModule>>().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<IDbConnectionFactory>(_ => 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<BlogContext, OutboxMessageHandler>();
services.AddMediatR(c =>
@ -71,6 +79,8 @@ public static class BlogStartup
services.ConfigureDomainServices<BlogContext>();
services.AddScoped<IBlogModule, BlogModule>();
services.AddSingleton(publisher);
}

View file

@ -0,0 +1,5 @@
using Femto.Common.Domain;
namespace Femto.Modules.Blog.Application.Commands.DeletePost;
public record DeletePostCommand(Guid PostId, Guid InitiatingUserId) : ICommand;

View file

@ -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<TResponse> PostCommand<TResponse>(
Task<TResponse> Command<TResponse>(
ICommand<TResponse> command,
CancellationToken cancellationToken = default
);
Task<TResponse> PostQuery<TResponse>(
Task<TResponse> Query<TResponse>(
IQuery<TResponse> query,
CancellationToken cancellationToken = default
);