feat: Add CheckPdfAttachments feature - Application layer
- Add CheckPdfAttachmentsQuery with dual input support (byte[] + Base64) - Add CheckPdfAttachmentsQueryHandler with IPdfProcessor integration - Add CheckPdfAttachmentsQueryValidator with FluentValidation rules - Add AttachmentCheckResult DTO for API response - Handler converts byte[] → MemoryStream for IPdfProcessor.CheckAttachmentsAsync() - AutoMapper maps AttachmentInfo → AttachmentCheckResult
This commit is contained in:
@@ -0,0 +1,48 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using DocumentOperator.Application.Common.DTOs;
|
||||||
|
using DocumentOperator.Application.Common.Interfaces;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace DocumentOperator.Application.CheckPdfAttachments.Queries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query for checking PDF attachments (supports both byte array and Base64 input)
|
||||||
|
/// </summary>
|
||||||
|
public record CheckPdfAttachmentsQuery : IRequest<AttachmentCheckResult>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// PDF as byte array (direct upload via multipart/form-data)
|
||||||
|
/// </summary>
|
||||||
|
public byte[]? PdfBytes { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PDF as Base64 string (for API clients using application/json)
|
||||||
|
/// </summary>
|
||||||
|
public string? Base64Pdf { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handler for CheckPdfAttachmentsQuery
|
||||||
|
/// Orchestrates PDF attachment checking using IPdfProcessor and AutoMapper
|
||||||
|
/// </summary>
|
||||||
|
public class CheckPdfAttachmentsQueryHandler(IPdfProcessor PdfProcessor, IMapper Mapper)
|
||||||
|
: IRequestHandler<CheckPdfAttachmentsQuery, AttachmentCheckResult>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Checks PDF attachments and returns detailed metadata
|
||||||
|
/// </summary>
|
||||||
|
public async Task<AttachmentCheckResult> Handle(CheckPdfAttachmentsQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
// Use byte[] if available, otherwise convert Base64
|
||||||
|
byte[] pdfBytes = request.PdfBytes ?? Convert.FromBase64String(request.Base64Pdf!);
|
||||||
|
|
||||||
|
// Convert to stream for IPdfProcessor
|
||||||
|
using var pdfStream = new MemoryStream(pdfBytes);
|
||||||
|
|
||||||
|
// Call DevExpress service (exceptions propagate naturally)
|
||||||
|
var attachmentInfo = await PdfProcessor.CheckAttachmentsAsync(pdfStream);
|
||||||
|
|
||||||
|
// Map DTO to response DTO using AutoMapper
|
||||||
|
return Mapper.Map<AttachmentCheckResult>(attachmentInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace DocumentOperator.Application.CheckPdfAttachments.Queries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validator for CheckPdfAttachmentsQuery
|
||||||
|
/// Ensures exactly one input type (PdfBytes OR Base64Pdf) is provided
|
||||||
|
/// </summary>
|
||||||
|
public class CheckPdfAttachmentsQueryValidator : AbstractValidator<CheckPdfAttachmentsQuery>
|
||||||
|
{
|
||||||
|
public CheckPdfAttachmentsQueryValidator()
|
||||||
|
{
|
||||||
|
// Rule 1: Exactly ONE input must be provided (XOR logic)
|
||||||
|
RuleFor(x => x)
|
||||||
|
.Must(x => (x.PdfBytes != null && x.PdfBytes.Length > 0) ^
|
||||||
|
(!string.IsNullOrWhiteSpace(x.Base64Pdf)))
|
||||||
|
.WithMessage("Either PdfBytes or Base64Pdf must be provided, but not both");
|
||||||
|
|
||||||
|
// Rule 2: Base64 format validation (if provided)
|
||||||
|
RuleFor(x => x.Base64Pdf)
|
||||||
|
.Must(BeValidBase64)
|
||||||
|
.When(x => !string.IsNullOrWhiteSpace(x.Base64Pdf))
|
||||||
|
.WithMessage("Base64Pdf must be a valid Base64 string");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates if a string is a valid Base64 format
|
||||||
|
/// </summary>
|
||||||
|
private bool BeValidBase64(string? base64)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(base64))
|
||||||
|
return true; // Skip validation if null/empty (handled by Rule 1)
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Convert.FromBase64String(base64);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
namespace DocumentOperator.Application.Common.DTOs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO for attachment check result returned to API layer
|
||||||
|
/// </summary>
|
||||||
|
public record AttachmentCheckResult
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates whether the PDF contains any attachments
|
||||||
|
/// </summary>
|
||||||
|
public bool HasAttachments { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Total number of attachments in the PDF
|
||||||
|
/// </summary>
|
||||||
|
public int AttachmentCount { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// List of attachment metadata (file details)
|
||||||
|
/// </summary>
|
||||||
|
public List<AttachmentDto> Attachments { get; init; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DTO for individual attachment metadata
|
||||||
|
/// </summary>
|
||||||
|
public record AttachmentDto
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Attachment file name (e.g., "invoice.xml", "document.pdf")
|
||||||
|
/// </summary>
|
||||||
|
public string FileName { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// MIME type of the attachment (e.g., "text/xml", "application/pdf")
|
||||||
|
/// </summary>
|
||||||
|
public string MimeType { get; init; } = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attachment file size in bytes
|
||||||
|
/// </summary>
|
||||||
|
public long Size { get; init; }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user