()
+ {
+ new(){
+ Id = 1,
+ Name = "DocumentReceived",
+ Body = "Guten Tag [NAME_RECEIVER],
\r\n
\r\n[NAME_SENDER] hat Ihnen ein Dokument zum [SIGNATURE_TYPE] gesendet.
\r\n
\r\nÜber den folgenden Link können Sie das Dokument einsehen und elektronisch unterschreiben: [LINK_TO_DOCUMENT_TEXT]
\r\n
\r\n[MESSAGE]
\r\n
\r\nMit freundlichen Grüßen
\r\n
\r\n[NAME_PORTAL]",
+ Subject = "Dokument erhalten: '[DOCUMENT_TITLE]'"
+ },
+ new(){
+ Id = 2,
+ Name = "DocumentDeleted",
+ Body = "Guten Tag [NAME_RECEIVER],
\r\n
\r\n[NAME_SENDER] hat den Umschlag '[DOCUMENT_TITLE]' gelöscht/zurückgezogen.
\rBegründung:
[REASON]
\r\n
\r\nMit freundlichen Grüßen
\r\n
\r\n[NAME_PORTAL]",
+ Subject = "Umschlag zurückgezogen: '[DOCUMENT_TITLE]'"
+ },
+ new(){
+ Id = 3,
+ Name = "DocumentSigned",
+ Body = "Guten Tag [NAME_RECEIVER],
\r\n
\r\nhiermit bestätigen wir Ihnen die erfolgreiche Signatur für den Vorgang '[DOCUMENT_TITLE]'.
\r\nWenn alle Vertragspartner unterzeichnet haben, erhalten Sie ebenfalls per email ein unterschriebenes Exemplar mit dem Signierungszertifikat!\r\n
\r\nMit freundlichen Grüßen
\r\n
\r\n[NAME_PORTAL]",
+ Subject = "Dokument unterschrieben: '[DOCUMENT_TITLE]'"
+ },
+ new(){
+ Id = 4,
+ Name = "DocumentCompleted",
+ Body = "Guten Tag [NAME_RECEIVER],
\r\n
\r\nDer Signaturvorgang '[DOCUMENT_TITLE]' wurde erfolgreich abgeschlossen.
\r\n
\r\nSie erhalten das Dokument mit einem detaillierten Ergebnisbericht als Anhang zu dieser Email.
\r\n
\r\nMit freundlichen Grüßen
\r\n
\r\n[NAME_PORTAL]",
+ Subject = "Umschlag abgeschlossen: '[DOCUMENT_TITLE]'"
+ },
+ new(){
+ Id = 5,
+ Name = "DocumentAccessCodeReceived",
+ Body = "Guten Tag [NAME_RECEIVER],
\r\n
\r\n[NAME_SENDER] hat Ihnen ein Dokument zum [SIGNATURE_TYPE] gesendet.
\r\n
\r\nVerwenden Sie den folgenden Zugriffscode, um das Dokument einzusehen:
\r\n
\r\n[DOCUMENT_ACCESS_CODE]
\r\n
\r\nMit freundlichen Grüßen
\r\n
\r\n[NAME_PORTAL]",
+ Subject = "Zugriffscode für Dokument erhalten: '[DOCUMENT_TITLE]'"
+ },
+ new(){
+ Id = 6,
+ Name = "DocumentRejected_ADM",
+ Body = "Guten Tag [NAME_SENDER],
[NAME_RECEIVER] hat den Umschlag '[DOCUMENT_TITLE]' mit folgendem Grund abgelehnt:
\r\n[REASON] \r\n
Der Umschlag wurde auf den Status Rejected gesetzt.
\r\nMit freundlichen Grüßen
\r\n
\r\n[NAME_PORTAL]",
+ Subject = "'[DOCUMENT_TITLE]' - Unterzeichnungsvorgang zurückgezogen"
+ },
+ new(){
+ Id = 9,
+ Name = "DocumentRejected_REC",
+ Body = "Guten Tag [NAME_RECEIVER],\r\n
Hiermit bestätigen wir Ihnen die Ablehnung des Unterzeichnungsvorganges '[DOCUMENT_TITLE]'!
Der Vertragsinhaber [NAME_SENDER] wurde über die Ablehnung informiert.
\r\nMit freundlichen Grüßen
\r\n
\r\n[NAME_PORTAL]",
+ Subject = "'[DOCUMENT_TITLE]' - Bestätigung Ablehnung"
+ },
+ new(){
+ Id = 10,
+ Name = "DocumentRejected_REC_2",
+ Body = "Guten Tag [NAME_RECEIVER],\r\n
Der Unterzeichnungsvorganges '[DOCUMENT_TITLE]' wurde durch einen anderen Vertragspartner abgelehnt! Ihre notwendige Unterzeichnung wurde verworfen.
Der Vertragsinhaber [NAME_SENDER] wird sich bei Bedarf mit Ihnen in Verbindung setzen.
\r\nMit freundlichen Grüßen
\r\n
\r\n[NAME_PORTAL]",
+ Subject = "'[DOCUMENT_TITLE]' - Unterzeichnungsvorgang abgelehnt."
+ },
+ new(){
+ Id = 11,
+ Name = "DocumentShared",
+ Body = "Guten Tag,
[NAME_RECEIVER] hat Ihnen ein Dokument zum Ansehen gesendet.
Über den folgenden Link können Sie das Dokument einsehen: [LINK_TO_DOCUMENT_TEXT]
Mit freundlichen Grüßen
[NAME_PORTAL]",
+ Subject = "Dokument geteilt: '[DOCUMENT_TITLE]'"
+ },
+ new(){
+ Id = 12,
+ Name = "TotpSecret",
+ Body = "Guten Tag,
Sie können auf Ihren Zwei-Faktor-Authentifizierungscode zugreifen, indem Sie den unten stehenden QR-Code mit einer beliebigen Authentifizierungs-App auf Ihrem Telefon scannen (Google Authenticator, Microsoft Authenticator usw.). Dieser Code ist bis zum [TFA_EXPIRATION] gültig.

\r\n
Mit freundlichen Grüßen
[NAME_PORTAL]",
+ Subject = "2-Faktor-Verifizierung QR-Code"
+ }
+ };
+
+}
diff --git a/EnvelopeGenerator.Application/EmailTemplates/Commands/Update/UpdateEmailTemplateCommand.cs b/EnvelopeGenerator.Application/EmailTemplates/Commands/Update/UpdateEmailTemplateCommand.cs
index 63d2a7fd..1f3d7525 100644
--- a/EnvelopeGenerator.Application/EmailTemplates/Commands/Update/UpdateEmailTemplateCommand.cs
+++ b/EnvelopeGenerator.Application/EmailTemplates/Commands/Update/UpdateEmailTemplateCommand.cs
@@ -1,4 +1,5 @@
-using System.Text.Json.Serialization;
+using MediatR;
+using System.Text.Json.Serialization;
namespace EnvelopeGenerator.Application.EmailTemplates.Commands.Update;
@@ -12,11 +13,17 @@ namespace EnvelopeGenerator.Application.EmailTemplates.Commands.Update;
///
/// (Optional) Der neue Betreff der E-Mail. Wenn null, bleibt der vorhandene Betreff unverändert.
///
-public record UpdateEmailTemplateCommand(string? Body = null, string? Subject = null)
+public record UpdateEmailTemplateCommand(string? Body = null, string? Subject = null) : IRequest
{
///
/// Die Abfrage, die die E-Mail-Vorlage darstellt, die aktualisiert werden soll.
///
[JsonIgnore]
public EmailTemplateQuery? EmailTemplateQuery { get; set; }
+
+ ///
+ ///
+ ///
+ [JsonIgnore]
+ public DateTime ChangedWhen { get; init; } = DateTime.Now;
}
diff --git a/EnvelopeGenerator.Application/EmailTemplates/Commands/Update/UpdateEmailTemplateCommandHandler.cs b/EnvelopeGenerator.Application/EmailTemplates/Commands/Update/UpdateEmailTemplateCommandHandler.cs
new file mode 100644
index 00000000..4912e2e6
--- /dev/null
+++ b/EnvelopeGenerator.Application/EmailTemplates/Commands/Update/UpdateEmailTemplateCommandHandler.cs
@@ -0,0 +1,63 @@
+using DigitalData.Core.Abstractions.Infrastructure;
+using EnvelopeGenerator.Application.DTOs;
+using EnvelopeGenerator.Application.Exceptions;
+using EnvelopeGenerator.Domain.Entities;
+using MediatR;
+
+namespace EnvelopeGenerator.Application.EmailTemplates.Commands.Update;
+
+///
+///
+///
+public class UpdateEmailTemplateCommandHandler : IRequestHandler
+{
+ private readonly IRepository _repository;
+
+ ///
+ ///
+ ///
+ ///
+ public UpdateEmailTemplateCommandHandler(IRepository repository)
+ {
+ _repository = repository;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task Handle(UpdateEmailTemplateCommand request, CancellationToken cancel)
+ {
+ EmailTemplateDto? temp;
+
+ if (request.EmailTemplateQuery?.Id is int id)
+ {
+ temp = await _repository.ReadOrDefaultAsync(t => t.Id == id, single: false, cancel);
+ }
+ else if (request!.EmailTemplateQuery!.Type is Common.Constants.EmailTemplateType type)
+ {
+ temp = await _repository.ReadOrDefaultAsync(t => t.Name == type.ToString(), single: false, cancel);
+ }
+ else
+ {
+ throw new InvalidOperationException("Both id and type is null. Id: " + request.EmailTemplateQuery.Id +". Type: " + request.EmailTemplateQuery.Type.ToString());
+ }
+
+ if (temp == null)
+ {
+ throw new NotFoundException();
+ }
+
+ if (request.Body is not null)
+ temp.Body = request.Body;
+
+ if (request.Subject is not null)
+ temp.Subject = request.Subject;
+
+ await _repository.UpdateAsync(temp, t => t.Id == temp.Id, cancel);
+ }
+}
diff --git a/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateMappingProfile.cs b/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateMappingProfile.cs
new file mode 100644
index 00000000..ccdb298d
--- /dev/null
+++ b/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateMappingProfile.cs
@@ -0,0 +1,23 @@
+using AutoMapper;
+using EnvelopeGenerator.Domain.Entities;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EnvelopeGenerator.Application.EmailTemplates.Queries.Read;
+
+///
+///
+///
+public class ReadEmailTemplateMappingProfile : Profile
+{
+ ///
+ ///
+ ///
+ public ReadEmailTemplateMappingProfile()
+ {
+ CreateMap();
+ }
+}
diff --git a/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateQuery.cs b/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateQuery.cs
index 0c82bab2..b3108a30 100644
--- a/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateQuery.cs
+++ b/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateQuery.cs
@@ -1,10 +1,12 @@
-namespace EnvelopeGenerator.Application.EmailTemplates.Queries.Read;
+using MediatR;
+
+namespace EnvelopeGenerator.Application.EmailTemplates.Queries.Read;
///
/// Stellt eine Abfrage dar, um eine E-Mail-Vorlage zu lesen.
/// Diese Klasse erbt von .
///
-public record ReadEmailTemplateQuery : EmailTemplateQuery
+public record ReadEmailTemplateQuery : EmailTemplateQuery, IRequest
{
}
diff --git a/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateQueryHandler.cs b/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateQueryHandler.cs
new file mode 100644
index 00000000..6059ec33
--- /dev/null
+++ b/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateQueryHandler.cs
@@ -0,0 +1,52 @@
+using AutoMapper;
+using EnvelopeGenerator.Application.Contracts.Repositories;
+using EnvelopeGenerator.Application.DTOs;
+using EnvelopeGenerator.Common;
+using MediatR;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+
+namespace EnvelopeGenerator.Application.EmailTemplates.Queries.Read;
+
+///
+///
+///
+public class ReadEmailTemplateQueryHandler : IRequestHandler
+{
+ private readonly IMapper _mapper;
+
+ private readonly IEmailTemplateRepository _repository;
+
+ ///
+ /// Initialisiert eine neue Instanz der -Klasse.
+ ///
+ ///
+ ///
+ /// Die AutoMapper-Instanz, die zum Zuordnen von Objekten verwendet wird.
+ ///
+ public ReadEmailTemplateQueryHandler(IMapper mapper, IEmailTemplateRepository repository)
+ {
+ _mapper = mapper;
+ _repository = repository;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task Handle(ReadEmailTemplateQuery request, CancellationToken cancellationToken)
+ {
+ var temp = request.Id is int id
+ ? await _repository.ReadByIdAsync(id)
+ : request.Type is Constants.EmailTemplateType type
+ ? await _repository.ReadByNameAsync(type)
+ : throw new InvalidOperationException("Either a valid integer ID or a valid EmailTemplateType must be provided in the request.");
+
+ var res = _mapper.Map(temp);
+
+ return res;
+ }
+}
diff --git a/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateResponse.cs b/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateResponse.cs
index 3234c86a..df68fa94 100644
--- a/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateResponse.cs
+++ b/EnvelopeGenerator.Application/EmailTemplates/Queries/Read/ReadEmailTemplateResponse.cs
@@ -3,18 +3,35 @@
///
/// Stellt die Antwort für eine Abfrage von E-Mail-Vorlagen bereit.
///
-/// Die eindeutige Kennung der E-Mail-Vorlage.
-/// Der Typ der E-Mail-Vorlage.
-/// Das Datum und die Uhrzeit, wann die Vorlage hinzugefügt wurde.
-/// Der Inhalt (Body) der E-Mail-Vorlage. Kann null sein.
-/// Der Betreff der E-Mail-Vorlage. Kann null sein.
-/// Das Datum und die Uhrzeit, wann die Vorlage zuletzt geändert wurde. Kann null sein.
-public record ReadEmailTemplateResponse(
- int Id,
- int Type,
- DateTime AddedWhen,
- string? Body = null,
- string? Subject = null,
- DateTime? ChangedWhen = null)
+public class ReadEmailTemplateResponse
{
+ ///
+ /// Die eindeutige Kennung der E-Mail-Vorlage.
+ ///
+ public int Id { get; set; }
+
+ ///
+ /// Name des Typs
+ ///
+ public required string Name { get; set; }
+
+ ///
+ /// Das Datum und die Uhrzeit, wann die Vorlage hinzugefügt wurde.
+ ///
+ public DateTime AddedWhen { get; set; }
+
+ ///
+ /// Der Inhalt (Body) der E-Mail-Vorlage. Kann null sein.
+ ///
+ public string? Body { get; set; }
+
+ ///
+ /// Der Betreff der E-Mail-Vorlage. Kann null sein.
+ ///
+ public string? Subject { get; set; }
+
+ ///
+ /// Das Datum und die Uhrzeit, wann die Vorlage zuletzt geändert wurde. Kann null sein.
+ ///
+ public DateTime? ChangedWhen { get; set; }
}
diff --git a/EnvelopeGenerator.Application/EnvelopeGenerator.Application.csproj b/EnvelopeGenerator.Application/EnvelopeGenerator.Application.csproj
index 73ba45fe..e285a702 100644
--- a/EnvelopeGenerator.Application/EnvelopeGenerator.Application.csproj
+++ b/EnvelopeGenerator.Application/EnvelopeGenerator.Application.csproj
@@ -13,6 +13,7 @@
+
@@ -20,6 +21,7 @@
+
diff --git a/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverCommand.cs b/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverCommand.cs
index 7000950d..fb82abaf 100644
--- a/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverCommand.cs
+++ b/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverCommand.cs
@@ -1,4 +1,5 @@
-using MediatR;
+using EnvelopeGenerator.Application.Envelopes.Commands;
+using MediatR;
using System.ComponentModel.DataAnnotations;
namespace EnvelopeGenerator.Application.EnvelopeReceivers.Commands.Create;
@@ -17,43 +18,4 @@ public record CreateEnvelopeReceiverCommand(
[Required] DocumentCreateCommand Document,
[Required] IEnumerable Receivers,
bool TFAEnabled = false
- ) : IRequest;
-
-#region DTOs
-///
-/// Signaturposition auf einem Dokument.
-///
-/// X-Position
-/// Y-Position
-/// Seite, auf der sie sich befindet
-public record Signature([Required] int X, [Required] int Y, [Required] int Page);
-
-///
-/// DTO für Empfänger, die erstellt oder abgerufen werden sollen.
-/// Wenn nicht, wird sie erstellt und mit einer Signatur versehen.
-///
-/// Unterschriften auf Dokumenten.
-/// Der Name, mit dem der Empfänger angesprochen werden soll. Bei Null oder keinem Wert wird der zuletzt verwendete Name verwendet.
-/// Sollte mit Vorwahl geschrieben werden
-public record ReceiverGetOrCreateCommand([Required] IEnumerable Signatures, string? Salution = null, string? PhoneNumber = null)
-{
- private string _emailAddress = string.Empty;
-
- ///
- /// E-Mail-Adresse des Empfängers.
- ///
- [Required]
- public required string EmailAddress { get => _emailAddress.ToLower(); init => _emailAddress = _emailAddress.ToLower(); }
-};
-
-///
-/// DTO zum Erstellen eines Dokuments.
-///
-///
-/// Die Dokumentdaten im Byte-Array-Format. Wird verwendet, wenn das Dokument als Roh-Binärdaten bereitgestellt wird.
-///
-///
-/// Die Dokumentdaten im Base64-String-Format. Wird verwendet, wenn das Dokument als Base64-codierter String bereitgestellt wird.
-///
-public record DocumentCreateCommand(byte[]? DataAsByte = null, string? DataAsBase64 = null);
-#endregion
\ No newline at end of file
+ ) : CreateEnvelopeCommand(Title, Message, TFAEnabled), IRequest;
\ No newline at end of file
diff --git a/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverCommandHandler.cs b/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverCommandHandler.cs
new file mode 100644
index 00000000..3ab6fda1
--- /dev/null
+++ b/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverCommandHandler.cs
@@ -0,0 +1,67 @@
+using AutoMapper;
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using EnvelopeGenerator.Application.DTOs.Receiver;
+using EnvelopeGenerator.Domain.Entities;
+using MediatR;
+
+namespace EnvelopeGenerator.Application.EnvelopeReceivers.Commands.Create;
+
+///
+/// Handles the creation of an envelope along with its associated document and recipients.
+/// This command processes the envelope data, including title, message, document content,
+/// recipient list, and optional two-factor authentication settings.
+///
+public class CreateEnvelopeReceiverCommandHandler : IRequestHandler
+{
+ private readonly IMapper _mapper;
+
+ private readonly IEnvelopeExecutor _envelopeExecutor;
+
+ private readonly IEnvelopeReceiverExecutor _erExecutor;
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public CreateEnvelopeReceiverCommandHandler(IMapper mapper, IEnvelopeExecutor envelopeExecutor, IEnvelopeReceiverExecutor erExecutor)
+ {
+ _mapper = mapper;
+ _envelopeExecutor = envelopeExecutor;
+ _erExecutor = erExecutor;
+ }
+
+ ///
+ /// Handles the execution of the .
+ /// Responsible for validating input data, creating or retrieving recipients, associating signatures,
+ /// and storing the envelope and document details.
+ ///
+ /// The command containing all necessary information to create an envelope.
+ /// Token to observe while waiting for the task to complete.
+ /// A task representing the asynchronous operation.
+ public async Task Handle(CreateEnvelopeReceiverCommand request, CancellationToken cancel)
+ {
+ int userId = request.UserId ?? throw new InvalidOperationException("UserId cannot be null when creating an envelope.");
+
+ var envelope = await _envelopeExecutor.CreateEnvelopeAsync(userId, request.Title, request.Message, request.TFAEnabled, cancel);
+
+ List sentRecipients = new();
+ List unsentRecipients = new();
+
+ foreach (var receiver in request.Receivers)
+ {
+ var envelopeReceiver = await _erExecutor.AddEnvelopeReceiverAsync(envelope.Uuid, receiver.EmailAddress, receiver.Salution, receiver.PhoneNumber, cancel);
+
+ if (envelopeReceiver is null)
+ unsentRecipients.Add(receiver);
+ else
+ sentRecipients.Add(envelopeReceiver);
+ }
+
+ var res = _mapper.Map(envelope);
+ res.UnsentReceivers = unsentRecipients;
+ res.SentReceiver = _mapper.Map>(sentRecipients);
+ return res;
+ }
+}
diff --git a/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverDtos.cs b/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverDtos.cs
new file mode 100644
index 00000000..dc8072e0
--- /dev/null
+++ b/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverDtos.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace EnvelopeGenerator.Application.EnvelopeReceivers.Commands.Create;
+
+#region DTOs
+///
+/// Signaturposition auf einem Dokument.
+///
+/// X-Position
+/// Y-Position
+/// Seite, auf der sie sich befindet
+public record Signature([Required] double X, [Required] double Y, [Required] int Page);
+
+///
+/// DTO für Empfänger, die erstellt oder abgerufen werden sollen.
+/// Wenn nicht, wird sie erstellt und mit einer Signatur versehen.
+///
+/// Unterschriften auf Dokumenten.
+/// Der Name, mit dem der Empfänger angesprochen werden soll. Bei Null oder keinem Wert wird der zuletzt verwendete Name verwendet.
+/// Sollte mit Vorwahl geschrieben werden
+public record ReceiverGetOrCreateCommand([Required] IEnumerable Signatures, string? Salution = null, string? PhoneNumber = null)
+{
+ private string _emailAddress = string.Empty;
+
+ ///
+ /// E-Mail-Adresse des Empfängers.
+ ///
+ [Required]
+ public string EmailAddress { get => _emailAddress.ToLower(); init => _emailAddress = value.ToLower(); }
+};
+
+///
+/// DTO zum Erstellen eines Dokuments.
+///
+public record DocumentCreateCommand()
+{
+ ///
+ /// Die Dokumentdaten im Base64-String-Format. Wird verwendet, wenn das Dokument als Base64-codierter String bereitgestellt wird.
+ ///
+ [Required]
+ public required string DataAsBase64 { get; init; }
+};
+#endregion
\ No newline at end of file
diff --git a/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverMappingProfile.cs b/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverMappingProfile.cs
new file mode 100644
index 00000000..0f49aaed
--- /dev/null
+++ b/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverMappingProfile.cs
@@ -0,0 +1,21 @@
+using AutoMapper;
+using EnvelopeGenerator.Application.DTOs.Receiver;
+using EnvelopeGenerator.Application.Envelopes.Commands;
+using EnvelopeGenerator.Domain.Entities;
+
+namespace EnvelopeGenerator.Application.EnvelopeReceivers.Commands.Create;
+
+///
+///
+///
+public class CreateEnvelopeReceiverMappingProfile : Profile
+{
+ ///
+ ///
+ ///
+ public CreateEnvelopeReceiverMappingProfile()
+ {
+ CreateMap();
+ CreateMap();
+ }
+}
diff --git a/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverResponse.cs b/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverResponse.cs
new file mode 100644
index 00000000..251e6ceb
--- /dev/null
+++ b/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/Create/CreateEnvelopeReceiverResponse.cs
@@ -0,0 +1,39 @@
+using DigitalData.UserManager.Domain.Entities;
+using EnvelopeGenerator.Application.DTOs.Receiver;
+using EnvelopeGenerator.Application.Envelopes.Commands;
+
+namespace EnvelopeGenerator.Application.EnvelopeReceivers.Commands.Create;
+
+///
+///
+///
+public record CreateEnvelopeReceiverResponse : CreateEnvelopeResponse
+{
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public CreateEnvelopeReceiverResponse(int Id, int UserId, int Status, string Uuid, string? Message, DateTime AddedWhen, DateTime? ChangedWhen, string? Title, string Language, bool TFAEnabled, User User) : base(Id, UserId, Status, Uuid, Message, AddedWhen, ChangedWhen, Title, Language, TFAEnabled, User)
+ {
+ }
+
+ ///
+ ///
+ ///
+ public IEnumerable SentReceiver { get; set; } = new List();
+
+ ///
+ ///
+ ///
+ public IEnumerable UnsentReceivers { get; set; } = new List();
+}
diff --git a/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommand.cs b/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommand.cs
new file mode 100644
index 00000000..bf267203
--- /dev/null
+++ b/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommand.cs
@@ -0,0 +1,26 @@
+using MediatR;
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+
+namespace EnvelopeGenerator.Application.Envelopes.Commands;
+
+///
+/// Befehl zur Erstellung eines Umschlags.
+///
+/// Der Titel des Umschlags. Dies ist ein Pflichtfeld.
+/// Die Nachricht, die im Umschlag enthalten sein soll. Dies ist ein Pflichtfeld.
+/// Gibt an, ob die Zwei-Faktor-Authentifizierung für den Umschlag aktiviert ist. Standardmäßig false.
+public record CreateEnvelopeCommand(
+ [Required] string Title,
+ [Required] string Message,
+ bool TFAEnabled = false
+ ) : IRequest
+{
+ ///
+ /// Id of receiver
+ ///
+ [JsonIgnore]
+ [BindNever]
+ public int? UserId { get; set; }
+};
diff --git a/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommandHandler.cs b/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommandHandler.cs
new file mode 100644
index 00000000..5c8cb3e9
--- /dev/null
+++ b/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeCommandHandler.cs
@@ -0,0 +1,41 @@
+using AutoMapper;
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using MediatR;
+
+namespace EnvelopeGenerator.Application.Envelopes.Commands;
+
+///
+///
+///
+public class CreateEnvelopeCommandHandler : IRequestHandler
+{
+ private readonly IEnvelopeExecutor _envelopeExecutor;
+
+ private readonly IMapper _mapper;
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public CreateEnvelopeCommandHandler(IEnvelopeExecutor envelopeExecutor, IMapper mapper)
+ {
+ _envelopeExecutor = envelopeExecutor;
+ _mapper = mapper;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task Handle(CreateEnvelopeCommand request, CancellationToken cancellationToken)
+ {
+ int userId = request.UserId ?? throw new InvalidOperationException("UserId cannot be null when creating an envelope.");
+
+ var envelope = await _envelopeExecutor.CreateEnvelopeAsync(userId, request.Title, request.Message, request.TFAEnabled, cancellationToken);
+
+ return _mapper.Map(envelope);
+ }
+}
diff --git a/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeMappingProfile.cs b/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeMappingProfile.cs
new file mode 100644
index 00000000..9cf72359
--- /dev/null
+++ b/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeMappingProfile.cs
@@ -0,0 +1,19 @@
+using AutoMapper;
+using EnvelopeGenerator.Application.EnvelopeReceivers.Commands.Create;
+using EnvelopeGenerator.Domain.Entities;
+
+namespace EnvelopeGenerator.Application.Envelopes.Commands;
+
+///
+///
+///
+public class CreateEnvelopeMappingProfile : Profile
+{
+ ///
+ ///
+ ///
+ public CreateEnvelopeMappingProfile()
+ {
+ CreateMap();
+ }
+}
diff --git a/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeResponse.cs b/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeResponse.cs
new file mode 100644
index 00000000..998b80e0
--- /dev/null
+++ b/EnvelopeGenerator.Application/Envelopes/Commands/CreateEnvelopeResponse.cs
@@ -0,0 +1,20 @@
+using EnvelopeGenerator.Application.Envelopes.Queries.Read;
+
+namespace EnvelopeGenerator.Application.Envelopes.Commands;
+
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+///
+public record CreateEnvelopeResponse(int Id, int UserId, int Status, string Uuid, string? Message, DateTime AddedWhen, DateTime? ChangedWhen, string? Title, string Language, bool TFAEnabled, DigitalData.UserManager.Domain.Entities.User User)
+ : ReadEnvelopeResponse(Id, UserId, Status, Uuid, Message, AddedWhen, ChangedWhen, Title, Language, TFAEnabled, User);
diff --git a/EnvelopeGenerator.Application/Envelopes/Queries/ReceiverName/ReadReceiverNameQuery.cs b/EnvelopeGenerator.Application/Envelopes/Queries/ReceiverName/ReadReceiverNameQuery.cs
index fe114dd8..55b477a3 100644
--- a/EnvelopeGenerator.Application/Envelopes/Queries/ReceiverName/ReadReceiverNameQuery.cs
+++ b/EnvelopeGenerator.Application/Envelopes/Queries/ReceiverName/ReadReceiverNameQuery.cs
@@ -6,8 +6,6 @@ namespace EnvelopeGenerator.Application.Envelopes.Queries.ReceiverName;
/// Eine Abfrage, um die zuletzt verwendete Anrede eines Empfängers zu ermitteln,
/// damit diese für zukünftige Umschläge wiederverwendet werden kann.
///
-/// Der Umschlag, für den die Anrede des Empfängers ermittelt werden soll.
-/// Gibt an, ob nur die zuletzt verwendete Anrede zurückgegeben werden soll.
-public record ReadReceiverNameQuery(EnvelopeQuery? Envelope = null, bool OnlyLast = true) : ReadReceiverQuery
+public record ReadReceiverNameQuery() : ReadReceiverQuery
{
}
diff --git a/EnvelopeGenerator.Application/Exceptions/BadRequestException.cs b/EnvelopeGenerator.Application/Exceptions/BadRequestException.cs
new file mode 100644
index 00000000..ce45ba75
--- /dev/null
+++ b/EnvelopeGenerator.Application/Exceptions/BadRequestException.cs
@@ -0,0 +1,22 @@
+namespace EnvelopeGenerator.Application.Exceptions;
+
+///
+/// Represents an exception that is thrown when a bad request is encountered.
+///
+public class BadRequestException : Exception
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public BadRequestException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with a specified error message.
+ ///
+ /// The message that describes the error.
+ public BadRequestException(string? message) : base(message)
+ {
+ }
+}
diff --git a/EnvelopeGenerator.Application/Exceptions/NotFoundException.cs b/EnvelopeGenerator.Application/Exceptions/NotFoundException.cs
new file mode 100644
index 00000000..67b58ef3
--- /dev/null
+++ b/EnvelopeGenerator.Application/Exceptions/NotFoundException.cs
@@ -0,0 +1,22 @@
+namespace EnvelopeGenerator.Application.Exceptions;
+
+///
+/// Represents an exception that is thrown when a requested resource is not found.
+///
+public class NotFoundException : Exception
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public NotFoundException()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class with a specified error message.
+ ///
+ /// The message that describes the error.
+ public NotFoundException(string? message) : base(message)
+ {
+ }
+}
diff --git a/EnvelopeGenerator.Application/Histories/Queries/Read/ReadHistoryQuery.cs b/EnvelopeGenerator.Application/Histories/Queries/Read/ReadHistoryQuery.cs
index 2f8ba313..7439b900 100644
--- a/EnvelopeGenerator.Application/Histories/Queries/Read/ReadHistoryQuery.cs
+++ b/EnvelopeGenerator.Application/Histories/Queries/Read/ReadHistoryQuery.cs
@@ -4,17 +4,14 @@ using EnvelopeGenerator.Common;
namespace EnvelopeGenerator.Application.Histories.Queries.Read;
+//TODO: Add sender query
///
/// Repräsentiert eine Abfrage für die Verlaufshistorie eines Umschlags.
///
/// Die eindeutige Kennung des Umschlags.
-/// Die Abfrage, die den Umschlag beschreibt.
-/// Die Abfrage, die den Empfänger beschreibt.
/// Abfrage, die angibt, worauf sich der Datensatz bezieht. Ob er sich auf den Empfänger, den Sender oder das System bezieht, wird durch 0, 1 bzw. 2 dargestellt.
/// Abfrage zur Steuerung, ob nur der aktuelle Status oder der gesamte Datensatz zurückgegeben wird.
public record ReadHistoryQuery(
- int EnvelopeId,
- ReadEnvelopeQuery? Envelope = null,
- ReadReceiverQuery? Receiver = null,
+ int? EnvelopeId = null,
Constants.ReferenceType? Related = null,
bool? OnlyLast = true);
\ No newline at end of file
diff --git a/EnvelopeGenerator.Application/SQL/DocumentCreateReadSQL.cs b/EnvelopeGenerator.Application/SQL/DocumentCreateReadSQL.cs
new file mode 100644
index 00000000..ed682b0e
--- /dev/null
+++ b/EnvelopeGenerator.Application/SQL/DocumentCreateReadSQL.cs
@@ -0,0 +1,42 @@
+using Dapper;
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using EnvelopeGenerator.Domain.Entities;
+
+namespace EnvelopeGenerator.Application.SQL;
+
+///
+///
+///
+public class DocumentCreateReadSQL : ISQL
+{
+ ///
+ /// Base64, OUT_UID
+ ///
+ public string Raw => @"
+ DECLARE @BYTE_DATA1 as VARBINARY(MAX)
+ SET @BYTE_DATA1 = @ByteData
+ DECLARE @OUT_DOCID int
+
+ EXEC [dbo].[PRSIG_API_ADD_DOC]
+ {0},
+ @BYTE_DATA1,
+ @OUT_DOCID OUTPUT
+
+ SELECT TOP(1) *
+ FROM [dbo].[TBSIG_ENVELOPE_DOCUMENT]
+ WHERE [GUID] = @OUT_DOCID
+ ";
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static DynamicParameters CreateParmas(string base64)
+ {
+ var parameters = new DynamicParameters();
+ byte[] byteData = Convert.FromBase64String(base64);
+ parameters.Add("ByteData", byteData, System.Data.DbType.Binary);
+ return parameters;
+ }
+}
diff --git a/EnvelopeGenerator.Application/SQL/EnvelopeCreateReadSQL.cs b/EnvelopeGenerator.Application/SQL/EnvelopeCreateReadSQL.cs
new file mode 100644
index 00000000..40b05e68
--- /dev/null
+++ b/EnvelopeGenerator.Application/SQL/EnvelopeCreateReadSQL.cs
@@ -0,0 +1,49 @@
+using Dapper;
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using EnvelopeGenerator.Domain.Entities;
+using System.Data;
+
+namespace EnvelopeGenerator.Application.SQL;
+
+///
+///
+///
+public class EnvelopeCreateReadSQL : ISQL
+{
+ ///
+ /// USER_ID, TITLE, TFAEnabled, MESSAGE
+ ///
+ public string Raw => @"
+ DECLARE @OUT_UID varchar(36);
+
+ EXEC [dbo].[PRSIG_API_CREATE_ENVELOPE]
+ {0},
+ {1},
+ {2},
+ {3},
+ @OUT_UID OUTPUT;
+
+ SELECT TOP(1) *
+ FROM [dbo].[TBSIG_ENVELOPE]
+ WHERE [ENVELOPE_UUID] = @OUT_UID;
+ ";
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static DynamicParameters CreateParams(int userId, string title = "", string message = "", bool tfaEnabled = false)
+ {
+ var parameters = new DynamicParameters();
+ parameters.Add("@UserId", userId);
+ parameters.Add("@Title", title);
+ parameters.Add("@TfaEnabled", tfaEnabled ? 1 : 0);
+ parameters.Add("@Message", message);
+ parameters.Add("@OutUid", dbType: DbType.String, size: 36, direction: ParameterDirection.Output);
+ return parameters;
+ }
+}
\ No newline at end of file
diff --git a/EnvelopeGenerator.Application/SQL/EnvelopeReceiverAddReadSQL.cs b/EnvelopeGenerator.Application/SQL/EnvelopeReceiverAddReadSQL.cs
new file mode 100644
index 00000000..73fc5de2
--- /dev/null
+++ b/EnvelopeGenerator.Application/SQL/EnvelopeReceiverAddReadSQL.cs
@@ -0,0 +1,47 @@
+using Dapper;
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using EnvelopeGenerator.Domain.Entities;
+
+namespace EnvelopeGenerator.Application.SQL;
+
+///
+///
+///
+public class EnvelopeReceiverAddReadSQL : ISQL
+{
+ ///
+ /// ENV_UID, EMAIL_ADRESS, SALUTATION, PHONE,
+ ///
+ public string Raw => @"
+ DECLARE @OUT_RECEIVER_ID int
+
+ EXEC [dbo].[PRSIG_API_CREATE_RECEIVER]
+ {0},
+ {1},
+ {2},
+ {3},
+ @OUT_RECEIVER_ID OUTPUT
+
+ SELECT TOP(1) [ENVELOPE_ID] As EnvelopeId, [RECEIVER_ID] As ReceiverId
+ FROM [dbo].[TBSIG_ENVELOPE_RECEIVER]
+ WHERE [GUID] = @OUT_RECEIVER_ID;
+ ";
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static DynamicParameters CreateParameters(string envelope_uuid, string emailAddress, string? salutation = null, string? phone = null)
+ {
+ var parameters = new DynamicParameters();
+ parameters.Add("@ENV_UID", envelope_uuid);
+ parameters.Add("@EMAIL_ADRESS", emailAddress);
+ parameters.Add("@SALUTATION", salutation);
+ parameters.Add("@PHONE", phone);
+ return parameters;
+ }
+}
diff --git a/EnvelopeGenerator.Application/SQL/ParamsExtensions.cs b/EnvelopeGenerator.Application/SQL/ParamsExtensions.cs
new file mode 100644
index 00000000..9f8ad1ec
--- /dev/null
+++ b/EnvelopeGenerator.Application/SQL/ParamsExtensions.cs
@@ -0,0 +1,30 @@
+using System.Globalization;
+
+namespace EnvelopeGenerator.Application.SQL;
+
+///
+/// Extension method for converting objects to SQL parameter strings.
+///
+public static class ParamsExtensions
+{
+ ///
+ /// Converts a .NET object to its corresponding SQL-safe parameter string.
+ ///
+ /// The object to convert.
+ /// A string representing the SQL parameter.
+ public static string ToSqlParam(this object? obj)
+ {
+ if (obj is null)
+ return "NULL";
+ else if (obj is string strVal)
+ return $"'{strVal}'";
+ else if (obj is bool boolVal)
+ return boolVal ? "1" : "0";
+ else if (obj is double doubleVal)
+ return $"'{doubleVal.ToString(CultureInfo.InvariantCulture)}'";
+ else if (obj is int intVal)
+ return intVal.ToString();
+ else
+ throw new NotSupportedException($"Type '{obj.GetType().FullName}' is not supported for SQL parameter conversion.");
+ }
+}
diff --git a/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs b/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs
index ddcd3e2a..dcde6d75 100644
--- a/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs
+++ b/EnvelopeGenerator.Application/Services/EnvelopeReceiverService.cs
@@ -10,6 +10,8 @@ using Microsoft.Extensions.Logging;
using EnvelopeGenerator.Extensions;
using EnvelopeGenerator.Application.DTOs.Messaging;
using EnvelopeGenerator.Application.Contracts.Services;
+using EnvelopeGenerator.Application.Envelopes;
+using EnvelopeGenerator.Application.Receivers.Queries.Read;
namespace EnvelopeGenerator.Application.Services;
@@ -137,9 +139,25 @@ public class EnvelopeReceiverService : BasicCRUDService>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, params int[] ignore_statuses)
+ public async Task>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, EnvelopeQuery? envelopeQuery = null, ReadReceiverQuery? receiverQuery = null, params int[] ignore_statuses)
{
var er_list = await _repository.ReadByUsernameAsync(username: username, min_status: min_status, max_status: max_status, ignore_statuses: ignore_statuses);
+
+ if(envelopeQuery?.Id is int eId)
+ er_list = er_list.Where(er => er.EnvelopeId == eId);
+
+ if (envelopeQuery?.Uuid is string uuid)
+ er_list = er_list.Where(er => er.Envelope?.Uuid == uuid);
+
+ if (envelopeQuery?.Status is int status)
+ er_list = er_list.Where(er => er.Envelope?.Status == status);
+
+ if(receiverQuery?.Id is int id)
+ er_list = er_list.Where(er => er.Receiver?.Id == id);
+
+ if (receiverQuery?.Signature is string signature)
+ er_list = er_list.Where(er => er.Receiver?.Signature == signature);
+
var dto_list = _mapper.Map>(er_list);
return Result.Success(dto_list);
}
diff --git a/EnvelopeGenerator.Common/Constants.vb b/EnvelopeGenerator.Common/Constants.vb
index 59570ee1..2e61cefb 100644
--- a/EnvelopeGenerator.Common/Constants.vb
+++ b/EnvelopeGenerator.Common/Constants.vb
@@ -21,7 +21,6 @@
DocumentOpened = 2004
DocumentSigned = 2005
DocumentForwarded = 4001
- SignatureConfirmed = 2006
DocumentRejected = 2007
EnvelopeShared = 2008
EnvelopeViewed = 2009
@@ -33,6 +32,20 @@
DocumentMod_Rotation = 4001
End Enum
+ Public Class Status
+ Public Shared ReadOnly NonHist As IReadOnlyList(Of EnvelopeStatus) = New List(Of EnvelopeStatus) From {
+ EnvelopeStatus.Invalid,
+ EnvelopeStatus.EnvelopeSaved,
+ EnvelopeStatus.EnvelopeSent,
+ EnvelopeStatus.EnvelopePartlySigned
+ }
+
+ Public Shared ReadOnly RelatedToFormApp As IReadOnlyList(Of EnvelopeStatus) = New List(Of EnvelopeStatus) From {
+ EnvelopeStatus.EnvelopeCreated,
+ EnvelopeStatus.DocumentMod_Rotation
+ }
+ End Class
+
'TODO: standardize in xwiki
Public Enum ReferenceType
Receiver = 0
diff --git a/EnvelopeGenerator.Domain/Entities/EmailTemplate.cs b/EnvelopeGenerator.Domain/Entities/EmailTemplate.cs
index aef2df9d..689df6b9 100644
--- a/EnvelopeGenerator.Domain/Entities/EmailTemplate.cs
+++ b/EnvelopeGenerator.Domain/Entities/EmailTemplate.cs
@@ -1,4 +1,5 @@
using DigitalData.Core.Abstractions;
+using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
@@ -20,5 +21,13 @@ namespace EnvelopeGenerator.Domain.Entities
[Column("SUBJECT", TypeName = "nvarchar(512)")]
public string? Subject { get; set; }
+
+ [Required]
+ [Column("ADDED_WHEN", TypeName = "datetime")]
+ [DefaultValue("GETDATE()")]
+ public DateTime AddedWhen { get; set; }
+
+ [Column("CHANGED_WHEN", TypeName = "datetime")]
+ public DateTime? ChangedWhen { get; set; }
}
}
\ No newline at end of file
diff --git a/EnvelopeGenerator.Domain/Entities/Envelope.cs b/EnvelopeGenerator.Domain/Entities/Envelope.cs
index e4db872e..1711113f 100644
--- a/EnvelopeGenerator.Domain/Entities/Envelope.cs
+++ b/EnvelopeGenerator.Domain/Entities/Envelope.cs
@@ -28,7 +28,6 @@ namespace EnvelopeGenerator.Domain.Entities
[Column("ENVELOPE_UUID", TypeName = "nvarchar(36)")]
public required string Uuid { get; init; }
- [Required]
[Column("MESSAGE", TypeName = "nvarchar(max)")]
public string? Message { get; set; }
@@ -52,7 +51,7 @@ namespace EnvelopeGenerator.Domain.Entities
public int? ContractType { get; set; }
[Column("LANGUAGE", TypeName = "nvarchar(5)")]
- public required string Language { get; set; }
+ public string? Language { get; set; }
[Column("SEND_REMINDER_EMAILS")]
public bool? SendReminderEmails { get; set; }
@@ -87,6 +86,9 @@ namespace EnvelopeGenerator.Domain.Entities
[Column("TFA_ENABLED", TypeName = "bit")]
public bool TFAEnabled { get; set; }
+ [Column("DOC_RESULT", TypeName = "varbinary(max)")]
+ public byte[]? DocResult { get; init; }
+
///
/// The sender of envelope
///
diff --git a/EnvelopeGenerator.Extensions/EnvelopeGenerator.Extensions.csproj b/EnvelopeGenerator.Extensions/EnvelopeGenerator.Extensions.csproj
index cb187fa2..dbaad97c 100644
--- a/EnvelopeGenerator.Extensions/EnvelopeGenerator.Extensions.csproj
+++ b/EnvelopeGenerator.Extensions/EnvelopeGenerator.Extensions.csproj
@@ -13,6 +13,7 @@
+
diff --git a/EnvelopeGenerator.Extensions/MemoryCacheExtensions.cs b/EnvelopeGenerator.Extensions/MemoryCacheExtensions.cs
new file mode 100644
index 00000000..86ac56bb
--- /dev/null
+++ b/EnvelopeGenerator.Extensions/MemoryCacheExtensions.cs
@@ -0,0 +1,16 @@
+using Microsoft.Extensions.Caching.Memory;
+
+namespace EnvelopeGenerator.Extensions;
+
+public static class MemoryCacheExtensions
+{
+ private static readonly Guid BaseId = Guid.NewGuid();
+
+ public static IDictionary GetEnumAsDictionary(this IMemoryCache memoryCache)
+ where TEnum : Enum
+ => memoryCache.GetOrCreate(BaseId + typeof(TEnum).FullName, _ =>
+ Enum.GetValues(typeof(TEnum))
+ .Cast()
+ .ToDictionary(e => e.ToString(), e => Convert.ToInt32(e)))
+ ?? throw new InvalidOperationException($"Failed to cache or retrieve enum dictionary for type '{typeof(TEnum).FullName}'.");
+}
diff --git a/EnvelopeGenerator.GeneratorAPI/Controllers/AuthController.cs b/EnvelopeGenerator.GeneratorAPI/Controllers/AuthController.cs
index a5e8f748..23b9bfdb 100644
--- a/EnvelopeGenerator.GeneratorAPI/Controllers/AuthController.cs
+++ b/EnvelopeGenerator.GeneratorAPI/Controllers/AuthController.cs
@@ -118,16 +118,8 @@ public partial class AuthController : ControllerBase
[HttpPost("logout")]
public async Task Logout()
{
- try
- {
- await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
- return Ok();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Unexpected error occurred.\n{ErrorMessage}", ex.Message);
- return StatusCode(StatusCodes.Status500InternalServerError);
- }
+ await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
+ return Ok();
}
///
diff --git a/EnvelopeGenerator.GeneratorAPI/Controllers/ControllerExtensions.cs b/EnvelopeGenerator.GeneratorAPI/Controllers/ControllerExtensions.cs
index 1121a9b8..beac46da 100644
--- a/EnvelopeGenerator.GeneratorAPI/Controllers/ControllerExtensions.cs
+++ b/EnvelopeGenerator.GeneratorAPI/Controllers/ControllerExtensions.cs
@@ -3,22 +3,60 @@ using System.Security.Claims;
namespace EnvelopeGenerator.GeneratorAPI.Controllers
{
+ ///
+ /// Provides extension methods for extracting user information from a .
+ ///
public static class ControllerExtensions
{
- public static int? GetId(this ClaimsPrincipal user)
- => int.TryParse(user.FindFirst(ClaimTypes.NameIdentifier)?.Value, out int result)
+ ///
+ /// Attempts to retrieve the user's ID from the claims. Returns null if the ID is not found or invalid.
+ ///
+ /// The representing the user.
+ /// The user's ID as an integer, or null if not found or invalid.
+ public static int? GetIdOrDefault(this ClaimsPrincipal user)
+ => int.TryParse(user.FindFirstValue(ClaimTypes.NameIdentifier) ?? user.FindFirstValue("sub"), out int result)
? result : null;
-
- public static string? GetUsername(this ClaimsPrincipal user)
+
+ ///
+ /// Retrieves the user's ID from the claims. Throws an exception if the ID is missing or invalid.
+ ///
+ /// The representing the user.
+ /// The user's ID as an integer.
+ /// Thrown if the user ID claim is missing or invalid.
+ public static int GetId(this ClaimsPrincipal user)
+ => user.GetIdOrDefault()
+ ?? throw new InvalidOperationException("User ID claim is missing or invalid. This may indicate a misconfigured or forged JWT token.");
+
+ ///
+ /// Retrieves the username from the claims, if available.
+ ///
+ /// The representing the user.
+ /// The username as a string, or null if not found.
+ public static string? GetUsernameOrDefault(this ClaimsPrincipal user)
=> user.FindFirst(ClaimTypes.Name)?.Value;
-
- public static string? GetName(this ClaimsPrincipal user)
+
+ ///
+ /// Retrieves the user's surname (last name) from the claims, if available.
+ ///
+ /// The representing the user.
+ /// The surname as a string, or null if not found.
+ public static string? GetNameOrDefault(this ClaimsPrincipal user)
=> user.FindFirst(ClaimTypes.Surname)?.Value;
-
- public static string? GetPrename(this ClaimsPrincipal user)
+
+ ///
+ /// Retrieves the user's given name (first name) from the claims, if available.
+ ///
+ /// The representing the user.
+ /// The given name as a string, or null if not found.
+ public static string? GetPrenameOrDefault(this ClaimsPrincipal user)
=> user.FindFirst(ClaimTypes.GivenName)?.Value;
-
- public static string? GetEmail(this ClaimsPrincipal user)
+
+ ///
+ /// Retrieves the user's email address from the claims, if available.
+ ///
+ /// The representing the user.
+ /// The email address as a string, or null if not found.
+ public static string? GetEmailOrDefault(this ClaimsPrincipal user)
=> user.FindFirst(ClaimTypes.Email)?.Value;
}
}
\ No newline at end of file
diff --git a/EnvelopeGenerator.GeneratorAPI/Controllers/EmailTemplateController.cs b/EnvelopeGenerator.GeneratorAPI/Controllers/EmailTemplateController.cs
index fa4fb84a..41927e85 100644
--- a/EnvelopeGenerator.GeneratorAPI/Controllers/EmailTemplateController.cs
+++ b/EnvelopeGenerator.GeneratorAPI/Controllers/EmailTemplateController.cs
@@ -5,11 +5,17 @@ using EnvelopeGenerator.Application.EmailTemplates.Commands.Reset;
using EnvelopeGenerator.Application.EmailTemplates.Queries.Read;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using EnvelopeGenerator.Application.Contracts.Repositories;
+using EnvelopeGenerator.Application.DTOs;
+using MediatR;
+using System.Threading.Tasks;
+using DigitalData.UserManager.Application.Services;
+using EnvelopeGenerator.Application.Exceptions;
namespace EnvelopeGenerator.GeneratorAPI.Controllers;
///
-/// Controller for managing email templates.
+/// Controller for managing temp templates.
/// Steuerung zur Verwaltung von E-Mail-Vorlagen.
///
[Route("api/[controller]")]
@@ -17,17 +23,27 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers;
[Authorize]
public class EmailTemplateController : ControllerBase
{
+ private readonly ILogger _logger;
+
private readonly IMapper _mapper;
+ private readonly IEmailTemplateRepository _repository;
+
+ private readonly IMediator _mediator;
+
///
/// Initialisiert eine neue Instanz der -Klasse.
///
///
+ ///
/// Die AutoMapper-Instanz, die zum Zuordnen von Objekten verwendet wird.
///
- public EmailTemplateController(IMapper mapper)
+ public EmailTemplateController(IMapper mapper, IEmailTemplateRepository repository, ILogger logger, IMediator mediator)
{
_mapper = mapper;
+ _repository = repository;
+ _logger = logger;
+ _mediator = mediator;
}
///
@@ -45,17 +61,25 @@ public class EmailTemplateController : ControllerBase
/// Wenn der Benutzer nicht authentifiziert ist.
/// Wenn die gesuchte Abfrage nicht gefunden wird.
[HttpGet]
- public IActionResult Get([FromQuery] ReadEmailTemplateQuery? emailTemplate = null)
+ public async Task Get([FromQuery] ReadEmailTemplateQuery? emailTemplate = null)
{
- // Implementation logic here
- return Ok(); // Placeholder for actual implementation
+ if (emailTemplate is null || (emailTemplate.Id is null && emailTemplate.Type is null))
+ {
+ var temps = await _repository.ReadAllAsync();
+ return Ok(_mapper.Map>(temps));
+ }
+ else
+ {
+ var temp = await _mediator.Send(emailTemplate);
+ return temp is null ? NotFound() : Ok(temp);
+ }
}
///
- /// Updates an email template or resets it if no update command is provided.
+ /// Updates an temp template or resets it if no update command is provided.
/// Aktualisiert eine E-Mail-Vorlage oder setzt sie zurück, wenn kein Aktualisierungsbefehl angegeben ist.
///
- /// Die E-Mail-Vorlagenabfrage.
+ /// Die E-Mail-Vorlagenabfrage.
/// Der Aktualisierungsbefehl für die E-Mail-Vorlage.
/// Wird auf Standardwert aktualisiert, wenn die Anfrage ohne http-Body gesendet wird.
///
@@ -73,19 +97,22 @@ public class EmailTemplateController : ControllerBase
/// Wenn der Benutzer nicht authentifiziert ist.
/// Wenn die gesuchte Abfrage nicht gefunden wird.
[HttpPut]
- public IActionResult Update([FromQuery] EmailTemplateQuery email, [FromBody] UpdateEmailTemplateCommand? update = null)
+ public async Task Update([FromQuery] EmailTemplateQuery? temp = null, [FromBody] UpdateEmailTemplateCommand? update = null)
{
if (update is null)
{
- var reset = _mapper.Map(email);
- // Logic for resetting the email template
+ await _mediator.Send(new ResetEmailTemplateCommand(temp));
+ return Ok();
+ }
+ else if (temp is null)
+ {
+ return BadRequest("No both id and type");
}
else
{
- update.EmailTemplateQuery = email;
- // Logic for updating the email template
+ update.EmailTemplateQuery = temp;
+ await _mediator.Send(update);
+ return Ok();
}
-
- return Ok(); // Placeholder for actual implementation
}
}
diff --git a/EnvelopeGenerator.GeneratorAPI/Controllers/EnvelopeController.cs b/EnvelopeGenerator.GeneratorAPI/Controllers/EnvelopeController.cs
index 049c0186..9deca564 100644
--- a/EnvelopeGenerator.GeneratorAPI/Controllers/EnvelopeController.cs
+++ b/EnvelopeGenerator.GeneratorAPI/Controllers/EnvelopeController.cs
@@ -1,8 +1,13 @@
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.Contracts.Services;
+using EnvelopeGenerator.Application.Envelopes.Commands;
using EnvelopeGenerator.Application.Envelopes.Queries.Read;
+using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Newtonsoft.Json;
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations.Schema;
namespace EnvelopeGenerator.GeneratorAPI.Controllers;
@@ -27,16 +32,19 @@ public class EnvelopeController : ControllerBase
{
private readonly ILogger _logger;
private readonly IEnvelopeService _envelopeService;
+ private readonly IMediator _mediator;
///
/// Erstellt eine neue Instanz des EnvelopeControllers.
///
/// Der Logger, der für das Protokollieren von Informationen verwendet wird.
/// Der Dienst, der für die Verarbeitung von Umschlägen zuständig ist.
- public EnvelopeController(ILogger logger, IEnvelopeService envelopeService)
+ ///
+ public EnvelopeController(ILogger logger, IEnvelopeService envelopeService, IMediator mediator)
{
_logger = logger;
_envelopeService = envelopeService;
+ _mediator = mediator;
}
///
@@ -53,26 +61,97 @@ public class EnvelopeController : ControllerBase
[HttpGet]
public async Task GetAsync([FromQuery] ReadEnvelopeQuery envelope)
{
- try
+ if (User.GetId() is int intId)
+ return await _envelopeService.ReadByUserAsync(intId, min_status: envelope.Status, max_status: envelope.Status).ThenAsync(
+ Success: envelopes =>
+ {
+ if (envelope.Id is int id)
+ envelopes = envelopes.Where(e => e.Id == id);
+
+ if (envelope.Status is int status)
+ envelopes = envelopes.Where(e => e.Status == status);
+
+ if (envelope.Uuid is string uuid)
+ envelopes = envelopes.Where(e => e.Uuid == uuid);
+
+ return Ok(envelopes);
+ },
+ Fail: IActionResult (msg, ntc) =>
+ {
+ _logger.LogNotice(ntc);
+ return StatusCode(StatusCodes.Status500InternalServerError);
+ });
+ else
{
- if (User.GetId() is int intId)
- return await _envelopeService.ReadByUserAsync(intId, min_status: envelope.Status, max_status: envelope.Status).ThenAsync(
- Success: Ok,
- Fail: IActionResult (msg, ntc) =>
- {
- _logger.LogNotice(ntc);
- return StatusCode(StatusCodes.Status500InternalServerError);
- });
- else
- {
- _logger.LogError("Trotz erfolgreicher Autorisierung wurde die Benutzer-ID nicht als Ganzzahl erkannt. Dies könnte auf eine fehlerhafte Erstellung der Anspruchsliste zurückzuführen sein.");
- return StatusCode(StatusCodes.Status500InternalServerError);
- }
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "{Message}", ex.Message);
+ _logger.LogError("Trotz erfolgreicher Autorisierung wurde die Benutzer-ID nicht als Ganzzahl erkannt. Dies könnte auf eine fehlerhafte Erstellung der Anspruchsliste zurückzuführen sein.");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
+
+ ///
+ /// Ruft das Ergebnis eines Dokuments basierend auf der ID ab.
+ ///
+ /// Die eindeutige ID des Umschlags.
+ /// Gibt an, ob das Dokument inline angezeigt werden soll (true) oder als Download bereitgestellt wird (false).
+ /// Eine IActionResult-Instanz, die das Dokument oder einen Fehlerstatus enthält.
+ /// Das Dokument wurde erfolgreich abgerufen.
+ /// Das Dokument wurde nicht gefunden oder ist nicht verfügbar.
+ /// Ein unerwarteter Fehler ist aufgetreten.
+ [HttpGet("doc-result")]
+ public async Task GetDocResultAsync([FromQuery] int id, [FromQuery] bool view = false)
+ {
+ if (User.GetId() is int intId)
+ return await _envelopeService.ReadByUserAsync(intId).ThenAsync(
+ Success: envelopes =>
+ {
+ var envelope = envelopes.Where(e => e.Id == id).FirstOrDefault();
+
+ if (envelope is null)
+ return NotFound("Envelope not available.");
+ else if (envelope?.DocResult is null)
+ return NotFound("The document has not been fully signed or the result has not yet been released.");
+ else
+ {
+ if (view)
+ {
+ Response.Headers.Append("Content-Disposition", "inline; filename=\"" + envelope.Uuid + ".pdf\"");
+ return File(envelope.DocResult, "application/pdf");
+ }
+ else
+ return File(envelope.DocResult, "application/pdf", $"{envelope.Uuid}.pdf");
+ }
+ },
+ Fail: IActionResult (msg, ntc) =>
+ {
+ _logger.LogNotice(ntc);
+ return StatusCode(StatusCodes.Status500InternalServerError);
+ });
+ else
+ {
+ _logger.LogError("Trotz erfolgreicher Autorisierung wurde die Benutzer-ID nicht als Ganzzahl erkannt. Dies könnte auf eine fehlerhafte Erstellung der Anspruchsliste zurückzuführen sein.");
+ return StatusCode(StatusCodes.Status500InternalServerError);
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ [NonAction]
+ [Authorize]
+ [HttpPost]
+ public async Task CreateAsync([FromQuery] CreateEnvelopeCommand envelope)
+ {
+ envelope.UserId = User.GetId();
+ var res = await _mediator.Send(envelope);
+
+ if (res is null)
+ {
+ _logger.LogError("Failed to create envelope. Envelope details: {EnvelopeDetails}", JsonConvert.SerializeObject(envelope));
+ return StatusCode(StatusCodes.Status500InternalServerError);
+ }
+ else
+ return Ok(res);
+ }
}
diff --git a/EnvelopeGenerator.GeneratorAPI/Controllers/EnvelopeReceiverController.cs b/EnvelopeGenerator.GeneratorAPI/Controllers/EnvelopeReceiverController.cs
index dd8e828a..2fa2245b 100644
--- a/EnvelopeGenerator.GeneratorAPI/Controllers/EnvelopeReceiverController.cs
+++ b/EnvelopeGenerator.GeneratorAPI/Controllers/EnvelopeReceiverController.cs
@@ -1,11 +1,20 @@
-using DigitalData.Core.DTO;
+using AutoMapper;
+using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.Contracts.Services;
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using EnvelopeGenerator.Application.DTOs.Receiver;
using EnvelopeGenerator.Application.EnvelopeReceivers.Commands.Create;
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries.Read;
using EnvelopeGenerator.Application.Envelopes.Queries.ReceiverName;
+using EnvelopeGenerator.Application.SQL;
+using EnvelopeGenerator.Domain.Entities;
+using EnvelopeGenerator.GeneratorAPI.Models;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.Options;
+using System.Data;
namespace EnvelopeGenerator.GeneratorAPI.Controllers;
@@ -26,17 +35,37 @@ public class EnvelopeReceiverController : ControllerBase
private readonly IMediator _mediator;
+ private readonly IMapper _mapper;
+
+ private readonly IEnvelopeExecutor _envelopeExecutor;
+
+ private readonly IEnvelopeReceiverExecutor _erExecutor;
+
+ private readonly IDocumentExecutor _documentExecutor;
+
+ private readonly string _cnnStr;
+
///
/// Konstruktor für den EnvelopeReceiverController.
///
/// Logger-Instanz zur Protokollierung von Informationen und Fehlern.
/// Service zur Verwaltung von Umschlagempfängern.
/// Mediator-Instanz zur Verarbeitung von Befehlen und Abfragen.
- public EnvelopeReceiverController(ILogger logger, IEnvelopeReceiverService envelopeReceiverService, IMediator mediator)
+ ///
+ ///
+ ///
+ ///
+ ///
+ public EnvelopeReceiverController(ILogger logger, IEnvelopeReceiverService envelopeReceiverService, IMediator mediator, IMapper mapper, IEnvelopeExecutor envelopeExecutor, IEnvelopeReceiverExecutor erExecutor, IDocumentExecutor documentExecutor, IOptions csOpt)
{
_logger = logger;
_erService = envelopeReceiverService;
_mediator = mediator;
+ _mapper = mapper;
+ _envelopeExecutor = envelopeExecutor;
+ _erExecutor = erExecutor;
+ _documentExecutor = documentExecutor;
+ _cnnStr = csOpt.Value.Value;
}
///
@@ -55,30 +84,29 @@ public class EnvelopeReceiverController : ControllerBase
[HttpGet]
public async Task GetEnvelopeReceiver([FromQuery] ReadEnvelopeReceiverQuery envelopeReceiver)
{
- try
- {
- var username = User.GetUsername();
+ var username = User.GetUsernameOrDefault();
- if (username is null)
- {
- _logger.LogError(@"Envelope Receiver dto cannot be sent because username claim is null. Potential authentication and authorization error. The value of other claims are [id: {id}], [username: {username}], [name: {name}], [prename: {prename}], [email: {email}].",
- User.GetId(), User.GetUsername(), User.GetName(), User.GetPrename(), User.GetEmail());
- return StatusCode(StatusCodes.Status500InternalServerError);
- }
-
- return await _erService.ReadByUsernameAsync(username: username).ThenAsync(
- Success: Ok,
- Fail: IActionResult (msg, ntc) =>
- {
- _logger.LogNotice(ntc);
- return StatusCode(StatusCodes.Status500InternalServerError, msg);
- });
- }
- catch (Exception ex)
+ if (username is null)
{
- _logger.LogError(ex, "An unexpected error occurred. {message}", ex.Message);
- return new StatusCodeResult(StatusCodes.Status500InternalServerError);
+ _logger.LogError(@"Envelope Receiver dto cannot be sent because username claim is null. Potential authentication and authorization error. The value of other claims are [id: {id}], [username: {username}], [name: {name}], [prename: {prename}], [email: {email}].",
+ User.GetId(), User.GetUsernameOrDefault(), User.GetNameOrDefault(), User.GetPrenameOrDefault(), User.GetEmailOrDefault());
+ return StatusCode(StatusCodes.Status500InternalServerError);
}
+
+ return await _erService.ReadByUsernameAsync(
+ username: username,
+ min_status: envelopeReceiver.Status?.Min,
+ max_status: envelopeReceiver.Status?.Max,
+ envelopeQuery: envelopeReceiver.Envelope,
+ receiverQuery: envelopeReceiver.Receiver,
+ ignore_statuses: envelopeReceiver.Status?.Ignore ?? Array.Empty())
+ .ThenAsync(
+ Success: Ok,
+ Fail: IActionResult (msg, ntc) =>
+ {
+ _logger.LogNotice(ntc);
+ return StatusCode(StatusCodes.Status500InternalServerError, msg);
+ });
}
///
@@ -97,31 +125,25 @@ public class EnvelopeReceiverController : ControllerBase
[HttpGet("salute")]
public async Task GetReceiverName([FromQuery] ReadReceiverNameQuery receiverName)
{
- try
- {
- return await _erService.ReadLastUsedReceiverNameByMail(receiverName.EmailAddress).ThenAsync(
- Success: res => res is null ? Ok(string.Empty) : Ok(res),
- Fail: IActionResult (msg, ntc) =>
- {
- if (ntc.HasFlag(Flag.NotFound))
- return NotFound();
+ if (receiverName.EmailAddress is null)
+ return BadRequest();
- _logger.LogNotice(ntc);
- return StatusCode(StatusCodes.Status500InternalServerError);
- });
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "{message}", ex.Message);
- return StatusCode(StatusCodes.Status500InternalServerError);
- }
+ return await _erService.ReadLastUsedReceiverNameByMail(receiverName.EmailAddress).ThenAsync(
+ Success: res => res is null ? Ok(string.Empty) : Ok(res),
+ Fail: IActionResult (msg, ntc) =>
+ {
+ if (ntc.HasFlag(Flag.NotFound))
+ return NotFound();
+
+ _logger.LogNotice(ntc);
+ return StatusCode(StatusCodes.Status500InternalServerError);
+ });
}
///
/// Datenübertragungsobjekt mit Informationen zu Umschlägen, Empfängern und Unterschriften.
///
- ///
- /// Token to cancel the operation
+ ///
/// HTTP-Antwort
///
/// Sample request:
@@ -157,9 +179,133 @@ public class EnvelopeReceiverController : ControllerBase
/// Es handelt sich um einen unerwarteten Fehler. Die Protokolle sollten überprüft werden.
[Authorize]
[HttpPost]
- public async Task CreateAsync([FromBody] CreateEnvelopeReceiverCommand createEnvelopeQuery, CancellationToken cancellationToken)
+ public async Task CreateAsync([FromBody] CreateEnvelopeReceiverCommand request)
{
- await _mediator.Send(createEnvelopeQuery, cancellationToken);
- return Accepted();
+ CancellationToken cancel = default;
+ int userId = User.GetId();
+
+ #region Create Envelope
+ var envelope = await _envelopeExecutor.CreateEnvelopeAsync(userId, request.Title, request.Message, request.TFAEnabled, cancel);
+ #endregion
+
+ #region Add receivers
+ List sentReceivers = new();
+ List unsentReceivers = new();
+
+ foreach (var receiver in request.Receivers)
+ {
+ var envelopeReceiver = await _erExecutor.AddEnvelopeReceiverAsync(envelope.Uuid, receiver.EmailAddress, receiver.Salution, receiver.PhoneNumber, cancel);
+
+ if (envelopeReceiver is null)
+ unsentReceivers.Add(receiver);
+ else
+ sentReceivers.Add(envelopeReceiver);
+ }
+
+ var res = _mapper.Map(envelope);
+ res.UnsentReceivers = unsentReceivers;
+ res.SentReceiver = _mapper.Map>(sentReceivers.Select(er => er.Receiver));
+ #endregion
+
+ #region Add document
+ var document = await _documentExecutor.CreateDocumentAsync(request.Document.DataAsBase64, envelope.Uuid, cancel);
+
+ if (document is null)
+ return StatusCode(StatusCodes.Status500InternalServerError, "Document creation is failed.");
+ #endregion
+
+ #region Add document element
+ // @DOC_ID, @RECEIVER_ID, @POSITION_X, @POSITION_Y, @PAGE
+ string sql = @"
+ DECLARE @OUT_SUCCESS bit;
+
+ EXEC [dbo].[PRSIG_API_ADD_DOC_RECEIVER_ELEM]
+ {0},
+ {1},
+ {2},
+ {3},
+ {4},
+ @OUT_SUCCESS OUTPUT;
+
+ SELECT @OUT_SUCCESS as [@OUT_SUCCESS];";
+
+ foreach (var rcv in res.SentReceiver)
+ foreach (var sign in request.Receivers.Where(r => r.EmailAddress == rcv.EmailAddress).FirstOrDefault()?.Signatures ?? Array.Empty())
+ {
+ using (SqlConnection conn = new(_cnnStr))
+ {
+ conn.Open();
+
+ var formattedSQL = string.Format(sql, document.Id.ToSqlParam(), rcv.Id.ToSqlParam(), sign.X.ToSqlParam(), sign.Y.ToSqlParam(), sign.Page.ToSqlParam());
+
+ using SqlCommand cmd = new SqlCommand(formattedSQL, conn);
+ cmd.CommandType = CommandType.Text;
+
+ using SqlDataReader reader = cmd.ExecuteReader();
+ if (reader.Read())
+ {
+ bool outSuccess = reader.GetBoolean(0);
+ }
+ }
+ }
+ #endregion
+
+ #region Create history
+ // ENV_UID, STATUS_ID, USER_ID,
+ string sql_hist = @"
+ USE [DD_ECM]
+
+ DECLARE @OUT_SUCCESS bit;
+
+ EXEC [dbo].[PRSIG_API_ADD_HISTORY_STATE]
+ {0},
+ {1},
+ {2},
+ @OUT_SUCCESS OUTPUT;
+
+ SELECT @OUT_SUCCESS as [@OUT_SUCCESS];";
+
+ using (SqlConnection conn = new(_cnnStr))
+ {
+ conn.Open();
+ var formattedSQL_hist = string.Format(sql_hist, envelope.Uuid.ToSqlParam(), 1003.ToSqlParam(), userId.ToSqlParam());
+ using (SqlCommand cmd = new SqlCommand(formattedSQL_hist, conn))
+ {
+ cmd.CommandType = CommandType.Text;
+
+ using (SqlDataReader reader = cmd.ExecuteReader())
+ {
+ if (reader.Read())
+ {
+ bool outSuccess = reader.GetBoolean(0);
+ }
+ }
+ }
+ }
+ #endregion
+
+ return Ok(res);
}
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool IsBase64String(string input)
+ {
+ if (string.IsNullOrWhiteSpace(input))
+ return false;
+
+ try
+ {
+ Convert.FromBase64String(input);
+ return true;
+ }
+ catch (FormatException)
+ {
+ return false;
+ }
+ }
+
}
\ No newline at end of file
diff --git a/EnvelopeGenerator.GeneratorAPI/Controllers/EnvelopeTypeController.cs b/EnvelopeGenerator.GeneratorAPI/Controllers/EnvelopeTypeController.cs
index bf2ea067..c49d9a3b 100644
--- a/EnvelopeGenerator.GeneratorAPI/Controllers/EnvelopeTypeController.cs
+++ b/EnvelopeGenerator.GeneratorAPI/Controllers/EnvelopeTypeController.cs
@@ -21,20 +21,12 @@ public class EnvelopeTypeController : ControllerBase
[HttpGet]
public async Task GetAllAsync()
{
- try
- {
- return await _service.ReadAllAsync().ThenAsync(
- Success: Ok,
- Fail: IActionResult (msg, ntc) =>
- {
- _logger.LogNotice(ntc);
- return ntc.HasFlag(Flag.NotFound) ? NotFound() : StatusCode(StatusCodes.Status500InternalServerError);
- });
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "{Message}", ex.Message);
- return StatusCode(StatusCodes.Status500InternalServerError);
- }
+ return await _service.ReadAllAsync().ThenAsync(
+ Success: Ok,
+ Fail: IActionResult (msg, ntc) =>
+ {
+ _logger.LogNotice(ntc);
+ return ntc.HasFlag(Flag.NotFound) ? NotFound() : StatusCode(StatusCodes.Status500InternalServerError);
+ });
}
}
\ No newline at end of file
diff --git a/EnvelopeGenerator.GeneratorAPI/Controllers/HistoryController.cs b/EnvelopeGenerator.GeneratorAPI/Controllers/HistoryController.cs
index 0569bf0d..5c55b567 100644
--- a/EnvelopeGenerator.GeneratorAPI/Controllers/HistoryController.cs
+++ b/EnvelopeGenerator.GeneratorAPI/Controllers/HistoryController.cs
@@ -1,8 +1,10 @@
using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
using EnvelopeGenerator.Application.Contracts.Services;
using EnvelopeGenerator.Application.Histories.Queries.Read;
+using EnvelopeGenerator.Extensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Caching.Memory;
using static EnvelopeGenerator.Common.Constants;
@@ -20,15 +22,18 @@ public class HistoryController : ControllerBase
private readonly IEnvelopeHistoryService _service;
+ private readonly IMemoryCache _memoryCache;
+
///
/// Konstruktor für den HistoryController.
///
/// Der Logger, der für das Protokollieren von Informationen verwendet wird.
/// Der Dienst, der für die Verarbeitung der Umschlaghistorie verantwortlich ist.
- public HistoryController(ILogger logger, IEnvelopeHistoryService service)
+ public HistoryController(ILogger logger, IEnvelopeHistoryService service, IMemoryCache memoryCache)
{
_logger = logger;
_service = service;
+ _memoryCache = memoryCache;
}
///
@@ -47,19 +52,9 @@ public class HistoryController : ControllerBase
///
[HttpGet("related")]
[Authorize]
- public IActionResult GetReferenceTypes()
+ public IActionResult GetReferenceTypes(ReferenceType? referenceType = null)
{
- // Enum zu Schlüssel-Wert-Paar
- var referenceTypes = Enum.GetValues(typeof(ReferenceType))
- .Cast()
- .ToDictionary(rt =>
- {
- var key = rt.ToString();
- var keyAsCamelCase = char.ToLower(key[0]) + key[1..];
- return keyAsCamelCase;
- }, rt => (int)rt);
-
- return Ok(referenceTypes);
+ return referenceType is null ? Ok(_memoryCache.GetEnumAsDictionary()) : Ok(referenceType.ToString());
}
///
@@ -91,7 +86,7 @@ public class HistoryController : ControllerBase
/// 3004: MessageDeletionSent
/// 3005: MessageCompletionSent
///
- ///
+ ///
/// Abfrageparameter, der angibt, auf welche Referenz sich der Status bezieht.
/// 0 - Sender: Historische Datensätze, die sich auf den Status des Absenders beziehen. Sie haben Statuscodes, die mit 1* beginnen.
/// 1 - Receiver: Historische Datensätze über den Status der Empfänger. Diese haben Statuscodes, die mit 2* beginnen.
@@ -102,19 +97,9 @@ public class HistoryController : ControllerBase
///
[HttpGet("status")]
[Authorize]
- public IActionResult GetEnvelopeStatus([FromQuery] ReferenceType? related = null)
+ public IActionResult GetEnvelopeStatus([FromQuery] EnvelopeStatus? status = null)
{
- // Enum zu Schlüssel-Wert-Paar
- var referenceTypes = Enum.GetValues(typeof(EnvelopeStatus))
- .Cast()
- .ToDictionary(rt =>
- {
- var key = rt.ToString();
- var keyAsCamelCase = char.ToLower(key[0]) + key[1..];
- return keyAsCamelCase;
- }, rt => (int)rt);
-
- return Ok(referenceTypes);
+ return status is null ? Ok(_memoryCache.GetEnumAsDictionary()) : Ok(status.ToString());
}
///
@@ -131,6 +116,8 @@ public class HistoryController : ControllerBase
[Authorize]
public async Task GetAllAsync([FromQuery] ReadHistoryQuery history)
{
+
+
bool withReceiver = false;
bool withSender = false;
diff --git a/EnvelopeGenerator.GeneratorAPI/Controllers/ReceiverController.cs b/EnvelopeGenerator.GeneratorAPI/Controllers/ReceiverController.cs
index 9920b5de..0062a43f 100644
--- a/EnvelopeGenerator.GeneratorAPI/Controllers/ReceiverController.cs
+++ b/EnvelopeGenerator.GeneratorAPI/Controllers/ReceiverController.cs
@@ -38,24 +38,23 @@ public class ReceiverController : CRUDControllerBaseWithErrorHandling Get([FromQuery] ReadReceiverQuery receiver)
{
- if (receiver.EmailAddress is null && receiver.Signature is null)
+ if (receiver.Id is null && receiver.EmailAddress is null && receiver.Signature is null)
return await base.GetAll();
- try
- {
- return await _service.ReadByAsync(emailAddress: receiver.EmailAddress, signature: receiver.Signature).ThenAsync(
- Success: Ok,
- Fail: IActionResult (msg, ntc) =>
- {
- _logger.LogNotice(ntc);
- return StatusCode(StatusCodes.Status500InternalServerError);
- });
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "{Message}", ex.Message);
- return StatusCode(StatusCodes.Status500InternalServerError);
- }
+ if (receiver.Id is int id)
+ return await _service.ReadByIdAsync(id).ThenAsync(
+ Success: Ok,
+ Fail: IActionResult (msg, ntc) =>
+ {
+ return NotFound();
+ });
+
+ return await _service.ReadByAsync(emailAddress: receiver.EmailAddress, signature: receiver.Signature).ThenAsync(
+ Success: Ok,
+ Fail: IActionResult (msg, ntc) =>
+ {
+ return NotFound();
+ });
}
#region REMOVED ENDPOINTS
diff --git a/EnvelopeGenerator.GeneratorAPI/EnvelopeGenerator.GeneratorAPI.csproj b/EnvelopeGenerator.GeneratorAPI/EnvelopeGenerator.GeneratorAPI.csproj
index 6e922bad..418df9b2 100644
--- a/EnvelopeGenerator.GeneratorAPI/EnvelopeGenerator.GeneratorAPI.csproj
+++ b/EnvelopeGenerator.GeneratorAPI/EnvelopeGenerator.GeneratorAPI.csproj
@@ -10,9 +10,9 @@
Digital Data GmbH
Digital Data GmbH
EnvelopeGenerator.GeneratorAPI
- 1.2.0
- 1.2.0
- 1.2.0
+ 1.2.2
+ 1.2.2
+ 1.2.2
Copyright © 2025 Digital Data GmbH. All rights reserved.
bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml
diff --git a/EnvelopeGenerator.GeneratorAPI/Middleware/ExceptionHandlingMiddleware.cs b/EnvelopeGenerator.GeneratorAPI/Middleware/ExceptionHandlingMiddleware.cs
new file mode 100644
index 00000000..f9d700a1
--- /dev/null
+++ b/EnvelopeGenerator.GeneratorAPI/Middleware/ExceptionHandlingMiddleware.cs
@@ -0,0 +1,84 @@
+namespace EnvelopeGenerator.GeneratorAPI.Middleware;
+
+using EnvelopeGenerator.Application.Exceptions;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using System.Net;
+using System.Text.Json;
+
+///
+/// Middleware for handling exceptions globally in the application.
+/// Captures exceptions thrown during the request pipeline execution,
+/// logs them, and returns an appropriate HTTP response with a JSON error message.
+///
+public class ExceptionHandlingMiddleware
+{
+ private readonly RequestDelegate _next;
+ private readonly ILogger _logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The next middleware in the request pipeline.
+ /// The logger instance for logging exceptions.
+ public ExceptionHandlingMiddleware(RequestDelegate next, ILogger logger)
+ {
+ _next = next;
+ _logger = logger;
+ }
+
+ ///
+ /// Invokes the middleware to handle the HTTP request.
+ ///
+ /// The HTTP context of the current request.
+ /// A task that represents the asynchronous operation.
+ public async Task InvokeAsync(HttpContext context)
+ {
+ try
+ {
+ await _next(context); // Continue down the pipeline
+ }
+ catch (Exception ex)
+ {
+ await HandleExceptionAsync(context, ex, _logger);
+ }
+ }
+
+ ///
+ /// Handles exceptions by logging them and writing an appropriate JSON response.
+ ///
+ /// The HTTP context of the current request.
+ /// The exception that occurred.
+ /// The logger instance for logging the exception.
+ /// A task that represents the asynchronous operation.
+ private static async Task HandleExceptionAsync(HttpContext context, Exception exception, ILogger logger)
+ {
+ context.Response.ContentType = "application/json";
+
+ string message;
+
+ switch (exception)
+ {
+ case BadRequestException badRequestEx:
+ context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
+ message = badRequestEx.Message;
+ break;
+
+ case NotFoundException notFoundEx:
+ context.Response.StatusCode = (int)HttpStatusCode.NotFound;
+ message = notFoundEx.Message;
+ break;
+
+ default:
+ logger.LogError(exception, "Unhandled exception occurred.");
+ context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
+ message = "An unexpected error occurred.";
+ break;
+ }
+
+ await context.Response.WriteAsync(JsonSerializer.Serialize(new
+ {
+ message
+ }));
+ }
+}
diff --git a/EnvelopeGenerator.GeneratorAPI/Models/ConnectionString.cs b/EnvelopeGenerator.GeneratorAPI/Models/ConnectionString.cs
new file mode 100644
index 00000000..0adb8e5b
--- /dev/null
+++ b/EnvelopeGenerator.GeneratorAPI/Models/ConnectionString.cs
@@ -0,0 +1,12 @@
+namespace EnvelopeGenerator.GeneratorAPI.Models;
+
+///
+///
+///
+public class ConnectionString
+{
+ ///
+ ///
+ ///
+ public string Value { get; set; } = string.Empty;
+}
diff --git a/EnvelopeGenerator.GeneratorAPI/Models/Login.cs b/EnvelopeGenerator.GeneratorAPI/Models/Login.cs
index 7aaeea50..a3f9d30e 100644
--- a/EnvelopeGenerator.GeneratorAPI/Models/Login.cs
+++ b/EnvelopeGenerator.GeneratorAPI/Models/Login.cs
@@ -6,8 +6,8 @@ namespace EnvelopeGenerator.GeneratorAPI.Models;
/// Repräsentiert ein Login-Modell mit erforderlichem Passwort und optionaler ID und Benutzername.
///
/// Das erforderliche Passwort für das Login.
-/// Die optionale ID des Benutzers.
+/// Die optionale ID des Benutzers.
/// Der optionale Benutzername.
-public record Login([Required] string Password, int? Id = null, string? Username = null)
+public record Login([Required] string Password, int? UserId = null, string? Username = null)
{
}
diff --git a/EnvelopeGenerator.GeneratorAPI/Program.cs b/EnvelopeGenerator.GeneratorAPI/Program.cs
index bfdb024b..b41d4b62 100644
--- a/EnvelopeGenerator.GeneratorAPI/Program.cs
+++ b/EnvelopeGenerator.GeneratorAPI/Program.cs
@@ -15,6 +15,7 @@ using EnvelopeGenerator.GeneratorAPI.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using DigitalData.Core.Abstractions.Security.Extensions;
+using EnvelopeGenerator.GeneratorAPI.Middleware;
var builder = WebApplication.CreateBuilder(args);
@@ -90,6 +91,9 @@ builder.Services.AddSwaggerGen(options =>
builder.Services.AddOpenApi();
// DbContext
var connStr = config.GetConnectionString("Default") ?? throw new InvalidOperationException("There is no default connection string in appsettings.json.");
+
+builder.Services.Configure(cs => cs.Value = connStr);
+
builder.Services.AddDbContext(options => options.UseSqlServer(connStr));
builder.Services.AddAuthHubClient(config.GetSection("AuthClientParams"));
@@ -159,13 +163,15 @@ builder.Services.AddCookieBasedLocalizer() ;
// Envelope generator serives
builder.Services
- .AddEnvelopeGeneratorRepositories()
+ .AddEnvelopeGeneratorInfrastructureServices(sqlExecutorConfigureOptions: executor => executor.ConnectionString = connStr)
.AddEnvelopeGeneratorServices(config);
var app = builder.Build();
deferredProvider.Factory = () => app.Services;
+app.UseMiddleware();
+
app.MapOpenApi();
// Configure the HTTP request pipeline.
diff --git a/EnvelopeGenerator.GeneratorAPI/Properties/launchSettings.json b/EnvelopeGenerator.GeneratorAPI/Properties/launchSettings.json
index 8857b777..1b89a1c9 100644
--- a/EnvelopeGenerator.GeneratorAPI/Properties/launchSettings.json
+++ b/EnvelopeGenerator.GeneratorAPI/Properties/launchSettings.json
@@ -22,9 +22,9 @@
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
- "launchBrowser": true,
+ "launchBrowser": false,
"launchUrl": "swagger",
- "applicationUrl": "https://localhost:7174;http://localhost:5131",
+ "applicationUrl": "https://localhost:8088;http://localhost:5131",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
diff --git a/EnvelopeGenerator.GeneratorAPI/appsettings.json b/EnvelopeGenerator.GeneratorAPI/appsettings.json
index c03dbb20..a4ab1aa8 100644
--- a/EnvelopeGenerator.GeneratorAPI/appsettings.json
+++ b/EnvelopeGenerator.GeneratorAPI/appsettings.json
@@ -36,5 +36,211 @@
"QueryString": "AuthToken",
"Issuer": "auth.digitaldata.works",
"Audience": "work-flow.digitaldata.works"
+ },
+ "PSPDFKitLicenseKey": "SXCtGGY9XA-31OGUXQK-r7c6AkdLGPm2ljuyDr1qu0kkhLvydg-Do-fxpNUF4Rq3fS_xAnZRNFRHbXpE6sQ2BMcCSVTcXVJO6tPviexjpiT-HnrDEySlUERJnnvh-tmeOWprxS6BySPnSILkmaVQtUfOIUS-cUbvvEYHTvQBKbSF8di4XHQFyfv49ihr51axm3NVV3AXwh2EiKL5C5XdqBZ4sQ4O7vXBjM2zvxdPxlxdcNYmiU83uAzw7B83O_jubPzya4CdUHh_YH7Nlp2gP56MeG1Sw2JhMtfG3Rj14Sg4ctaeL9p6AEWca5dDjJ2li5tFIV2fQSsw6A_cowLu0gtMm5i8IfJXeIcQbMC2-0wGv1oe9hZYJvFMdzhTM_FiejM0agemxt3lJyzuyP8zbBSOgp7Si6A85krLWPZptyZBTG7pp7IHboUHfPMxCXqi-zMsqewOJtQBE2mjntU-lPryKnssOpMPfswwQX7QSkJYV5EMqNmEhQX6mEkp2wcqFzMC7bJQew1aO4pOpvChUaMvb1vgRek0HxLag0nwQYX2YrYGh7F_xXJs-8HNwJe8H0-eW4x4faayCgM5rB5772CCCsD9ThZcvXFrjNHHLGJ8WuBUFm6LArvSfFQdii_7j-_sqHMpeKZt26NFgivj1A==",
+ "Content-Security-Policy": [ // The first format parameter {0} will be replaced by the nonce value.
+ "default-src 'self'",
+ "script-src 'self' 'nonce-{0}' 'unsafe-eval'",
+ "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com:*",
+ "img-src 'self' data: https: blob:",
+ "font-src 'self' https://fonts.gstatic.com:*",
+ "connect-src 'self' https://nominatim.openstreetmap.org:* http://localhost:* https://localhost:* ws://localhost:* wss://localhost:* blob:",
+ "frame-src 'self'",
+ "media-src 'self'",
+ "object-src 'self'"
+ ],
+ "NLog": {
+ "throwConfigExceptions": true,
+ "variables": {
+ "logDirectory": "E:\\LogFiles\\Digital Data\\signFlow",
+ "logFileNamePrefix": "${shortdate}-ECM.EnvelopeGenerator.Web"
+ },
+ "targets": {
+ "infoLogs": {
+ "type": "File",
+ "fileName": "${logDirectory}\\${logFileNamePrefix}-Info.log",
+ "maxArchiveDays": 30
+ },
+ "errorLogs": {
+ "type": "File",
+ "fileName": "${logDirectory}\\${logFileNamePrefix}-Error.log",
+ "maxArchiveDays": 30
+ },
+ "criticalLogs": {
+ "type": "File",
+ "fileName": "${logDirectory}\\${logFileNamePrefix}-Critical.log",
+ "maxArchiveDays": 30
+ }
+ },
+ // Trace, Debug, Info, Warn, Error and *Fatal*
+ "rules": [
+ {
+ "logger": "*",
+ "minLevel": "Info",
+ "maxLevel": "Warn",
+ "writeTo": "infoLogs"
+ },
+ {
+ "logger": "*",
+ "level": "Error",
+ "writeTo": "errorLogs"
+ },
+ {
+ "logger": "*",
+ "level": "Fatal",
+ "writeTo": "criticalLogs"
+ }
+ ]
+ },
+ "ContactLink": {
+ "Label": "Kontakt",
+ "Href": "https://digitaldata.works/",
+ "HrefLang": "de",
+ "Target": "_blank",
+ "Title": "Digital Data GmbH"
+ },
+ /* Resx naming format is -> Resource.language.resx (eg: Resource.de_DE.resx).
+ To add a new language, first you should write the required resx file.
+ first is the default culture name. */
+ "Cultures": [
+ {
+ "Language": "de-DE",
+ "FIClass": "fi-de"
+ },
+ {
+ "Language": "en-US",
+ "FIClass": "fi-us"
+ }
+ ],
+ "DisableMultiLanguage": false,
+ "Regexes": [
+ {
+ "Pattern": "/^\\p{L}+(?:([\\ \\-\\']|(\\.\\ ))\\p{L}+)*$/u",
+ "Name": "City",
+ "Platforms": [ ".NET" ]
+ },
+ {
+ "Pattern": "/^[a-zA-Z\\u0080-\\u024F]+(?:([\\ \\-\\']|(\\.\\ ))[a-zA-Z\\u0080-\\u024F]+)*$/",
+ "Name": "City",
+ "Platforms": [ "javascript" ]
+ }
+ ],
+ "CustomImages": {
+ "App": {
+ "Src": "/img/DD_signFLOW_LOGO.png",
+ "Classes": {
+ "Main": "signFlow-logo"
+ }
+ },
+ "Company": {
+ "Src": "/img/digital_data.svg",
+ "Classes": {
+ "Show": "dd-show-logo",
+ "Locked": "dd-locked-logo"
+ }
+ }
+ },
+ "DispatcherParams": {
+ "SendingProfile": 1,
+ "AddedWho": "DDEnvelopGenerator",
+ "ReminderTypeId": 202377,
+ "EmailAttmt1": ""
+ },
+ "MailParams": {
+ "Placeholders": {
+ "[NAME_PORTAL]": "signFlow",
+ "[SIGNATURE_TYPE]": "signieren",
+ "[REASON]": ""
+ }
+ },
+ "GtxMessagingParams": {
+ "Uri": "https://rest.gtx-messaging.net",
+ "Path": "smsc/sendsms/f566f7e5-bdf2-4a9a-bf52-ed88215a432e/json",
+ "Headers": {},
+ "QueryParams": {
+ "from": "signFlow"
+ }
+ },
+ "TFARegParams": {
+ "TimeLimit": "00:30:00"
+ },
+ "DbTriggerParams": {
+ "Envelope": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
+ "EnvelopeHistory": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
+ "EmailOut": [ "TBEMLP_EMAIL_OUT_AFT_INS", "TBEMLP_EMAIL_OUT_AFT_UPD" ],
+ "EnvelopeReceiverReadOnly": [ "TBSIG_ENVELOPE_RECEIVER_READ_ONLY_UPD" ],
+ "Receiver": [],
+ "EmailTemplate": [ "TBSIG_EMAIL_TEMPLATE_AFT_UPD" ]
+ },
+ "MainPageTitle": null,
+ "AnnotationParams": {
+ "Background": {
+ "Margin": 0.20,
+ "BackgroundColor": {
+ "R": 222,
+ "G": 220,
+ "B": 215
+ },
+ "BorderColor": {
+ "R": 204,
+ "G": 202,
+ "B": 198
+ },
+ "BorderStyle": "underline",
+ "BorderWidth": 4
+ },
+ "DefaultAnnotation": {
+ "Width": 1,
+ "Height": 0.5,
+ "MarginTop": 1
+ },
+ "Annotations": [
+ {
+ "Name": "Signature",
+ "MarginTop": 0
+ },
+ {
+ "Name": "PositionLabel",
+ "VerBoundAnnotName": "Signature",
+ "WidthRatio": 1.2,
+ "HeightRatio": 0.5,
+ "MarginTopRatio": 0.22
+ },
+ {
+ "Name": "Position",
+ "VerBoundAnnotName": "PositionLabel",
+ "WidthRatio": 1.2,
+ "HeightRatio": 0.5,
+ "MarginTopRatio": -0.05
+ },
+ {
+ "Name": "CityLabel",
+ "VerBoundAnnotName": "Position",
+ "WidthRatio": 1.2,
+ "HeightRatio": 0.5,
+ "MarginTopRatio": 0.05
+ },
+ {
+ "Name": "City",
+ "VerBoundAnnotName": "CityLabel",
+ "WidthRatio": 1.2,
+ "HeightRatio": 0.5,
+ "MarginTopRatio": -0.05
+ },
+ {
+ "Name": "DateLabel",
+ "VerBoundAnnotName": "City",
+ "WidthRatio": 1.55,
+ "HeightRatio": 0.5,
+ "MarginTopRatio": 0.05
+ },
+ {
+ "Name": "Date",
+ "VerBoundAnnotName": "DateLabel",
+ "WidthRatio": 1.55,
+ "HeightRatio": 0.5,
+ "MarginTopRatio": -0.1
+ }
+ ]
}
}
diff --git a/EnvelopeGenerator.Infrastructure/DIExtensions.cs b/EnvelopeGenerator.Infrastructure/DependencyExtensions.cs
similarity index 52%
rename from EnvelopeGenerator.Infrastructure/DIExtensions.cs
rename to EnvelopeGenerator.Infrastructure/DependencyExtensions.cs
index c457d264..f7e4adb4 100644
--- a/EnvelopeGenerator.Infrastructure/DIExtensions.cs
+++ b/EnvelopeGenerator.Infrastructure/DependencyExtensions.cs
@@ -6,6 +6,13 @@ using Microsoft.EntityFrameworkCore;
using DigitalData.Core.Infrastructure;
using EnvelopeGenerator.Domain.Entities;
using DigitalData.Core.Infrastructure.AutoMapper;
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using Microsoft.Extensions.Configuration;
+using EnvelopeGenerator.Infrastructure.Executor;
+using Dapper;
+using System.ComponentModel.DataAnnotations.Schema;
+using System.Reflection;
+using DigitalData.UserManager.Domain.Entities;
namespace EnvelopeGenerator.Infrastructure;
@@ -25,7 +32,10 @@ public static class DIExtensions
/// This method ensures that the repositories are registered as scoped services, meaning that a new instance of each repository
/// will be created per HTTP request (or per scope) within the dependency injection container.
///
- public static IServiceCollection AddEnvelopeGeneratorRepositories(this IServiceCollection services, Action? dbContextOptions = null)
+ public static IServiceCollection AddEnvelopeGeneratorInfrastructureServices(this IServiceCollection services,
+ Action? dbContextOptions = null,
+ IConfiguration? sqlExecutorConfiguration = null,
+ Action? sqlExecutorConfigureOptions = null)
{
if(dbContextOptions is not null)
services.AddDbContext(dbContextOptions);
@@ -59,6 +69,92 @@ public static class DIExtensions
services.AddDbRepository(context => context.UserReceivers).UseAutoMapper();
services.AddDbRepository(context => context.EnvelopeReceiverReadOnlys).UseAutoMapper();
+ services.AddSQLExecutor();
+ services.AddSQLExecutor();
+ services.AddSQLExecutor();
+ services.AddSQLExecutor();
+ services.AddSQLExecutor();
+
+ SetDapperTypeMap();
+ SetDapperTypeMap();
+ SetDapperTypeMap();
+ SetDapperTypeMap();
+ SetDapperTypeMap();
+ SetDapperTypeMap();
+
+ services.AddScoped();
+ services.AddScoped();
+ services.AddScoped();
+
+ if (sqlExecutorConfiguration is not null || sqlExecutorConfigureOptions is not null)
+ services.AddSQLExecutor(sqlExecutorConfiguration, sqlExecutorConfigureOptions);
+
+ return services;
+ }
+
+ public static IServiceCollection AddSQLExecutor(this IServiceCollection services, IConfiguration? configuration = null, Action? configureOptions = null)
+ {
+ if(configuration is not null && configureOptions is not null)
+ throw new InvalidOperationException("Cannot use both 'configuration' and 'configureOptions'. Only one should be provided.");
+
+ if (configuration is not null)
+ services.Configure(configuration);
+
+ if(configureOptions is not null)
+ services.Configure(configureOptions);
+
+ return services.AddSingleton();
+ }
+
+ private static void SetDapperTypeMap()
+ {
+ Dapper.SqlMapper.SetTypeMap(typeof(TModel), new CustomPropertyTypeMap(
+ typeof(TModel),
+ (type, columnName) =>
+ {
+ return type.GetProperties().FirstOrDefault(prop =>
+ {
+ var attr = prop.GetCustomAttribute();
+ return attr != null && string.Equals(attr.Name, columnName, StringComparison.OrdinalIgnoreCase)
+ || string.Equals(prop.Name, columnName, StringComparison.OrdinalIgnoreCase);
+ })!;
+ }
+ ));
+ }
+
+ public static IServiceCollection AddSQLExecutor(this IServiceCollection services, IConfiguration? configuration = null, Action? configureOptions = null) where T : class
+ {
+ if (configuration is not null && configureOptions is not null)
+ throw new InvalidOperationException("Cannot use both 'configuration' and 'configureOptions'. Only one should be provided.");
+
+ if (configuration is not null)
+ services.Configure(configuration);
+
+ services.AddScoped, SQLExecutor>();
+
+ var interfaceType = typeof(ISQL<>);
+ var targetGenericType = interfaceType.MakeGenericType(typeof(T));
+
+ var implementations = AppDomain.CurrentDomain.GetAssemblies()
+ .SelectMany(a =>
+ {
+ try { return a.GetTypes(); }
+ catch { return Array.Empty(); }
+ })
+ .Where(t =>
+ t is { IsClass: true, IsAbstract: false } &&
+ t.GetInterfaces().Any(i =>
+ i.IsGenericType &&
+ i.GetGenericTypeDefinition() == interfaceType &&
+ i.GenericTypeArguments[0] == typeof(T)
+ )
+ );
+
+ foreach (var impl in implementations)
+ {
+ services.AddSingleton(impl);
+ }
+
return services;
}
}
diff --git a/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj b/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj
index f93f27c1..8ad9d9f9 100644
--- a/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj
+++ b/EnvelopeGenerator.Infrastructure/EnvelopeGenerator.Infrastructure.csproj
@@ -7,6 +7,7 @@
+
diff --git a/EnvelopeGenerator.Infrastructure/Executor/DocumentExecutor.cs b/EnvelopeGenerator.Infrastructure/Executor/DocumentExecutor.cs
new file mode 100644
index 00000000..11088c63
--- /dev/null
+++ b/EnvelopeGenerator.Infrastructure/Executor/DocumentExecutor.cs
@@ -0,0 +1,29 @@
+using Dapper;
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using EnvelopeGenerator.Application.SQL;
+using EnvelopeGenerator.Domain.Entities;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace EnvelopeGenerator.Infrastructure.Executor;
+
+public class DocumentExecutor : SQLExecutor, IDocumentExecutor
+{
+ public DocumentExecutor(IServiceProvider provider, IOptions sqlExecutorParamsOptions) : base(provider, sqlExecutorParamsOptions)
+ {
+ }
+
+ public async Task CreateDocumentAsync(string base64, string envelope_uuid, CancellationToken cancellation = default)
+ {
+ using var connection = new SqlConnection(Params.ConnectionString);
+ var sql = Provider.GetRequiredService();
+ var formattedSql = string.Format(sql.Raw, envelope_uuid.ToSqlParam());
+ var param = DocumentCreateReadSQL.CreateParmas(base64);
+ await connection.OpenAsync(cancellation);
+ var documents = await connection.QueryAsync(formattedSql, param);
+ return documents.FirstOrDefault()
+ ?? throw new InvalidOperationException($"Document creation failed. Parameters:" +
+ $"base64={base64}, envelope_uuid='{envelope_uuid}'.");
+ }
+}
diff --git a/EnvelopeGenerator.Infrastructure/Executor/EnvelopeExecutor.cs b/EnvelopeGenerator.Infrastructure/Executor/EnvelopeExecutor.cs
new file mode 100644
index 00000000..945f1ac7
--- /dev/null
+++ b/EnvelopeGenerator.Infrastructure/Executor/EnvelopeExecutor.cs
@@ -0,0 +1,37 @@
+using Dapper;
+using DigitalData.UserManager.Application.Contracts.Repositories;
+using EnvelopeGenerator.Application.Contracts.Repositories;
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using EnvelopeGenerator.Application.SQL;
+using EnvelopeGenerator.Domain.Entities;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace EnvelopeGenerator.Infrastructure.Executor;
+
+public class EnvelopeExecutor : SQLExecutor, IEnvelopeExecutor
+{
+ private readonly IUserRepository _userRepository;
+
+ public EnvelopeExecutor(IServiceProvider provider, IOptions sqlExecutorParamsOptions, IUserRepository userRepository) : base(provider, sqlExecutorParamsOptions)
+ {
+ _userRepository = userRepository;
+ }
+
+ public async Task CreateEnvelopeAsync(int userId, string title = "", string message = "", bool tfaEnabled = false, CancellationToken cancellation = default)
+ {
+ using var connection = new SqlConnection(Params.ConnectionString);
+ var sql = Provider.GetRequiredService();
+ var formattedSql = string.Format(sql.Raw, userId.ToSqlParam(), title.ToSqlParam(), tfaEnabled.ToSqlParam(), message.ToSqlParam());
+ await connection.OpenAsync(cancellation);
+ var envelopes = await connection.QueryAsync(formattedSql);
+ var envelope = envelopes.FirstOrDefault()
+ ?? throw new InvalidOperationException($"Envelope creation failed. Parameters:" +
+ $"userId={userId}, title='{title}', message='{message}', tfaEnabled={tfaEnabled}."); ;
+
+ envelope.User = await _userRepository.ReadByIdAsync(envelope.UserId);
+
+ return envelope;
+ }
+}
\ No newline at end of file
diff --git a/EnvelopeGenerator.Infrastructure/Executor/EnvelopeReceiverExecutor.cs b/EnvelopeGenerator.Infrastructure/Executor/EnvelopeReceiverExecutor.cs
new file mode 100644
index 00000000..ddcd9d28
--- /dev/null
+++ b/EnvelopeGenerator.Infrastructure/Executor/EnvelopeReceiverExecutor.cs
@@ -0,0 +1,35 @@
+using Dapper;
+using EnvelopeGenerator.Application.Contracts.Repositories;
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using EnvelopeGenerator.Application.SQL;
+using EnvelopeGenerator.Domain.Entities;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace EnvelopeGenerator.Infrastructure.Executor;
+
+public class EnvelopeReceiverExecutor: SQLExecutor, IEnvelopeReceiverExecutor
+{
+ private readonly IEnvelopeReceiverRepository _erRepository;
+
+ public EnvelopeReceiverExecutor(IServiceProvider provider, IOptions sqlExecutorParamsOptions, IEnvelopeReceiverRepository erRepository) : base(provider, sqlExecutorParamsOptions)
+ {
+ _erRepository = erRepository;
+ }
+
+ public async Task AddEnvelopeReceiverAsync(string envelope_uuid, string emailAddress, string? salutation, string? phone = null, CancellationToken cancellation = default)
+ {
+ using var connection = new SqlConnection(Params.ConnectionString);
+ var sql = Provider.GetRequiredService();
+ var formattedSql = string.Format(sql.Raw, envelope_uuid.ToSqlParam(), emailAddress.ToSqlParam(), salutation.ToSqlParam(), phone.ToSqlParam());
+ await connection.OpenAsync(cancellation);
+ var envelopeReceivers = await connection.QueryAsync(formattedSql);
+ var er = envelopeReceivers.FirstOrDefault();
+
+ if (er is null)
+ return null;
+
+ return await _erRepository.ReadByIdAsync(envelopeId: er.EnvelopeId, receiverId: er.ReceiverId);
+ }
+}
diff --git a/EnvelopeGenerator.Infrastructure/Executor/Query.cs b/EnvelopeGenerator.Infrastructure/Executor/Query.cs
new file mode 100644
index 00000000..5664d0be
--- /dev/null
+++ b/EnvelopeGenerator.Infrastructure/Executor/Query.cs
@@ -0,0 +1,41 @@
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using Microsoft.EntityFrameworkCore;
+
+namespace EnvelopeGenerator.Infrastructure.Executor;
+
+public sealed record Query : IQuery
+{
+ private readonly IQueryable _query;
+
+ internal Query(IQueryable queryable)
+ {
+ _query = queryable;
+ }
+
+ public TEntity First() => _query.First();
+
+ public Task FirstAsync() => _query.FirstAsync();
+
+ public TEntity? FirstOrDefault() => _query.FirstOrDefault();
+
+
+ public Task FirstOrDefaultAsync() => _query.FirstOrDefaultAsync();
+
+
+ public TEntity Single() => _query.Single();
+
+
+ public Task SingleAsync() => _query.SingleAsync();
+
+
+ public TEntity? SingleOrDefault() => _query.SingleOrDefault();
+
+
+ public Task SingleOrDefaultAsync() => _query.SingleOrDefaultAsync();
+
+
+ public IEnumerable ToList() => _query.ToList();
+
+
+ public async Task> ToListAsync() => await _query.ToListAsync();
+}
\ No newline at end of file
diff --git a/EnvelopeGenerator.Infrastructure/Executor/QueryExtension.cs b/EnvelopeGenerator.Infrastructure/Executor/QueryExtension.cs
new file mode 100644
index 00000000..58c72850
--- /dev/null
+++ b/EnvelopeGenerator.Infrastructure/Executor/QueryExtension.cs
@@ -0,0 +1,9 @@
+namespace EnvelopeGenerator.Infrastructure.Executor;
+
+public static class QueryExtension
+{
+ public static Query ToQuery(this IQueryable queryable) where TEntity : class
+ {
+ return new Query(queryable);
+ }
+}
\ No newline at end of file
diff --git a/EnvelopeGenerator.Infrastructure/Executor/SQLExecutor.cs b/EnvelopeGenerator.Infrastructure/Executor/SQLExecutor.cs
new file mode 100644
index 00000000..5b8a0a3f
--- /dev/null
+++ b/EnvelopeGenerator.Infrastructure/Executor/SQLExecutor.cs
@@ -0,0 +1,33 @@
+using Dapper;
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using Microsoft.Data.SqlClient;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace EnvelopeGenerator.Infrastructure.Executor;
+
+public class SQLExecutor : ISQLExecutor
+{
+ protected readonly SQLExecutorParams Params;
+
+ protected readonly IServiceProvider Provider;
+
+ public SQLExecutor(IServiceProvider provider, IOptions sqlExecutorParamsOptions)
+ {
+ Provider = provider;
+ Params = sqlExecutorParamsOptions.Value;
+ }
+
+ public async Task> Execute(string sql, DynamicParameters parameters, CancellationToken cancellation = default)
+ {
+ using var connection = new SqlConnection(Params.ConnectionString);
+ await connection.OpenAsync(cancellation);
+ return await connection.QueryAsync(sql, parameters);
+ }
+
+ public Task> Execute(DynamicParameters parameters, CancellationToken cancellation = default) where TSQL : ISQL
+ {
+ var sql = Provider.GetRequiredService();
+ return Execute(sql.Raw, parameters, cancellation);
+ }
+}
\ No newline at end of file
diff --git a/EnvelopeGenerator.Infrastructure/Executor/SQLExecutorBaseEntity.cs b/EnvelopeGenerator.Infrastructure/Executor/SQLExecutorBaseEntity.cs
new file mode 100644
index 00000000..d3e8b6d3
--- /dev/null
+++ b/EnvelopeGenerator.Infrastructure/Executor/SQLExecutorBaseEntity.cs
@@ -0,0 +1,31 @@
+using EnvelopeGenerator.Application.Contracts.SQLExecutor;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace EnvelopeGenerator.Infrastructure.Executor;
+
+public sealed class SQLExecutor : SQLExecutor, ISQLExecutor where T : class
+{
+ private readonly EGDbContext _context;
+
+ private readonly IServiceProvider _provider;
+
+ public SQLExecutor(EGDbContext context, IServiceProvider provider, IOptions options) : base(provider, options)
+ {
+ _context = context;
+ _provider = provider;
+ }
+
+ public IQuery Execute(string sql, CancellationToken cancellation = default, params object[] parameters)
+ => _context
+ .Set()
+ .FromSqlRaw(sql, parameters)
+ .ToQuery();
+
+ public IQuery Execute(CancellationToken cancellation = default, params object[] parameters) where TSQL : ISQL
+ {
+ var sql = _provider.GetRequiredService();
+ return Execute(sql.Raw);
+ }
+}
diff --git a/EnvelopeGenerator.Infrastructure/Executor/SQLExecutorParams.cs b/EnvelopeGenerator.Infrastructure/Executor/SQLExecutorParams.cs
new file mode 100644
index 00000000..734d4a5e
--- /dev/null
+++ b/EnvelopeGenerator.Infrastructure/Executor/SQLExecutorParams.cs
@@ -0,0 +1,6 @@
+namespace EnvelopeGenerator.Infrastructure.Executor;
+
+public class SQLExecutorParams
+{
+ public string? ConnectionString { get; set; }
+}
diff --git a/EnvelopeGenerator.Infrastructure/Repositories/EnvelopeHistoryRepository.cs b/EnvelopeGenerator.Infrastructure/Repositories/EnvelopeHistoryRepository.cs
index 18ccc27e..cfa8313a 100644
--- a/EnvelopeGenerator.Infrastructure/Repositories/EnvelopeHistoryRepository.cs
+++ b/EnvelopeGenerator.Infrastructure/Repositories/EnvelopeHistoryRepository.cs
@@ -13,7 +13,7 @@ public class EnvelopeHistoryRepository : CRUDRepository By(int? envelopeId = null, string? userReference = null, int? status = null, bool withSender = false, bool withReceiver = false)
{
- var query = _dbSet.AsQueryable();
+ var query = _dbSet.AsNoTracking();
if (envelopeId is not null)
query = query.Where(eh => eh.EnvelopeId == envelopeId);
diff --git a/EnvelopeGenerator.Infrastructure/Repositories/EnvlopeReceiverRepository.cs b/EnvelopeGenerator.Infrastructure/Repositories/EnvlopeReceiverRepository.cs
index 8d5f471b..279d7f77 100644
--- a/EnvelopeGenerator.Infrastructure/Repositories/EnvlopeReceiverRepository.cs
+++ b/EnvelopeGenerator.Infrastructure/Repositories/EnvlopeReceiverRepository.cs
@@ -48,9 +48,19 @@ public class EnvelopeReceiverRepository : CRUDRepository CountAsync(string uuid, string signature) => await ReadWhere(uuid: uuid, signature: signature).CountAsync();
- private IQueryable ReadById(int envelopeId, int receiverId, bool readOnly = true)
+ private IQueryable ReadById(int envelopeId, int receiverId, bool withEnvelope = true, bool withReceiver = true, bool readOnly = true)
{
var query = readOnly ? _dbSet.AsNoTracking() : _dbSet;
+
+ if (withEnvelope)
+ query = query
+ .Include(er => er.Envelope).ThenInclude(e => e!.Documents!).ThenInclude(d => d.Elements!.Where(e => e.Receiver!.Id == receiverId))
+ .Include(er => er.Envelope).ThenInclude(e => e!.History)
+ .Include(er => er.Envelope).ThenInclude(e => e!.User);
+
+ if (withReceiver)
+ query = query.Include(er => er.Receiver);
+
return query.Where(er => er.EnvelopeId == envelopeId && er.ReceiverId == receiverId);
}
diff --git a/EnvelopeGenerator.Terminal/DependencyInjection.cs b/EnvelopeGenerator.Terminal/DependencyInjection.cs
index ad011146..421a57d2 100644
--- a/EnvelopeGenerator.Terminal/DependencyInjection.cs
+++ b/EnvelopeGenerator.Terminal/DependencyInjection.cs
@@ -27,11 +27,11 @@ public static class DependencyInjection
});
// Add envelope generator services
- services.AddEnvelopeGeneratorRepositories(options => options.UseSqlServer(connStr));
+ services.AddEnvelopeGeneratorInfrastructureServices(options => options.UseSqlServer(connStr));
return services
.AddSingleton()
- .AddEnvelopeGeneratorRepositories()
+ .AddEnvelopeGeneratorInfrastructureServices()
.AddEnvelopeGeneratorServices(configuration)
.AddSingleton(sp =>
{
diff --git a/EnvelopeGenerator.Tests.Application/Mock.cs b/EnvelopeGenerator.Tests.Application/Mock.cs
index 10bc7ea6..b14108e8 100644
--- a/EnvelopeGenerator.Tests.Application/Mock.cs
+++ b/EnvelopeGenerator.Tests.Application/Mock.cs
@@ -17,7 +17,7 @@ public class Mock
builder.Configuration.AddJsonFile(configPath, optional: true, reloadOnChange: true);
builder.Services
- .AddEnvelopeGeneratorRepositories(opt =>
+ .AddEnvelopeGeneratorInfrastructureServices(opt =>
{
if (useRealDb)
{
diff --git a/EnvelopeGenerator.Web/Program.cs b/EnvelopeGenerator.Web/Program.cs
index 9448b5e4..08e71c8a 100644
--- a/EnvelopeGenerator.Web/Program.cs
+++ b/EnvelopeGenerator.Web/Program.cs
@@ -16,6 +16,7 @@ using EnvelopeGenerator.Infrastructure;
using EnvelopeGenerator.Web.Sanitizers;
using EnvelopeGenerator.Application.Contracts.Services;
using EnvelopeGenerator.Web.Models.Annotation;
+using DigitalData.UserManager.DependencyInjection;
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Info("Logging initialized!");
@@ -90,7 +91,7 @@ try
});
// Add envelope generator services
- builder.Services.AddEnvelopeGeneratorRepositories(options => options.UseSqlServer(connStr));
+ builder.Services.AddEnvelopeGeneratorInfrastructureServices(options => options.UseSqlServer(connStr));
builder.Services.AddEnvelopeGeneratorServices(config);
@@ -168,6 +169,8 @@ try
builder.ConfigureBySection();
+ builder.Services.AddUserManager();
+
var app = builder.Build();
// Configure the HTTP request pipeline.
diff --git a/EnvelopeGenerator.Web/appsettings.json b/EnvelopeGenerator.Web/appsettings.json
index 1c6fb8c4..6c058cd7 100644
--- a/EnvelopeGenerator.Web/appsettings.json
+++ b/EnvelopeGenerator.Web/appsettings.json
@@ -140,14 +140,15 @@
}
},
"TFARegParams": {
- "TimeLimit": "00:30:00"
+ "TimeLimit": "90.00:00:00"
},
"DbTriggerParams": {
"Envelope": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
"EnvelopeHistory": [ "TBSIG_ENVELOPE_HISTORY_AFT_INS" ],
"EmailOut": [ "TBEMLP_EMAIL_OUT_AFT_INS", "TBEMLP_EMAIL_OUT_AFT_UPD" ],
"EnvelopeReceiverReadOnly": [ "TBSIG_ENVELOPE_RECEIVER_READ_ONLY_UPD" ],
- "Receiver": []
+ "Receiver": [],
+ "EmailTemplate": [ "TBSIG_EMAIL_TEMPLATE_AFT_UPD" ]
},
"MainPageTitle": null,
"AnnotationParams": {