using DocumentService.Application.Common.DTOs; using DocumentService.Application.ExtractZugferd; using DocumentService.Application.HasZugferd.Queries; using DocumentService.Client.Models.Requests; using DocumentService.Domain.Common.Exceptions; using MediatR; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; namespace DocumentService.API.Controllers; /// /// Controller for ZUGFeRD operations (detection, extraction) /// [ApiController] [Route("api/pdf/zugferd")] [Produces("application/json")] public class ZugferdController(IMediator mediator) : ControllerBase { /// /// Checks if a PDF contains ZUGFeRD XML attachment. /// Supports multipart/form-data file upload. /// /// The PDF file to check for ZUGFeRD /// Cancellation token /// ZUGFeRD check result with metadata /// PDF successfully checked - returns ZUGFeRD status /// Invalid input (file missing, not a PDF, or corrupted) /// Internal server error during PDF processing [HttpPost("has-zugferd")] [Consumes("multipart/form-data")] [ProducesResponseType(typeof(ZugferdCheckResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)] public async Task HasZugferdFromFile( IFormFile file, CancellationToken cancellationToken) { // Use IFormFile stream directly (no intermediate byte[] conversion) using var pdfStream = file.OpenReadStream(); // Send query to MediatR (ValidationBehavior runs automatically) var query = new HasZugferdQuery { PdfStream = pdfStream }; var result = await mediator.Send(query, cancellationToken); return Ok(result); } /// /// Checks if a PDF contains ZUGFeRD XML attachment. /// Supports Base64-encoded PDF via JSON payload. /// /// Request containing Base64-encoded PDF /// Cancellation token /// ZUGFeRD check result with metadata /// PDF successfully checked - returns ZUGFeRD status /// Invalid input (Base64 format error, not a PDF, or corrupted) /// Internal server error during PDF processing [HttpPost("has-zugferd")] [Consumes("application/json")] [ProducesResponseType(typeof(ZugferdCheckResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)] public async Task HasZugferdFromBase64( [FromBody] HasZugferdRequest request, CancellationToken cancellationToken) { // Convert Base64 to stream (wrap in try-catch to throw BadRequestException) byte[] pdfBytes; try { pdfBytes = Convert.FromBase64String(request.Base64Pdf); } catch (FormatException ex) { throw new BadRequestException("Invalid Base64 format: " + ex.Message); } using var pdfStream = new MemoryStream(pdfBytes); // Send query to MediatR (ValidationBehavior runs automatically) var query = new HasZugferdQuery { PdfStream = pdfStream }; var result = await mediator.Send(query, cancellationToken); return Ok(result); } /// /// Extracts ZUGFeRD XML from a PDF document. /// Supports multipart/form-data file upload. /// /// The PDF file to extract ZUGFeRD from /// if true, 'file' (returns XML file directly); otherwise output format: 'json' (default, returns metadata + XML content) /// Cancellation token /// ZUGFeRD XML content and metadata (JSON) or XML file (application/xml) /// ZUGFeRD XML extracted successfully /// Invalid input (file missing, not a PDF, or corrupted) /// PDF contains no ZUGFeRD XML /// Internal server error during PDF processing [HttpPost("extract")] [Consumes("multipart/form-data")] [ProducesResponseType(typeof(ZugferdExtractionResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)] public async Task ExtractZugferdFromFile( IFormFile file, [FromQuery] bool asFile = true, CancellationToken cancellationToken = default) { // Use IFormFile stream directly (no intermediate byte[] conversion) using var pdfStream = file.OpenReadStream(); // Send command to MediatR var command = new ExtractZugferdCommand { PdfStream = pdfStream }; var result = await mediator.Send(command, cancellationToken); // Return as file or JSON based on format parameter if (asFile) { byte[] xmlBytes = System.Text.Encoding.UTF8.GetBytes(result.XmlContent); return File(xmlBytes, "application/xml", result.FileName); } return Ok(result); } /// /// Extracts ZUGFeRD XML from a PDF document. /// Supports Base64-encoded PDF via JSON payload. /// /// Request containing Base64-encoded PDF /// Output format: 'json' (default, returns metadata + XML content) or 'file' (returns XML file directly) /// Cancellation token /// ZUGFeRD XML content and metadata (JSON) or XML file (application/xml) /// ZUGFeRD XML extracted successfully /// Invalid input (Base64 format error, not a PDF, or corrupted) /// PDF contains no ZUGFeRD XML /// Internal server error during PDF processing [HttpPost("extract")] [Consumes("application/json")] [ProducesResponseType(typeof(ZugferdExtractionResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)] [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)] public async Task ExtractZugferdFromBase64( [FromBody] ExtractZugferdRequest request, [FromQuery] string format = "json", CancellationToken cancellationToken = default) { // Convert Base64 to stream (wrap in try-catch to throw BadRequestException) byte[] pdfBytes; try { pdfBytes = Convert.FromBase64String(request.Base64Pdf); } catch (FormatException ex) { throw new BadRequestException("Invalid Base64 format: " + ex.Message); } using var pdfStream = new MemoryStream(pdfBytes); // Send command to MediatR var command = new ExtractZugferdCommand { PdfStream = pdfStream }; var result = await mediator.Send(command, cancellationToken); // Return as file or JSON based on format parameter if (format.Equals("file", StringComparison.OrdinalIgnoreCase)) { byte[] xmlBytes = System.Text.Encoding.UTF8.GetBytes(result.XmlContent); return File(xmlBytes, "application/xml", result.FileName); } return Ok(result); } }