Refactor email fetching and handling logic
Consolidated email fetching queries into a unified `ReadEmailQuery` to support multiple protocols (IMAP, IMAP OAuth2, POP3) dynamically. Removed redundant query handlers (`ReadEmailViaImapQuery`, `ReadEmailViaOAuth2Query`, `ReadEmailViaPop3Query`) and merged their functionality into `ReadEmailQueryHandler` with protocol-specific methods. Updated `FetchEmailsQueryValidator` to validate the new query. Refactored `EmailController` to use the unified query, simplifying endpoints and consolidating functionality. Removed obsolete endpoints for POP3 and OAuth2 fetching. Performed code cleanup, including removing redundant imports and consolidating attachment handling logic. Updated project version to `2.0.0-beta` to reflect these significant changes. Improved documentation to align with the new structure.
This commit is contained in:
@@ -1,37 +1,71 @@
|
||||
using DigitalData.MessagingService.Application.EmailSending.Commands;
|
||||
using DigitalData.MessagingService.Application.Common.Dto;
|
||||
using DigitalData.MessagingService.Application.EmailReceiving.Queries;
|
||||
using DigitalData.MessagingService.Application.EmailSending.Commands;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using DigitalData.MessagingService.Application.Common.Dto;
|
||||
|
||||
namespace DigitalData.MessagingService.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// Email sending API controller.
|
||||
/// Enqueues outgoing emails to RabbitMQ for async processing.
|
||||
/// Email API controller.
|
||||
/// Handles sending and receiving emails across all supported protocols and auth methods.
|
||||
/// </summary>
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class EmailController(IMediator mediator) : ControllerBase
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// Controls which field(s) to return from a fetch-emails response.
|
||||
/// </summary>
|
||||
public enum OnlyFilter
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <summary>Returns only the HTML body of the first matching email.</summary>
|
||||
HtmlBody,
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <summary>Returns only the UID list of matching emails.</summary>
|
||||
Uid,
|
||||
}
|
||||
|
||||
#region Receive
|
||||
/// <summary>
|
||||
/// Fetch emails from an email account.
|
||||
/// The protocol (IMAP, POP3) and authentication method (basic, OAuth2) are determined
|
||||
/// by the <c>IncomingProtocol</c> field configured on the account.
|
||||
/// </summary>
|
||||
/// <param name="query">Query parameters for filtering and fetching emails.</param>
|
||||
/// <param name="only">Optional filter to return only specific fields.</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] ReadEmailQuery query,
|
||||
[FromQuery] OnlyFilter? only = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var res = await mediator.Send(query, cancellationToken);
|
||||
|
||||
if (!res.Emails.Any())
|
||||
return NotFound("No emails found matching the specified criteria.");
|
||||
|
||||
if (only == OnlyFilter.HtmlBody)
|
||||
{
|
||||
if (res.Emails.FirstOrDefault()?.HtmlBody is string htmlBody)
|
||||
return Content(htmlBody, "text/html");
|
||||
else
|
||||
return NotFound();
|
||||
}
|
||||
else if (only == OnlyFilter.Uid)
|
||||
return Ok(res.Emails.Select(e => e.Uid).ToList());
|
||||
else
|
||||
return Ok(res);
|
||||
}
|
||||
#endregion Receive
|
||||
|
||||
#region SMTP Send
|
||||
/// <summary>
|
||||
/// Send an email, optionally with file attachments.
|
||||
/// Send an email via SMTP, optionally with file attachments.
|
||||
/// Omit the <c>attachments</c> field for a plain send.
|
||||
/// </summary>
|
||||
/// <param name="command">Email fields as form values</param>
|
||||
@@ -53,66 +87,8 @@ public class EmailController(IMediator mediator) : ControllerBase
|
||||
var eventId = await mediator.Send(commandWithAttachments, cancellationToken);
|
||||
return Accepted(new { Id = eventId });
|
||||
}
|
||||
|
||||
private static async Task<IEnumerable<EmailAttachmentDto>> BuildAttachmentsAsync(
|
||||
IFormFileCollection? files,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (files is null || files.Count == 0)
|
||||
return [];
|
||||
|
||||
var result = new List<EmailAttachmentDto>(files.Count);
|
||||
|
||||
foreach (var file in files)
|
||||
{
|
||||
using var ms = new MemoryStream();
|
||||
await file.CopyToAsync(ms, cancellationToken);
|
||||
|
||||
result.Add(new EmailAttachmentDto
|
||||
{
|
||||
FileName = file.FileName,
|
||||
Content = ms.ToArray(),
|
||||
ContentType = file.ContentType
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endregion SMTP Send
|
||||
|
||||
#region IMAP Receive
|
||||
/// <summary>
|
||||
/// Fetch emails from an IMAP mailbox.
|
||||
/// </summary>
|
||||
/// <param name="query">Query parameters for filtering and fetching emails from the IMAP mailbox.</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] ReadEmailViaImapQuery query, [FromQuery] OnlyFilter? only = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var res = await mediator.Send(query, cancellationToken);
|
||||
|
||||
if(!res.Emails.Any())
|
||||
return NotFound("No emails found matching the specified criteria.");
|
||||
|
||||
if (only == OnlyFilter.HtmlBody)
|
||||
{
|
||||
if (res.Emails.FirstOrDefault()?.HtmlBody is string htmlBody)
|
||||
return Content(htmlBody, "text/html");
|
||||
else
|
||||
return NotFound();
|
||||
}
|
||||
else if (only == OnlyFilter.Uid)
|
||||
return Ok(res.Emails.Select(e => e.Uid).ToList());
|
||||
else
|
||||
return Ok(res);
|
||||
}
|
||||
#endregion IMAP Receive
|
||||
|
||||
#region IMAP Send
|
||||
/// <summary>
|
||||
/// Send an email using IMAP account credentials (via RabbitMQ queue).
|
||||
@@ -139,43 +115,6 @@ public class EmailController(IMediator mediator) : ControllerBase
|
||||
}
|
||||
#endregion IMAP Send
|
||||
|
||||
#region POP3 Receive
|
||||
/// <summary>
|
||||
/// Fetch emails from a POP3 mailbox.
|
||||
/// Triggers an on-demand POP3 sync before returning results.
|
||||
/// </summary>
|
||||
/// <param name="query">Query parameters for filtering and fetching emails from the POP3 mailbox.</param>
|
||||
/// <param name="only">Optional filter to return only specific fields.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>HTTP 200 with list of received emails.</returns>
|
||||
[HttpGet("pop3")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> FetchEmailsViaPop3(
|
||||
[FromQuery] ReadEmailViaPop3Query query,
|
||||
[FromQuery] OnlyFilter? only = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var res = await mediator.Send(query, cancellationToken);
|
||||
|
||||
if (!res.Emails.Any())
|
||||
return NotFound("No emails found matching the specified criteria.");
|
||||
|
||||
if (only == OnlyFilter.HtmlBody)
|
||||
{
|
||||
if (res.Emails.FirstOrDefault()?.HtmlBody is string htmlBody)
|
||||
return Content(htmlBody, "text/html");
|
||||
else
|
||||
return NotFound();
|
||||
}
|
||||
else if (only == OnlyFilter.Uid)
|
||||
return Ok(res.Emails.Select(e => e.Uid).ToList());
|
||||
else
|
||||
return Ok(res);
|
||||
}
|
||||
#endregion POP3 Receive
|
||||
|
||||
#region OAuth2 Send
|
||||
/// <summary>
|
||||
/// Send an email via SMTP using OAuth2 authentication (via RabbitMQ queue).
|
||||
@@ -202,40 +141,28 @@ public class EmailController(IMediator mediator) : ControllerBase
|
||||
}
|
||||
#endregion OAuth2 Send
|
||||
|
||||
#region OAuth2 Receive
|
||||
/// <summary>
|
||||
/// Fetch emails from an IMAP mailbox using OAuth2 authentication.
|
||||
/// The account must have <c>UseOAuth2 = true</c> and valid OAuth2 credentials configured.
|
||||
/// </summary>
|
||||
/// <param name="query">Query parameters for filtering and fetching emails.</param>
|
||||
/// <param name="only">Optional filter to return only specific fields.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>HTTP 200 with list of received emails.</returns>
|
||||
[HttpGet("oauth2/imap")]
|
||||
[ProducesResponseType(StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(StatusCodes.Status400BadRequest)]
|
||||
[ProducesResponseType(StatusCodes.Status404NotFound)]
|
||||
public async Task<IActionResult> FetchEmailsViaOAuth2(
|
||||
[FromQuery] ReadEmailViaOAuth2Query query,
|
||||
[FromQuery] OnlyFilter? only = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
private static async Task<IEnumerable<EmailAttachmentDto>> BuildAttachmentsAsync(
|
||||
IFormFileCollection? files,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var res = await mediator.Send(query, cancellationToken);
|
||||
if (files is null || files.Count == 0)
|
||||
return [];
|
||||
|
||||
if (!res.Emails.Any())
|
||||
return NotFound("No emails found matching the specified criteria.");
|
||||
var result = new List<EmailAttachmentDto>(files.Count);
|
||||
|
||||
if (only == OnlyFilter.HtmlBody)
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (res.Emails.FirstOrDefault()?.HtmlBody is string htmlBody)
|
||||
return Content(htmlBody, "text/html");
|
||||
else
|
||||
return NotFound();
|
||||
using var ms = new MemoryStream();
|
||||
await file.CopyToAsync(ms, cancellationToken);
|
||||
|
||||
result.Add(new EmailAttachmentDto
|
||||
{
|
||||
FileName = file.FileName,
|
||||
Content = ms.ToArray(),
|
||||
ContentType = file.ContentType
|
||||
});
|
||||
}
|
||||
else if (only == OnlyFilter.Uid)
|
||||
return Ok(res.Emails.Select(e => e.Uid).ToList());
|
||||
else
|
||||
return Ok(res);
|
||||
|
||||
return result;
|
||||
}
|
||||
#endregion OAuth2 Receive
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
<Authors>Digital Data GmbH</Authors>
|
||||
<Company>Digital Data GmbH</Company>
|
||||
<Product>DigitalData.MessagingService.API</Product>
|
||||
<Version>1.0.0-beta</Version>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<Version>2.0.0-beta</Version>
|
||||
<FileVersion>2.0.0.0</FileVersion>
|
||||
<AssemblyVersion>2.0.0.0</AssemblyVersion>
|
||||
<Copyright>Copyright © 2026 Digital Data GmbH. All rights reserved.</Copyright>
|
||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
Reference in New Issue
Block a user