Migrate from User to Account domain concept
Renames core domain entities and services from "User" to "Account" Refactors project-wide namespaces, classes, and database tables to use "Account" terminology Updates related components, services, and database schema to reflect new domain naming Standardizes naming conventions across authentication and organization setup features
This commit is contained in:
parent
e5e7c1c19f
commit
88812177a9
29 changed files with 288 additions and 298 deletions
|
|
@ -0,0 +1,43 @@
|
|||
using System.Diagnostics;
|
||||
using Microsoft.ApplicationInsights;
|
||||
using Microsoft.ApplicationInsights.DataContracts;
|
||||
using PlanTempus.Core.CommandQueries;
|
||||
|
||||
namespace PlanTempus.Components.Accounts.Create;
|
||||
|
||||
public class CommandHandlerDecorator<TCommand>(
|
||||
ICommandHandler<TCommand> decoratedHandler,
|
||||
TelemetryClient telemetryClient) : ICommandHandler<TCommand>, ICommandHandlerDecorator where TCommand : ICommand
|
||||
{
|
||||
public async Task<CommandResponse> Handle(TCommand command)
|
||||
{
|
||||
command.TransactionId = Guid.NewGuid();
|
||||
using var operation =
|
||||
telemetryClient.StartOperation<RequestTelemetry>($"Handle {decoratedHandler.GetType().FullName}",
|
||||
command.CorrelationId.ToString());
|
||||
try
|
||||
{
|
||||
operation.Telemetry.Properties["CorrelationId"] = command.CorrelationId.ToString();
|
||||
operation.Telemetry.Properties["TransactionId"] = command.TransactionId.ToString();
|
||||
|
||||
var result = await decoratedHandler.Handle(command);
|
||||
|
||||
operation.Telemetry.Properties["RequestId"] = result.RequestId.ToString();
|
||||
operation.Telemetry.Success = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
operation.Telemetry.Success = false;
|
||||
|
||||
telemetryClient.TrackException(ex, new Dictionary<string, string>
|
||||
{
|
||||
["CorrelationId"] = command.CorrelationId.ToString(),
|
||||
["Command"] = command.GetType().Name,
|
||||
["CommandHandler"] = decoratedHandler.GetType().FullName
|
||||
});
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
using PlanTempus.Core.CommandQueries;
|
||||
|
||||
namespace PlanTempus.Components.Accounts.Create
|
||||
{
|
||||
public class CreateAccountCommand : Command
|
||||
{
|
||||
public required string Email { get; set; }
|
||||
public required string Password { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlanTempus.Core.CommandQueries;
|
||||
|
||||
namespace PlanTempus.Components.Accounts.Create
|
||||
{
|
||||
[ApiController]
|
||||
[Route("api/accounts")]
|
||||
public class CreateAccountController(CreateAccountHandler handler, CreateAccountValidator validator) : ControllerBase
|
||||
{
|
||||
[HttpPost]
|
||||
public async Task<ActionResult<CommandResponse>> Create([FromBody] CreateAccountCommand command)
|
||||
{
|
||||
try
|
||||
{
|
||||
var validationResult = await validator.ValidateAsync(command);
|
||||
if (!validationResult.IsValid)
|
||||
{
|
||||
return BadRequest(validationResult.Errors);
|
||||
}
|
||||
|
||||
var result = await handler.Handle(command);
|
||||
|
||||
return Accepted($"/api/requests/{result.RequestId}", result);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return BadRequest(ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
using Insight.Database;
|
||||
using Microsoft.ApplicationInsights;
|
||||
using Npgsql;
|
||||
using PlanTempus.Components.Accounts.Exceptions;
|
||||
using PlanTempus.Core;
|
||||
using PlanTempus.Core.CommandQueries;
|
||||
using PlanTempus.Core.Database;
|
||||
|
||||
namespace PlanTempus.Components.Accounts.Create
|
||||
{
|
||||
public class CreateAccountHandler(
|
||||
IDatabaseOperations databaseOperations,
|
||||
ISecureTokenizer secureTokenizer) : ICommandHandler<CreateAccountCommand>
|
||||
{
|
||||
public async Task<CommandResponse> Handle(CreateAccountCommand command)
|
||||
{
|
||||
using var db = databaseOperations.CreateScope(nameof(CreateAccountHandler));
|
||||
try
|
||||
{
|
||||
var sql = @"
|
||||
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";
|
||||
|
||||
await db.Connection.QuerySqlAsync(sql, new
|
||||
{
|
||||
command.Email,
|
||||
PasswordHash = secureTokenizer.TokenizeText(command.Password),
|
||||
SecurityStamp = Guid.NewGuid().ToString("N"),
|
||||
EmailConfirmed = false,
|
||||
AccessFailedCount = 0,
|
||||
LockoutEnabled = false,
|
||||
command.IsActive,
|
||||
});
|
||||
|
||||
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))
|
||||
{
|
||||
db.Error(ex);
|
||||
throw new EmailAlreadyRegistreredException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
db.Error(ex);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
PlanTempus.Components/Accounts/Create/CreateAccountResult.cs
Normal file
11
PlanTempus.Components/Accounts/Create/CreateAccountResult.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
namespace PlanTempus.Components.Accounts.Create
|
||||
{
|
||||
public class CreateAccountResult
|
||||
{
|
||||
public long Id { get; set; }
|
||||
public string Email { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using FluentValidation;
|
||||
|
||||
namespace PlanTempus.Components.Accounts.Create
|
||||
{
|
||||
public class CreateAccountValidator : AbstractValidator<CreateAccountCommand>
|
||||
{
|
||||
public CreateAccountValidator()
|
||||
{
|
||||
RuleFor(x => x.Email)
|
||||
.NotEmpty().WithMessage("Email skal angives.")
|
||||
.EmailAddress().WithMessage("Ugyldig emailadresse.")
|
||||
.MaximumLength(256).WithMessage("Email må højst være 256 tegn.");
|
||||
|
||||
RuleFor(x => x.Password)
|
||||
.NotEmpty().WithMessage("Password skal angives.")
|
||||
.MinimumLength(8).WithMessage("Password skal være mindst 8 tegn.")
|
||||
.Matches("[A-Z]").WithMessage("Password skal indeholde mindst ét stort bogstav.")
|
||||
.Matches("[a-z]").WithMessage("Password skal indeholde mindst ét lille bogstav.")
|
||||
.Matches("[0-9]").WithMessage("Password skal indeholde mindst ét tal.")
|
||||
.Matches("[^a-zA-Z0-9]").WithMessage("Password skal indeholde mindst ét specialtegn.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue