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; /// /// Loads receiver page data directly from MediatR and distributed cache. /// public class EnvelopeReceiverPageDataService( IMediator mediator, IDistributedCache cache, IOptions cacheOptions, IMemoryCache memoryCache) { private const string SignatureCacheKeyPrefix = "envelope-generator.receiver-ui.signature:"; /// /// Loads the PDF document bytes for the authenticated receiver. /// public async Task GetDocumentOfReceiverAsync(ClaimsPrincipal user, CancellationToken cancellationToken = default) { var document = await mediator.Send(new ReadDocumentQuery(EnvelopeId: user.EnvelopeId()), cancellationToken); return document.ByteData; } /// /// Loads the PDF document bytes for the authenticated receiver. /// public async Task 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."); } /// /// Loads the full document DTO (including receiver elements) for the authenticated sender. /// public async Task GetDocumentDetailsOfSenderAsync(ClaimsPrincipal user, int envelopeId, CancellationToken cancellationToken = default) { return await mediator.Send(new ReadDocumentQuery(EnvelopeId: envelopeId, UserId: user.SenderId()), cancellationToken); } /// /// Loads the current receiver's signature placeholders. /// public async Task> 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 elements) return []; var signatures = elements .Where(element => element.ReceiverId == receiverId) .Select(MapSignature) .ToList(); return signatures.Convert(UnitOfLength.Point); } /// /// Loads the envelope receiver data for the specified envelope key. /// public async Task GetEnvelopeReceiverAsync(string envelopeKey, CancellationToken cancellationToken = default) { var result = await mediator.Send(new ReadEnvelopeReceiverQuery { Key = envelopeKey }, cancellationToken); return result.SingleOrDefault(); } /// /// Loads the cached signature for the authenticated receiver. /// public async Task GetCachedSignatureAsync(ClaimsPrincipal user, CancellationToken cancellationToken = default) { var json = await cache.GetStringAsync(GetSignatureCacheKey(user), cancellationToken); return json is null ? null : JsonSerializer.Deserialize(json); } /// /// Saves the cached signature for the authenticated receiver. /// 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); } /// /// Deletes the cached signature for the authenticated receiver. /// 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> 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); }) ?? []; } }