From 1162a07454efbf9034efb66e304a026cab77cb22 Mon Sep 17 00:00:00 2001 From: TekH Date: Tue, 11 Aug 2026 09:48:22 +0200 Subject: [PATCH] Add FetchEmailByUid endpoint and improve middleware Added a new endpoint to `EmailController` for fetching a single email by UID, including route and query parameter handling, and appropriate Swagger annotations. Refactored `ExceptionHandlingMiddleware` to use constructor injection, simplified exception handling with a `switch` expression, and improved logging and response formatting. Added support for `ValidationException`. Updated `FormatValidationErrors` to handle `ValidationException` and improved error formatting. Made minor documentation updates in `EmailController` and performed general code cleanup for readability and modernization. --- .../Controllers/EmailController.cs | 23 ++++ .../Middleware/ExceptionHandlingMiddleware.cs | 112 ++++++++---------- 2 files changed, 74 insertions(+), 61 deletions(-) diff --git a/src/presentation/DigitalData.MessagingService.API/Controllers/EmailController.cs b/src/presentation/DigitalData.MessagingService.API/Controllers/EmailController.cs index ec252a5..c6c9ce3 100644 --- a/src/presentation/DigitalData.MessagingService.API/Controllers/EmailController.cs +++ b/src/presentation/DigitalData.MessagingService.API/Controllers/EmailController.cs @@ -72,6 +72,7 @@ public class EmailController(IMediator mediator) : ControllerBase /// Fetch emails from an IMAP mailbox. /// /// Query parameters for filtering and fetching emails from the IMAP mailbox. + /// /// Cancellation token. /// HTTP 200 with list of received emails. [HttpGet] @@ -91,6 +92,28 @@ public class EmailController(IMediator mediator) : ControllerBase return Ok(emails); } + /// + /// Fetch a single email by its UID from an IMAP mailbox. + /// + /// The unique identifier (UID) of the message. + /// Query parameters including account, folder and attachment flag. + /// Cancellation token. + /// HTTP 200 with the matched email, or HTTP 404 if not found. + [HttpGet("{uid:long}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public async Task FetchEmailByUid([FromRoute] long uid, [FromQuery] FetchEmailByUidQuery query, CancellationToken cancellationToken = default) + { + var queryWithUid = query.WithUid(uid); + var email = await mediator.Send(queryWithUid, cancellationToken); + + if (email is null) + return NotFound($"No email found with UID {uid}."); + + return Ok(email); + } + /// /// Mark a single IMAP message as seen (read). /// diff --git a/src/presentation/DigitalData.MessagingService.API/Middleware/ExceptionHandlingMiddleware.cs b/src/presentation/DigitalData.MessagingService.API/Middleware/ExceptionHandlingMiddleware.cs index c9d4abe..8f41e08 100644 --- a/src/presentation/DigitalData.MessagingService.API/Middleware/ExceptionHandlingMiddleware.cs +++ b/src/presentation/DigitalData.MessagingService.API/Middleware/ExceptionHandlingMiddleware.cs @@ -1,89 +1,79 @@ using System.Net; using System.Text.Json; using DigitalData.MessagingService.Domain.Exceptions; +using FluentValidation; namespace DigitalData.MessagingService.API.Middleware; /// /// Global exception handling middleware /// -public class ExceptionHandlingMiddleware +public class ExceptionHandlingMiddleware(RequestDelegate Next, ILogger Logger) { private static readonly JsonSerializerOptions _jsonSerializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; - private readonly RequestDelegate _next; - private readonly ILogger _logger; - - public ExceptionHandlingMiddleware( - RequestDelegate next, - ILogger logger) - { - _next = next; - _logger = logger; - } - + /// + /// + /// + /// + /// public async Task InvokeAsync(HttpContext context) { try { - await _next(context); + await Next(context); } catch (Exception ex) { - await HandleExceptionAsync(context, ex); + context.Response.ContentType = "application/json"; + + var (statusCode, message) = ex switch + { + NotFoundException notFoundEx => + (HttpStatusCode.NotFound, notFoundEx.Message), + + AuthenticationFailedException authEx => + (HttpStatusCode.Unauthorized, authEx.Message), + + ValidationException validationEx => + (HttpStatusCode.BadRequest, FormatValidationErrors(validationEx)), + + _ => (HttpStatusCode.InternalServerError, "An internal server error occurred") + }; + + context.Response.StatusCode = (int)statusCode; + + // Log the exception + if (statusCode == HttpStatusCode.InternalServerError) + { + Logger.LogError(ex, "Unhandled exception: {Message}", ex.Message); + } + else + { + Logger.LogWarning(ex, "Exception handled: {StatusCode} - {Message}", + statusCode, message); + } + + var response = new + { + StatusCode = (int)statusCode, + Message = message, + DetailedMessage = statusCode == HttpStatusCode.InternalServerError + ? ex.Message + : null, + Timestamp = DateTime.Now + }; + + var json = JsonSerializer.Serialize(response, _jsonSerializerOptions); + + await context.Response.WriteAsync(json); } } - private async Task HandleExceptionAsync(HttpContext context, Exception exception) - { - context.Response.ContentType = "application/json"; - - var (statusCode, message) = exception switch - { - NotFoundException notFoundEx => - (HttpStatusCode.NotFound, notFoundEx.Message), - - AuthenticationFailedException authEx => - (HttpStatusCode.Unauthorized, authEx.Message), - - FluentValidation.ValidationException validationEx => - (HttpStatusCode.BadRequest, FormatValidationErrors(validationEx)), - - _ => (HttpStatusCode.InternalServerError, "An internal server error occurred") - }; - - context.Response.StatusCode = (int)statusCode; - - // Log the exception - if (statusCode == HttpStatusCode.InternalServerError) - { - _logger.LogError(exception, "Unhandled exception: {Message}", exception.Message); - } - else - { - _logger.LogWarning(exception, "Exception handled: {StatusCode} - {Message}", - statusCode, message); - } - - var response = new - { - StatusCode = (int)statusCode, - Message = message, - DetailedMessage = statusCode == HttpStatusCode.InternalServerError - ? exception.Message - : null, - Timestamp = DateTime.Now - }; - - var json = JsonSerializer.Serialize(response, _jsonSerializerOptions); - - await context.Response.WriteAsync(json); - } - - private static string FormatValidationErrors(FluentValidation.ValidationException exception) + private static string FormatValidationErrors(ValidationException exception) { var errors = exception.Errors .Select(e => $"{e.PropertyName}: {e.ErrorMessage}")