diff --git a/EnvelopeGenerator.Application/Common/Dto/Receiver/RecentReceiverSuggestionDto.cs b/EnvelopeGenerator.Application/Common/Dto/Receiver/RecentReceiverSuggestionDto.cs index 1928f56e..37a167e0 100644 --- a/EnvelopeGenerator.Application/Common/Dto/Receiver/RecentReceiverSuggestionDto.cs +++ b/EnvelopeGenerator.Application/Common/Dto/Receiver/RecentReceiverSuggestionDto.cs @@ -2,7 +2,9 @@ namespace EnvelopeGenerator.Application.Common.Dto.Receiver; public class RecentReceiverSuggestionDto { - public required string FullName { get; set; } + public required string FullName { get; init; } - public required string EmailAddress { get; set; } + public required string EmailAddress { get; init; } + + public required DateTime LastUsedAt { get; init; } } diff --git a/EnvelopeGenerator.Application/EnvelopeReceivers/Queries/ReadRecentReceiverSuggestionsQuery.cs b/EnvelopeGenerator.Application/EnvelopeReceivers/Queries/ReadRecentReceiverSuggestionsQuery.cs index c9017cd6..f6cef12f 100644 --- a/EnvelopeGenerator.Application/EnvelopeReceivers/Queries/ReadRecentReceiverSuggestionsQuery.cs +++ b/EnvelopeGenerator.Application/EnvelopeReceivers/Queries/ReadRecentReceiverSuggestionsQuery.cs @@ -1,4 +1,3 @@ -using AutoMapper; using DigitalData.Core.Abstraction.Application.Repository; using EnvelopeGenerator.Application.Common.Dto.Receiver; using EnvelopeGenerator.Domain.Entities; @@ -21,7 +20,7 @@ public record ReadRecentReceiverSuggestionsQuery : IRequest - /// Desired number of results. Effective range is clamped to 3..5. + /// Desired number of results. Default value is 5. /// public int Take { get; init; } = 5; @@ -39,7 +38,7 @@ public record ReadRecentReceiverSuggestionsQuery : IRequest /// Handles . /// -public class ReadRecentReceiverSuggestionsQueryHandler(IRepository repository, IMapper mapper) +public class ReadRecentReceiverSuggestionsQueryHandler(IRepository repository) : IRequestHandler> { /// @@ -50,23 +49,50 @@ public class ReadRecentReceiverSuggestionsQueryHandler(IRepositoryOrdered 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) + // 1) For each email, compute the latest usage timestamp. + var latestPerEmail = query + .GroupBy(er => er.Receiver!.EmailAddress) + .Select(g => new + { + EmailAddress = g.Key, + LastUsedAt = g.Max(er => er.AddedWhen), + }); + + // 2) Join back to rows that match the latest timestamp per email. + var latestRows = + from er in query + join latest in latestPerEmail + on new { EmailAddress = er.Receiver!.EmailAddress, LastUsedAt = er.AddedWhen } + equals new { latest.EmailAddress, latest.LastUsedAt } + select new + { + EmailAddress = er.Receiver!.EmailAddress, + FullName = er.Name, + LastUsedAt = er.AddedWhen, + }; + + // 3) Tie-break: if multiple rows share the same latest timestamp for an email, + // pick a deterministic name via Max(FullName), then order by most recent usage. + var suggestions = await latestRows + .GroupBy(x => new { x.EmailAddress, x.LastUsedAt }) + .Select(g => new RecentReceiverSuggestionDto + { + EmailAddress = g.Key.EmailAddress, + FullName = g.Max(x => x.FullName) ?? string.Empty, + LastUsedAt = g.Key.LastUsedAt, + }) + .OrderByDescending(x => x.LastUsedAt) + .ThenBy(x => x.EmailAddress) + .Take(request.Take) .ToListAsync(cancellationToken); - return mapper.Map>(rows); + return suggestions; } }