- expose POST /api/Envelope for sender-authenticated create/update/send operations - replace client draft-specific call with EnvelopeService.UpsertAsync(CreateEnvelopeCommand) - update sender editor save/send flow to persist draft first and send by envelope id - allow editor hydration without document by making sender document fetch nullable
147 lines
6.3 KiB
C#
147 lines
6.3 KiB
C#
using System.Security.Claims;
|
|
using System.Text.Json;
|
|
using DigitalData.Core.Exceptions;
|
|
using EnvelopeGenerator.Application.Common.Dto;
|
|
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
|
using EnvelopeGenerator.Application.Documents.Queries;
|
|
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
|
using EnvelopeGenerator.Application.Receivers.Queries;
|
|
using EnvelopeGenerator.Server.Client.Models;
|
|
using EnvelopeGenerator.Server.Client.Models.Constants;
|
|
using EnvelopeGenerator.Server.Extensions;
|
|
using EnvelopeGenerator.Server.Options;
|
|
using MediatR;
|
|
using Microsoft.Extensions.Caching.Distributed;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Microsoft.Extensions.Options;
|
|
using ApplicationEnvelopeReceiverDto = EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver.EnvelopeReceiverDto;
|
|
|
|
namespace EnvelopeGenerator.Server.Services;
|
|
|
|
/// <summary>
|
|
/// Loads receiver page data directly from MediatR and distributed cache.
|
|
/// </summary>
|
|
public class EnvelopeReceiverPageDataService(
|
|
IMediator mediator,
|
|
IDistributedCache cache,
|
|
IOptions<CacheOptions> cacheOptions,
|
|
IMemoryCache memoryCache)
|
|
{
|
|
private const string SignatureCacheKeyPrefix = "envelope-generator.receiver-ui.signature:";
|
|
|
|
/// <summary>
|
|
/// Loads the PDF document bytes for the authenticated receiver.
|
|
/// </summary>
|
|
public async Task<byte[]?> GetDocumentOfReceiverAsync(ClaimsPrincipal user, CancellationToken cancellationToken = default)
|
|
{
|
|
var document = await mediator.Send(new ReadDocumentQuery(EnvelopeId: user.EnvelopeId()), cancellationToken);
|
|
return document.ByteData;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the PDF document bytes for the authenticated receiver.
|
|
/// </summary>
|
|
public async Task<byte[]?> GetDocumentOfSenderAsync(ClaimsPrincipal user, int envelopeId, CancellationToken cancellationToken = default)
|
|
{
|
|
var document = await mediator.Send(new ReadDocumentQuery(EnvelopeId: envelopeId, UserId: user.SenderId()), cancellationToken);
|
|
return (document.ByteData is byte[] data && data.Length > 0)
|
|
? data
|
|
: throw new NotFoundException($"Document for envelope {envelopeId} not found.");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the full document DTO (including receiver elements) for the authenticated sender.
|
|
/// </summary>
|
|
public async Task<DocumentDto?> GetDocumentDetailsOfSenderAsync(ClaimsPrincipal user, int envelopeId, CancellationToken cancellationToken = default)
|
|
{
|
|
return await mediator.Send(new ReadDocumentQuery(EnvelopeId: envelopeId, UserId: user.SenderId()), cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the current receiver's signature placeholders.
|
|
/// </summary>
|
|
public async Task<IReadOnlyList<SignatureDto>> GetSignaturesAsync(ClaimsPrincipal user, CancellationToken cancellationToken = default)
|
|
{
|
|
var receiverId = user.ReceiverId();
|
|
var document = await mediator.Send(new ReadDocumentQuery(EnvelopeId: user.EnvelopeId()), cancellationToken);
|
|
|
|
if (document.Elements is not IEnumerable<DocReceiverElementDto> elements)
|
|
return [];
|
|
|
|
var signatures = elements
|
|
.Where(element => element.ReceiverId == receiverId)
|
|
.Select(MapSignature)
|
|
.ToList();
|
|
|
|
return signatures.Convert(UnitOfLength.Point);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the envelope receiver data for the specified envelope key.
|
|
/// </summary>
|
|
public async Task<ApplicationEnvelopeReceiverDto?> GetEnvelopeReceiverAsync(string envelopeKey, CancellationToken cancellationToken = default)
|
|
{
|
|
var result = await mediator.Send(new ReadEnvelopeReceiverQuery { Key = envelopeKey }, cancellationToken);
|
|
return result.SingleOrDefault();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Loads the cached signature for the authenticated receiver.
|
|
/// </summary>
|
|
public async Task<SignatureCaptureDto?> GetCachedSignatureAsync(ClaimsPrincipal user, CancellationToken cancellationToken = default)
|
|
{
|
|
var json = await cache.GetStringAsync(GetSignatureCacheKey(user), cancellationToken);
|
|
return json is null ? null : JsonSerializer.Deserialize<SignatureCaptureDto>(json);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves the cached signature for the authenticated receiver.
|
|
/// </summary>
|
|
public async Task SaveCachedSignatureAsync(ClaimsPrincipal user, SignatureCaptureDto signature, CancellationToken cancellationToken = default)
|
|
{
|
|
var json = JsonSerializer.Serialize(signature);
|
|
var options = cacheOptions.Value.SignatureCacheExpiration.HasValue
|
|
? new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = cacheOptions.Value.SignatureCacheExpiration.Value }
|
|
: new DistributedCacheEntryOptions();
|
|
|
|
await cache.SetStringAsync(GetSignatureCacheKey(user), json, options, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deletes the cached signature for the authenticated receiver.
|
|
/// </summary>
|
|
public Task DeleteCachedSignatureAsync(ClaimsPrincipal user, CancellationToken cancellationToken = default)
|
|
=> cache.RemoveAsync(GetSignatureCacheKey(user), cancellationToken);
|
|
|
|
private static string GetSignatureCacheKey(ClaimsPrincipal user)
|
|
=> $"{SignatureCacheKeyPrefix}{user.ReceiverSignature()}";
|
|
|
|
private static SignatureDto MapSignature(DocReceiverElementDto element) => new()
|
|
{
|
|
Id = element.Id,
|
|
X = element.X,
|
|
Y = element.Y,
|
|
Page = element.Page,
|
|
SenderAppType = (EnvelopeGenerator.Server.Client.Models.Constants.SenderAppType)element.SenderAppType
|
|
};
|
|
|
|
private static readonly string ReceiverEmailSearchCacheKey = Guid.NewGuid().ToString();
|
|
|
|
public async Task<IEnumerable<string>> SearchReceiverEMailsAsync(string emailSearchTerm, CancellationToken cancellationToken = default)
|
|
{
|
|
|
|
return await memoryCache.GetOrCreateAsync(ReceiverEmailSearchCacheKey + emailSearchTerm, async entry =>
|
|
{
|
|
var query = new ReadReceiverQuery { EmailAddressSearch = emailSearchTerm };
|
|
var receivers = await mediator.Send(query, cancellationToken);
|
|
|
|
if(receivers.Any())
|
|
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(30);
|
|
else
|
|
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(10);
|
|
|
|
return receivers.Select(r => r.EmailAddress);
|
|
}) ?? [];
|
|
}
|
|
}
|