refactor(application): Simplify SendEmailCommand and IEmailQueue/IEmailService interfaces, add Shared reference

This commit is contained in:
2026-07-23 11:19:54 +02:00
parent 828bb168eb
commit 71e29ac3bb
5 changed files with 18 additions and 51 deletions

View File

@@ -1,4 +1,5 @@
using DigitalData.EmailProfiler.Domain.Entities; using DigitalData.EmailProfiler.Application.Common.Events;
using DigitalData.EmailProfiler.Application.EmailSending.Commands;
namespace DigitalData.EmailProfiler.Application.Common.Interfaces; namespace DigitalData.EmailProfiler.Application.Common.Interfaces;
@@ -7,14 +8,14 @@ namespace DigitalData.EmailProfiler.Application.Common.Interfaces;
/// </summary> /// </summary>
public interface IEmailQueue public interface IEmailQueue
{ {
Task EnqueueAsync(EmailOutbox email, CancellationToken cancellationToken = default); Task EnqueueAsync(OutgoingEmailEvent outgoingEmailEvent, CancellationToken cancellationToken = default);
Task<EmailOutbox?> DequeueAsync(CancellationToken cancellationToken = default); Task<OutgoingEmailEvent?> DequeueAsync(CancellationToken cancellationToken = default);
Task<int> GetQueueDepthAsync(CancellationToken cancellationToken = default); Task<int> GetQueueDepthAsync(CancellationToken cancellationToken = default);
/// <summary> /// <summary>
/// Start event-driven consumer that calls callback when message received /// Start event-driven consumer that calls callback when mail received
/// </summary> /// </summary>
/// <param name="onMessageReceived">Callback to process received message</param> /// <param name="onMailReceived">Callback to process received mail</param>
/// <param name="cancellationToken">Cancellation token</param> /// <param name="cancellationToken">Cancellation token</param>
Task StartConsumerAsync(Func<EmailOutbox, Task> onMessageReceived, CancellationToken cancellationToken = default); Task StartConsumerAsync(Func<OutgoingEmailEvent, Task> onMailReceived, CancellationToken cancellationToken = default);
} }

View File

@@ -1,5 +1,3 @@
using DigitalData.EmailProfiler.Application.Common.Dtos;
namespace DigitalData.EmailProfiler.Application.Common.Interfaces; namespace DigitalData.EmailProfiler.Application.Common.Interfaces;
/// <summary> /// <summary>

View File

@@ -18,4 +18,8 @@
<PackageReference Include="MimeKit" Version="4.17.0" /> <PackageReference Include="MimeKit" Version="4.17.0" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Folder Include="Common\Dtos\" />
</ItemGroup>
</Project> </Project>

View File

@@ -1,6 +1,6 @@
using AutoMapper; using AutoMapper;
using DigitalData.EmailProfiler.Application.Common.Events;
using DigitalData.EmailProfiler.Application.Common.Interfaces; using DigitalData.EmailProfiler.Application.Common.Interfaces;
using DigitalData.EmailProfiler.Domain.Entities;
using MediatR; using MediatR;
namespace DigitalData.EmailProfiler.Application.EmailSending.Commands; namespace DigitalData.EmailProfiler.Application.EmailSending.Commands;
@@ -8,13 +8,8 @@ namespace DigitalData.EmailProfiler.Application.EmailSending.Commands;
/// <summary> /// <summary>
/// Command to send an email (enqueue to RabbitMQ) /// Command to send an email (enqueue to RabbitMQ)
/// </summary> /// </summary>
public record SendEmailCommand : IRequest<int> public record SendEmailCommand : IRequest<OutgoingEmailEvent>
{ {
/// <summary>
/// Email account ID to use for sending
/// </summary>
public required int EmailAccountId { get; init; }
/// <summary> /// <summary>
/// Recipient email address /// Recipient email address
/// </summary> /// </summary>
@@ -34,37 +29,20 @@ public record SendEmailCommand : IRequest<int>
/// Is HTML email (default: true) /// Is HTML email (default: true)
/// </summary> /// </summary>
public bool IsHtml { get; init; } = true; public bool IsHtml { get; init; } = true;
/// <summary>
/// Optional reference string (e.g., ticket number, order ID)
/// </summary>
public string? ReferenceId { get; init; }
/// <summary>
/// Optional comment
/// </summary>
public string? Comment { get; init; }
} }
/// <summary> /// <summary>
/// Handler for SendEmailCommand /// Handler for SendEmailCommand
/// Creates EmailOutbox entity via AutoMapper and enqueues to RabbitMQ /// Creates EmailOutbox entity via AutoMapper and enqueues to RabbitMQ
/// </summary> /// </summary>
public class SendEmailCommandHandler(IEmailQueue EmailQueue, IMapper Mapper) : IRequestHandler<SendEmailCommand, int> public class SendEmailCommandHandler(IEmailQueue EmailQueue, IMapper Mapper) : IRequestHandler<SendEmailCommand, OutgoingEmailEvent>
{ {
public async Task<int> Handle(SendEmailCommand request, CancellationToken cancellationToken) public async Task<OutgoingEmailEvent> Handle(SendEmailCommand request, CancellationToken cancellationToken)
{ {
// Map command to EmailOutbox entity using AutoMapper var outgoingEmailEvent = Mapper.Map<OutgoingEmailEvent>(request);
var emailOutbox = Mapper.Map<EmailOutbox>(request);
// Set audit fields
emailOutbox.AddedWhen = DateTime.Now;
emailOutbox.Sent = false;
emailOutbox.RetryCount = 0;
// Enqueue to RabbitMQ // Enqueue to RabbitMQ
await EmailQueue.EnqueueAsync(emailOutbox, cancellationToken); await EmailQueue.EnqueueAsync(outgoingEmailEvent, cancellationToken);
return outgoingEmailEvent;
return emailOutbox.Id;
} }
} }

View File

@@ -10,10 +10,6 @@ public class SendEmailCommandValidator : AbstractValidator<SendEmailCommand>
{ {
public SendEmailCommandValidator() public SendEmailCommandValidator()
{ {
RuleFor(x => x.EmailAccountId)
.GreaterThan(0)
.WithMessage("EmailAccountId must be greater than 0");
RuleFor(x => x.Recipient) RuleFor(x => x.Recipient)
.NotEmpty() .NotEmpty()
.WithMessage("Recipient is required") .WithMessage("Recipient is required")
@@ -31,15 +27,5 @@ public class SendEmailCommandValidator : AbstractValidator<SendEmailCommand>
RuleFor(x => x.Body) RuleFor(x => x.Body)
.NotEmpty() .NotEmpty()
.WithMessage("Body is required"); .WithMessage("Body is required");
RuleFor(x => x.ReferenceId)
.MaximumLength(200)
.When(x => !string.IsNullOrEmpty(x.ReferenceId))
.WithMessage("ReferenceId must not exceed 200 characters");
RuleFor(x => x.Comment)
.MaximumLength(500)
.When(x => !string.IsNullOrEmpty(x.Comment))
.WithMessage("Comment must not exceed 500 characters");
} }
} }