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}")