Skip to content

Commit

Permalink
Add logic to create a user with the preferred locale during new tenan…
Browse files Browse the repository at this point in the history
…t signup
  • Loading branch information
tjementum committed Jan 14, 2025
1 parent 53898d8 commit b564c3c
Show file tree
Hide file tree
Showing 9 changed files with 45 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public sealed record CompleteSignupCommand(string OneTimePassword) : ICommand, I
{
[JsonIgnore] // Removes this property from the API contract
public SignupId Id { get; init; } = null!;

public string? PreferredLocale { get; init; }
}

public sealed class CompleteSignupHandler(
Expand Down Expand Up @@ -65,7 +67,10 @@ public async Task<Result> Handle(CompleteSignupCommand command, CancellationToke
return Result.BadRequest("The code is no longer valid, please request a new code.", true);
}

var result = await mediator.Send(new CreateTenantCommand(signup.TenantId, signup.Email, true), cancellationToken);
var result = await mediator.Send(
new CreateTenantCommand(signup.TenantId, signup.Email, true, command.PreferredLocale),
cancellationToken
);

var user = await userRepository.GetByIdAsync(result.Value!, cancellationToken);
authenticationTokenService.CreateAndSetAuthenticationTokens(user!.Adapt<UserInfo>());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
namespace PlatformPlatform.AccountManagement.Features.Tenants.Commands;

[PublicAPI]
public sealed record CreateTenantCommand(TenantId Id, string OwnerEmail, bool EmailConfirmed)
public sealed record CreateTenantCommand(TenantId Id, string OwnerEmail, bool EmailConfirmed, string? Locale)
: ICommand, IRequest<Result<UserId>>;

public sealed class CreateTenantHandler(ITenantRepository tenantRepository, IMediator mediator, ITelemetryEventsCollector events)
Expand All @@ -23,8 +23,8 @@ public async Task<Result<UserId>> Handle(CreateTenantCommand command, Cancellati
events.CollectEvent(new TenantCreated(tenant.Id, tenant.State));

var result = await mediator.Send(
new CreateUserCommand(tenant.Id, command.OwnerEmail, UserRole.Owner, command.EmailConfirmed)
, cancellationToken
new CreateUserCommand(tenant.Id, command.OwnerEmail, UserRole.Owner, command.EmailConfirmed, command.Locale),
cancellationToken
);

return result.Value!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@
using PlatformPlatform.SharedKernel.Cqrs;
using PlatformPlatform.SharedKernel.Domain;
using PlatformPlatform.SharedKernel.ExecutionContext;
using PlatformPlatform.SharedKernel.SinglePageApp;
using PlatformPlatform.SharedKernel.Telemetry;
using PlatformPlatform.SharedKernel.Validation;

namespace PlatformPlatform.AccountManagement.Features.Users.Commands;

[PublicAPI]
public sealed record CreateUserCommand(TenantId TenantId, string Email, UserRole UserRole, bool EmailConfirmed)
public sealed record CreateUserCommand(
TenantId TenantId,
string Email,
UserRole UserRole,
bool EmailConfirmed,
string? PreferredLocale
)
: ICommand, IRequest<Result<UserId>>;

public sealed class CreateUserValidator : AbstractValidator<CreateUserCommand>
Expand Down Expand Up @@ -50,7 +57,10 @@ public async Task<Result<UserId>> Handle(CreateUserCommand command, Cancellation
throw new UnreachableException("Only when signing up a new tenant, is the TenantID allowed to different than the current tenant.");
}

var user = User.Create(command.TenantId, command.Email, command.UserRole, command.EmailConfirmed);
var locale = SinglePageAppConfiguration.SupportedLocalizations.Contains(command.PreferredLocale)
? command.PreferredLocale
: string.Empty;
var user = User.Create(command.TenantId, command.Email, command.UserRole, command.EmailConfirmed, locale);

await userRepository.AddAsync(user, cancellationToken);
var gravatar = await gravatarClient.GetGravatar(user.Id, user.Email, cancellationToken);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,10 @@ public async Task<Result> Handle(InviteUserCommand command, CancellationToken ca
return Result.Forbidden("Only owners are allowed to invite other users.");
}

var result = await mediator.Send(new CreateUserCommand(executionContext.TenantId!, command.Email, UserRole.Member, false), cancellationToken);
var result = await mediator.Send(
new CreateUserCommand(executionContext.TenantId!, command.Email, UserRole.Member, false, null),
cancellationToken
);

events.CollectEvent(new UserInvited(result.Value!));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ public sealed class User : AggregateRoot<UserId>, ITenantScopedEntity
{
private string _email = string.Empty;

private User(TenantId tenantId, string email, UserRole role, bool emailConfirmed)
private User(TenantId tenantId, string email, UserRole role, bool emailConfirmed, string? locale)
: base(UserId.NewId())
{
Email = email;
TenantId = tenantId;
Role = role;
EmailConfirmed = emailConfirmed;
Locale = locale ?? string.Empty;
Avatar = new Avatar();
}

Expand All @@ -34,13 +35,13 @@ public string Email

public Avatar Avatar { get; private set; }

public string Locale { get; private set; } = string.Empty;
public string Locale { get; private set; }

public TenantId TenantId { get; }

public static User Create(TenantId tenantId, string email, UserRole role, bool emailConfirmed)
public static User Create(TenantId tenantId, string email, UserRole role, bool emailConfirmed, string? locale)
{
return new User(tenantId, email, role, emailConfirmed);
return new User(tenantId, email, role, emailConfirmed, locale);
}

public void Update(string firstName, string lastName, string title)
Expand Down
2 changes: 1 addition & 1 deletion application/account-management/Tests/DatabaseSeeder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public DatabaseSeeder(AccountManagementDbContext accountManagementDbContext)
{
Tenant1 = Tenant.Create(new TenantId("tenant-1"), "[email protected]");
accountManagementDbContext.Tenants.AddRange(Tenant1);
User1 = User.Create(Tenant1.Id, "[email protected]", UserRole.Owner, true);
User1 = User.Create(Tenant1.Id, "[email protected]", UserRole.Owner, true, null);
accountManagementDbContext.Users.AddRange(User1);

accountManagementDbContext.SaveChanges();
Expand Down
8 changes: 4 additions & 4 deletions application/account-management/Tests/Users/CreateUserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public async Task CreateUser_WhenValid_ShouldCreateUser()
{
// Arrange
var existingTenantId = DatabaseSeeder.Tenant1.Id;
var command = new CreateUserCommand(existingTenantId, Faker.Internet.Email(), UserRole.Member, false);
var command = new CreateUserCommand(existingTenantId, Faker.Internet.Email(), UserRole.Member, false, null);

// Act
var response = await AuthenticatedHttpClient.PostAsJsonAsync("/api/account-management/users", command);
Expand All @@ -34,7 +34,7 @@ public async Task CreateUser_WhenInvalidEmail_ShouldReturnBadRequest()
// Arrange
var existingTenantId = DatabaseSeeder.Tenant1.Id;
var invalidEmail = Faker.InvalidEmail();
var command = new CreateUserCommand(existingTenantId, invalidEmail, UserRole.Member, false);
var command = new CreateUserCommand(existingTenantId, invalidEmail, UserRole.Member, false, null);

// Act
var response = await AuthenticatedHttpClient.PostAsJsonAsync("/api/account-management/users", command);
Expand All @@ -53,7 +53,7 @@ public async Task CreateUser_WhenUserExists_ShouldReturnBadRequest()
// Arrange
var existingTenantId = DatabaseSeeder.Tenant1.Id;
var existingUserEmail = DatabaseSeeder.User1.Email;
var command = new CreateUserCommand(existingTenantId, existingUserEmail, UserRole.Member, false);
var command = new CreateUserCommand(existingTenantId, existingUserEmail, UserRole.Member, false, null);

// Act
var response = await AuthenticatedHttpClient.PostAsJsonAsync("/api/account-management/users", command);
Expand All @@ -71,7 +71,7 @@ public async Task CreateUser_WhenTenantDoesNotExists_ShouldReturnBadRequest()
{
// Arrange
var unknownTenantId = new TenantId(Faker.Subdomain());
var command = new CreateUserCommand(unknownTenantId, Faker.Internet.Email(), UserRole.Member, false);
var command = new CreateUserCommand(unknownTenantId, Faker.Internet.Email(), UserRole.Member, false, null);

// Act
var response = await AuthenticatedHttpClient.PostAsJsonAsync("/api/account-management/users", command);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { getSignupState, setSignupState } from "./-shared/signupState";
import { api } from "@/shared/lib/api/client";
import { FormErrorMessage } from "@repo/ui/components/FormErrorMessage";
import { loggedInPath, signedUpPath } from "@repo/infrastructure/auth/constants";
import { preferredLocaleKey } from "@repo/infrastructure/translations/constants";
import { useActionState, useEffect } from "react";
import { useIsAuthenticated } from "@repo/infrastructure/auth/hooks";

Expand Down Expand Up @@ -80,6 +81,7 @@ export function CompleteSignupForm() {
<div className="w-full max-w-sm space-y-3">
<Form action={action} validationErrors={errors} validationBehavior="aria">
<input type="hidden" name="id" value={signupId} />
<input type="hidden" name="preferredLocale" value={localStorage.getItem(preferredLocaleKey) ?? ""} />
<div className="flex w-full flex-col gap-4 rounded-lg px-6 pt-8 pb-4">
<div className="flex justify-center">
<Link href="/">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,10 @@
"properties": {
"oneTimePassword": {
"type": "string"
},
"preferredLocale": {
"type": "string",
"nullable": true
}
}
},
Expand Down Expand Up @@ -1129,6 +1133,10 @@
},
"emailConfirmed": {
"type": "boolean"
},
"preferredLocale": {
"type": "string",
"nullable": true
}
}
},
Expand Down

0 comments on commit b564c3c

Please sign in to comment.