From 845080691684954dcb3ad39c23da4f702968bd1a Mon Sep 17 00:00:00 2001 From: TekH Date: Tue, 18 Aug 2026 15:26:21 +0200 Subject: [PATCH] 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. --- .../EmailReceiving/Queries/ReadEmailQuery.cs | 125 +++++++++++ .../Queries/ReadEmailViaImapQuery.cs | 58 ----- .../Queries/ReadEmailViaOAuth2Query.cs | 77 ------- .../Queries/ReadEmailViaPop3Query.cs | 74 ------- .../Validators/FetchEmailsQueryValidator.cs | 4 +- .../Controllers/EmailController.cs | 201 ++++++------------ .../DigitalData.MessagingService.API.csproj | 6 +- 7 files changed, 194 insertions(+), 351 deletions(-) create mode 100644 src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailQuery.cs delete mode 100644 src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailViaImapQuery.cs delete mode 100644 src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailViaOAuth2Query.cs delete mode 100644 src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailViaPop3Query.cs diff --git a/src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailQuery.cs b/src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailQuery.cs new file mode 100644 index 0000000..1c5c943 --- /dev/null +++ b/src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailQuery.cs @@ -0,0 +1,125 @@ +#if NET +using AutoMapper; +using DigitalData.MessagingService.Application.Common.Dto; +using DigitalData.MessagingService.Application.Common.Dto.MailSearch; +using DigitalData.MessagingService.Application.Common.Interfaces; +using DigitalData.MessagingService.Application.Common.Interfaces.Repositories; +using DigitalData.MessagingService.Application.EmailAccounts.Queries; +using DigitalData.MessagingService.Domain.Entities; +using DigitalData.MessagingService.Domain.Enums; +using DigitalData.MessagingService.Domain.Exceptions; +using MediatR; +using Microsoft.Extensions.Logging; + +namespace DigitalData.MessagingService.Application.EmailReceiving.Queries; + +/// +/// Query to fetch emails from an email account. +/// The protocol and authentication method are determined by . +/// +public record ReadEmailQuery : IRequest +{ + /// + /// Identifies the email account to use. + /// + public required GetEmailAccountQuery Account { get; init; } + + /// + /// Mail query used to filter and limit the emails retrieved. + /// + public MailSearchFilter Mail { get; init; } = new(); +} + +public class ReadEmailQueryHandler( + IMapper Mapper, + ILogger Logger, + IRepository EmailAccountRepo, + IReceivedEmailRepository MailRepo, + IImapEmailService ImapEmailService, + IPop3EmailService Pop3EmailService) : IRequestHandler +{ + public async Task Handle(ReadEmailQuery request, CancellationToken cancellationToken) + { + var accounts = await EmailAccountRepo.FindAsync( + request.Account.Id is int id ? x => x.Id == id : x => x.Username == request.Account.Username, + cancellationToken: cancellationToken); + + if (accounts.Count() > 1) + Logger.LogWarning("Multiple email accounts found for the given criteria ({Criteria}). Returning the first one.", + request.Account.Id is not null ? $"Id: {request.Account.Id}" : $"Username: {request.Account.Username}"); + + var account = accounts.FirstOrDefault() + ?? throw new NotFoundException($"No email account found for the given criteria (Id: {request.Account.Id}, Username: {request.Account.Username})."); + + return account.IncomingProtocol switch + { + IncomingProtocol.Imap => await HandleImapAsync(account, request.Mail, cancellationToken), + IncomingProtocol.ImapOAuth2 => await HandleImapOAuth2Async(account, request.Mail, cancellationToken), + IncomingProtocol.Pop3 => await HandlePop3Async(account, request.Mail, cancellationToken), + IncomingProtocol.Pop3OAuth2 => throw new NotImplementedException( + $"POP3 OAuth2 is not yet supported. Account: '{account.Username}' (Id: {account.Id})."), + IncomingProtocol.None => throw new BadRequestException( + $"Account '{account.Username}' (Id: {account.Id}) has no incoming protocol configured (IncomingProtocol = None)."), + _ => throw new BadRequestException( + $"Unknown IncomingProtocol value '{account.IncomingProtocol}' for account '{account.Username}' (Id: {account.Id}).") + }; + } + + private async Task HandleImapAsync(EmailAccount account, MailSearchFilter filter, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(account.ImapServer)) + throw new BadRequestException( + $"IMAP is not configured for account '{account.Username}' (Id: {account.Id}). Set ImapServer in EmailAccounts configuration."); + + var mails = await MailRepo.FindAsync(filter, account, cancellationToken); + var lastSync = ImapEmailService.GetLastImapSyncDate(account.Id, filter.Folder); + + return new ReadEmailQueryResponse + { + LastSync = lastSync, + Emails = Mapper.Map>(mails) + }; + } + + private async Task HandleImapOAuth2Async(EmailAccount account, MailSearchFilter filter, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(account.OAuth2ClientId) || string.IsNullOrWhiteSpace(account.OAuth2ClientSecret)) + throw new BadRequestException( + $"OAuth2 credentials (ClientId, ClientSecret) are not configured for account '{account.Username}' (Id: {account.Id})."); + + if (string.IsNullOrWhiteSpace(account.ImapServer)) + throw new BadRequestException( + $"IMAP is not configured for account '{account.Username}' (Id: {account.Id}). Set ImapServer in EmailAccounts configuration."); + + var mails = await MailRepo.FindAsync(filter, account, cancellationToken); + var lastSync = ImapEmailService.GetLastImapSyncDate(account.Id, filter.Folder); + + return new ReadEmailQueryResponse + { + LastSync = lastSync, + Emails = Mapper.Map>(mails) + }; + } + + private async Task HandlePop3Async(EmailAccount account, MailSearchFilter filter, CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(account.Pop3Server)) + throw new BadRequestException( + $"POP3 is not configured for account '{account.Username}' (Id: {account.Id}). Set Pop3Server in EmailAccounts configuration."); + + // Trigger on-demand POP3 sync before querying local storage + await Pop3EmailService.SyncEmailsAsync(account, cancellationToken); + + // POP3 has no folder concept — always query INBOX + var pop3Filter = filter with { Folder = "INBOX" }; + var mails = await MailRepo.FindAsync(pop3Filter, account, cancellationToken); + var lastSync = Pop3EmailService.GetLastPop3SyncDate(account.Id); + + return new ReadEmailQueryResponse + { + LastSync = lastSync, + Emails = Mapper.Map>(mails) + }; + } +} +#endif diff --git a/src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailViaImapQuery.cs b/src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailViaImapQuery.cs deleted file mode 100644 index e6e2454..0000000 --- a/src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailViaImapQuery.cs +++ /dev/null @@ -1,58 +0,0 @@ -#if NET -using AutoMapper; -using DigitalData.MessagingService.Application.Common.Dto; -using DigitalData.MessagingService.Application.Common.Dto.MailSearch; -using DigitalData.MessagingService.Application.Common.Interfaces; -using DigitalData.MessagingService.Application.Common.Interfaces.Repositories; -using DigitalData.MessagingService.Application.EmailAccounts.Queries; -using DigitalData.MessagingService.Domain.Entities; -using DigitalData.MessagingService.Domain.Exceptions; -using MediatR; -using Microsoft.Extensions.Logging; - -namespace DigitalData.MessagingService.Application.EmailReceiving.Queries; - -/// -/// Query to fetch emails from an IMAP mailbox. -/// -public record ReadEmailViaImapQuery : IRequest -{ - /// - /// Identifies the email account to use. - /// - public required GetEmailAccountQuery Account { get; init; } - - /// - /// Mail query used to filter and limit the emails retrieved. - /// - public MailSearchFilter Mail { get; init; } = new(); -} - -public class ReadEmailViaImapQueryHandler(IMapper Mapper, ILogger Logger, IRepository EmailAccountRepo, IReceivedEmailRepository MailRepo, IImapEmailService imapEmailService) : IRequestHandler -{ - public async Task Handle(ReadEmailViaImapQuery request, CancellationToken cancellationToken) - { - var accounts = await EmailAccountRepo.FindAsync(request.Account.Id is int id ? x => x.Id == id : x => x.Username == request.Account.Username, cancellationToken: cancellationToken); - - if (accounts.Count() > 1) - Logger.LogWarning("Multiple email accounts found for the given criteria ({Criteria}). Returning the first one.", request.Account.Id is not null ? $"Id: {request.Account.Id}" : $"Username: {request.Account.Username}"); - - var account = accounts.FirstOrDefault() - ?? 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 BadRequestException( - $"IMAP is not configured for account '{account.Username}' (Id: {account.Id}). Set ImapServer in EmailAccounts configuration."); - - var mails = await MailRepo.FindAsync(request.Mail, account, cancellationToken); - - var lastSync = imapEmailService.GetLastImapSyncDate(account.Id, request.Mail.Folder); - - return new ReadEmailQueryResponse - { - LastSync = lastSync, - Emails = Mapper.Map>(mails) - }; - } -} -#endif \ No newline at end of file diff --git a/src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailViaOAuth2Query.cs b/src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailViaOAuth2Query.cs deleted file mode 100644 index 51bf42c..0000000 --- a/src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailViaOAuth2Query.cs +++ /dev/null @@ -1,77 +0,0 @@ -#if NET -using AutoMapper; -using DigitalData.MessagingService.Application.Common.Dto; -using DigitalData.MessagingService.Application.Common.Dto.MailSearch; -using DigitalData.MessagingService.Application.Common.Interfaces; -using DigitalData.MessagingService.Application.Common.Interfaces.Repositories; -using DigitalData.MessagingService.Application.EmailAccounts.Queries; -using DigitalData.MessagingService.Application.EmailReceiving.Queries; -using DigitalData.MessagingService.Domain.Entities; -using DigitalData.MessagingService.Domain.Exceptions; -using MediatR; -using Microsoft.Extensions.Logging; - -namespace DigitalData.MessagingService.Application.EmailReceiving.Queries; - -/// -/// Query to fetch emails from an IMAP mailbox using OAuth2 authentication. -/// The account must have UseOAuth2 = true and valid OAuth2 credentials configured. -/// -public record ReadEmailViaOAuth2Query : IRequest -{ - /// - /// Identifies the email account to use. - /// - public required GetEmailAccountQuery Account { get; init; } - - /// - /// Mail query used to filter and limit the emails retrieved. - /// - public MailSearchFilter Mail { get; init; } = new(); -} - -public class ReadEmailViaOAuth2QueryHandler( - IMapper Mapper, - ILogger Logger, - IRepository EmailAccountRepo, - IReceivedEmailRepository MailRepo, - IImapEmailService imapEmailService) : IRequestHandler -{ - public async Task Handle(ReadEmailViaOAuth2Query request, CancellationToken cancellationToken) - { - var accounts = await EmailAccountRepo.FindAsync( - request.Account.Id is int id ? x => x.Id == id : x => x.Username == request.Account.Username, - cancellationToken: cancellationToken); - - if (accounts.Count() > 1) - Logger.LogWarning("Multiple email accounts found ({Criteria}). Using first.", - request.Account.Id is not null ? $"Id: {request.Account.Id}" : $"Username: {request.Account.Username}"); - - var account = accounts.FirstOrDefault() - ?? throw new NotFoundException($"No email account found (Id: {request.Account.Id}, Username: {request.Account.Username})."); - - if (!account.UseOAuth2) - throw new BadRequestException( - $"Account '{account.Username}' (Id: {account.Id}) is not configured for OAuth2. Set UseOAuth2 = true."); - - if (string.IsNullOrWhiteSpace(account.OAuth2ClientId) || - string.IsNullOrWhiteSpace(account.OAuth2ClientSecret)) - throw new BadRequestException( - $"OAuth2 credentials (ClientId, ClientSecret) are not configured for account '{account.Username}'."); - - if (string.IsNullOrWhiteSpace(account.ImapServer)) - throw new BadRequestException( - $"IMAP is not configured for account '{account.Username}' (Id: {account.Id}). Set ImapServer in EmailAccounts configuration."); - - var mails = await MailRepo.FindAsync(request.Mail, account, cancellationToken); - - var lastSync = imapEmailService.GetLastImapSyncDate(account.Id, request.Mail.Folder); - - return new ReadEmailQueryResponse - { - LastSync = lastSync, - Emails = Mapper.Map>(mails) - }; - } -} -#endif diff --git a/src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailViaPop3Query.cs b/src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailViaPop3Query.cs deleted file mode 100644 index 0f3bee0..0000000 --- a/src/core/DigitalData.MessagingService.Application/EmailReceiving/Queries/ReadEmailViaPop3Query.cs +++ /dev/null @@ -1,74 +0,0 @@ -#if NET -using AutoMapper; -using DigitalData.MessagingService.Application.Common.Dto; -using DigitalData.MessagingService.Application.Common.Dto.MailSearch; -using DigitalData.MessagingService.Application.Common.Interfaces; -using DigitalData.MessagingService.Application.Common.Interfaces.Repositories; -using DigitalData.MessagingService.Application.EmailAccounts.Queries; -using DigitalData.MessagingService.Application.EmailReceiving.Queries; -using DigitalData.MessagingService.Domain.Entities; -using DigitalData.MessagingService.Domain.Exceptions; -using MediatR; -using Microsoft.Extensions.Logging; - -namespace DigitalData.MessagingService.Application.EmailReceiving.Queries; - -/// -/// Query to fetch emails from a POP3 mailbox. -/// Triggers an on-demand sync and returns stored results filtered by . -/// -public record ReadEmailViaPop3Query : IRequest -{ - /// - /// Identifies the email account to use. - /// - public required GetEmailAccountQuery Account { get; init; } - - /// - /// Mail query used to filter and limit the emails retrieved from local storage. - /// Note: POP3 has no folder concept — all messages are stored under "INBOX". - /// - public MailSearchFilter Mail { get; init; } = new(); -} - -public class ReadEmailViaPop3QueryHandler( - IMapper Mapper, - ILogger Logger, - IRepository EmailAccountRepo, - IReceivedEmailRepository MailRepo, - IPop3EmailService pop3EmailService) : IRequestHandler -{ - public async Task Handle(ReadEmailViaPop3Query request, CancellationToken cancellationToken) - { - var accounts = await EmailAccountRepo.FindAsync( - request.Account.Id is int id ? x => x.Id == id : x => x.Username == request.Account.Username, - cancellationToken: cancellationToken); - - if (accounts.Count() > 1) - Logger.LogWarning("Multiple email accounts found ({Criteria}). Using first.", - request.Account.Id is not null ? $"Id: {request.Account.Id}" : $"Username: {request.Account.Username}"); - - var account = accounts.FirstOrDefault() - ?? throw new NotFoundException($"No email account found (Id: {request.Account.Id}, Username: {request.Account.Username})."); - - if (string.IsNullOrWhiteSpace(account.Pop3Server)) - throw new BadRequestException( - $"POP3 is not configured for account '{account.Username}' (Id: {account.Id}). Set Pop3Server in EmailAccounts configuration."); - - // Trigger on-demand POP3 sync before querying local storage - await pop3EmailService.SyncEmailsAsync(account, cancellationToken); - - // POP3 has no folder concept — always query INBOX - var filter = request.Mail with { Folder = "INBOX" }; - var mails = await MailRepo.FindAsync(filter, account, cancellationToken); - - var lastSync = pop3EmailService.GetLastPop3SyncDate(account.Id); - - return new ReadEmailQueryResponse - { - LastSync = lastSync, - Emails = Mapper.Map>(mails) - }; - } -} -#endif diff --git a/src/core/DigitalData.MessagingService.Application/EmailReceiving/Validators/FetchEmailsQueryValidator.cs b/src/core/DigitalData.MessagingService.Application/EmailReceiving/Validators/FetchEmailsQueryValidator.cs index e9e7919..4b49283 100644 --- a/src/core/DigitalData.MessagingService.Application/EmailReceiving/Validators/FetchEmailsQueryValidator.cs +++ b/src/core/DigitalData.MessagingService.Application/EmailReceiving/Validators/FetchEmailsQueryValidator.cs @@ -5,9 +5,9 @@ using FluentValidation; namespace DigitalData.MessagingService.Application.EmailReceiving.Validators; /// -/// Validates a before it is handled by . +/// Validates a before it is handled by . /// -public class FetchEmailsQueryValidator : AbstractValidator +public class FetchEmailsQueryValidator : AbstractValidator { public FetchEmailsQueryValidator() { diff --git a/src/presentation/DigitalData.MessagingService.API/Controllers/EmailController.cs b/src/presentation/DigitalData.MessagingService.API/Controllers/EmailController.cs index ba7b2fc..c39d8c1 100644 --- a/src/presentation/DigitalData.MessagingService.API/Controllers/EmailController.cs +++ b/src/presentation/DigitalData.MessagingService.API/Controllers/EmailController.cs @@ -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; /// -/// 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. /// [ApiController] [Route("api/[controller]")] public class EmailController(IMediator mediator) : ControllerBase { /// - /// + /// Controls which field(s) to return from a fetch-emails response. /// public enum OnlyFilter { - /// - /// - /// + /// Returns only the HTML body of the first matching email. HtmlBody, - /// - /// - /// + /// Returns only the UID list of matching emails. Uid, } + #region Receive + /// + /// Fetch emails from an email account. + /// The protocol (IMAP, POP3) and authentication method (basic, OAuth2) are determined + /// by the IncomingProtocol field configured on the account. + /// + /// Query parameters for filtering and fetching emails. + /// Optional filter to return only specific fields. + /// Cancellation token. + /// HTTP 200 with list of received emails. + [HttpGet] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task 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 /// - /// Send an email, optionally with file attachments. + /// Send an email via SMTP, optionally with file attachments. /// Omit the attachments field for a plain send. /// /// Email fields as form values @@ -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> BuildAttachmentsAsync( - IFormFileCollection? files, - CancellationToken cancellationToken) - { - if (files is null || files.Count == 0) - return []; - - var result = new List(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 - /// - /// Fetch emails from an IMAP mailbox. - /// - /// Query parameters for filtering and fetching emails from the IMAP mailbox. - /// - /// Cancellation token. - /// HTTP 200 with list of received emails. - [HttpGet] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task 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 /// /// 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 - /// - /// Fetch emails from a POP3 mailbox. - /// Triggers an on-demand POP3 sync before returning results. - /// - /// Query parameters for filtering and fetching emails from the POP3 mailbox. - /// Optional filter to return only specific fields. - /// Cancellation token. - /// HTTP 200 with list of received emails. - [HttpGet("pop3")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task 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 /// /// 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 - /// - /// Fetch emails from an IMAP mailbox using OAuth2 authentication. - /// The account must have UseOAuth2 = true and valid OAuth2 credentials configured. - /// - /// Query parameters for filtering and fetching emails. - /// Optional filter to return only specific fields. - /// Cancellation token. - /// HTTP 200 with list of received emails. - [HttpGet("oauth2/imap")] - [ProducesResponseType(StatusCodes.Status200OK)] - [ProducesResponseType(StatusCodes.Status400BadRequest)] - [ProducesResponseType(StatusCodes.Status404NotFound)] - public async Task FetchEmailsViaOAuth2( - [FromQuery] ReadEmailViaOAuth2Query query, - [FromQuery] OnlyFilter? only = null, - CancellationToken cancellationToken = default) + private static async Task> 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(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 } diff --git a/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj b/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj index 688810a..dc2a075 100644 --- a/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj +++ b/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj @@ -10,9 +10,9 @@ Digital Data GmbH Digital Data GmbH DigitalData.MessagingService.API - 1.0.0-beta - 1.0.0.0 - 1.0.0.0 + 2.0.0-beta + 2.0.0.0 + 2.0.0.0 Copyright © 2026 Digital Data GmbH. All rights reserved. bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml