Refactor email search with new filter models
Replaced the old `SearchFilter` with the new `MailSearchFilter` model to enable more flexible and granular email search criteria. Introduced `DateFilter`, `UidFilter`, and `MailSortOrder` to support advanced filtering options such as date ranges, UID ranges, and sorting order. Updated `IImapEmailService` and `FetchEmailsQuery` to use the new models. Added validators (`DateFilterValidator`, `MailSearchFilterValidator`, `UidFilterValidator`) to ensure input correctness. Refactored `LimilabsImapEmailService` to dynamically construct IMAP search queries based on the new filter properties, supporting unseen messages, text filters, and client-side attachment filtering. Improved maintainability and scalability by cleaning up redundant code and leveraging the new models and validation framework.
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
using System.Text;
|
||||
using DigitalData.MessagingService.Application.Common.Interfaces;
|
||||
using DigitalData.MessagingService.Abstraction;
|
||||
using DigitalData.MessagingService.Application.Common.Interfaces;
|
||||
using DigitalData.MessagingService.Application.Common.Models.MailSearch;
|
||||
using DigitalData.MessagingService.Domain.Exceptions;
|
||||
using DigitalData.MessagingService.Infrastructure.Services.Extensions;
|
||||
using Limilabs.Client.IMAP;
|
||||
using Limilabs.Mail;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Text;
|
||||
|
||||
namespace DigitalData.MessagingService.Infrastructure.Services;
|
||||
|
||||
@@ -25,7 +26,7 @@ public class LimilabsImapEmailService(
|
||||
// Public API
|
||||
public async Task<IEnumerable<ReceivedEmailContext>> FetchEmailsAsync(
|
||||
EmailAccountDto account,
|
||||
FetchEmailsQuery.SearchFilter filter,
|
||||
MailSearchFilter filter,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var imap = new Imap();
|
||||
@@ -34,17 +35,59 @@ public class LimilabsImapEmailService(
|
||||
await ConnectAndAuthenticateAsync(imap, account);
|
||||
await SelectFolderAsync(imap, filter.Folder);
|
||||
|
||||
// Get UIDs to fetch
|
||||
List<long> uids = filter.UnseenOnly
|
||||
? [.. await imap.SearchAsync(Flag.Unseen, cancellationToken)]
|
||||
: [.. await imap.GetAllAsync(cancellationToken)];
|
||||
List<ICriterion> criterions = [];
|
||||
|
||||
// Most-recent first; honour maxCount
|
||||
uids.Reverse();
|
||||
if (filter.MaxCount > 0 && uids.Count() > filter.MaxCount)
|
||||
if (filter.UnseenOnly)
|
||||
criterions.Add((ICriterion)Flag.Unseen);
|
||||
|
||||
if (filter.SubjectContains is not null)
|
||||
criterions.Add(Expression.Subject(filter.SubjectContains));
|
||||
|
||||
if (filter.SenderContains is not null)
|
||||
criterions.Add(Expression.From(filter.SenderContains));
|
||||
|
||||
if (filter.RecipientContains is not null)
|
||||
criterions.Add(Expression.To(filter.RecipientContains));
|
||||
|
||||
if (filter.BodyContains is not null)
|
||||
criterions.Add(Expression.Body(filter.BodyContains));
|
||||
|
||||
if (filter.Uid is UidFilter uidF)
|
||||
{
|
||||
if (uidF.Absolute is long exactUid)
|
||||
{
|
||||
criterions.Add(Expression.UID(new Limilabs.Client.IMAP.Range(exactUid, exactUid)));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Open-ended ranges: fall back to 1 / null when one side is omitted
|
||||
long lo = uidF.Min ?? 1L;
|
||||
long? hi = uidF.Max;
|
||||
criterions.Add(Expression.UID(new Limilabs.Client.IMAP.Range(lo, hi)));
|
||||
}
|
||||
}
|
||||
|
||||
if (filter.Date is DateFilter dateF)
|
||||
{
|
||||
if (dateF.After is DateTime after)
|
||||
criterions.Add(Expression.SentSince(after.Date));
|
||||
|
||||
// IMAP BEFORE is exclusive, so add one day to make the bound inclusive
|
||||
if (dateF.Before is DateTime before)
|
||||
criterions.Add(Expression.SentBefore(before.Date.AddDays(1)));
|
||||
}
|
||||
|
||||
// Get UIDs to fetch
|
||||
List<long> uids = [.. await imap.SearchAsync(Expression.And([.. criterions]), cancellationToken)];
|
||||
|
||||
// Apply requested sort order
|
||||
if (filter.SortOrder == MailSortOrder.NewestFirst)
|
||||
uids.Reverse();
|
||||
|
||||
if (filter.MaxCount > 0 && uids.Count > filter.MaxCount)
|
||||
uids = [.. uids.Take(filter.MaxCount)];
|
||||
|
||||
var results = new List<ReceivedEmailContext>(uids.Count());
|
||||
var results = new List<ReceivedEmailContext>(uids.Count);
|
||||
|
||||
foreach (var uid in uids)
|
||||
{
|
||||
@@ -52,10 +95,14 @@ public class LimilabsImapEmailService(
|
||||
|
||||
try
|
||||
{
|
||||
var eml = await imap.PeekMessageByUIDAsync(uid, cancellationToken);
|
||||
var mail = new MailBuilder().CreateFromEml(eml);
|
||||
var eml = await imap.PeekMessageByUIDAsync(uid, cancellationToken);
|
||||
var mail = new MailBuilder().CreateFromEml(eml);
|
||||
var flags = await imap.GetFlagsByUIDAsync(uid, cancellationToken);
|
||||
|
||||
// Client-side attachment filter — IMAP has no native criterion for this
|
||||
if (filter.HasAttachments && mail.Attachments.Count == 0 && mail.Visuals.Count == 0)
|
||||
continue;
|
||||
|
||||
results.Add(MapToContext(uid, mail, flags));
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
Reference in New Issue
Block a user