feat: Add specialized client implementations for PDF operations

This commit is contained in:
2026-08-11 10:29:24 +02:00
parent 14250f0b4b
commit 058cb9327d
7 changed files with 711 additions and 0 deletions

View File

@@ -0,0 +1,124 @@
using DocumentService.Client.Interfaces;
using DocumentService.Client.Models.Requests;
using Microsoft.Extensions.Logging;
using System.IO.Compression;
using System.Net.Http;
namespace DocumentService.Client.Clients;
/// <summary>
/// Implementation of PDF attachment client.
/// </summary>
public class PdfAttachmentClient(HttpClient httpClient, ILogger<PdfAttachmentClient> logger)
: BaseDocumentClient(httpClient, logger), IPdfAttachmentClient
{
/// <inheritdoc />
public async Task<AttachmentCheckResult> CheckAttachmentsAsync(Stream pdfStream, CancellationToken cancellationToken = default)
{
Logger.LogDebug("Checking PDF attachments from stream (multipart)");
var result = await SendMultipartAsync<AttachmentCheckResult>(
"/api/pdf/attachments/check",
pdfStream,
"document.pdf",
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
/// <inheritdoc />
public async Task<AttachmentCheckResult> CheckAttachmentsAsync(byte[] pdfBytes, CancellationToken cancellationToken = default)
{
Logger.LogDebug("Checking PDF attachments from byte array (Base64 JSON)");
var request = new CheckPdfAttachmentsRequest { Base64Pdf = ToBase64(pdfBytes) };
var result = await SendJsonAsync<CheckPdfAttachmentsRequest, AttachmentCheckResult>(
"/api/pdf/attachments/check",
request,
cancellationToken);
return result ?? throw new InvalidOperationException("API returned null response");
}
/// <inheritdoc />
public async Task<Dictionary<string, Stream>> ExtractAttachmentsAsync(Stream pdfStream, CancellationToken cancellationToken = default)
{
Logger.LogDebug("Extracting PDF attachments from stream (multipart)");
var zipStream = await SendMultipartForStreamAsync(
"/api/pdf/attachments/extract",
pdfStream,
"document.pdf",
cancellationToken);
return UnzipToStreams(zipStream);
}
/// <inheritdoc />
public async Task<Dictionary<string, Stream>> ExtractAttachmentsAsync(byte[] pdfBytes, CancellationToken cancellationToken = default)
{
Logger.LogDebug("Extracting PDF attachments from byte array (Base64 JSON)");
var request = new ExtractPdfAttachmentsRequest { Base64Pdf = ToBase64(pdfBytes) };
var zipStream = await SendJsonForStreamAsync(
"/api/pdf/attachments/extract",
request,
cancellationToken);
return UnzipToStreams(zipStream);
}
/// <inheritdoc />
[Obsolete("API endpoint not implemented yet")]
public Task<Stream> AddAttachmentsAsync(Stream pdfStream, List<AttachmentRequestDto> attachments, CancellationToken cancellationToken = default)
{
Logger.LogWarning("AddAttachments endpoint is not implemented yet in API");
throw new NotImplementedException("API endpoint /api/pdf/attachments/add is not implemented yet");
}
/// <inheritdoc />
[Obsolete("API endpoint not implemented yet")]
public Task<Stream> AddAttachmentsAsync(byte[] pdfBytes, List<AttachmentRequestDto> attachments, CancellationToken cancellationToken = default)
{
Logger.LogWarning("AddAttachments endpoint is not implemented yet in API");
throw new NotImplementedException("API endpoint /api/pdf/attachments/add is not implemented yet");
}
// ?????????????????????????????????????????????????????????????????????????
// Private helpers
// ?????????????????????????????????????????????????????????????????????????
/// <summary>
/// Reads a ZIP stream and returns a dictionary mapping each entry's full name
/// to an in-memory <see cref="MemoryStream"/> containing the decompressed bytes.
/// The caller owns the returned streams and is responsible for disposing them.
/// </summary>
private static Dictionary<string, Stream> UnzipToStreams(Stream zipStream)
{
var result = new Dictionary<string, Stream>(StringComparer.OrdinalIgnoreCase);
using var archive = new ZipArchive(zipStream, ZipArchiveMode.Read, leaveOpen: false);
foreach (var entry in archive.Entries)
{
// Skip directory entries (name ends with '/')
if (string.IsNullOrEmpty(entry.Name))
continue;
var ms = new MemoryStream((int)entry.Length);
using (var entryStream = entry.Open())
{
entryStream.CopyTo(ms);
}
ms.Position = 0;
result[entry.FullName] = ms;
}
return result;
}
}