From 364b755f95ce319d1939caf6d0d064855f0bbe07 Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 20 Jul 2026 11:56:03 +0200 Subject: [PATCH] feat: Add CheckPdfAttachments feature - Application layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../Queries/CheckPdfAttachmentsQuery.cs | 48 +++++++++++++++++++ .../CheckPdfAttachmentsQueryValidator.cs | 44 +++++++++++++++++ .../Common/DTOs/AttachmentCheckResult.cs | 43 +++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 DocumentOperator.Application/CheckPdfAttachments/Queries/CheckPdfAttachmentsQuery.cs create mode 100644 DocumentOperator.Application/CheckPdfAttachments/Queries/CheckPdfAttachmentsQueryValidator.cs create mode 100644 DocumentOperator.Application/Common/DTOs/AttachmentCheckResult.cs diff --git a/DocumentOperator.Application/CheckPdfAttachments/Queries/CheckPdfAttachmentsQuery.cs b/DocumentOperator.Application/CheckPdfAttachments/Queries/CheckPdfAttachmentsQuery.cs new file mode 100644 index 0000000..f8e5480 --- /dev/null +++ b/DocumentOperator.Application/CheckPdfAttachments/Queries/CheckPdfAttachmentsQuery.cs @@ -0,0 +1,48 @@ +using AutoMapper; +using DocumentOperator.Application.Common.DTOs; +using DocumentOperator.Application.Common.Interfaces; +using MediatR; + +namespace DocumentOperator.Application.CheckPdfAttachments.Queries; + +/// +/// Query for checking PDF attachments (supports both byte array and Base64 input) +/// +public record CheckPdfAttachmentsQuery : IRequest +{ + /// + /// PDF as byte array (direct upload via multipart/form-data) + /// + public byte[]? PdfBytes { get; init; } + + /// + /// PDF as Base64 string (for API clients using application/json) + /// + public string? Base64Pdf { get; init; } +} + +/// +/// Handler for CheckPdfAttachmentsQuery +/// Orchestrates PDF attachment checking using IPdfProcessor and AutoMapper +/// +public class CheckPdfAttachmentsQueryHandler(IPdfProcessor PdfProcessor, IMapper Mapper) + : IRequestHandler +{ + /// + /// Checks PDF attachments and returns detailed metadata + /// + public async Task 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(attachmentInfo); + } +} diff --git a/DocumentOperator.Application/CheckPdfAttachments/Queries/CheckPdfAttachmentsQueryValidator.cs b/DocumentOperator.Application/CheckPdfAttachments/Queries/CheckPdfAttachmentsQueryValidator.cs new file mode 100644 index 0000000..78aa602 --- /dev/null +++ b/DocumentOperator.Application/CheckPdfAttachments/Queries/CheckPdfAttachmentsQueryValidator.cs @@ -0,0 +1,44 @@ +using FluentValidation; + +namespace DocumentOperator.Application.CheckPdfAttachments.Queries; + +/// +/// Validator for CheckPdfAttachmentsQuery +/// Ensures exactly one input type (PdfBytes OR Base64Pdf) is provided +/// +public class CheckPdfAttachmentsQueryValidator : AbstractValidator +{ + 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"); + } + + /// + /// Validates if a string is a valid Base64 format + /// + 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; + } + } +} diff --git a/DocumentOperator.Application/Common/DTOs/AttachmentCheckResult.cs b/DocumentOperator.Application/Common/DTOs/AttachmentCheckResult.cs new file mode 100644 index 0000000..8a9b57e --- /dev/null +++ b/DocumentOperator.Application/Common/DTOs/AttachmentCheckResult.cs @@ -0,0 +1,43 @@ +namespace DocumentOperator.Application.Common.DTOs; + +/// +/// DTO for attachment check result returned to API layer +/// +public record AttachmentCheckResult +{ + /// + /// Indicates whether the PDF contains any attachments + /// + public bool HasAttachments { get; init; } + + /// + /// Total number of attachments in the PDF + /// + public int AttachmentCount { get; init; } + + /// + /// List of attachment metadata (file details) + /// + public List Attachments { get; init; } = new(); +} + +/// +/// DTO for individual attachment metadata +/// +public record AttachmentDto +{ + /// + /// Attachment file name (e.g., "invoice.xml", "document.pdf") + /// + public string FileName { get; init; } = string.Empty; + + /// + /// MIME type of the attachment (e.g., "text/xml", "application/pdf") + /// + public string MimeType { get; init; } = string.Empty; + + /// + /// Attachment file size in bytes + /// + public long Size { get; init; } +}