diff --git a/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/DocumentReceivedNotification.cs b/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/DocumentReceivedNotification.cs new file mode 100644 index 00000000..7ca460ab --- /dev/null +++ b/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/DocumentReceivedNotification.cs @@ -0,0 +1,73 @@ +using EnvelopeGenerator.Domain.Constants; + +namespace EnvelopeGenerator.Application.Common.Interfaces.Services.Notification; + +/// +/// +/// +public record DocumentReceivedNotification : EnvelopeNotificationBase +{ + /// + /// + /// + public required string DocumentTitle { get; init; } + + /// + /// + /// + public required string NameReceiver { get; init; } + + /// + /// + /// + public required string NameSender { get; init; } + + /// + /// [SIGNATURE_TYPE] + /// + public required bool IsReadAndConfirm { private get; init; } + + /// + /// + /// + public string SignatureType => IsReadAndConfirm ? "Signieren" : "Lesen und bestätigen"; + + /// + /// + /// + public required bool AdvancedSignature { private get; init; } + + /// + /// + /// + public string DocumentProcess => AdvancedSignature ? " und elektronisch unterschreiben" : string.Empty; + + /// + /// + /// + public required string LinkToDocument { get; init; } + + /// + /// + /// + public string LinkToDocumentText => LinkToDocument[..Math.Min(LinkToDocument.Length, 40)]; + + /// + /// + /// + public required string Message { get; init; } + + /// + /// + /// + public string NamePortal { get; set; } = "signFLOW Portal"; + + /// + /// + /// + /// + public override EmailTemplateType GetTemplateType() + { + return EmailTemplateType.DocumentReceived; + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/DocumentSignedNotification.cs b/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/DocumentSignedNotification.cs index 01fb3573..90784253 100644 --- a/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/DocumentSignedNotification.cs +++ b/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/DocumentSignedNotification.cs @@ -1,9 +1,11 @@ -namespace EnvelopeGenerator.Application.Common.Interfaces.Services.Notification; +using EnvelopeGenerator.Domain.Constants; + +namespace EnvelopeGenerator.Application.Common.Interfaces.Services.Notification; /// /// /// -public record DocumentSignedNotification +public record DocumentSignedNotification : EnvelopeNotificationBase { /// /// @@ -19,11 +21,11 @@ public record DocumentSignedNotification /// /// public string NamePortal { get; init; } = "signFLOW Portal"; - + /// /// /// - public required byte[] Attachment { get; init; } + public override EmailTemplateType GetTemplateType() => EmailTemplateType.DocumentSigned; } /* diff --git a/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/EnvelopeNotificationBase.cs b/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/EnvelopeNotificationBase.cs new file mode 100644 index 00000000..1d22fd01 --- /dev/null +++ b/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/EnvelopeNotificationBase.cs @@ -0,0 +1,19 @@ +using EnvelopeGenerator.Domain.Constants; + +namespace EnvelopeGenerator.Application.Common.Interfaces.Services.Notification; + +/// +/// +/// +public abstract record EnvelopeNotificationBase +{ + /// + /// + /// + public abstract EmailTemplateType GetTemplateType(); + + /// + /// + /// + public List<(string FileName, byte[] Content)>? Attachments { get; init; } +} diff --git a/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/IEnvelopeNotificationService.cs b/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/IEnvelopeNotificationService.cs index 2fb618f7..7ad10284 100644 --- a/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/IEnvelopeNotificationService.cs +++ b/EnvelopeGenerator.Application/Common/Interfaces/Services/Notification/IEnvelopeNotificationService.cs @@ -8,9 +8,11 @@ public interface IEnvelopeNotificationService /// /// /// + /// /// - /// + /// /// /// - public Task Notify(string email, DocumentSignedNotification documentSigned, CancellationToken cancel = default); + public Task Notify(string email, TEnvelopeNotificationBase notification, CancellationToken cancel = default) + where TEnvelopeNotificationBase : EnvelopeNotificationBase; } diff --git a/EnvelopeGenerator.Application/PdfSigning/FinalizeEnvelopeCommand.cs b/EnvelopeGenerator.Application/PdfSigning/FinalizeEnvelopeCommand.cs index de3f469b..2a7aff81 100644 --- a/EnvelopeGenerator.Application/PdfSigning/FinalizeEnvelopeCommand.cs +++ b/EnvelopeGenerator.Application/PdfSigning/FinalizeEnvelopeCommand.cs @@ -98,7 +98,9 @@ public class FinalizeEnvelopeCommandHandler(IRepository Repo, ISender { DocumentTitle = envelope.Title, NameReceiver = er.Name, - Attachment = doc + Attachments = [ + ($"unterzeichnetes-dokument.pdf", doc) + ] }, cancel); } diff --git a/EnvelopeGenerator.Infrastructure.Notification/EnvelopeNotificationExtensions.cs b/EnvelopeGenerator.Infrastructure.Notification/EnvelopeNotificationExtensions.cs new file mode 100644 index 00000000..f080363c --- /dev/null +++ b/EnvelopeGenerator.Infrastructure.Notification/EnvelopeNotificationExtensions.cs @@ -0,0 +1,15 @@ +using DigitalData.MessagingService.Application.Common.Dto; + +namespace EnvelopeGenerator.Infrastructure.Notification; + +public static class EnvelopeNotificationExtensions +{ + public static EmailAttachmentDto ToEmailAttachmentDto(this (string FileName, byte[] Content) attachment) => new() + { + FileName = attachment.FileName, + Content = attachment.Content + }; + + public static IEnumerable ToEmailAttachmentDto(this IEnumerable<(string FileName, byte[] Content)> attachments) + => attachments.Select(ToEmailAttachmentDto); +} \ No newline at end of file diff --git a/EnvelopeGenerator.Infrastructure.Notification/EnvelopeNotificationService.cs b/EnvelopeGenerator.Infrastructure.Notification/EnvelopeNotificationService.cs index 79e87020..5bf02122 100644 --- a/EnvelopeGenerator.Infrastructure.Notification/EnvelopeNotificationService.cs +++ b/EnvelopeGenerator.Infrastructure.Notification/EnvelopeNotificationService.cs @@ -1,11 +1,11 @@ using DigitalData.Core.Abstraction.Application.Repository; -using DigitalData.Core.Exceptions; using DigitalData.MessagingService.Application.Common.Dto; using DigitalData.MessagingService.Client; using DigitalData.MessagingService.Domain.Entities; using EnvelopeGenerator.Application.Common.Interfaces.Services.Notification; +using EnvelopeGenerator.Domain.Constants; using EnvelopeGenerator.Domain.Entities; -using MediatR; +using EnvelopeGenerator.Domain.Exceptions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; @@ -13,32 +13,23 @@ namespace EnvelopeGenerator.Infrastructure.Notification; public class EnvelopeNotificationService(IOptions EmailAccountOptions, IRepository TempRepo) : IEnvelopeNotificationService { - public async Task Notify(string email, DocumentSignedNotification documentSigned, CancellationToken cancel = default) + private async Task GetTemplateAsync(EmailTemplateType templateType, CancellationToken cancel = default) { - var template = await TempRepo.Where(t => t.Name == "DocumentCompleted").FirstOrDefaultAsync(cancel) - ?? throw new NotFoundException("DocumentCompleted email template not found"); - var subjectTemplate = Replace(template.Subject, documentSigned); - var bodyTemplate = Replace(template.Body, documentSigned); + return await TempRepo.Where(t => t.Name == templateType.ToString()).FirstOrDefaultAsync(cancel) + ?? throw new DataIntegrityException($"{templateType} email template not found"); + } + public async Task Notify(string email, TEnvelopeNotificationBase notification, CancellationToken cancel = default) + where TEnvelopeNotificationBase : EnvelopeNotificationBase + { + var template = await GetTemplateAsync(notification.GetTemplateType(), cancel); await EmailSender.Send(new EmailContext() { - Body = bodyTemplate, - Subject = subjectTemplate, + Body = template.Body.ReplacePlaceholders(notification), + Subject = template.Subject.ReplacePlaceholders(notification), Recipients = [email], Sender = EmailAccountOptions.Value, - Attachments = [new EmailAttachmentDto() - { - FileName = "unterzeichnetes-dokument.pdf", - Content = documentSigned.Attachment - }] + Attachments = notification.Attachments?.ToEmailAttachmentDto() ?? [] }); } - - private static string Replace(string template, DocumentSignedNotification documentSigned) - { - return template - .Replace("[DOCUMENT_TITLE]", documentSigned.DocumentTitle) - .Replace("[NAME_PORTAL]", documentSigned.NamePortal) - .Replace("[NAME_RECEIVER]", documentSigned.NameReceiver); - } -} +} \ No newline at end of file diff --git a/EnvelopeGenerator.Infrastructure.Notification/ReplacePlaceholderExtensions.cs b/EnvelopeGenerator.Infrastructure.Notification/ReplacePlaceholderExtensions.cs new file mode 100644 index 00000000..3ffccb4c --- /dev/null +++ b/EnvelopeGenerator.Infrastructure.Notification/ReplacePlaceholderExtensions.cs @@ -0,0 +1,47 @@ +using System.Collections.Concurrent; +using System.Reflection; +using System.Text.RegularExpressions; + +namespace EnvelopeGenerator.Infrastructure.Notification; + +public static class ReplacePlaceholderExtensions +{ + private sealed record PlaceholderProperty(string Placeholder, PropertyInfo Property); + + private static readonly ConcurrentDictionary PropertyCache = new(); + + public static string ReplacePlaceholders(this string template, object data) + { + var properties = PropertyCache.GetOrAdd(data.GetType(), CreatePlaceholderProperties); + + foreach (var item in properties) + { + var value = item.Property.GetValue(data)?.ToString() ?? string.Empty; + template = template.Replace( + item.Placeholder, + value, + StringComparison.Ordinal); + } + + return template; + } + + private static PlaceholderProperty[] CreatePlaceholderProperties( + Type type) + { + return [.. type + .GetProperties(BindingFlags.Public | BindingFlags.Instance) + .Where(p => p.CanRead && p.GetIndexParameters().Length == 0) + .Select(p => new PlaceholderProperty( + $"[{ToCapitalSnakeCase(p.Name)}]", + p))]; + } + + private static string ToCapitalSnakeCase(string name) + { + return Regex.Replace( + Regex.Replace(name, @"([A-Z]+)([A-Z][a-z])", "$1_$2"), + @"([a-z0-9])([A-Z])", + "$1_$2").ToUpperInvariant(); + } +} \ No newline at end of file