From 67d0b07926c18bea7f5d1087a8257d1f90f5813b Mon Sep 17 00:00:00 2001 From: TekH Date: Tue, 20 Oct 2026 10:55:41 +0200 Subject: [PATCH] Add support for signing PDFs with DocReceiverElement Extended `DocReceiverElementDto` to include an optional `Document` reference. Enhanced `ReadDocReceiverElementQuery` with `Id` and `IncludeDocument` properties for filtering and document inclusion. Updated `ReadDocReceiverElementQueryHandler` to handle new query properties, enabling filtering by `Id` and optionally including associated documents. Introduced `SignPdfByElementCommand` and its handler to support signing PDFs using either a provided `DocReceiverElementDto` or an `ElementId`. Added validation via `SignPdfByElementCommandValidator` to ensure proper usage. Refactored and added necessary `using` directives for new dependencies. --- .../Common/Dto/DocReceiverElementDto.cs | 5 ++ .../Queries/ReadDocReceiverElementQuery.cs | 17 +++- .../PdfSigning/SignPdfByElementCommand.cs | 85 +++++++++++++++++++ .../SignPdfByElementCommandValidator.cs | 18 ++++ 4 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 EnvelopeGenerator.Application/PdfSigning/SignPdfByElementCommand.cs create mode 100644 EnvelopeGenerator.Application/PdfSigning/SignPdfByElementCommandValidator.cs diff --git a/EnvelopeGenerator.Application/Common/Dto/DocReceiverElementDto.cs b/EnvelopeGenerator.Application/Common/Dto/DocReceiverElementDto.cs index eedeb896..be38a95c 100644 --- a/EnvelopeGenerator.Application/Common/Dto/DocReceiverElementDto.cs +++ b/EnvelopeGenerator.Application/Common/Dto/DocReceiverElementDto.cs @@ -18,6 +18,11 @@ public class DocReceiverElementDto : IDocReceiverElement, ISignatureInkPayload /// public int DocumentId { get; set; } + /// + /// + /// + public virtual DocumentDto? Document { get; set; } + /// /// Gets or sets the identifier of the receiver. /// diff --git a/EnvelopeGenerator.Application/DocReceiverElements/Queries/ReadDocReceiverElementQuery.cs b/EnvelopeGenerator.Application/DocReceiverElements/Queries/ReadDocReceiverElementQuery.cs index 3139051b..95dcac71 100644 --- a/EnvelopeGenerator.Application/DocReceiverElements/Queries/ReadDocReceiverElementQuery.cs +++ b/EnvelopeGenerator.Application/DocReceiverElements/Queries/ReadDocReceiverElementQuery.cs @@ -14,6 +14,15 @@ namespace EnvelopeGenerator.Application.DocReceiverElements.Queries; /// public record ReadDocReceiverElementQuery : EnvelopeReceiverQueryBase, IRequest> { + /// + /// + /// + public int? Id { get; set; } = null; + + /// + /// + /// + public bool IncludeDocument { get; set; } = false; } /// @@ -47,7 +56,10 @@ public class ReadDocReceiverElementQueryHandler : IRequestHandler e.Id == request.Id); + + if (request.Envelope.Id is int envelopeId) q = q.Where(e => e.Document.EnvelopeId == envelopeId); if (request.Envelope.Uuid is string envelopeUuid) @@ -59,6 +71,9 @@ public class ReadDocReceiverElementQueryHandler : IRequestHandler e.Receiver.Signature == signature); + if( request.IncludeDocument) + q = q.Include(e => e.Document); + var elements = await q.ToListAsync(cancellationToken); return _mapper.Map>(elements); diff --git a/EnvelopeGenerator.Application/PdfSigning/SignPdfByElementCommand.cs b/EnvelopeGenerator.Application/PdfSigning/SignPdfByElementCommand.cs new file mode 100644 index 00000000..988973a4 --- /dev/null +++ b/EnvelopeGenerator.Application/PdfSigning/SignPdfByElementCommand.cs @@ -0,0 +1,85 @@ +using DigitalData.Core.Exceptions; +using EnvelopeGenerator.Application.Common.Dto; +using EnvelopeGenerator.Application.Common.Interfaces.Doc; +using EnvelopeGenerator.Application.Common.Interfaces.Services; +using EnvelopeGenerator.Application.DocReceiverElements.Queries; +using MediatR; + +namespace EnvelopeGenerator.Application.PdfSigning; + +/// +/// +/// +public record SignPdfByElementCommand : IRequest +{ + /// + /// + /// + public DocReceiverElementDto? Element { get; set; } + + /// + /// + /// + public int? ElementId { get; init; } +} + +/// +/// +/// +/// +public class SignPdfCommandHandler(ISender sender, IPdfDigitalSignatureService signService, IPdfPageImageRenderer imgRenderer) : IRequestHandler +{ + /// + /// + /// + /// + /// + /// + public async Task Handle(SignPdfByElementCommand request, CancellationToken cancellationToken) + { + // ensure element + if(request.Element is null) + { + var elements = await sender.Send(new ReadDocReceiverElementQuery() + { + Id = request.ElementId!, + IncludeDocument= true + }, cancellationToken); + + if (!elements.Any()) + throw new NotFoundException($"Element with ID {request.ElementId} not found."); + + request.Element = elements.FirstOrDefault(); + } + + // ensure document + var doc = request.Element!.Document; + + if(doc is null || doc.ByteData is null || doc.ByteData.Length == 0) + throw new NotFoundException("Document data is missing or empty."); + + // create signature image from element + var img = imgRenderer.ComposeSignaturePreviewPng(request.Element); + + // sign pdf + var inputPdf = new MemoryStream(doc.ByteData); + var outputPdf = new MemoryStream(); + + await signService.SignPdfAsync( + inputPdf: inputPdf, + outputPdf: outputPdf, + options: new PdfSignOptionsDto + { + Appearance = new PdfSignatureAppearanceDto( + PageNumber: request.Element.Page, + Left: request.Element.Left, + Bottom: request.Element.Y, + Width: request.Element.Width, + Height: request.Element.Height) + }, + image: img, + cancellationToken: cancellationToken); + + return outputPdf; + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Application/PdfSigning/SignPdfByElementCommandValidator.cs b/EnvelopeGenerator.Application/PdfSigning/SignPdfByElementCommandValidator.cs new file mode 100644 index 00000000..35a9019e --- /dev/null +++ b/EnvelopeGenerator.Application/PdfSigning/SignPdfByElementCommandValidator.cs @@ -0,0 +1,18 @@ +using FluentValidation; + +namespace EnvelopeGenerator.Application.PdfSigning; + +public class SignPdfByElementCommandValidator : AbstractValidator +{ + public SignPdfByElementCommandValidator() + { + RuleFor(x => x) + .Must(x => (x.Element is not null) ^ x.ElementId.HasValue) + .WithMessage("Exactly one of Element or ElementId must be provided."); + + RuleFor(x => x.ElementId) + .GreaterThan(0) + .When(x => x.ElementId.HasValue) + .WithMessage("ElementId must be greater than 0."); + } +}