diff --git a/Femto.Api/Controllers/Auth/AuthController.cs b/Femto.Api/Controllers/Auth/AuthController.cs index b3ad33f..e45e73c 100644 --- a/Femto.Api/Controllers/Auth/AuthController.cs +++ b/Femto.Api/Controllers/Auth/AuthController.cs @@ -1,16 +1,26 @@ +using Femto.Api.Auth; using Femto.Api.Sessions; using Femto.Common; +using Femto.Modules.Auth.Application.Interface.CreateSignupCode; +using Femto.Modules.Auth.Application.Interface.GetSignupCodesQuery; +using Femto.Modules.Auth.Application.Interface.Register; using Femto.Modules.Auth.Application.Services; using Femto.Modules.Auth.Contracts; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; namespace Femto.Api.Controllers.Auth; [ApiController] [Route("auth")] -public class AuthController(ICurrentUserContext currentUserContext, IAuthService authService) - : ControllerBase +public class AuthController( + IAuthModule authModule, + IOptions cookieSettings, + ICurrentUserContext currentUserContext, + ILogger logger, + IAuthService authService +) : ControllerBase { [HttpPost("login")] public async Task> Login( @@ -18,17 +28,16 @@ public class AuthController(ICurrentUserContext currentUserContext, IAuthService CancellationToken cancellationToken ) { - var result = await authService.GetUserWithCredentials( + var user = await authService.GetUserWithCredentials( request.Username, request.Password, - request.RememberMe, cancellationToken ); - - if (result is null) + + if (user is null) return this.BadRequest(); - - var (user, session) = result; + + var session = await authService.CreateStrongSession(user.Id); HttpContext.SetSession(session, user); @@ -38,15 +47,13 @@ public class AuthController(ICurrentUserContext currentUserContext, IAuthService [HttpPost("register")] public async Task> Register([FromBody] RegisterRequest request) { - var (user, session) = await authService.CreateUserWithCredentials( - request.Username, - request.Password, - request.SignupCode, - request.RememberMe + var user = await authModule.Command( + new RegisterCommand(request.Username, request.Password, request.SignupCode) ); - HttpContext.SetSession(session, user); + var session = await authService.CreateStrongSession(user.Id); + HttpContext.SetSession(session, user); return new RegisterResponse( user.Id, user.Username, @@ -99,7 +106,10 @@ public class AuthController(ICurrentUserContext currentUserContext, IAuthService CancellationToken cancellationToken ) { - await authService.AddSignupCode(request.Code, request.Name, cancellationToken); + await authModule.Command( + new CreateSignupCodeCommand(request.Code, request.Email, request.Name), + cancellationToken + ); return Ok(new { }); } @@ -110,7 +120,7 @@ public class AuthController(ICurrentUserContext currentUserContext, IAuthService CancellationToken cancellationToken ) { - var codes = await authService.GetSignupCodes(cancellationToken); + var codes = await authModule.Query(new GetSignupCodesQuery(), cancellationToken); return new ListSignupCodesResult( codes.Select(c => new SignupCodeDto( diff --git a/Femto.Api/Controllers/Auth/LoginRequest.cs b/Femto.Api/Controllers/Auth/LoginRequest.cs index 6c09e64..8366d14 100644 --- a/Femto.Api/Controllers/Auth/LoginRequest.cs +++ b/Femto.Api/Controllers/Auth/LoginRequest.cs @@ -1,3 +1,3 @@ namespace Femto.Api.Controllers.Auth; -public record LoginRequest(string Username, string Password, bool RememberMe); \ No newline at end of file +public record LoginRequest(string Username, string Password); \ No newline at end of file diff --git a/Femto.Api/Controllers/Auth/RegisterRequest.cs b/Femto.Api/Controllers/Auth/RegisterRequest.cs index ee21297..f386198 100644 --- a/Femto.Api/Controllers/Auth/RegisterRequest.cs +++ b/Femto.Api/Controllers/Auth/RegisterRequest.cs @@ -1,3 +1,3 @@ namespace Femto.Api.Controllers.Auth; -public record RegisterRequest(string Username, string Password, string SignupCode, bool RememberMe); \ No newline at end of file +public record RegisterRequest(string Username, string Password, string SignupCode, string? Email); \ No newline at end of file diff --git a/Femto.Common/Infrastructure/DomainServiceExtensions.cs b/Femto.Common/Infrastructure/DomainServiceExtensions.cs index 9812c93..e83469e 100644 --- a/Femto.Common/Infrastructure/DomainServiceExtensions.cs +++ b/Femto.Common/Infrastructure/DomainServiceExtensions.cs @@ -12,7 +12,7 @@ public static class DomainServiceExtensions services.AddScoped(s => s.GetRequiredService()); services.AddTransient( typeof(IPipelineBehavior<,>), - typeof(DDDPipelineBehaviour<,>) + typeof(SaveChangesPipelineBehaviour<,>) ); } diff --git a/Femto.Common/Infrastructure/DDDPipelineBehaviour.cs b/Femto.Common/Infrastructure/SaveChangesPipelineBehaviour.cs similarity index 88% rename from Femto.Common/Infrastructure/DDDPipelineBehaviour.cs rename to Femto.Common/Infrastructure/SaveChangesPipelineBehaviour.cs index e5f338f..b86a7e4 100644 --- a/Femto.Common/Infrastructure/DDDPipelineBehaviour.cs +++ b/Femto.Common/Infrastructure/SaveChangesPipelineBehaviour.cs @@ -5,10 +5,10 @@ using Microsoft.Extensions.Logging; namespace Femto.Common.Infrastructure; -public class DDDPipelineBehaviour( +public class SaveChangesPipelineBehaviour( DbContext context, IPublisher publisher, - ILogger> logger + ILogger> logger ) : IPipelineBehavior where TRequest : notnull { diff --git a/Femto.Modules.Auth/Application/AuthStartup.cs b/Femto.Modules.Auth/Application/AuthStartup.cs index 4fb7b22..c78e923 100644 --- a/Femto.Modules.Auth/Application/AuthStartup.cs +++ b/Femto.Modules.Auth/Application/AuthStartup.cs @@ -41,6 +41,7 @@ public static class AuthStartup } ); + rootContainer.ExposeScopedService(); rootContainer.ExposeScopedService(); rootContainer.AddHostedService(services => new AuthApplication(host)); @@ -84,12 +85,8 @@ public static class AuthStartup services.AddSingleton(publisher); services.AddSingleton(); - - services.AddScoped( - typeof(IPipelineBehavior<,>), - typeof(SaveChangesPipelineBehaviour<,>) - ); + services.AddScoped(); services.AddScoped(); } diff --git a/Femto.Modules.Auth/Application/Interface/CreateSignupCode/CreateSignupCodeCommand.cs b/Femto.Modules.Auth/Application/Interface/CreateSignupCode/CreateSignupCodeCommand.cs new file mode 100644 index 0000000..be24aa9 --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/CreateSignupCode/CreateSignupCodeCommand.cs @@ -0,0 +1,5 @@ +using Femto.Common.Domain; + +namespace Femto.Modules.Auth.Application.Interface.CreateSignupCode; + +public record CreateSignupCodeCommand(string Code, string RecipientEmail, string RecipientName): ICommand; \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/CreateSignupCode/CreateSignupCodeCommandHandler.cs b/Femto.Modules.Auth/Application/Interface/CreateSignupCode/CreateSignupCodeCommandHandler.cs new file mode 100644 index 0000000..cfbb44a --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/CreateSignupCode/CreateSignupCodeCommandHandler.cs @@ -0,0 +1,15 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Data; +using Femto.Modules.Auth.Models; + +namespace Femto.Modules.Auth.Application.Interface.CreateSignupCode; + +internal class CreateSignupCodeCommandHandler(AuthContext context) : ICommandHandler +{ + public async Task Handle(CreateSignupCodeCommand command, CancellationToken cancellationToken) + { + var code = new SignupCode(command.RecipientEmail, command.RecipientName, command.Code); + + await context.SignupCodes.AddAsync(code, cancellationToken); + } +} diff --git a/Femto.Modules.Auth/Application/Interface/Deauthenticate/DeauthenticateCommand.cs b/Femto.Modules.Auth/Application/Interface/Deauthenticate/DeauthenticateCommand.cs new file mode 100644 index 0000000..44c346f --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/Deauthenticate/DeauthenticateCommand.cs @@ -0,0 +1,6 @@ + +using Femto.Common.Domain; + +namespace Femto.Modules.Auth.Application.Interface.Deauthenticate; + +public record DeauthenticateCommand(Guid UserId, string SessionId, string? RememberMeToken) : ICommand; \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/Deauthenticate/DeauthenticateCommandHandler.cs b/Femto.Modules.Auth/Application/Interface/Deauthenticate/DeauthenticateCommandHandler.cs new file mode 100644 index 0000000..435718c --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/Deauthenticate/DeauthenticateCommandHandler.cs @@ -0,0 +1,12 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Data; + +namespace Femto.Modules.Auth.Application.Interface.Deauthenticate; + +internal class DeauthenticateCommandHandler(AuthContext context) : ICommandHandler +{ + public async Task Handle(DeauthenticateCommand request, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/GetSignupCodesQuery/GetSignupCodesQuery.cs b/Femto.Modules.Auth/Application/Interface/GetSignupCodesQuery/GetSignupCodesQuery.cs new file mode 100644 index 0000000..422a09d --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/GetSignupCodesQuery/GetSignupCodesQuery.cs @@ -0,0 +1,6 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; + +namespace Femto.Modules.Auth.Application.Interface.GetSignupCodesQuery; + +public record GetSignupCodesQuery: IQuery>; \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/GetSignupCodesQuery/GetSignupCodesQueryHandler.cs b/Femto.Modules.Auth/Application/Interface/GetSignupCodesQuery/GetSignupCodesQueryHandler.cs new file mode 100644 index 0000000..201fdce --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/GetSignupCodesQuery/GetSignupCodesQueryHandler.cs @@ -0,0 +1,55 @@ +using Dapper; +using Femto.Common.Domain; +using Femto.Common.Infrastructure.DbConnection; +using Femto.Modules.Auth.Application.Dto; + +namespace Femto.Modules.Auth.Application.Interface.GetSignupCodesQuery; + +public class GetSignupCodesQueryHandler(IDbConnectionFactory connectionFactory) + : IQueryHandler> +{ + public async Task> Handle( + GetSignupCodesQuery request, + CancellationToken cancellationToken + ) + { + using var conn = connectionFactory.GetConnection(); + + // lang=sql + const string sql = """ + SELECT + sc.code as Code, + sc.recipient_email as Email, + sc.recipient_name as Name, + sc.redeeming_user_id as RedeemedByUserId, + u.username as RedeemedByUsername, + sc.expires_at as ExpiresOn + FROM authn.signup_code sc + LEFT JOIN authn.user_identity u ON u.id = sc.redeeming_user_id + ORDER BY sc.created_at DESC + """; + + var result = await conn.QueryAsync(sql); + + return result + .Select(row => new SignupCodeDto( + row.Code, + row.Email, + row.Name, + row.RedeemedByUserId, + row.RedeemedByUsername, + row.ExpiresOn + )) + .ToList(); + } + + private class QueryResultRow + { + public string Code { get; set; } + public string Email { get; set; } + public string Name { get; set; } + public Guid? RedeemedByUserId { get; set; } + public string? RedeemedByUsername { get; set; } + public DateTimeOffset? ExpiresOn { get; set; } + } +} diff --git a/Femto.Modules.Auth/Application/Interface/GetUserInfo/GetUserInfoCommand.cs b/Femto.Modules.Auth/Application/Interface/GetUserInfo/GetUserInfoCommand.cs new file mode 100644 index 0000000..430b0d3 --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/GetUserInfo/GetUserInfoCommand.cs @@ -0,0 +1,6 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; + +namespace Femto.Modules.Auth.Application.Interface.GetUserInfo; + +public record GetUserInfoCommand(Guid ForUser) : ICommand; \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/GetUserInfo/GetUserInfoCommandHandler.cs b/Femto.Modules.Auth/Application/Interface/GetUserInfo/GetUserInfoCommandHandler.cs new file mode 100644 index 0000000..72c5f20 --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/GetUserInfo/GetUserInfoCommandHandler.cs @@ -0,0 +1,27 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; +using Femto.Modules.Auth.Data; +using Microsoft.EntityFrameworkCore; + +namespace Femto.Modules.Auth.Application.Interface.GetUserInfo; + +internal class GetUserInfoCommandHandler(AuthContext context) + : ICommandHandler +{ + public async Task Handle( + GetUserInfoCommand request, + CancellationToken cancellationToken + ) + { + + var user = await context.Users.SingleOrDefaultAsync( + u => u.Id == request.ForUser, + cancellationToken + ); + + if (user is null) + return null; + + return new UserInfo(user); + } +} diff --git a/Femto.Modules.Auth/Application/Interface/Register/RegisterCommand.cs b/Femto.Modules.Auth/Application/Interface/Register/RegisterCommand.cs new file mode 100644 index 0000000..87332cb --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/Register/RegisterCommand.cs @@ -0,0 +1,6 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; + +namespace Femto.Modules.Auth.Application.Interface.Register; + +public record RegisterCommand(string Username, string Password, string SignupCode) : ICommand; \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Interface/Register/RegisterCommandHandler.cs b/Femto.Modules.Auth/Application/Interface/Register/RegisterCommandHandler.cs new file mode 100644 index 0000000..7bb17be --- /dev/null +++ b/Femto.Modules.Auth/Application/Interface/Register/RegisterCommandHandler.cs @@ -0,0 +1,43 @@ +using Femto.Common.Domain; +using Femto.Modules.Auth.Application.Dto; +using Femto.Modules.Auth.Data; +using Femto.Modules.Auth.Models; +using Microsoft.EntityFrameworkCore; + +namespace Femto.Modules.Auth.Application.Interface.Register; + +internal class RegisterCommandHandler(AuthContext context) + : ICommandHandler +{ + public async Task Handle(RegisterCommand request, CancellationToken cancellationToken) + { + var now = DateTimeOffset.UtcNow; + + var code = await context + .SignupCodes.Where(c => c.Code == request.SignupCode) + .Where(c => c.ExpiresAt == null || c.ExpiresAt > now) + .Where(c => c.RedeemingUserId == null) + .SingleOrDefaultAsync(cancellationToken); + + if (code is null) + throw new DomainError("invalid signup code"); + + var usernameTaken = await context.Users.AnyAsync( + u => u.Username == request.Username, + cancellationToken + ); + + if (usernameTaken) + throw new DomainError("username taken"); + + var user = new UserIdentity(request.Username); + + await context.AddAsync(user, cancellationToken); + + user.SetPassword(request.Password); + + code.Redeem(user.Id); + + return new UserInfo(user); + } +} diff --git a/Femto.Modules.Auth/Application/Services/AuthModule.cs b/Femto.Modules.Auth/Application/Services/AuthModule.cs new file mode 100644 index 0000000..f64d78f --- /dev/null +++ b/Femto.Modules.Auth/Application/Services/AuthModule.cs @@ -0,0 +1,20 @@ +using Femto.Common.Domain; +using MediatR; + +namespace Femto.Modules.Auth.Application.Services; + +internal class AuthModule(IMediator mediator) : IAuthModule +{ + public async Task Command(ICommand command, CancellationToken cancellationToken = default) => + await mediator.Send(command, cancellationToken); + + public async Task Command( + ICommand command, + CancellationToken cancellationToken = default + ) => await mediator.Send(command, cancellationToken); + + public async Task Query( + IQuery query, + CancellationToken cancellationToken = default + ) => await mediator.Send(query, cancellationToken); +} diff --git a/Femto.Modules.Auth/Application/Services/AuthService.cs b/Femto.Modules.Auth/Application/Services/AuthService.cs index 4c741fc..1a9f868 100644 --- a/Femto.Modules.Auth/Application/Services/AuthService.cs +++ b/Femto.Modules.Auth/Application/Services/AuthService.cs @@ -1,6 +1,4 @@ -using Dapper; using Femto.Common.Domain; -using Femto.Common.Infrastructure.DbConnection; using Femto.Modules.Auth.Application.Dto; using Femto.Modules.Auth.Data; using Femto.Modules.Auth.Infrastructure; @@ -9,35 +7,25 @@ using Microsoft.EntityFrameworkCore; namespace Femto.Modules.Auth.Application.Services; -internal class AuthService( - AuthContext context, - SessionStorage storage, - IDbConnectionFactory connectionFactory -) : IAuthService +internal class AuthService(AuthContext context, SessionStorage storage) : IAuthService { - public async Task GetUserWithCredentials(string username, + public async Task GetUserWithCredentials( + string username, string password, - bool createLongTermSession, - CancellationToken cancellationToken = default) + CancellationToken cancellationToken = default + ) { var user = await context .Users.Where(u => u.Username == username) .SingleOrDefaultAsync(cancellationToken); - + if (user is null) return null; if (!user.HasPassword(password)) return null; - - var session = new Session(user.Id, true); - - await storage.AddSession(session); - - return new( - new UserInfo(user.Id, user.Username, user.Roles.Select(r => r.Role).ToList()), - session - ); + + return new UserInfo(user.Id, user.Username, user.Roles.Select(r => r.Role).ToList()); } public Task GetUserWithId(Guid? userId, CancellationToken cancellationToken) @@ -76,96 +64,6 @@ internal class AuthService( await storage.DeleteSession(sessionId); } - public async Task CreateUserWithCredentials(string username, - string password, - string signupCode, - bool createLongTermSession, - CancellationToken cancellationToken = default) - { - var now = DateTimeOffset.UtcNow; - - var code = await context - .SignupCodes.Where(c => c.Code == signupCode) - .Where(c => c.ExpiresAt == null || c.ExpiresAt > now) - .Where(c => c.RedeemingUserId == null) - .SingleOrDefaultAsync(cancellationToken); - - if (code is null) - throw new DomainError("invalid signup code"); - - var usernameTaken = await context.Users.AnyAsync( - u => u.Username == username, - cancellationToken - ); - - if (usernameTaken) - throw new DomainError("username taken"); - - var user = new UserIdentity(username); - - await context.AddAsync(user, cancellationToken); - - user.SetPassword(password); - - code.Redeem(user.Id); - - var session = new Session(user.Id, true); - - await storage.AddSession(session); - - await context.SaveChangesAsync(cancellationToken); - - return new(new UserInfo(user), session); - } - - public async Task AddSignupCode( - string code, - string recipientName, - CancellationToken cancellationToken - ) - { - await context.SignupCodes.AddAsync( - new SignupCode("", recipientName, code), - cancellationToken - ); - - await context.SaveChangesAsync(cancellationToken); - } - - public async Task> GetSignupCodes( - CancellationToken cancellationToken = default - ) - { - using var conn = connectionFactory.GetConnection(); - - // lang=sql - const string sql = """ - SELECT - sc.code as Code, - sc.recipient_email as Email, - sc.recipient_name as Name, - sc.redeeming_user_id as RedeemedByUserId, - u.username as RedeemedByUsername, - sc.expires_at as ExpiresOn - FROM authn.signup_code sc - LEFT JOIN authn.user_identity u ON u.id = sc.redeeming_user_id - ORDER BY sc.created_at DESC - """; - - var result = await conn.QueryAsync(sql, cancellationToken); - - return result - .Select(row => new SignupCodeDto( - row.Code, - row.Email, - row.Name, - row.RedeemedByUserId, - row.RedeemedByUsername, - row.ExpiresOn - )) - .ToList(); - } - public async Task CreateLongTermSession(Guid userId, bool isStrong) { throw new NotImplementedException(); @@ -185,14 +83,4 @@ internal class AuthService( { throw new NotImplementedException(); } - - private class GetSignupCodesQueryResultRow - { - public string Code { get; set; } - public string Email { get; set; } - public string Name { get; set; } - public Guid? RedeemedByUserId { get; set; } - public string? RedeemedByUsername { get; set; } - public DateTimeOffset? ExpiresOn { get; set; } - } } diff --git a/Femto.Modules.Auth/Application/Services/IAuthModule.cs b/Femto.Modules.Auth/Application/Services/IAuthModule.cs new file mode 100644 index 0000000..df34366 --- /dev/null +++ b/Femto.Modules.Auth/Application/Services/IAuthModule.cs @@ -0,0 +1,10 @@ +using Femto.Common.Domain; + +namespace Femto.Modules.Auth.Application.Services; + +public interface IAuthModule +{ + Task Command(ICommand command, CancellationToken cancellationToken = default); + Task Command(ICommand command, CancellationToken cancellationToken = default); + Task Query(IQuery query, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/Femto.Modules.Auth/Application/Services/IAuthService.cs b/Femto.Modules.Auth/Application/Services/IAuthService.cs index c8c252d..2858053 100644 --- a/Femto.Modules.Auth/Application/Services/IAuthService.cs +++ b/Femto.Modules.Auth/Application/Services/IAuthService.cs @@ -11,36 +11,10 @@ namespace Femto.Modules.Auth.Application.Services; /// public interface IAuthService { - public Task GetUserWithCredentials( - string username, - string password, - bool createLongTermSession, - CancellationToken cancellationToken = default - ); - public Task GetUserWithId( - Guid? userId, - CancellationToken cancellationToken = default - ); + public Task GetUserWithCredentials(string username, string password, CancellationToken cancellationToken = default); + public Task GetUserWithId(Guid? userId, CancellationToken cancellationToken = default); public Task CreateStrongSession(Guid userId); public Task CreateWeakSession(Guid userId); public Task GetSession(string sessionId); public Task DeleteSession(string sessionId); - - public Task CreateUserWithCredentials(string username, - string password, - string signupCode, - bool createLongTermSession, - CancellationToken cancellationToken = default); - - public Task AddSignupCode( - string code, - string recipientName, - CancellationToken cancellationToken = default - ); - - public Task> GetSignupCodes( - CancellationToken cancellationToken = default - ); -} - -public record UserAndSession(UserInfo User, Session Session); +} \ No newline at end of file diff --git a/Femto.Modules.Auth/Data/AuthContext.cs b/Femto.Modules.Auth/Data/AuthContext.cs index ac395ba..e4488e4 100644 --- a/Femto.Modules.Auth/Data/AuthContext.cs +++ b/Femto.Modules.Auth/Data/AuthContext.cs @@ -1,10 +1,6 @@ -using Femto.Common.Domain; using Femto.Common.Infrastructure.Outbox; using Femto.Modules.Auth.Models; -using MediatR; using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.Extensions.Logging; namespace Femto.Modules.Auth.Data; @@ -21,43 +17,4 @@ internal class AuthContext(DbContextOptions options) : DbContext(op builder.HasDefaultSchema("authn"); builder.ApplyConfigurationsFromAssembly(typeof(AuthContext).Assembly); } - - public override int SaveChanges() - { - throw new InvalidOperationException("Use SaveChangesAsync instead"); - } - - public override async Task SaveChangesAsync(CancellationToken cancellationToken = default) - { - await EmitDomainEvents(cancellationToken); - - return await base.SaveChangesAsync(cancellationToken); - } - - private async Task EmitDomainEvents(CancellationToken cancellationToken) - { - var logger = this.GetService>(); - var publisher = this.GetService(); - var domainEvents = this - .ChangeTracker.Entries() - .SelectMany(e => - { - var events = e.Entity.DomainEvents; - e.Entity.ClearDomainEvents(); - return events; - }) - .ToList(); - - logger.LogTrace("loaded {Count} domain events", domainEvents.Count); - - foreach (var domainEvent in domainEvents) - { - logger.LogTrace( - "publishing {Type} domain event {Id}", - domainEvent.GetType().Name, - domainEvent.EventId - ); - await publisher.Publish(domainEvent, cancellationToken); - } - } } \ No newline at end of file diff --git a/Femto.Modules.Auth/Infrastructure/SaveChangesPipelineBehaviour.cs b/Femto.Modules.Auth/Infrastructure/SaveChangesPipelineBehaviour.cs deleted file mode 100644 index cc4f983..0000000 --- a/Femto.Modules.Auth/Infrastructure/SaveChangesPipelineBehaviour.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Femto.Modules.Auth.Data; -using MediatR; - -namespace Femto.Modules.Auth.Infrastructure; - -internal class SaveChangesPipelineBehaviour(AuthContext context) - : IPipelineBehavior - where TRequest : notnull -{ - public async Task Handle( - TRequest request, - RequestHandlerDelegate next, - CancellationToken cancellationToken - ) - { - var response = await next(cancellationToken); - - if (context.ChangeTracker.HasChanges()) - await context.SaveChangesAsync(cancellationToken); - - return response; - } -}