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:
Janus C. H. Knudsen 2026-01-09 22:14:46 +01:00
parent e5e7c1c19f
commit 88812177a9
29 changed files with 288 additions and 298 deletions

View file

@ -1,9 +1,9 @@
using System.Diagnostics;
using System.Diagnostics;
using Microsoft.ApplicationInsights;
using Microsoft.ApplicationInsights.DataContracts;
using PlanTempus.Core.CommandQueries;
namespace PlanTempus.Components.Users.Create;
namespace PlanTempus.Components.Accounts.Create;
public class CommandHandlerDecorator<TCommand>(
ICommandHandler<TCommand> decoratedHandler,
@ -14,30 +14,30 @@ public class CommandHandlerDecorator<TCommand>(
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;
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;
}
}
}
}

View file

@ -1,11 +1,11 @@
using PlanTempus.Core.CommandQueries;
namespace PlanTempus.Components.Users.Create
namespace PlanTempus.Components.Accounts.Create
{
public class CreateUserCommand : Command
public class CreateAccountCommand : Command
{
public required string Email { get; set; }
public required string Password { get; set; }
public bool IsActive { get; set; } = true;
}
}
}

View file

@ -1,14 +1,14 @@
using Microsoft.AspNetCore.Mvc;
using PlanTempus.Core.CommandQueries;
namespace PlanTempus.Components.Users.Create
namespace PlanTempus.Components.Accounts.Create
{
[ApiController]
[Route("api/users")]
public class CreateUserController(CreateUserHandler handler, CreateUserValidator validator) : ControllerBase
[Route("api/accounts")]
public class CreateAccountController(CreateAccountHandler handler, CreateAccountValidator validator) : ControllerBase
{
[HttpPost]
public async Task<ActionResult<CommandResponse>> Create([FromBody] CreateUserCommand command)
public async Task<ActionResult<CommandResponse>> Create([FromBody] CreateAccountCommand command)
{
try
{
@ -28,4 +28,4 @@ namespace PlanTempus.Components.Users.Create
}
}
}
}
}

View file

@ -1,25 +1,25 @@
using Insight.Database;
using Microsoft.ApplicationInsights;
using Npgsql;
using PlanTempus.Components.Users.Exceptions;
using PlanTempus.Components.Accounts.Exceptions;
using PlanTempus.Core;
using PlanTempus.Core.CommandQueries;
using PlanTempus.Core.Database;
namespace PlanTempus.Components.Users.Create
namespace PlanTempus.Components.Accounts.Create
{
public class CreateUserHandler(
public class CreateAccountHandler(
IDatabaseOperations databaseOperations,
ISecureTokenizer secureTokenizer) : ICommandHandler<CreateUserCommand>
ISecureTokenizer secureTokenizer) : ICommandHandler<CreateAccountCommand>
{
public async Task<CommandResponse> Handle(CreateUserCommand command)
public async Task<CommandResponse> Handle(CreateAccountCommand command)
{
using var db = databaseOperations.CreateScope(nameof(CreateUserHandler));
using var db = databaseOperations.CreateScope(nameof(CreateAccountHandler));
try
{
var sql = @"
INSERT INTO system.users(email , password_hash, security_stamp, email_confirmed,
access_failed_count, lockout_enabled,
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)
@ -35,11 +35,10 @@ namespace PlanTempus.Components.Users.Create
LockoutEnabled = false,
command.IsActive,
});
//lav en mapping mellem requestid og userid
return new CommandResponse(command.CorrelationId, command.GetType().Name, command.TransactionId);
}
catch (PostgresException ex) when (ex.SqlState == "23505" && ex.ConstraintName.Equals("users_email_key", StringComparison.InvariantCultureIgnoreCase))
catch (PostgresException ex) when (ex.SqlState == "23505" && ex.ConstraintName.Equals("accounts_email_key", StringComparison.InvariantCultureIgnoreCase))
{
db.Error(ex);
throw new EmailAlreadyRegistreredException();
@ -51,4 +50,4 @@ namespace PlanTempus.Components.Users.Create
}
}
}
}
}

View file

@ -1,11 +1,11 @@
namespace PlanTempus.Components.Users.Create
namespace PlanTempus.Components.Accounts.Create
{
public class CreateUserResult
public class CreateAccountResult
{
public long Id { get; set; }
public string Email { get; set; }
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
}
}
}

View file

@ -1,10 +1,10 @@
using FluentValidation;
namespace PlanTempus.Components.Users.Create
namespace PlanTempus.Components.Accounts.Create
{
public class CreateUserValidator : AbstractValidator<CreateUserCommand>
public class CreateAccountValidator : AbstractValidator<CreateAccountCommand>
{
public CreateUserValidator()
public CreateAccountValidator()
{
RuleFor(x => x.Email)
.NotEmpty().WithMessage("Email skal angives.")
@ -20,4 +20,4 @@ namespace PlanTempus.Components.Users.Create
.Matches("[^a-zA-Z0-9]").WithMessage("Password skal indeholde mindst ét specialtegn.");
}
}
}
}

View file

@ -1,6 +1,6 @@
namespace PlanTempus.Components.Users.Exceptions;
namespace PlanTempus.Components.Accounts.Exceptions;
public class EmailAlreadyRegistreredException: Exception
{
}
}

View file

@ -1,5 +1,4 @@
using Autofac;
using PlanTempus.Components.Users.Create;
using PlanTempus.Core.CommandQueries;
namespace PlanTempus.Components;

View file

@ -1,5 +1,5 @@
using Autofac;
using PlanTempus.Components.Users.Create;
using PlanTempus.Components.Accounts.Create;
using PlanTempus.Core.CommandQueries;
using PlanTempus.Core.SeqLogging;

View file

@ -3,6 +3,6 @@
public class CreateOrganizationCommand
{
public string ConnectionString { get; set; }
public Guid UserId { get; set; }
public Guid AccountId { get; set; }
}
}

View file

@ -26,9 +26,9 @@ namespace PlanTempus.Components.Organizations.Create
var orgResult = data.First();
await db.Connection.ExecuteAsync(@$"
INSERT INTO user_organizations (user_id, organization_id)
VALUES (@UserId, @OrganizationId)",
new { command.UserId, OrganizationId = orgResult.Id });
INSERT INTO account_organizations (account_id, organization_id)
VALUES (@AccountId, @OrganizationId)",
new { command.AccountId, OrganizationId = orgResult.Id });
transaction.Commit();