fix injectionses
This commit is contained in:
parent
b93115d787
commit
cd078ca643
11 changed files with 119 additions and 55 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>());
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
"Femto": "Debug",
|
||||
"Microsoft.AspNetCore": "Warning",
|
||||
"Microsoft.EntityFrameworkCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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<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));
|
||||
|
||||
|
||||
services.AddDbContext<AuthContext>(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<AuthContext>();
|
||||
|
||||
services.AddSingleton(publisher);
|
||||
|
||||
|
||||
services.AddScoped<IAuthModule, AuthModule>();
|
||||
}
|
||||
|
||||
|
||||
private static async Task EventSubscriber(
|
||||
IEvent evt,
|
||||
IServiceProvider provider,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
using Femto.Common.Domain;
|
||||
|
||||
namespace Femto.Modules.Blog.Application.Commands.DeletePost;
|
||||
|
||||
public record DeletePostCommand(Guid PostId, Guid InitiatingUserId) : ICommand;
|
|
@ -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
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue