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:
@@ -2,6 +2,8 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using DigitalData.MessagingService.Application.EmailSending.Commands;
|
using DigitalData.MessagingService.Application.EmailSending.Commands;
|
||||||
using DigitalData.MessagingService.Application.Common.Dto;
|
using DigitalData.MessagingService.Application.Common.Dto;
|
||||||
|
using DigitalData.MessagingService.Domain.Entities;
|
||||||
|
using DigitalData.MessagingService.Application.Common.Dto.EmailAccounts;
|
||||||
|
|
||||||
namespace DigitalData.MessagingService.Application.Common.Mappings;
|
namespace DigitalData.MessagingService.Application.Common.Mappings;
|
||||||
|
|
||||||
@@ -17,6 +19,10 @@ public class EmailMappingProfile : Profile
|
|||||||
CreateMap<PublishEmailCommand, EmailContext>()
|
CreateMap<PublishEmailCommand, EmailContext>()
|
||||||
.ForMember(dest => dest.Sender, opt => opt.Ignore())
|
.ForMember(dest => dest.Sender, opt => opt.Ignore())
|
||||||
.ForMember(dest => dest.Attachments, opt => opt.MapFrom(src => src.Attachments));
|
.ForMember(dest => dest.Attachments, opt => opt.MapFrom(src => src.Attachments));
|
||||||
|
|
||||||
|
// EmailAccountDto -> EmailAccount
|
||||||
|
CreateMap<EmailAccount, EmailAccountDto>();
|
||||||
|
CreateMap<EmailAccountModificationDto, EmailAccount>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
#if NET
|
#if NET
|
||||||
|
using DigitalData.MessagingService.Application.Common.Dto;
|
||||||
|
using DigitalData.MessagingService.Application.Common.Dto.EmailAccounts;
|
||||||
|
using DigitalData.MessagingService.Application.EmailAccounts.Commands;
|
||||||
using DigitalData.MessagingService.Domain.Entities;
|
using DigitalData.MessagingService.Domain.Entities;
|
||||||
|
|
||||||
namespace DigitalData.MessagingService.Application.Common.Options;
|
namespace DigitalData.MessagingService.Application.Common.Options;
|
||||||
@@ -14,7 +17,7 @@ public class EmailAccountsOptions
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The list of configured email accounts.
|
/// The list of configured email accounts.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required IEnumerable<EmailAccount> Accounts { get; init; } = [];
|
public required IEnumerable<EmailAccountModificationDto> Accounts { get; init; } = [];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How often the IMAP sync worker polls for new emails, in seconds.
|
/// How often the IMAP sync worker polls for new emails, in seconds.
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
#if NET
|
#if NET
|
||||||
using DigitalData.MessagingService.Application.Common.Interfaces;
|
using DigitalData.MessagingService.Application.Common.Interfaces;
|
||||||
|
using DigitalData.MessagingService.Application.Common.Interfaces.Repositories;
|
||||||
using DigitalData.MessagingService.Application.EmailAccounts.Queries;
|
using DigitalData.MessagingService.Application.EmailAccounts.Queries;
|
||||||
|
using DigitalData.MessagingService.Domain.Entities;
|
||||||
using DigitalData.MessagingService.Domain.Exceptions;
|
using DigitalData.MessagingService.Domain.Exceptions;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace DigitalData.MessagingService.Application.EmailReceiving.Commands;
|
namespace DigitalData.MessagingService.Application.EmailReceiving.Commands;
|
||||||
|
|
||||||
@@ -11,7 +14,7 @@ namespace DigitalData.MessagingService.Application.EmailReceiving.Commands;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public record MarkEmailAsSeenCommand : IRequest
|
public record MarkEmailAsSeenCommand : IRequest
|
||||||
{
|
{
|
||||||
public required GetSenderQuery Account { get; init; }
|
public required GetEmailAccountQuery Account { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// UID of the message to mark as seen.
|
/// UID of the message to mark as seen.
|
||||||
@@ -24,19 +27,20 @@ public record MarkEmailAsSeenCommand : IRequest
|
|||||||
public string Folder { get; init; } = "INBOX";
|
public string Folder { get; init; } = "INBOX";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MarkEmailAsSeenCommandHandler(
|
public class MarkEmailAsSeenCommandHandler(IImapEmailService ImapService, ILogger<MarkEmailAsSeenCommandHandler> Logger, IRepository<EmailAccount> Repo) : IRequestHandler<MarkEmailAsSeenCommand>
|
||||||
ISender Sender,
|
|
||||||
IImapEmailService ImapService) : IRequestHandler<MarkEmailAsSeenCommand>
|
|
||||||
{
|
{
|
||||||
public async Task Handle(MarkEmailAsSeenCommand request, CancellationToken cancellationToken)
|
public async Task Handle(MarkEmailAsSeenCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var account = await Sender.Send(request.Account, cancellationToken)
|
var accounts = await Repo.FindAsync(request.Account.Id is int id ? x => x.Id == id : x => x.Username == request.Account.Username, cancellationToken: cancellationToken);
|
||||||
?? throw new NotFoundException(
|
|
||||||
$"No email account found for the given criteria (Id: {request.Account.Id}, Username: {request.Account.Username}).");
|
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))
|
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.");
|
||||||
$"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);
|
await ImapService.MarkAsSeenAsync(account, request.Uid, request.Folder, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,12 @@
|
|||||||
using DigitalData.MessagingService.Application.Common.Dto;
|
using DigitalData.MessagingService.Application.Common.Dto;
|
||||||
using DigitalData.MessagingService.Application.Common.Dto.MailSearch;
|
using DigitalData.MessagingService.Application.Common.Dto.MailSearch;
|
||||||
using DigitalData.MessagingService.Application.Common.Interfaces;
|
using DigitalData.MessagingService.Application.Common.Interfaces;
|
||||||
|
using DigitalData.MessagingService.Application.Common.Interfaces.Repositories;
|
||||||
using DigitalData.MessagingService.Application.EmailAccounts.Queries;
|
using DigitalData.MessagingService.Application.EmailAccounts.Queries;
|
||||||
|
using DigitalData.MessagingService.Domain.Entities;
|
||||||
using DigitalData.MessagingService.Domain.Exceptions;
|
using DigitalData.MessagingService.Domain.Exceptions;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace DigitalData.MessagingService.Application.EmailReceiving.Queries;
|
namespace DigitalData.MessagingService.Application.EmailReceiving.Queries;
|
||||||
|
|
||||||
@@ -16,7 +19,7 @@ public record FetchEmailsQuery : IRequest<IEnumerable<ReceivedEmailDto>>
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Identifies the email account to use.
|
/// Identifies the email account to use.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public required GetSenderQuery Account { get; init; }
|
public required GetEmailAccountQuery Account { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Mail query used to filter and limit the emails retrieved.
|
/// 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 MailSearchFilter Mail { get; init; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FetchEmailsQueryHandler(
|
public class FetchEmailsQueryHandler(IImapEmailService ImapService, ILogger<FetchEmailsQueryHandler> Logger, IRepository<EmailAccount> Repo) : IRequestHandler<FetchEmailsQuery, IEnumerable<ReceivedEmailDto>>
|
||||||
ISender Sender,
|
|
||||||
IImapEmailService ImapService) : IRequestHandler<FetchEmailsQuery, IEnumerable<ReceivedEmailDto>>
|
|
||||||
{
|
{
|
||||||
public async Task<IEnumerable<ReceivedEmailDto>> Handle(FetchEmailsQuery request, CancellationToken cancellationToken)
|
public async Task<IEnumerable<ReceivedEmailDto>> Handle(FetchEmailsQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var account = await Sender.Send(request.Account, cancellationToken)
|
var accounts = await Repo.FindAsync(request.Account.Id is int id ? x => x.Id == id : x => x.Username == request.Account.Username, cancellationToken: cancellationToken);
|
||||||
?? throw new NotFoundException(
|
|
||||||
$"No email account found for the given criteria (Id: {request.Account.Id}, Username: {request.Account.Username}).");
|
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))
|
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.");
|
$"IMAP is not configured for account '{account.Username}' (Id: {account.Id}). Set ImapServer in EmailAccounts configuration.");
|
||||||
|
|
||||||
return await ImapService.FetchEmailsAsync(
|
return await ImapService.FetchEmailsAsync(
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
#if NET
|
#if NET
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using DigitalData.MessagingService.Domain.Exceptions;
|
|
||||||
using DigitalData.MessagingService.Application.Common.Dto;
|
using DigitalData.MessagingService.Application.Common.Dto;
|
||||||
using MediatR;
|
|
||||||
using System.Text.Json.Serialization;
|
|
||||||
using DigitalData.MessagingService.Application.Common.Interfaces;
|
using DigitalData.MessagingService.Application.Common.Interfaces;
|
||||||
|
using DigitalData.MessagingService.Application.Common.Interfaces.Repositories;
|
||||||
using DigitalData.MessagingService.Application.EmailAccounts.Queries;
|
using DigitalData.MessagingService.Application.EmailAccounts.Queries;
|
||||||
|
using DigitalData.MessagingService.Domain.Entities;
|
||||||
|
using DigitalData.MessagingService.Domain.Exceptions;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace DigitalData.MessagingService.Application.EmailSending.Commands;
|
namespace DigitalData.MessagingService.Application.EmailSending.Commands;
|
||||||
|
|
||||||
@@ -14,7 +17,7 @@ namespace DigitalData.MessagingService.Application.EmailSending.Commands;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public record PublishEmailCommand : IRequest<Guid>
|
public record PublishEmailCommand : IRequest<Guid>
|
||||||
{
|
{
|
||||||
public required GetSenderQuery Sender { get; init; }
|
public required GetEmailAccountQuery Sender { get; init; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Recipient email addresses
|
/// Recipient email addresses
|
||||||
@@ -51,13 +54,17 @@ public record PublishEmailCommand : IRequest<Guid>
|
|||||||
/// Handler for PublishEmailCommand
|
/// Handler for PublishEmailCommand
|
||||||
/// Resolves the sender account via MediatR, maps to SendingEmailEvent and enqueues to RabbitMQ
|
/// Resolves the sender account via MediatR, maps to SendingEmailEvent and enqueues to RabbitMQ
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class PublishEmailCommandHandler(ISender Sender, ISendingEmailPublisher Publisher, IMapper Mapper) : IRequestHandler<PublishEmailCommand, Guid>
|
public class PublishEmailCommandHandler(IRepository<EmailAccount> Repo, ISendingEmailPublisher Publisher, IMapper Mapper, ILogger<PublishEmailCommandHandler> Logger) : IRequestHandler<PublishEmailCommand, Guid>
|
||||||
{
|
{
|
||||||
public async Task<Guid> Handle(PublishEmailCommand request, CancellationToken cancellationToken)
|
public async Task<Guid> Handle(PublishEmailCommand request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var senderAccount = await Sender.Send(request.Sender, cancellationToken)
|
var senderAccounts = await Repo.FindAsync(request.Sender.Id is int id ? x => x.Id == id : x => x.Username == request.Sender.Username, cancellationToken: cancellationToken);
|
||||||
?? throw new NotFoundException(
|
|
||||||
$"No email account found for the given sender criteria (Id: {request.Sender.Id}, Username: {request.Sender.Username}).");
|
if (senderAccounts.Count() > 1)
|
||||||
|
Logger.LogWarning("Multiple email accounts found for the given criteria ({Criteria}). Returning the first one.", request.Sender.Id is not null ? $"Id: {request.Sender.Id}" : $"Username: {request.Sender.Username}");
|
||||||
|
|
||||||
|
var senderAccount = senderAccounts.FirstOrDefault()
|
||||||
|
?? throw new NotFoundException($"No email account found for the given sender criteria (Id: {request.Sender.Id}, Username: {request.Sender.Username}).");
|
||||||
|
|
||||||
var email = Mapper.Map<EmailContext>(request) with { Sender = senderAccount };
|
var email = Mapper.Map<EmailContext>(request) with { Sender = senderAccount };
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user