Merge Features.Auth and Features.UserManagement into Features.Users

This commit is contained in:
Aaron Po
2026-08-30 04:12:21 -04:00
parent ade2128a73
commit bd78fe6533
78 changed files with 137 additions and 325 deletions

View File

@@ -0,0 +1,12 @@
using Features.Auth.Dtos;
using MediatR;
namespace Features.Auth.Commands.UpdateUsername;
/// <summary>Changes the username of the given user account.</summary>
/// <param name="UserAccountId">
/// The authenticated caller's own ID, extracted from the access token -- never bind this from
/// client-supplied input.
/// </param>
public record UpdateUsernameCommand(Guid UserAccountId, string NewUsername)
: IRequest<UpdateUsernamePayload>;

View File

@@ -0,0 +1,36 @@
using Domain.Exceptions;
using Features.Auth.Dtos;
using Features.Auth.Identity;
using MediatR;
using Microsoft.AspNetCore.Identity;
namespace Features.Auth.Commands.UpdateUsername;
/// <summary>Handles <see cref="UpdateUsernameCommand" /> via <see cref="UserManager{TUser}" />.</summary>
public class UpdateUsernameHandler(UserManager<ApplicationUser> userManager)
: IRequestHandler<UpdateUsernameCommand, UpdateUsernamePayload>
{
/// <exception cref="NotFoundException">Thrown when the account does not exist.</exception>
/// <exception cref="ConflictException">Thrown when the requested username is already taken.</exception>
public async Task<UpdateUsernamePayload> Handle(
UpdateUsernameCommand request,
CancellationToken cancellationToken
)
{
ApplicationUser user =
await userManager.FindByIdAsync(request.UserAccountId.ToString())
?? throw new NotFoundException("User account not found");
IdentityResult result = await userManager.SetUserNameAsync(user, request.NewUsername);
if (!result.Succeeded)
{
if (result.Errors.Any(e => e.Code == "DuplicateUserName"))
throw new ConflictException("Username already exists");
throw new ConflictException(
string.Join("; ", result.Errors.Select(e => e.Description))
);
}
return new UpdateUsernamePayload(user.Id, user.UserName);
}
}

View File

@@ -0,0 +1,20 @@
using FluentValidation;
namespace Features.Auth.Commands.UpdateUsername;
/// <summary>Validates <see cref="UpdateUsernameCommand" />, mirroring the username rules in <c>RegisterUserValidator</c>.</summary>
public class UpdateUsernameValidator : AbstractValidator<UpdateUsernameCommand>
{
public UpdateUsernameValidator()
{
RuleFor(x => x.NewUsername)
.NotEmpty()
.WithMessage("Username is required")
.Length(3, 64)
.WithMessage("Username must be between 3 and 64 characters")
.Matches("^[a-zA-Z0-9._-]+$")
.WithMessage(
"Username can only contain letters, numbers, dots, underscores, and hyphens"
);
}
}