Files
EnvelopeGenerator/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Services/EnvelopeReceiverPageDataService.cs
TekH ca24a96084 Add receiver management to EnvelopeSenderEditorPage
Introduced a new "Receivers" panel in `EnvelopeSenderEditorPage` to manage receivers. Added a popup for adding receivers with validation, email suggestions, and caching for performance. Updated the layout to display receiver details and signature fields.

Injected `EnvelopeReceiverPageDataService` for receiver-related operations. Added a `ReceiverDraft` model and implemented methods for managing receivers. Enhanced CSS for the new UI elements and ensured responsiveness. Minor refactoring and cleanup included.
2026-07-01 16:32:28 +02:00

127 lines
5.3 KiB
C#

using System.Security.Claims;
using System.Text.Json;
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[]?> GetDocumentAsync(ClaimsPrincipal user, CancellationToken cancellationToken = default)
{
var document = await mediator.Send(new ReadDocumentQuery(EnvelopeId: user.EnvelopeId()), cancellationToken);
return document.ByteData;
}
/// <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);
}) ?? [];
}
}