mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-10-14 17:54:42 +00:00
Replace shared email commands with MediatR notifications (#280)
This commit is contained in:
@@ -1,24 +0,0 @@
|
||||
using Features.Emails.Commands.SendRegistrationEmail;
|
||||
using Features.Emails.Services;
|
||||
using Moq;
|
||||
using Shared.Application.Emails;
|
||||
|
||||
namespace Features.Emails.Tests.Commands;
|
||||
|
||||
public class SendRegistrationEmailHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Handle_DelegatesToEmailDispatcher()
|
||||
{
|
||||
Mock<IEmailDispatcher> dispatcherMock = new();
|
||||
SendRegistrationEmailHandler handler = new(dispatcherMock.Object);
|
||||
SendRegistrationEmailCommand command = new("Aaron", "aaron@example.com", "token-123");
|
||||
|
||||
await handler.Handle(command, CancellationToken.None);
|
||||
|
||||
dispatcherMock.Verify(
|
||||
d => d.SendRegistrationEmailAsync("Aaron", "aaron@example.com", "token-123"),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using Features.Emails.Commands.SendResendConfirmationEmail;
|
||||
using Features.Emails.Services;
|
||||
using Moq;
|
||||
using Shared.Application.Emails;
|
||||
|
||||
namespace Features.Emails.Tests.Commands;
|
||||
|
||||
public class SendResendConfirmationEmailHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Handle_DelegatesToEmailDispatcher()
|
||||
{
|
||||
Mock<IEmailDispatcher> dispatcherMock = new();
|
||||
SendResendConfirmationEmailHandler handler = new(dispatcherMock.Object);
|
||||
SendResendConfirmationEmailCommand command = new("Aaron", "aaron@example.com", "token-456");
|
||||
|
||||
await handler.Handle(command, CancellationToken.None);
|
||||
|
||||
dispatcherMock.Verify(
|
||||
d => d.SendResendConfirmationEmailAsync("Aaron", "aaron@example.com", "token-456"),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Features.Auth.Notifications;
|
||||
using Features.Emails.Notifications;
|
||||
using Features.Emails.Services;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Features.Emails.Tests.Notifications;
|
||||
|
||||
public class ConfirmationEmailResendRequestedNotificationHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Handle_DelegatesToEmailDispatcher()
|
||||
{
|
||||
Mock<IEmailDispatcher> dispatcherMock = new();
|
||||
ConfirmationEmailResendRequestedNotificationHandler handler = new(
|
||||
dispatcherMock.Object,
|
||||
Mock.Of<ILogger<ConfirmationEmailResendRequestedNotificationHandler>>()
|
||||
);
|
||||
ConfirmationEmailResendRequestedNotification notification = new(
|
||||
Guid.NewGuid(),
|
||||
"Aaron",
|
||||
"aaron@example.com",
|
||||
"token-456"
|
||||
);
|
||||
|
||||
await handler.Handle(notification, CancellationToken.None);
|
||||
|
||||
dispatcherMock.Verify(
|
||||
d => d.SendResendConfirmationEmailAsync("Aaron", "aaron@example.com", "token-456"),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_SwallowsDispatcherFailure()
|
||||
{
|
||||
Mock<IEmailDispatcher> dispatcherMock = new();
|
||||
dispatcherMock
|
||||
.Setup(d =>
|
||||
d.SendResendConfirmationEmailAsync(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>()
|
||||
)
|
||||
)
|
||||
.ThrowsAsync(new Exception("smtp down"));
|
||||
ConfirmationEmailResendRequestedNotificationHandler handler = new(
|
||||
dispatcherMock.Object,
|
||||
Mock.Of<ILogger<ConfirmationEmailResendRequestedNotificationHandler>>()
|
||||
);
|
||||
ConfirmationEmailResendRequestedNotification notification = new(
|
||||
Guid.NewGuid(),
|
||||
"Aaron",
|
||||
"aaron@example.com",
|
||||
"token-456"
|
||||
);
|
||||
|
||||
Func<Task> act = async () => await handler.Handle(notification, CancellationToken.None);
|
||||
|
||||
await act.Should().NotThrowAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Features.Auth.Notifications;
|
||||
using Features.Emails.Notifications;
|
||||
using Features.Emails.Services;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
||||
namespace Features.Emails.Tests.Notifications;
|
||||
|
||||
public class UserRegisteredNotificationHandlerTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Handle_DelegatesToEmailDispatcher()
|
||||
{
|
||||
Mock<IEmailDispatcher> dispatcherMock = new();
|
||||
UserRegisteredNotificationHandler handler = new(
|
||||
dispatcherMock.Object,
|
||||
Mock.Of<ILogger<UserRegisteredNotificationHandler>>()
|
||||
);
|
||||
UserRegisteredNotification notification = new(
|
||||
Guid.NewGuid(),
|
||||
"Aaron",
|
||||
"aaron@example.com",
|
||||
"token-123"
|
||||
);
|
||||
|
||||
await handler.Handle(notification, CancellationToken.None);
|
||||
|
||||
dispatcherMock.Verify(
|
||||
d => d.SendRegistrationEmailAsync("Aaron", "aaron@example.com", "token-123"),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_SwallowsDispatcherFailure()
|
||||
{
|
||||
Mock<IEmailDispatcher> dispatcherMock = new();
|
||||
dispatcherMock
|
||||
.Setup(d =>
|
||||
d.SendRegistrationEmailAsync(
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>(),
|
||||
It.IsAny<string>()
|
||||
)
|
||||
)
|
||||
.ThrowsAsync(new Exception("smtp down"));
|
||||
UserRegisteredNotificationHandler handler = new(
|
||||
dispatcherMock.Object,
|
||||
Mock.Of<ILogger<UserRegisteredNotificationHandler>>()
|
||||
);
|
||||
UserRegisteredNotification notification = new(
|
||||
Guid.NewGuid(),
|
||||
"Aaron",
|
||||
"aaron@example.com",
|
||||
"token-123"
|
||||
);
|
||||
|
||||
Func<Task> act = async () => await handler.Handle(notification, CancellationToken.None);
|
||||
|
||||
await act.Should().NotThrowAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
using Features.Emails.Services;
|
||||
using MediatR;
|
||||
using Shared.Application.Emails;
|
||||
|
||||
namespace Features.Emails.Commands.SendRegistrationEmail;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="SendRegistrationEmailCommand" />, the cross-slice command sent by Features.Users
|
||||
/// after a new user registers.
|
||||
/// </summary>
|
||||
public class SendRegistrationEmailHandler(IEmailDispatcher emailDispatcher)
|
||||
: IRequestHandler<SendRegistrationEmailCommand>
|
||||
{
|
||||
public Task Handle(SendRegistrationEmailCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
return emailDispatcher.SendRegistrationEmailAsync(
|
||||
request.FirstName,
|
||||
request.Email,
|
||||
request.ConfirmationToken
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
using Features.Emails.Services;
|
||||
using MediatR;
|
||||
using Shared.Application.Emails;
|
||||
|
||||
namespace Features.Emails.Commands.SendResendConfirmationEmail;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="SendResendConfirmationEmailCommand" />, the cross-slice command sent by Features.Users
|
||||
/// when a user requests a fresh confirmation link.
|
||||
/// </summary>
|
||||
public class SendResendConfirmationEmailHandler(IEmailDispatcher emailDispatcher)
|
||||
: IRequestHandler<SendResendConfirmationEmailCommand>
|
||||
{
|
||||
public Task Handle(
|
||||
SendResendConfirmationEmailCommand request,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
return emailDispatcher.SendResendConfirmationEmailAsync(
|
||||
request.FirstName,
|
||||
request.Email,
|
||||
request.ConfirmationToken
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -18,5 +18,6 @@
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Email\Infrastructure.Email.csproj" />
|
||||
<ProjectReference Include="..\..\Infrastructure\Infrastructure.Email.Templates\Infrastructure.Email.Templates.csproj" />
|
||||
<ProjectReference Include="..\..\Shared\Shared.Application\Shared.Application.csproj" />
|
||||
<ProjectReference Include="..\Features.Users\Features.Users.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using Features.Auth.Notifications;
|
||||
using Features.Emails.Services;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Features.Emails.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="ConfirmationEmailResendRequestedNotification" />, the cross-slice notification
|
||||
/// published by Features.Users when a user requests a fresh confirmation link, by sending the resend
|
||||
/// confirmation email.
|
||||
/// </summary>
|
||||
public class ConfirmationEmailResendRequestedNotificationHandler(
|
||||
IEmailDispatcher emailDispatcher,
|
||||
ILogger<ConfirmationEmailResendRequestedNotificationHandler> logger
|
||||
) : INotificationHandler<ConfirmationEmailResendRequestedNotification>
|
||||
{
|
||||
/// <remarks>
|
||||
/// Delivery failures are logged and swallowed rather than rethrown, so that a broken mail server
|
||||
/// never fails the resend request that already succeeded.
|
||||
/// </remarks>
|
||||
public async Task Handle(
|
||||
ConfirmationEmailResendRequestedNotification notification,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
try
|
||||
{
|
||||
await emailDispatcher.SendResendConfirmationEmailAsync(
|
||||
notification.FirstName,
|
||||
notification.Email,
|
||||
notification.ConfirmationToken
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(
|
||||
ex,
|
||||
"Failed to send resend confirmation email to user {UserId}",
|
||||
notification.UserId
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using Features.Auth.Notifications;
|
||||
using Features.Emails.Services;
|
||||
using MediatR;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Features.Emails.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="UserRegisteredNotification" />, the cross-slice notification published by
|
||||
/// Features.Users after a new user registers, by sending the registration confirmation email.
|
||||
/// </summary>
|
||||
public class UserRegisteredNotificationHandler(
|
||||
IEmailDispatcher emailDispatcher,
|
||||
ILogger<UserRegisteredNotificationHandler> logger
|
||||
) : INotificationHandler<UserRegisteredNotification>
|
||||
{
|
||||
/// <remarks>
|
||||
/// Delivery failures are logged and swallowed rather than rethrown, so that a broken mail server
|
||||
/// never fails the registration that already succeeded.
|
||||
/// </remarks>
|
||||
public async Task Handle(UserRegisteredNotification notification, CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
await emailDispatcher.SendRegistrationEmailAsync(
|
||||
notification.FirstName,
|
||||
notification.Email,
|
||||
notification.ConfirmationToken
|
||||
);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(
|
||||
ex,
|
||||
"Failed to send registration confirmation email to user {UserId}",
|
||||
notification.UserId
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,13 @@ using Domain.Exceptions;
|
||||
using Features.Auth.Commands.Authentication.RegisterUser;
|
||||
using Features.Auth.Dtos;
|
||||
using Features.Auth.Identity;
|
||||
using Features.Auth.Notifications;
|
||||
using Features.Auth.Services;
|
||||
using Features.Auth.Tests.TestSupport;
|
||||
using FluentAssertions;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Moq;
|
||||
using Shared.Application.Emails;
|
||||
|
||||
namespace Features.Auth.Tests.Commands;
|
||||
|
||||
@@ -86,10 +86,18 @@ public class RegisterUserHandlerTests
|
||||
result.Username.Should().Be(command.Username);
|
||||
result.AccessToken.Should().Be("access-token");
|
||||
result.RefreshToken.Should().Be("refresh-token");
|
||||
result.ConfirmationEmailSent.Should().BeTrue();
|
||||
|
||||
_mediatorMock.Verify(
|
||||
x => x.Send(It.IsAny<SendRegistrationEmailCommand>(), It.IsAny<CancellationToken>()),
|
||||
x =>
|
||||
x.Publish(
|
||||
It.Is<UserRegisteredNotification>(n =>
|
||||
n.UserId == expectedUserId
|
||||
&& n.FirstName == command.FirstName
|
||||
&& n.Email == command.Email
|
||||
&& n.ConfirmationToken == "confirmation-token"
|
||||
),
|
||||
It.IsAny<CancellationToken>()
|
||||
),
|
||||
Times.Once
|
||||
);
|
||||
}
|
||||
@@ -177,31 +185,4 @@ public class RegisterUserHandlerTests
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Handle_SwallowsEmailFailure_AndReportsEmailNotSent()
|
||||
{
|
||||
RegisterUserCommand command = ValidCommand();
|
||||
|
||||
_userManagerMock
|
||||
.Setup(x => x.CreateAsync(It.IsAny<ApplicationUser>(), command.Password))
|
||||
.Callback<ApplicationUser, string>((u, _) => u.Id = Guid.NewGuid())
|
||||
.ReturnsAsync(IdentityResult.Success);
|
||||
|
||||
_tokenServiceMock
|
||||
.Setup(x => x.GenerateAccessToken(It.IsAny<Guid>(), It.IsAny<string>()))
|
||||
.Returns("access-token");
|
||||
_tokenServiceMock
|
||||
.Setup(x => x.GenerateRefreshToken(It.IsAny<Guid>(), It.IsAny<string>()))
|
||||
.Returns("refresh-token");
|
||||
_mediatorMock
|
||||
.Setup(x =>
|
||||
x.Send(It.IsAny<SendRegistrationEmailCommand>(), It.IsAny<CancellationToken>())
|
||||
)
|
||||
.ThrowsAsync(new Exception("smtp down"));
|
||||
|
||||
RegistrationPayload result = await _handler.Handle(command, CancellationToken.None);
|
||||
|
||||
result.ConfirmationEmailSent.Should().BeFalse();
|
||||
result.AccessToken.Should().Be("access-token");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using Features.Auth.Commands.Authentication.ResendConfirmationEmail;
|
||||
using Features.Auth.Identity;
|
||||
using Features.Auth.Notifications;
|
||||
using Features.Auth.Services;
|
||||
using Features.Auth.Tests.TestSupport;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Moq;
|
||||
using Shared.Application.Emails;
|
||||
|
||||
namespace Features.Auth.Tests.Commands;
|
||||
|
||||
@@ -47,11 +47,12 @@ public class ResendConfirmationEmailHandlerTests
|
||||
|
||||
_mediatorMock.Verify(
|
||||
x =>
|
||||
x.Send(
|
||||
It.Is<SendResendConfirmationEmailCommand>(c =>
|
||||
c.FirstName == "Aaron"
|
||||
&& c.Email == "aaron@example.com"
|
||||
&& c.ConfirmationToken == "fresh-token"
|
||||
x.Publish(
|
||||
It.Is<ConfirmationEmailResendRequestedNotification>(n =>
|
||||
n.UserId == userId
|
||||
&& n.FirstName == "Aaron"
|
||||
&& n.Email == "aaron@example.com"
|
||||
&& n.ConfirmationToken == "fresh-token"
|
||||
),
|
||||
It.IsAny<CancellationToken>()
|
||||
),
|
||||
@@ -71,8 +72,8 @@ public class ResendConfirmationEmailHandlerTests
|
||||
|
||||
_mediatorMock.Verify(
|
||||
x =>
|
||||
x.Send(
|
||||
It.IsAny<SendResendConfirmationEmailCommand>(),
|
||||
x.Publish(
|
||||
It.IsAny<ConfirmationEmailResendRequestedNotification>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
),
|
||||
Times.Never
|
||||
@@ -92,8 +93,8 @@ public class ResendConfirmationEmailHandlerTests
|
||||
|
||||
_mediatorMock.Verify(
|
||||
x =>
|
||||
x.Send(
|
||||
It.IsAny<SendResendConfirmationEmailCommand>(),
|
||||
x.Publish(
|
||||
It.IsAny<ConfirmationEmailResendRequestedNotification>(),
|
||||
It.IsAny<CancellationToken>()
|
||||
),
|
||||
Times.Never
|
||||
|
||||
@@ -3,17 +3,17 @@ using Domain.Exceptions;
|
||||
using Features.Auth.Commands.Profile.CreateUserProfile;
|
||||
using Features.Auth.Dtos;
|
||||
using Features.Auth.Identity;
|
||||
using Features.Auth.Notifications;
|
||||
using Features.Auth.Services;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Shared.Application.Emails;
|
||||
|
||||
namespace Features.Auth.Commands.Authentication.RegisterUser;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="RegisterUserCommand" />: creates the user via <see cref="UserManager{TUser}" />
|
||||
/// (which validates uniqueness and hashes the password), issues access/refresh/confirmation tokens, and
|
||||
/// attempts to send the registration confirmation email via Features.Emails.
|
||||
/// publishes <see cref="UserRegisteredNotification" /> for Features.Emails to send the confirmation email.
|
||||
/// </summary>
|
||||
public class RegisterUserHandler(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
@@ -60,31 +60,19 @@ public class RegisterUserHandler(
|
||||
user.Id,
|
||||
user.UserName,
|
||||
string.Empty,
|
||||
string.Empty,
|
||||
false
|
||||
string.Empty
|
||||
);
|
||||
|
||||
bool emailSent = false;
|
||||
try
|
||||
{
|
||||
await mediator.Send(
|
||||
new SendRegistrationEmailCommand(user.FirstName, user.Email, confirmationToken),
|
||||
cancellationToken
|
||||
);
|
||||
emailSent = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await Console.Error.WriteLineAsync(ex.Message);
|
||||
Console.WriteLine("Could not send email.");
|
||||
}
|
||||
await mediator.Publish(
|
||||
new UserRegisteredNotification(user.Id, request.FirstName, user.Email, confirmationToken),
|
||||
cancellationToken
|
||||
);
|
||||
|
||||
return new RegistrationPayload(
|
||||
user.Id,
|
||||
user.UserName,
|
||||
refreshToken,
|
||||
accessToken,
|
||||
emailSent
|
||||
accessToken
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using Features.Auth.Identity;
|
||||
using Features.Auth.Notifications;
|
||||
using Features.Auth.Services;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Shared.Application.Emails;
|
||||
|
||||
namespace Features.Auth.Commands.Authentication.ResendConfirmationEmail;
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="ResendConfirmationEmailCommand" /> by generating a fresh confirmation token and
|
||||
/// sending it via Features.Emails.
|
||||
/// publishing <see cref="ConfirmationEmailResendRequestedNotification" /> for Features.Emails to send it.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns silently without sending an email if the user does not exist (to prevent user enumeration)
|
||||
@@ -33,8 +33,13 @@ public class ResendConfirmationEmailHandler(
|
||||
return; // Already confirmed, no-op
|
||||
|
||||
string confirmationToken = tokenService.GenerateConfirmationToken(user.Id, user.UserName);
|
||||
await mediator.Send(
|
||||
new SendResendConfirmationEmailCommand(user.FirstName, user.Email, confirmationToken),
|
||||
await mediator.Publish(
|
||||
new ConfirmationEmailResendRequestedNotification(
|
||||
user.Id,
|
||||
user.FirstName,
|
||||
user.Email,
|
||||
confirmationToken
|
||||
),
|
||||
cancellationToken
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,9 +13,7 @@ public record RegistrationPayload(
|
||||
Guid UserAccountId,
|
||||
string Username,
|
||||
string RefreshToken,
|
||||
string AccessToken,
|
||||
bool ConfirmationEmailSent
|
||||
);
|
||||
string AccessToken);
|
||||
|
||||
/// <summary>Payload returned to the client after a user account's email has been confirmed.</summary>
|
||||
public record ConfirmationPayload(Guid UserAccountId, DateTime ConfirmedDate);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Auth.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// Published after a user requests a fresh confirmation link. Handled by Features.Emails to send the
|
||||
/// resend confirmation email.
|
||||
/// </summary>
|
||||
public record ConfirmationEmailResendRequestedNotification(
|
||||
Guid UserId,
|
||||
string FirstName,
|
||||
string Email,
|
||||
string ConfirmationToken
|
||||
) : INotification;
|
||||
@@ -0,0 +1,14 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Features.Auth.Notifications;
|
||||
|
||||
/// <summary>
|
||||
/// Published after a new user registers. Handled by Features.Emails to send the registration
|
||||
/// confirmation email.
|
||||
/// </summary>
|
||||
public record UserRegisteredNotification(
|
||||
Guid UserId,
|
||||
string FirstName,
|
||||
string Email,
|
||||
string ConfirmationToken
|
||||
) : INotification;
|
||||
@@ -1,10 +0,0 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Shared.Application.Emails;
|
||||
|
||||
/// <summary>
|
||||
/// Cross-slice command sent by Features.Users and handled by Features.Emails to dispatch a
|
||||
/// registration confirmation email, without either slice taking a project reference on the other.
|
||||
/// </summary>
|
||||
public record SendRegistrationEmailCommand(string FirstName, string Email, string ConfirmationToken)
|
||||
: IRequest;
|
||||
@@ -1,13 +0,0 @@
|
||||
using MediatR;
|
||||
|
||||
namespace Shared.Application.Emails;
|
||||
|
||||
/// <summary>
|
||||
/// Cross-slice command sent by Features.Users and handled by Features.Emails to dispatch a
|
||||
/// fresh confirmation-link email, without either slice taking a project reference on the other.
|
||||
/// </summary>
|
||||
public record SendResendConfirmationEmailCommand(
|
||||
string FirstName,
|
||||
string Email,
|
||||
string ConfirmationToken
|
||||
) : IRequest;
|
||||
@@ -21,9 +21,7 @@ interface LoginPayload {
|
||||
accessToken: string;
|
||||
}
|
||||
|
||||
interface RegistrationPayload extends LoginPayload {
|
||||
confirmationEmailSent: boolean;
|
||||
}
|
||||
type RegistrationPayload = LoginPayload;
|
||||
|
||||
interface UpdateUsernamePayload {
|
||||
userAccountId: string;
|
||||
|
||||
Reference in New Issue
Block a user