Consolidated DTOs under `DocumentService.Application.Common.DTOs.Requests` to centralize and align them with the application layer. Introduced `PdfBase64RequestBase` to encapsulate shared properties, reducing redundancy across request DTOs. Updated controllers, clients, and tests to use the new DTO structure. Removed redundant DTOs and cleaned up unused namespaces and comments. Improved documentation and ensured consistent naming conventions across the codebase.
180 lines
8.2 KiB
C#
180 lines
8.2 KiB
C#
using DocumentService.Application.Common.DTOs;
|
|
using DocumentService.Application.ExtractZugferd;
|
|
using DocumentService.Application.HasZugferd.Queries;
|
|
using DocumentService.Application.Common.DTOs.Requests;
|
|
using DocumentService.Domain.Common.Exceptions;
|
|
using MediatR;
|
|
using Microsoft.AspNetCore.Http;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
namespace DocumentService.API.Controllers;
|
|
|
|
/// <summary>
|
|
/// Controller for ZUGFeRD operations (detection, extraction)
|
|
/// </summary>
|
|
[ApiController]
|
|
[Route("api/pdf/zugferd")]
|
|
[Produces("application/json")]
|
|
public class ZugferdController(IMediator mediator) : ControllerBase
|
|
{
|
|
/// <summary>
|
|
/// Checks if a PDF contains ZUGFeRD XML attachment.
|
|
/// Supports multipart/form-data file upload.
|
|
/// </summary>
|
|
/// <param name="file">The PDF file to check for ZUGFeRD</param>
|
|
/// <param name="cancellationToken">Cancellation token</param>
|
|
/// <returns>ZUGFeRD check result with metadata</returns>
|
|
/// <response code="200">PDF successfully checked - returns ZUGFeRD status</response>
|
|
/// <response code="400">Invalid input (file missing, not a PDF, or corrupted)</response>
|
|
/// <response code="500">Internal server error during PDF processing</response>
|
|
[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<IActionResult> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if a PDF contains ZUGFeRD XML attachment.
|
|
/// Supports Base64-encoded PDF via JSON payload.
|
|
/// </summary>
|
|
/// <param name="request">Request containing Base64-encoded PDF</param>
|
|
/// <param name="cancellationToken">Cancellation token</param>
|
|
/// <returns>ZUGFeRD check result with metadata</returns>
|
|
/// <response code="200">PDF successfully checked - returns ZUGFeRD status</response>
|
|
/// <response code="400">Invalid input (Base64 format error, not a PDF, or corrupted)</response>
|
|
/// <response code="500">Internal server error during PDF processing</response>
|
|
[HttpPost("has-zugferd")]
|
|
[Consumes("application/json")]
|
|
[ProducesResponseType(typeof(ZugferdCheckResult), StatusCodes.Status200OK)]
|
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
|
|
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
|
|
public async Task<IActionResult> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts ZUGFeRD XML from a PDF document.
|
|
/// Supports multipart/form-data file upload.
|
|
/// </summary>
|
|
/// <param name="file">The PDF file to extract ZUGFeRD from</param>
|
|
/// <param name="asFile">if true, 'file' (returns XML file directly); otherwise output format: 'json' (default, returns metadata + XML content)</param>
|
|
/// <param name="cancellationToken">Cancellation token</param>
|
|
/// <returns>ZUGFeRD XML content and metadata (JSON) or XML file (application/xml)</returns>
|
|
/// <response code="200">ZUGFeRD XML extracted successfully</response>
|
|
/// <response code="400">Invalid input (file missing, not a PDF, or corrupted)</response>
|
|
/// <response code="404">PDF contains no ZUGFeRD XML</response>
|
|
/// <response code="500">Internal server error during PDF processing</response>
|
|
[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<IActionResult> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts ZUGFeRD XML from a PDF document.
|
|
/// Supports Base64-encoded PDF via JSON payload.
|
|
/// </summary>
|
|
/// <param name="request">Request containing Base64-encoded PDF</param>
|
|
/// <param name="format">Output format: 'json' (default, returns metadata + XML content) or 'file' (returns XML file directly)</param>
|
|
/// <param name="cancellationToken">Cancellation token</param>
|
|
/// <returns>ZUGFeRD XML content and metadata (JSON) or XML file (application/xml)</returns>
|
|
/// <response code="200">ZUGFeRD XML extracted successfully</response>
|
|
/// <response code="400">Invalid input (Base64 format error, not a PDF, or corrupted)</response>
|
|
/// <response code="404">PDF contains no ZUGFeRD XML</response>
|
|
/// <response code="500">Internal server error during PDF processing</response>
|
|
[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<IActionResult> 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);
|
|
}
|
|
}
|