Refactor notification and email handling

Refactored `DocumentReceivedNotification` to use new properties `Origin`, `EnvelopeUuid`, and `ReceiverSignature` for constructing `EnvelopeURL`. Updated `LinkToDocument` and `LinkToDocumentText` to derive from `EnvelopeURL`.

Simplified `CreateEnvelopeCommandHandler` with record-like syntax, removing private fields and directly injecting dependencies.

Renamed and refactored `QueueInvitationEmailsAsync` to `SendInvitationEmailsAsync`, using `EnvNtfService.Notify` for email notifications, centralizing logic and improving clarity.

Improved error handling in email sending with logging. Updated variable names for clarity and cleaned up `using` directives.
This commit is contained in:
2026-10-29 15:54:07 +01:00
parent f0b4c8d00c
commit 5e00b6e9ec
2 changed files with 88 additions and 91 deletions

View File

@@ -1,4 +1,5 @@
using EnvelopeGenerator.Domain.Constants;
using EnvelopeGenerator.Application.Common.Extensions;
using EnvelopeGenerator.Domain.Constants;
namespace EnvelopeGenerator.Application.Common.Interfaces.Services.Notification;
@@ -45,12 +46,36 @@ public record DocumentReceivedNotification : EnvelopeNotificationBase
/// <summary>
///
/// </summary>
public required string LinkToDocument { get; init; }
public required Uri Origin { private get; init; }
/// <summary>
///
/// </summary>
public string LinkToDocumentText => LinkToDocument[..Math.Min(LinkToDocument.Length, 40)];
public required string EnvelopeUuid { private get; init; }
/// <summary>
///
/// </summary>
public required string ReceiverSignature { private get; init; }
private Uri EnvelopeURL => new(Origin, $"Envelope/{(EnvelopeUuid, ReceiverSignature).ToEnvelopeKey()}");
/// <summary>
///
/// </summary>
public string LinkToDocument => EnvelopeURL.AbsoluteUri;
/// <summary>
///
/// </summary>
public string LinkToDocumentText
{
get
{
var urlText = EnvelopeURL.ToString();
return $"{urlText[..Math.Min(urlText.Length, 40)]}..";
}
}
/// <summary>
///

View File

@@ -1,71 +1,66 @@
using AutoMapper;
using MediatR;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using DigitalData.Core.Abstraction.Application.Repository;
using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
using DigitalData.Core.Exceptions;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Application.Common.Dto;
using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
using DigitalData.UserManager.Domain.Entities;
using EnvelopeGenerator.Application.Common.Configurations;
using EnvelopeGenerator.Application.Common.Dto;
using EnvelopeGenerator.Application.Common.Extensions;
using EnvelopeGenerator.Application.Common.Interfaces;
using EnvelopeGenerator.Application.Common.Interfaces.Services;
using EnvelopeGenerator.Application.Common.Interfaces.Services.Notification;
using EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
using EnvelopeGenerator.Application.Histories.Commands;
using EnvelopeGenerator.Domain.Constants;
using EnvelopeGenerator.Domain;
using EnvelopeGenerator.Domain.Constants;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Domain.Interfaces;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace EnvelopeGenerator.Application.Envelopes.Commands;
/// <summary>
///
/// </summary>
public class CreateEnvelopeCommandHandler : IRequestHandler<CreateEnvelopeCommand, EnvelopeDto?>
/// <param name="Provider"></param>
/// <param name="Mapper"></param>
/// <param name="EnvNtfService"></param>
/// <param name="ReqCntxt"></param>
/// <param name="Logger"></param>
public class CreateEnvelopeCommandHandler(IServiceProvider Provider, IMapper Mapper, IEnvelopeNotificationService EnvNtfService, IRequestContext ReqCntxt, ILogger<CreateEnvelopeCommandHandler> Logger) : IRequestHandler<CreateEnvelopeCommand, EnvelopeDto?>
{
private readonly IServiceProvider _provider;
private IEnvelopeExecutor Executor => Provider.GetRequiredService<IEnvelopeExecutor>();
private IEnvelopeExecutor Executor => _provider.GetRequiredService<IEnvelopeExecutor>();
private IRepository<Envelope> Repository => Provider.GetRequiredService<IRepository<Envelope>>();
private IRepository<Envelope> Repository => _provider.GetRequiredService<IRepository<Envelope>>();
private IRepository<Document> DocumentRepository => Provider.GetRequiredService<IRepository<Document>>();
private IRepository<Document> DocumentRepository => _provider.GetRequiredService<IRepository<Document>>();
private IRepository<EnvelopeReceiver> EnvelopeReceiverRepository => Provider.GetRequiredService<IRepository<EnvelopeReceiver>>();
private IRepository<EnvelopeReceiver> EnvelopeReceiverRepository => _provider.GetRequiredService<IRepository<EnvelopeReceiver>>();
private IRepository<EnvelopeType> EnvelopeTypeRepository => Provider.GetRequiredService<IRepository<EnvelopeType>>();
private IRepository<EnvelopeType> EnvelopeTypeRepository => _provider.GetRequiredService<IRepository<EnvelopeType>>();
private IRepository<Receiver> ReceiverRepository => Provider.GetRequiredService<IRepository<Receiver>>();
private IRepository<Receiver> ReceiverRepository => _provider.GetRequiredService<IRepository<Receiver>>();
private IRepository<DocReceiverElement> DocReceiverElementRepository => Provider.GetRequiredService<IRepository<DocReceiverElement>>();
private IRepository<DocReceiverElement> DocReceiverElementRepository => _provider.GetRequiredService<IRepository<DocReceiverElement>>();
private IRepository<EmailTemplate> EmailTemplateRepository => Provider.GetRequiredService<IRepository<EmailTemplate>>();
private IRepository<EmailTemplate> EmailTemplateRepository => _provider.GetRequiredService<IRepository<EmailTemplate>>();
private IRepository<EmailOut> EmailOutRepository => Provider.GetRequiredService<IRepository<EmailOut>>();
private IRepository<EmailOut> EmailOutRepository => _provider.GetRequiredService<IRepository<EmailOut>>();
private MailParams MailParams => Provider.GetRequiredService<IOptions<MailParams>>().Value;
private MailParams MailParams => _provider.GetRequiredService<IOptions<MailParams>>().Value;
private DispatcherParams DispatcherParams => Provider.GetRequiredService<IOptions<DispatcherParams>>().Value;
private DispatcherParams DispatcherParams => _provider.GetRequiredService<IOptions<DispatcherParams>>().Value;
private SignatureFieldParams SignatureFieldParams => _provider.GetRequiredService<IOptionsMonitor<SignatureFieldParams>>().CurrentValue;
private SignatureFieldParams SignatureFieldParams => Provider.GetRequiredService<IOptionsMonitor<SignatureFieldParams>>().CurrentValue;
[Obsolete("Use IConfigService instead.")]
private IConfigService ConfigService => _provider.GetRequiredService<IConfigService>();
private IConfigService ConfigService => Provider.GetRequiredService<IConfigService>();
private IMediator Mediator => _provider.GetRequiredService<IMediator>();
private readonly IMapper _mapper;
/// <summary>
///
/// </summary>
/// <param name="provider"></param>
/// <param name="mapper"></param>
public CreateEnvelopeCommandHandler(IServiceProvider provider, IMapper mapper)
{
_provider = provider;
_mapper = mapper;
}
private IMediator Mediator => Provider.GetRequiredService<IMediator>();
/// <summary>
///
@@ -233,7 +228,7 @@ public class CreateEnvelopeCommandHandler : IRequestHandler<CreateEnvelopeComman
.ThenInclude(er => er.Receiver)
.FirstAsync(e => e.Id == envelope.Id, cancel);
return _mapper.Map<EnvelopeDto>(envelope);
return Mapper.Map<EnvelopeDto>(envelope);
}
private async Task<EnvelopeSettings> ResolveEnvelopeSettingsAsync(CreateEnvelopeCommand request, Envelope? existing, CancellationToken cancel)
@@ -532,15 +527,15 @@ public class CreateEnvelopeCommandHandler : IRequestHandler<CreateEnvelopeComman
if (!hasDocument)
throw new BadRequestException("Cannot send envelope without a document.");
var links = await EnvelopeReceiverRepository.Query
var envRcvs = await EnvelopeReceiverRepository.Query
.Include(er => er.Receiver)
.Where(er => er.EnvelopeId == envelope.Id)
.ToListAsync(cancel);
if (links.Count == 0)
if (envRcvs.Count == 0)
throw new BadRequestException("Cannot send envelope without receivers.");
var receiverIds = links.Select(l => l.ReceiverId).Distinct().ToList();
var receiverIds = envRcvs.Select(l => l.ReceiverId).Distinct().ToList();
var documentIds = await DocumentRepository.Query
.Where(d => d.EnvelopeId == envelope.Id)
.Select(d => d.Id)
@@ -553,7 +548,7 @@ public class CreateEnvelopeCommandHandler : IRequestHandler<CreateEnvelopeComman
.Select(g => new { ReceiverId = g.Key, Count = g.Count() })
.ToDictionaryAsync(x => x.ReceiverId, x => x.Count, cancel);
var receiversWithoutFields = links
var receiversWithoutFields = envRcvs
.Where(link => !receiverFieldCounts.TryGetValue(link.ReceiverId, out var count) || count <= 0)
.Select(link => link.Name)
.ToList();
@@ -577,62 +572,39 @@ public class CreateEnvelopeCommandHandler : IRequestHandler<CreateEnvelopeComman
Status = EnvelopeStatus.EnvelopeQueued
}, cancel);
await QueueInvitationEmailsAsync(envelope, links, userReference, cancel);
await SendInvitationEmailsAsync(envelope, envRcvs, cancel);
envelope.Status = EnvelopeStatus.EnvelopeQueued;
}
private async Task QueueInvitationEmailsAsync(
private async Task SendInvitationEmailsAsync(
Envelope envelope,
IReadOnlyCollection<EnvelopeReceiver> links,
string senderReference,
IReadOnlyCollection<EnvelopeReceiver> envRcvs,
CancellationToken cancel)
{
var template = await EmailTemplateRepository.Query
.SingleOrDefaultAsync(x => x.Name == EmailTemplateType.DocumentReceived.ToString(), cancel)
?? throw new InvalidOperationException($"Email template not found: {EmailTemplateType.DocumentReceived}");
var signatureHost = await ConfigService.ReadDefaultSignatureHost();
var senderName = senderReference;
foreach (var link in links)
foreach (var er in envRcvs)
{
var receiver = link.Receiver;
if (receiver is null || string.IsNullOrWhiteSpace(receiver.EmailAddress) || string.IsNullOrWhiteSpace(receiver.Signature))
continue;
var envelopeKey = (envelope.Uuid, receiver.Signature).ToEnvelopeKey();
var documentLink = $"{signatureHost.TrimEnd('/')}/envelope/login/{Uri.EscapeDataString(envelopeKey)}";
var placeholders = new Dictionary<string, string>(MailParams.Placeholders)
var advancedSignature = envelope.CertificationType == CertificationType.AdvancedElectronicSignature;
var origin = new Uri(ReqCntxt.Origin ?? throw new InvalidOperationException($"The request origin is missing in {nameof(CreateEnvelopeCommandHandler)}."));
try
{
["[NAME_RECEIVER]"] = link.Name ?? string.Empty,
["[NAME_SENDER]"] = senderName ?? string.Empty,
["[DOCUMENT_TITLE]"] = envelope.Title ?? string.Empty,
["[MESSAGE]"] = envelope.Message ?? string.Empty,
["[LINK_TO_DOCUMENT]"] = documentLink,
["[LINK_TO_DOCUMENT_TEXT]"] = documentLink[..Math.Min(40, documentLink.Length)] + ".."
};
var subject = ReplacePlaceHolders(template.Subject, placeholders);
var body = ReplacePlaceHolders(template.Body, placeholders);
var emailOut = new EmailOut
await EnvNtfService.Notify(er.Receiver.EmailAddress, new DocumentReceivedNotification
{
AdvancedSignature = advancedSignature,
DocumentTitle = envelope.Title ?? string.Empty,
NameReceiver = er.Name ?? string.Empty,
EnvelopeUuid = envelope.Uuid,
IsReadAndConfirm = envelope.IsReadAndConfirm(),
Message = envelope.Message ?? string.Empty,
NameSender = $"{envelope.User.Prename} {envelope.User.Name}",
Origin = origin,
ReceiverSignature = er.Receiver.Signature,
}, cancel);
}
catch(Exception ex)
{
EmailAddress = receiver.EmailAddress,
EmailSubj = subject,
EmailBody = body,
AddedWhen = DateTime.Now,
AddedWho = DispatcherParams.AddedWho,
SendingProfile = DispatcherParams.SendingProfile,
ReminderTypeId = DispatcherParams.ReminderTypeId,
EmailAttmt1 = DispatcherParams.EmailAttmt1,
WfId = (int)EnvelopeStatus.MessageInvitationSent,
ReferenceString = receiver.EmailAddress,
ReferenceId = link.ReceiverId
};
await EmailOutRepository.CreateAsync(emailOut, cancel);
Logger.LogError(ex, "Failed to send invitation email for receiver {emailAddress} in envelope {envelopeId}.", er.Receiver.EmailAddress, envelope.Id);
}
}
}