Refactor email fetching logic in EmailController

Consolidated email fetching endpoints by removing `FetchEmailUids`
and `FetchEmailByUid` endpoints and integrating their functionality
into the `FetchEmails` method. Introduced an `OnlyFilter` enum to
allow filtering responses for HTML body or UIDs. Removed related
methods (`FetchEmailUidsAsync`, `FetchEmailByUidAsync`) from
`IImapEmailService` and deleted associated query classes and
handlers.
This commit is contained in:
2026-08-12 12:42:15 +02:00
parent 118612206e
commit cb3b2e09ab
4 changed files with 28 additions and 177 deletions

View File

@@ -22,27 +22,6 @@ public interface IImapEmailService
MailSearchFilter filter,
CancellationToken cancellationToken = default);
/// <summary>
/// Fetches only the UIDs of messages matching the specified filter.
/// </summary>
Task<IEnumerable<long>> FetchEmailUidsAsync(
EmailAccountDto account,
MailSearchFilter filter,
CancellationToken cancellationToken = default);
/// <summary>
/// Fetches a single email by its UID.
/// </summary>
/// When <see langword="true"/> (default), the message is marked as <c>\Seen</c> on the server.
/// Set to <see langword="false"/> for a non-destructive read.
/// </param>
Task<ReceivedEmailContext?> FetchEmailByUidAsync(
EmailAccountDto account,
long uid,
string folder = "INBOX",
bool withAttachments = false,
CancellationToken cancellationToken = default);
/// <summary>
/// Marks a message as seen (read) on the server.
/// </summary>

View File

@@ -1,62 +0,0 @@
using DigitalData.MessagingService.Abstraction;
using DigitalData.MessagingService.Application.Common.Interfaces;
using DigitalData.MessagingService.Application.EmailAccount.Queries;
using DigitalData.MessagingService.Domain.Exceptions;
using MediatR;
namespace DigitalData.MessagingService.Application.EmailReceiving.Queries;
/// <summary>
/// Query to fetch a single email by its UID from an IMAP mailbox.
/// </summary>
public record FetchEmailByUidQuery : IRequest<ReceivedEmailContext?>
{
/// <summary>
/// Identifies the email account to use.
/// </summary>
public required GetSenderQuery Account { get; init; }
/// <summary>
/// UID of the message to fetch.
/// </summary>
internal long? Uid { get; private set; } = null;
public FetchEmailByUidQuery WithUid(long uid)
{
Uid = uid;
return this;
}
/// <summary>
/// Mailbox folder the message resides in (default: "INBOX").
/// </summary>
public string Folder { get; init; } = "INBOX";
/// <summary>
/// When <see langword="true"/>, attachment data is included in the result.
/// </summary>
public bool WithAttachments { get; init; } = false;
}
public class FetchEmailByUidQueryHandler(
ISender Sender,
IImapEmailService ImapService) : IRequestHandler<FetchEmailByUidQuery, ReceivedEmailContext?>
{
public async Task<ReceivedEmailContext?> Handle(FetchEmailByUidQuery request, CancellationToken cancellationToken)
{
var account = await Sender.Send(request.Account, cancellationToken)
?? throw new NotFoundException(
$"No email account found for the given criteria (Id: {request.Account.Id}, Username: {request.Account.Username}).");
if (string.IsNullOrWhiteSpace(account.ImapServer))
throw new InvalidOperationException(
$"IMAP is not configured for account '{account.Username}' (Id: {account.Id}). Set ImapServer in EmailAccounts configuration.");
return await ImapService.FetchEmailByUidAsync(
account,
(long)request.Uid!,
request.Folder,
request.WithAttachments,
cancellationToken);
}
}

View File

@@ -1,42 +0,0 @@
using DigitalData.MessagingService.Application.Common.Interfaces;
using DigitalData.MessagingService.Application.Common.Models.MailSearch;
using DigitalData.MessagingService.Application.EmailAccount.Queries;
using DigitalData.MessagingService.Domain.Exceptions;
using MediatR;
namespace DigitalData.MessagingService.Application.EmailReceiving.Queries;
/// <summary>
/// Query to fetch only the UIDs of matching emails from an IMAP mailbox.
/// </summary>
public record FetchEmailUidsQuery : IRequest<IEnumerable<long>>
{
/// <summary>
/// Identifies the email account to use.
/// </summary>
public required GetSenderQuery Account { get; init; }
/// <summary>
/// Mail query used to filter and limit the emails retrieved.
/// </summary>
public MailSearchFilter Mail { get; init; } = new();
}
public class FetchEmailUidsQueryHandler(ISender Sender, IImapEmailService ImapService) : IRequestHandler<FetchEmailUidsQuery, IEnumerable<long>>
{
public async Task<IEnumerable<long>> Handle(FetchEmailUidsQuery request, CancellationToken cancellationToken)
{
var account = await Sender.Send(request.Account, cancellationToken)
?? throw new NotFoundException(
$"No email account found for the given criteria (Id: {request.Account.Id}, Username: {request.Account.Username}).");
if (string.IsNullOrWhiteSpace(account.ImapServer))
throw new InvalidOperationException(
$"IMAP is not configured for account '{account.Username}' (Id: {account.Id}). Set ImapServer in EmailAccounts configuration.");
return await ImapService.FetchEmailUidsAsync(
account,
request.Mail,
cancellationToken);
}
}

View File

@@ -16,6 +16,21 @@ namespace DigitalData.MessagingService.API.Controllers;
[Route("api/[controller]")]
public class EmailController(IMediator mediator) : ControllerBase
{
/// <summary>
///
/// </summary>
public enum OnlyFilter
{
/// <summary>
///
/// </summary>
HtmlBody,
/// <summary>
///
/// </summary>
Uid,
}
#region Send
/// <summary>
/// Send an email, optionally with file attachments.
@@ -72,70 +87,31 @@ public class EmailController(IMediator mediator) : ControllerBase
/// Fetch emails from an IMAP mailbox.
/// </summary>
/// <param name="query">Query parameters for filtering and fetching emails from the IMAP mailbox.</param>
/// <param name="firstHtmlBodyOnly"></param>
/// <param name="only"></param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>HTTP 200 with list of received emails.</returns>
[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> FetchEmails([FromQuery] FetchEmailsQuery query, [FromQuery] bool firstHtmlBodyOnly = false, CancellationToken cancellationToken = default)
public async Task<IActionResult> FetchEmails([FromQuery] FetchEmailsQuery query, [FromQuery] OnlyFilter? only = null, CancellationToken cancellationToken = default)
{
var emails = await mediator.Send(query, cancellationToken);
if(!emails.Any())
return NotFound("No emails found matching the specified criteria.");
if (firstHtmlBodyOnly && emails.FirstOrDefault()?.HtmlBody is string htmlBody)
return Content(htmlBody, "text/html");
return Ok(emails);
}
/// <summary>
/// Fetch only the UIDs of emails matching the given filter from an IMAP mailbox.
/// </summary>
/// <param name="query">Query parameters for filtering emails.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>HTTP 200 with the list of matching UIDs, or HTTP 404 if none found.</returns>
[HttpGet("uid")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> FetchEmailUids([FromQuery] FetchEmailUidsQuery query, CancellationToken cancellationToken = default)
{
var uids = await mediator.Send(query, cancellationToken);
if (!uids.Any())
return NotFound("No emails found matching the specified criteria.");
return Ok(uids);
}
/// <summary>
/// Fetch a single email by its UID from an IMAP mailbox.
/// </summary>
/// <param name="uid">The unique identifier (UID) of the message.</param>
/// <param name="query">Query parameters including account, folder and attachment flag.</param>
/// <param name="htmlBodyOnly"></param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>HTTP 200 with the matched email, or HTTP 404 if not found.</returns>
[HttpGet("{uid:long}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<IActionResult> FetchEmailByUid([FromRoute] long uid, [FromQuery] FetchEmailByUidQuery query, [FromQuery] bool htmlBodyOnly = false, CancellationToken cancellationToken = default)
{
var queryWithUid = query.WithUid(uid);
var email = await mediator.Send(queryWithUid, cancellationToken);
if (email is null)
return NotFound($"No email found with UID {uid}.");
if (htmlBodyOnly)
return Content(email.HtmlBody, "text/html");
return Ok(email);
if (only == OnlyFilter.HtmlBody)
{
if (emails.FirstOrDefault()?.HtmlBody is string htmlBody)
return Content(htmlBody, "text/html");
else
return NotFound();
}
else if (only == OnlyFilter.Uid)
return Ok(emails.Select(e => e.Uid).ToList());
else
return Ok(emails);
}
/// <summary>