Add authenticated user profile page with locked-down account endpoint (#315)

This commit is contained in:
Aaron Po
2026-09-23 00:58:54 -04:00
committed by GitHub
parent a6a9046457
commit 0ff42c3c70
45 changed files with 1418 additions and 80 deletions

View File

@@ -1,17 +1,19 @@
using Domain.Entities;
using Features.Users.Dtos;
using Features.Users.Repository;
using MediatR;
namespace Features.Users.Queries.GetAllUsers;
public class GetAllUsersHandler(IUserListRepository repository)
: IRequestHandler<GetAllUsersQuery, IEnumerable<UserAccount>>
: IRequestHandler<GetAllUsersQuery, IEnumerable<PublicUserProfileDto>>
{
public Task<IEnumerable<UserAccount>> Handle(
public async Task<IEnumerable<PublicUserProfileDto>> Handle(
GetAllUsersQuery request,
CancellationToken cancellationToken
)
{
return repository.GetAllAsync(request.Limit, request.Offset);
IEnumerable<UserAccount> users = await repository.GetAllAsync(request.Limit, request.Offset);
return users.Select(user => user.ToPublicProfileDto());
}
}

View File

@@ -1,11 +1,11 @@
using Domain.Entities;
using Features.Users.Dtos;
using MediatR;
namespace Features.Users.Queries.GetAllUsers;
/// <summary>
/// Requests a page of user accounts, ordered by creation date descending.
/// Requests a page of user accounts' public profiles, ordered by creation date descending.
/// </summary>
/// <param name="Limit"><c>null</c> for no limit.</param>
/// <param name="Offset"><c>null</c> for no offset.</param>
public record GetAllUsersQuery(int? Limit, int? Offset) : IRequest<IEnumerable<UserAccount>>;
public record GetAllUsersQuery(int? Limit, int? Offset) : IRequest<IEnumerable<PublicUserProfileDto>>;

View File

@@ -0,0 +1,23 @@
using Domain.Entities;
using Domain.Exceptions;
using Features.Users.Dtos;
using Features.Users.Repository;
using MediatR;
namespace Features.Users.Queries.GetPublicUserProfileById;
public class GetPublicUserProfileByIdHandler(IUserListRepository repository)
: IRequestHandler<GetPublicUserProfileByIdQuery, PublicUserProfileDto>
{
/// <exception cref="NotFoundException">Thrown when no user account exists with the given ID.</exception>
public async Task<PublicUserProfileDto> Handle(
GetPublicUserProfileByIdQuery request,
CancellationToken cancellationToken
)
{
UserAccount? user = await repository.GetByIdAsync(request.UserAccountId);
if (user is null)
throw new NotFoundException($"User with ID {request.UserAccountId} not found");
return user.ToPublicProfileDto();
}
}

View File

@@ -0,0 +1,6 @@
using Features.Users.Dtos;
using MediatR;
namespace Features.Users.Queries.GetPublicUserProfileById;
public record GetPublicUserProfileByIdQuery(Guid UserAccountId) : IRequest<PublicUserProfileDto>;

View File

@@ -8,12 +8,18 @@ namespace Features.Users.Queries.GetUserById;
public class GetUserByIdHandler(IUserListRepository repository)
: IRequestHandler<GetUserByIdQuery, UserAccount>
{
/// <exception cref="ForbiddenException">
/// Thrown when the caller requests an account other than their own.
/// </exception>
/// <exception cref="NotFoundException">Thrown when no user account exists with the given ID.</exception>
public async Task<UserAccount> Handle(
GetUserByIdQuery request,
CancellationToken cancellationToken
)
{
if (request.UserAccountId != request.RequestingUserId)
throw new ForbiddenException("You are not authorized to view this account.");
UserAccount? user = await repository.GetByIdAsync(request.UserAccountId);
if (user is null)
throw new NotFoundException($"User with ID {request.UserAccountId} not found");

View File

@@ -3,4 +3,9 @@ using MediatR;
namespace Features.Users.Queries.GetUserById;
public record GetUserByIdQuery(Guid UserAccountId) : IRequest<UserAccount>;
/// <param name="UserAccountId">The account to fetch.</param>
/// <param name="RequestingUserId">
/// The authenticated caller's ID, from the access token. It must equal
/// <paramref name="UserAccountId" />. This query returns private fields, including <c>Email</c>.
/// </param>
public record GetUserByIdQuery(Guid UserAccountId, Guid RequestingUserId) : IRequest<UserAccount>;