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;
///
/// Reads recent receiver suggestions for envelope creation.
///
///
/// Sender scope is assigned via chain method to avoid exposing username mutation from API query binding.
///
public record ReadRecentReceiverSuggestionsQuery : IRequest>
{
///
/// Sender username used for scoping suggestion results.
///
internal string? Username { get; private init; }
///
/// Desired number of results. Effective range is clamped to 3..5.
///
public int Take { get; init; } = 5;
///
/// Returns a new query instance scoped to the specified sender username.
///
/// Sender username from authenticated claims.
/// Scoped query instance.
public ReadRecentReceiverSuggestionsQuery ForSender(string username)
{
return this with { Username = username };
}
}
///
/// Handles .
///
public class ReadRecentReceiverSuggestionsQueryHandler(IRepository repository, IMapper mapper)
: IRequestHandler>
{
///
/// Retrieves distinct recent receiver suggestions grouped by receiver email.
///
/// Suggestion query.
/// Cancellation token.
/// Ordered list of recent receiver suggestions.
public async Task> 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>(rows);
}
}