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:
@@ -22,27 +22,6 @@ public interface IImapEmailService
|
|||||||
MailSearchFilter filter,
|
MailSearchFilter filter,
|
||||||
CancellationToken cancellationToken = default);
|
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>
|
/// <summary>
|
||||||
/// Marks a message as seen (read) on the server.
|
/// Marks a message as seen (read) on the server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,6 +16,21 @@ namespace DigitalData.MessagingService.API.Controllers;
|
|||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
public class EmailController(IMediator mediator) : ControllerBase
|
public class EmailController(IMediator mediator) : ControllerBase
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public enum OnlyFilter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
HtmlBody,
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
Uid,
|
||||||
|
}
|
||||||
|
|
||||||
#region Send
|
#region Send
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Send an email, optionally with file attachments.
|
/// Send an email, optionally with file attachments.
|
||||||
@@ -72,72 +87,33 @@ public class EmailController(IMediator mediator) : ControllerBase
|
|||||||
/// Fetch emails from an IMAP mailbox.
|
/// Fetch emails from an IMAP mailbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="query">Query parameters for filtering and fetching emails from the IMAP mailbox.</param>
|
/// <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>
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
/// <returns>HTTP 200 with list of received emails.</returns>
|
/// <returns>HTTP 200 with list of received emails.</returns>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
[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);
|
var emails = await mediator.Send(query, cancellationToken);
|
||||||
|
|
||||||
if(!emails.Any())
|
if(!emails.Any())
|
||||||
return NotFound("No emails found matching the specified criteria.");
|
return NotFound("No emails found matching the specified criteria.");
|
||||||
|
|
||||||
if (firstHtmlBodyOnly && emails.FirstOrDefault()?.HtmlBody is string htmlBody)
|
if (only == OnlyFilter.HtmlBody)
|
||||||
|
{
|
||||||
|
if (emails.FirstOrDefault()?.HtmlBody is string htmlBody)
|
||||||
return Content(htmlBody, "text/html");
|
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);
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mark a single IMAP message as seen (read).
|
/// Mark a single IMAP message as seen (read).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user