Adds transactional outbox and email verification
Implements outbox pattern for reliable message delivery Adds email verification flow with Postmark integration Enhances account registration with secure token generation Introduces background processing for asynchronous email sending Implements database-level notification mechanism for message processing
This commit is contained in:
parent
88812177a9
commit
54b057886c
35 changed files with 1174 additions and 358 deletions
|
|
@ -1,26 +1,30 @@
|
|||
using Insight.Database;
|
||||
using Microsoft.ApplicationInsights;
|
||||
using Npgsql;
|
||||
using PlanTempus.Components.Accounts.Exceptions;
|
||||
using PlanTempus.Core;
|
||||
using PlanTempus.Core.CommandQueries;
|
||||
using PlanTempus.Core.Database;
|
||||
using PlanTempus.Core.Outbox;
|
||||
|
||||
namespace PlanTempus.Components.Accounts.Create
|
||||
{
|
||||
public class CreateAccountHandler(
|
||||
IDatabaseOperations databaseOperations,
|
||||
ISecureTokenizer secureTokenizer) : ICommandHandler<CreateAccountCommand>
|
||||
ISecureTokenizer secureTokenizer,
|
||||
IOutboxService outboxService) : ICommandHandler<CreateAccountCommand>
|
||||
{
|
||||
public async Task<CommandResponse> Handle(CreateAccountCommand command)
|
||||
{
|
||||
using var db = databaseOperations.CreateScope(nameof(CreateAccountHandler));
|
||||
using var transaction = db.Connection.BeginTransaction();
|
||||
|
||||
try
|
||||
{
|
||||
var securityStamp = Guid.NewGuid().ToString("N");
|
||||
|
||||
var sql = @"
|
||||
INSERT INTO system.accounts(email , password_hash, security_stamp, email_confirmed,
|
||||
access_failed_count, lockout_enabled,
|
||||
is_active)
|
||||
INSERT INTO system.accounts(email, password_hash, security_stamp, email_confirmed,
|
||||
access_failed_count, lockout_enabled, is_active)
|
||||
VALUES(@Email, @PasswordHash, @SecurityStamp, @EmailConfirmed,
|
||||
@AccessFailedCount, @LockoutEnabled, @IsActive)
|
||||
RETURNING id, created_at, email, is_active";
|
||||
|
|
@ -29,22 +33,36 @@ namespace PlanTempus.Components.Accounts.Create
|
|||
{
|
||||
command.Email,
|
||||
PasswordHash = secureTokenizer.TokenizeText(command.Password),
|
||||
SecurityStamp = Guid.NewGuid().ToString("N"),
|
||||
SecurityStamp = securityStamp,
|
||||
EmailConfirmed = false,
|
||||
AccessFailedCount = 0,
|
||||
LockoutEnabled = false,
|
||||
command.IsActive,
|
||||
});
|
||||
|
||||
await outboxService.EnqueueAsync(
|
||||
OutboxMessageTypes.VerificationEmail,
|
||||
new VerificationEmailPayload
|
||||
{
|
||||
Email = command.Email,
|
||||
UserName = command.Email,
|
||||
Token = securityStamp
|
||||
},
|
||||
db.Connection,
|
||||
transaction);
|
||||
|
||||
transaction.Commit();
|
||||
return new CommandResponse(command.CorrelationId, command.GetType().Name, command.TransactionId);
|
||||
}
|
||||
catch (PostgresException ex) when (ex.SqlState == "23505" && ex.ConstraintName.Equals("accounts_email_key", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
transaction.Rollback();
|
||||
db.Error(ex);
|
||||
throw new EmailAlreadyRegistreredException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
transaction.Rollback();
|
||||
db.Error(ex);
|
||||
throw;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue