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.
This commit is contained in:
@@ -18,6 +18,11 @@ public class DocReceiverElementDto : IDocReceiverElement, ISignatureInkPayload
|
||||
/// </summary>
|
||||
public int DocumentId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public virtual DocumentDto? Document { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the identifier of the receiver.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,6 +14,15 @@ namespace EnvelopeGenerator.Application.DocReceiverElements.Queries;
|
||||
/// </summary>
|
||||
public record ReadDocReceiverElementQuery : EnvelopeReceiverQueryBase, IRequest<IEnumerable<DocReceiverElementDto>>
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int? Id { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public bool IncludeDocument { get; set; } = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -47,7 +56,10 @@ public class ReadDocReceiverElementQueryHandler : IRequestHandler<ReadDocReceive
|
||||
{
|
||||
var q = _repository.Query;
|
||||
|
||||
if(request.Envelope.Id is int envelopeId)
|
||||
if (request.Id is not null)
|
||||
q = q.Where(e => 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<ReadDocReceive
|
||||
if (request.Receiver.Signature is string signature)
|
||||
q = q.Where(e => e.Receiver.Signature == signature);
|
||||
|
||||
if( request.IncludeDocument)
|
||||
q = q.Include(e => e.Document);
|
||||
|
||||
var elements = await q.ToListAsync(cancellationToken);
|
||||
|
||||
return _mapper.Map<IEnumerable<DocReceiverElementDto>>(elements);
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public record SignPdfByElementCommand : IRequest<Stream>
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public DocReceiverElementDto? Element { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public int? ElementId { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="signService"></param>
|
||||
public class SignPdfCommandHandler(ISender sender, IPdfDigitalSignatureService signService, IPdfPageImageRenderer imgRenderer) : IRequestHandler<SignPdfByElementCommand, Stream>
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="request"></param>
|
||||
/// <param name="cancellationToken"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<Stream> 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using FluentValidation;
|
||||
|
||||
namespace EnvelopeGenerator.Application.PdfSigning;
|
||||
|
||||
public class SignPdfByElementCommandValidator : AbstractValidator<SignPdfByElementCommand>
|
||||
{
|
||||
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.");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user