using DigitalData.Core.Exceptions; using EnvelopeGenerator.Application.Common.Configurations; using EnvelopeGenerator.Application.Common.Dto; using EnvelopeGenerator.Application.Common.Enums; using EnvelopeGenerator.Application.Common.Interfaces.Doc; using EnvelopeGenerator.Application.Common.Interfaces.Services; using EnvelopeGenerator.Application.DocReceiverElements.Queries; using MediatR; using Microsoft.Extensions.Options; namespace EnvelopeGenerator.Application.PdfSigning; /// /// /// public record SignPdfByElementCommand : IRequest { /// /// /// public DocReceiverElementDto? Element { get; set; } /// /// /// public int? ElementId { get; init; } /// /// Custom document /// public Stream? Document { get; set; } /// /// /// public string? Phone { get; set; } /// /// /// public string? Reason { get; set; } /// /// /// public PdfSignatureCertificationLevel CertificationLevel { get; init; } = PdfSignatureCertificationLevel.NoChangesAllowed; } /// /// /// /// /// /// /// public class SignPdfCommandHandler(ISender sender, IPdfDigitalSignatureService signService, IPdfPageImageRenderer imgRenderer, IOptionsMonitor options) : IRequestHandler { /// /// /// /// /// /// public async Task Handle(SignPdfByElementCommand request, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); // ensure element if (request.Element is null) { var elements = await sender.Send(new ReadDocReceiverElementQuery() { Id = request.ElementId!, IncludeDocument = request.Document is null }, cancellationToken); request.Element = elements.FirstOrDefault() ?? throw new NotFoundException($"Element with ID {request.ElementId} not found."); ; } // ensure document if(request.Document is null) { if (request.Element?.Document?.ByteData is not { Length: > 0 }) throw new NotFoundException("Document data is missing or empty."); request.Document = new MemoryStream(request.Element.Document.ByteData); } ValidateAfterDataIsComplete(request); // create signature image from element var img = imgRenderer.ComposeSignaturePreviewPng(request.Element); // sign pdf var outputPdf = new MemoryStream(); await signService.SignPdfAsync( inputPdf: request.Document, outputPdf: outputPdf, options: new PdfSignOptionsDto { Name = request.Element.FullName!, Location = request.Element.Position!, Phone = request.Phone, Reason = request.Reason ?? options.CurrentValue.DefaultReason, Email = request.Element.Receiver!.EmailAddress, ApplicationName = options.CurrentValue.ApplicationName, DocumentElementId = request.Element.Id, CertificationLevel = request.CertificationLevel, Appearance = new PdfSignatureAppearanceDto( PageNumber: request.Element.Page, Left: request.Element.Left, Bottom: request.Element.Y, Width: request.Element.Width, Height: request.Element.Height, CoordinateUnit: PdfSignatureCoordinateUnit.Inch, CoordinateOrigin: PdfSignatureCoordinateOrigin.TopLeft) }, image: img, cancellationToken: cancellationToken); return outputPdf; } private static void ValidateAfterDataIsComplete(SignPdfByElementCommand request) { if(request.Element!.FullName is null) throw new BadRequestException($"Document is not signed. FullName is required. Element ID: {request.Element.Id}"); else if(request.Element.Position is null) throw new BadRequestException($"Document is not signed. Position is required. Element ID: {request.Element.Id}"); } }