Add FetchEmailByUidQuery with handler and validation

Introduced `FetchEmailByUidQuery` to fetch a single email by its
UID from an IMAP mailbox. The query includes properties for
email account, UID, folder, and attachment inclusion, with a
fluent method `WithUid(long uid)` to set the UID.

Added `FetchEmailByUidQueryHandler` to process the query,
leveraging `ISender` for account retrieval and `IImapEmailService`
for email fetching. Included error handling for missing accounts
and misconfigured IMAP settings.

Implemented `FetchEmailByUidQueryValidator` to validate the
query, ensuring the `Account` is not null, `Uid` is set and
greater than 0, and `Folder` is not empty.
This commit is contained in:
2026-08-11 09:45:21 +02:00
parent 1fb7dcf98a
commit 5ff38391d0
2 changed files with 89 additions and 0 deletions

View File

@@ -0,0 +1,62 @@
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

@@ -0,0 +1,27 @@
using DigitalData.MessagingService.Application.EmailReceiving.Queries;
using FluentValidation;
namespace DigitalData.MessagingService.Application.EmailReceiving.Validators;
/// <summary>
/// Validates a <see cref="FetchEmailByUidQuery"/> before it is handled by <see cref="FetchEmailByUidQueryHandler"/>.
/// </summary>
public class FetchEmailByUidQueryValidator : AbstractValidator<FetchEmailByUidQuery>
{
public FetchEmailByUidQueryValidator()
{
RuleFor(x => x.Account)
.NotNull()
.WithMessage("Account query must not be null.");
RuleFor(x => x.Uid)
.NotNull()
.WithMessage("UID must be provided. Use WithUid() to set the UID before dispatching the query.")
.GreaterThan(0)
.WithMessage("UID must be greater than 0.");
RuleFor(x => x.Folder)
.NotEmpty()
.WithMessage("Folder must not be empty.");
}
}