diff --git a/src/core/DigitalData.MessagingService.Application/Common/Mappings/EmailMappingProfile.cs b/src/core/DigitalData.MessagingService.Application/Common/Mappings/EmailMappingProfile.cs index 9ada128..391de3c 100644 --- a/src/core/DigitalData.MessagingService.Application/Common/Mappings/EmailMappingProfile.cs +++ b/src/core/DigitalData.MessagingService.Application/Common/Mappings/EmailMappingProfile.cs @@ -14,6 +14,7 @@ public class EmailMappingProfile : Profile // SendEmailCommand -> Email // Sender is resolved via MediatR in the handler and set separately after mapping. CreateMap() - .ForMember(dest => dest.Sender, opt => opt.Ignore()); + .ForMember(dest => dest.Sender, opt => opt.Ignore()) + .ForMember(dest => dest.Attachments, opt => opt.MapFrom(src => src.Attachments)); } } diff --git a/src/core/DigitalData.MessagingService.Application/EmailSending/Commands/SendEmailCommand.cs b/src/core/DigitalData.MessagingService.Application/EmailSending/Commands/SendEmailCommand.cs index d376eb5..da54f6f 100644 --- a/src/core/DigitalData.MessagingService.Application/EmailSending/Commands/SendEmailCommand.cs +++ b/src/core/DigitalData.MessagingService.Application/EmailSending/Commands/SendEmailCommand.cs @@ -3,6 +3,7 @@ using DigitalData.MessagingService.Application.EmailAccount.Queries; using DigitalData.MessagingService.Domain.Exceptions; using DigitalData.MessagingService.Abstraction; using MediatR; +using System.Text.Json.Serialization; namespace DigitalData.MessagingService.Application.EmailSending.Commands; @@ -32,6 +33,16 @@ public record SendEmailCommand : IRequest /// Is HTML email (default: true) /// public bool IsHtml { get; init; } = true; + + [JsonIgnore] + internal IEnumerable Attachments { get; private init; } = []; + + /// + /// Returns a new command instance with the supplied attachments. + /// Called by the controller after resolving uploaded files. + /// + public SendEmailCommand WithAttachments(IEnumerable attachments) + => this with { Attachments = attachments }; } /// diff --git a/src/presentation/DigitalData.MessagingService.API/Controllers/EmailsController.cs b/src/presentation/DigitalData.MessagingService.API/Controllers/EmailsController.cs index 080b433..b21d5ba 100644 --- a/src/presentation/DigitalData.MessagingService.API/Controllers/EmailsController.cs +++ b/src/presentation/DigitalData.MessagingService.API/Controllers/EmailsController.cs @@ -1,33 +1,64 @@ using DigitalData.MessagingService.Application.EmailSending.Commands; +using DigitalData.MessagingService.Abstraction; using MediatR; using Microsoft.AspNetCore.Mvc; namespace DigitalData.MessagingService.API.Controllers; /// -/// Email sending API controller -/// Enqueues outgoing emails to RabbitMQ for async processing +/// Email sending API controller. +/// Enqueues outgoing emails to RabbitMQ for async processing. /// [ApiController] [Route("api/[controller]")] public class EmailsController(IMediator mediator) : ControllerBase { /// - /// Send email (enqueue to RabbitMQ for background processing) + /// Send an email, optionally with file attachments. + /// Omit the attachments field for a plain send. /// - /// Send email command + /// Email fields as form values + /// Optional uploaded files /// Cancellation token - /// HTTP 202 Accepted (queued for processing) - [HttpPost("send")] + /// HTTP 202 Accepted with the queued event ID + [HttpPost] + [Consumes("multipart/form-data")] [ProducesResponseType(StatusCodes.Status202Accepted)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public async Task SendEmail([FromBody] SendEmailCommand command, CancellationToken cancellationToken) + public async Task SendEmail( + [FromForm] SendEmailCommand command, + IFormFileCollection? attachments, + CancellationToken cancellationToken) { - var eventId = await mediator.Send(command, cancellationToken); + var commandWithAttachments = command.WithAttachments( + await BuildAttachmentsAsync(attachments, cancellationToken)); - return Accepted(new + 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) { - Id = eventId - }); + using var ms = new MemoryStream(); + await file.CopyToAsync(ms, cancellationToken); + + result.Add(new EmailAttachmentContext + { + FileName = file.FileName, + Content = ms.ToArray(), + ContentType = file.ContentType + }); + } + + return result; } } diff --git a/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj b/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj index b44a8c7..688810a 100644 --- a/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj +++ b/src/presentation/DigitalData.MessagingService.API/DigitalData.MessagingService.API.csproj @@ -36,4 +36,8 @@ + + + +