diff --git a/EnvelopeGenerator.API/Controllers/AnnotationController.cs b/EnvelopeGenerator.API/Controllers/AnnotationController.cs index be40fb0d..c1961687 100644 --- a/EnvelopeGenerator.API/Controllers/AnnotationController.cs +++ b/EnvelopeGenerator.API/Controllers/AnnotationController.cs @@ -1,5 +1,6 @@ using DigitalData.Core.Abstraction.Application.DTO; using DigitalData.Core.Exceptions; +using EnvelopeGenerator.Application.Common.Dto; using EnvelopeGenerator.Application.Common.Extensions; using EnvelopeGenerator.Application.Common.Interfaces.Services; using EnvelopeGenerator.Application.Common.Notifications.DocSigned; @@ -7,11 +8,13 @@ using EnvelopeGenerator.Application.EnvelopeReceivers.Queries; using EnvelopeGenerator.Application.Histories.Queries; using EnvelopeGenerator.Domain.Constants; using EnvelopeGenerator.API.Extensions; +using EnvelopeGenerator.API.Models; using MediatR; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using System.Dynamic; namespace EnvelopeGenerator.API.Controllers; @@ -115,4 +118,127 @@ public class AnnotationController : ControllerBase _logger.LogNotice(histRes.Notices); return StatusCode(500, histRes.Messages); } + + /// + /// Returns the signature placeholders (id, page, x, y, width, height) + /// the authenticated receiver must sign on the current envelope. + /// Used by the Blazor receiver UI to render a custom signature overlay + /// on top of the DevExpress PDF viewer. + /// + [Authorize(Policy = AuthPolicy.Receiver)] + [HttpGet("elements")] + public async Task GetElements(CancellationToken cancel) + { + var signature = User.GetReceiverSignatureOfReceiver(); + var uuid = User.GetEnvelopeUuidOfReceiver(); + + var envelopeReceiver = await _mediator.ReadEnvelopeReceiverAsync(uuid, signature, cancel) + ?? throw new NotFoundException("Envelope receiver is not found."); + + var elements = envelopeReceiver.Envelope?.Documents?.FirstOrDefault()?.Elements + ?? Enumerable.Empty(); + + // Only expose what the overlay needs (no internal columns). + var payload = elements + .Where(e => e.ReceiverId == envelopeReceiver.ReceiverId) + .Select(e => new + { + id = e.Id, + page = e.Page, + x = e.X, + y = e.Y, + width = e.Width, + height = e.Height, + required = e.Required, + tooltip = e.Tooltip, + }); + + return Ok(payload); + } + + /// + /// Signs the document for the authenticated receiver using a Blazor / + /// DevExpress friendly payload (one PNG image per placeholder, plus + /// optional position / city per placeholder). + /// + /// Internally produces a wrapping a + /// list of entries so that the + /// existing notification pipeline (mail / status / annotation persistence) + /// is fully reused. The Instant JSON is left blank because the Blazor + /// pipeline does not depend on PSPDFKit-side rendering. + /// + [Authorize(Policy = AuthPolicy.Receiver)] + [HttpPost("blazor")] + public async Task SignBlazor([FromBody] BlazorSignaturePayload payload, CancellationToken cancel) + { + if (payload is null || payload.Signatures.Count == 0) + return BadRequest(); + + var signature = User.GetReceiverSignatureOfReceiver(); + var uuid = User.GetEnvelopeUuidOfReceiver(); + + var envelopeReceiver = await _mediator.ReadEnvelopeReceiverAsync(uuid, signature, cancel) + ?? throw new NotFoundException("Envelope receiver is not found."); + + if (await _mediator.IsSignedAsync(uuid, signature, cancel)) + return Problem(statusCode: StatusCodes.Status409Conflict); + + if (await _mediator.AnyHistoryAsync(uuid, new[] { EnvelopeStatus.EnvelopeRejected, EnvelopeStatus.DocumentRejected }, cancel)) + return Problem(statusCode: StatusCodes.Status423Locked); + + // Build a structured AnnotationCreateDto list out of the lightweight + // payload. One DTO per (element, kind) tuple, mirroring how the legacy + // PSPDFKit pipeline produced multiple form fields per placeholder. + var structured = new List(); + foreach (var entry in payload.Signatures) + { + structured.Add(new AnnotationCreateDto + { + ElementId = entry.ElementId, + Name = $"{entry.ElementId}#signature", + Type = "signature", + Value = entry.SignatureDataUrl, + }); + if (!string.IsNullOrWhiteSpace(entry.Position)) + { + structured.Add(new AnnotationCreateDto + { + ElementId = entry.ElementId, + Name = $"{entry.ElementId}#position", + Type = "position", + Value = entry.Position!, + }); + } + if (!string.IsNullOrWhiteSpace(entry.City)) + { + structured.Add(new AnnotationCreateDto + { + ElementId = entry.ElementId, + Name = $"{entry.ElementId}#city", + Type = "city", + Value = entry.City!, + }); + } + structured.Add(new AnnotationCreateDto + { + ElementId = entry.ElementId, + Name = $"{entry.ElementId}#date", + Type = "date", + Value = entry.SignedAt.ToString("o"), + }); + } + + // No PSPDFKit Instant JSON for the Blazor flow — pass an empty object. + var psPdfKitAnnotation = new PsPdfKitAnnotation(new ExpandoObject(), structured); + + var docSignedNotification = await _mediator + .ReadEnvelopeReceiverAsync(uuid, signature, cancel) + .ToDocSignedNotification(psPdfKitAnnotation) + ?? throw new NotFoundException("Envelope receiver is not found."); + + await _mediator.PublishSafely(docSignedNotification, cancel); + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + + return Ok(); + } }