refactor(application): replace ISender dispatch with direct IRepository usage in email handlers

- FetchEmailsQuery, MarkEmailAsSeenCommand, PublishEmailCommand: resolve EmailAccount
  via IRepository<EmailAccount> instead of dispatching GetEmailAccountQuery through ISender
- Swap InvalidOperationException for BadRequestException when IMAP server is not configured
- Add ILogger to handlers; warn when multiple accounts match the lookup criteria
- EmailAccountsOptions.Accounts now typed as IEnumerable<EmailAccountModificationDto>
  instead of IEnumerable<EmailAccount> to decouple config from domain entity
- EmailMappingProfile: add EmailAccount <-> EmailAccountDto/EmailAccountModificationDto maps
This commit is contained in:
2026-08-13 11:57:57 +02:00
parent 50eaefb868
commit 14180860d3
5 changed files with 51 additions and 26 deletions

View File

@@ -1,8 +1,11 @@
#if NET
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.Commands;
@@ -11,7 +14,7 @@ namespace DigitalData.MessagingService.Application.EmailReceiving.Commands;
/// </summary>
public record MarkEmailAsSeenCommand : IRequest
{
public required GetSenderQuery Account { get; init; }
public required GetEmailAccountQuery Account { get; init; }
/// <summary>
/// UID of the message to mark as seen.
@@ -24,19 +27,20 @@ public record MarkEmailAsSeenCommand : IRequest
public string Folder { get; init; } = "INBOX";
}
public class MarkEmailAsSeenCommandHandler(
ISender Sender,
IImapEmailService ImapService) : IRequestHandler<MarkEmailAsSeenCommand>
public class MarkEmailAsSeenCommandHandler(IImapEmailService ImapService, ILogger<MarkEmailAsSeenCommandHandler> Logger, IRepository<EmailAccount> Repo) : IRequestHandler<MarkEmailAsSeenCommand>
{
public async Task Handle(MarkEmailAsSeenCommand 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}).");
var accounts = await Repo.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 InvalidOperationException(
$"IMAP is not configured for account '{account.Username}' (Id: {account.Id}). Set ImapServer in EmailAccounts configuration.");
throw new BadRequestException($"IMAP is not configured for account '{account.Username}' (Id: {account.Id}). Set ImapServer in EmailAccounts configuration.");
await ImapService.MarkAsSeenAsync(account, request.Uid, request.Folder, cancellationToken);
}

View File

@@ -2,9 +2,12 @@
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;
@@ -16,7 +19,7 @@ public record FetchEmailsQuery : IRequest<IEnumerable<ReceivedEmailDto>>
/// <summary>
/// Identifies the email account to use.
/// </summary>
public required GetSenderQuery Account { get; init; }
public required GetEmailAccountQuery Account { get; init; }
/// <summary>
/// Mail query used to filter and limit the emails retrieved.
@@ -24,18 +27,20 @@ public record FetchEmailsQuery : IRequest<IEnumerable<ReceivedEmailDto>>
public MailSearchFilter Mail { get; init; } = new();
}
public class FetchEmailsQueryHandler(
ISender Sender,
IImapEmailService ImapService) : IRequestHandler<FetchEmailsQuery, IEnumerable<ReceivedEmailDto>>
public class FetchEmailsQueryHandler(IImapEmailService ImapService, ILogger<FetchEmailsQueryHandler> Logger, IRepository<EmailAccount> Repo) : IRequestHandler<FetchEmailsQuery, IEnumerable<ReceivedEmailDto>>
{
public async Task<IEnumerable<ReceivedEmailDto>> Handle(FetchEmailsQuery 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}).");
var accounts = await Repo.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}");
EmailAccount 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 InvalidOperationException(
throw new BadRequestException(
$"IMAP is not configured for account '{account.Username}' (Id: {account.Id}). Set ImapServer in EmailAccounts configuration.");
return await ImapService.FetchEmailsAsync(