Add Blazor signature endpoints to AnnotationController
Added two endpoints for Blazor-based signature flows: - GET /elements: Returns signature placeholders for the receiver to render overlays in the Blazor UI. - POST /blazor: Accepts Blazor-friendly signature payloads, builds annotation DTOs, and triggers the signing/notification pipeline. Signs out the user after signing. Both endpoints are protected by the Receiver authorization policy and expose only necessary data for the client UI.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[Authorize(Policy = AuthPolicy.Receiver)]
|
||||
[HttpGet("elements")]
|
||||
public async Task<IActionResult> 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<Application.Common.Dto.SignatureDto>();
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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 <see cref="PsPdfKitAnnotation"/> wrapping a
|
||||
/// list of <see cref="AnnotationCreateDto"/> 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.
|
||||
/// </summary>
|
||||
[Authorize(Policy = AuthPolicy.Receiver)]
|
||||
[HttpPost("blazor")]
|
||||
public async Task<IActionResult> 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<AnnotationCreateDto>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user