Add recent receiver suggestions query and DTO
Introduce `RecentReceiverSuggestionDto` to represent recent receiver suggestions with `FullName` and `EmailAddress`. Add `ReadRecentReceiverSuggestionsQuery` to retrieve recent receiver suggestions scoped by sender username and limited to 3-5 results. Implement `ReadRecentReceiverSuggestionsQueryHandler` to process the query using Entity Framework Core and AutoMapper. Update `MappingProfile` to map `EnvelopeReceiver` to `RecentReceiverSuggestionDto`.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
using AutoMapper;
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Application.Common.Dto.Receiver;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Reads recent receiver suggestions for envelope creation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Sender scope is assigned via chain method to avoid exposing username mutation from API query binding.
|
||||
/// </remarks>
|
||||
public record ReadRecentReceiverSuggestionsQuery : IRequest<IReadOnlyList<RecentReceiverSuggestionDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Sender username used for scoping suggestion results.
|
||||
/// </summary>
|
||||
internal string? Username { get; private init; }
|
||||
|
||||
/// <summary>
|
||||
/// Desired number of results. Effective range is clamped to 3..5.
|
||||
/// </summary>
|
||||
public int Take { get; init; } = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Returns a new query instance scoped to the specified sender username.
|
||||
/// </summary>
|
||||
/// <param name="username">Sender username from authenticated claims.</param>
|
||||
/// <returns>Scoped query instance.</returns>
|
||||
public ReadRecentReceiverSuggestionsQuery ForSender(string username)
|
||||
{
|
||||
return this with { Username = username };
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="ReadRecentReceiverSuggestionsQuery"/>.
|
||||
/// </summary>
|
||||
public class ReadRecentReceiverSuggestionsQueryHandler(IRepository<EnvelopeReceiver> repository, IMapper mapper)
|
||||
: IRequestHandler<ReadRecentReceiverSuggestionsQuery, IReadOnlyList<RecentReceiverSuggestionDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Retrieves distinct recent receiver suggestions grouped by receiver email.
|
||||
/// </summary>
|
||||
/// <param name="request">Suggestion query.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Ordered list of recent receiver suggestions.</returns>
|
||||
public async Task<IReadOnlyList<RecentReceiverSuggestionDto>> Handle(ReadRecentReceiverSuggestionsQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var take = Math.Clamp(request.Take, 3, 5);
|
||||
|
||||
var query = repository.Query
|
||||
.AsNoTracking()
|
||||
.Include(er => er.Receiver)
|
||||
.Where(er => er.Receiver != null);
|
||||
|
||||
if (request.Username is not null)
|
||||
query = query.Where(er => er.Envelope != null && er.Envelope.User.Username == request.Username);
|
||||
|
||||
var rows = await query
|
||||
.GroupBy(er => er.Receiver!.EmailAddress.ToUpperInvariant())
|
||||
.Select(g => g.OrderByDescending(er => er.ChangedWhen ?? er.AddedWhen).First())
|
||||
.OrderByDescending(er => er.ChangedWhen ?? er.AddedWhen)
|
||||
.Take(take)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
return mapper.Map<List<RecentReceiverSuggestionDto>>(rows);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user