Compare commits

..

8 Commits

2691 changed files with 24016 additions and 49363 deletions

2
.gitignore vendored
View File

@@ -363,5 +363,3 @@ MigrationBackup/
FodyWeavers.xsd
/EnvelopeGenerator.Web/.config/dotnet-tools.json
/EnvelopeGenerator.GeneratorAPI/ClientApp/envelope-generator-ui/.vscode
/EnvelopeGenerator.Tests.Application/Services/BugFixTests.cs
/EnvelopeGenerator.Tests.Application/annotations.json

View File

@@ -1,33 +0,0 @@
namespace EnvelopeGenerator.Application.Configurations;
/// <summary>
///
/// </summary>
public class AuthenticatorParams
{
/// <summary>
/// Default value is 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789012345678901234567890123456789'
/// </summary>
public string CharPool { get; init; } = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789012345678901234567890123456789";
//TODO: Increase the DefaultTotpSecretKeyLength (e.g. to 32) but make sure that the QR code is generated correctly and can be scanned by the authenticator.
/// <summary>
/// Default value is 20
/// </summary>
public int DefaultTotpSecretKeyLength { get; init; } = 20;
/// <summary>
/// Default value is
/// </summary>
public string TotpIssuer { get; init; } = "signFlow";
/// <summary>
/// 0 is user email, 1 is secret key and 2 is issuer.
/// </summary>
public string TotpUrlFormat { get; init; } = "otpauth://totp/{0}?secret={1}&issuer={2}";
/// <summary>
/// Default value is 20.
/// </summary>
public int TotpQRPixelsPerModule { get; init; } = 20;
}

View File

@@ -1,8 +0,0 @@
namespace EnvelopeGenerator.Application.Configurations;
/// <summary>
///
/// </summary>
public class DbTriggerParams : Dictionary<string, ICollection<string>>
{
}

View File

@@ -0,0 +1,13 @@
namespace EnvelopeGenerator.Application.Configurations
{
public class DispatcherConfig
{
public int SendingProfile { get; init; } = 1;
public string AddedWho { get; init; } = "DDEnvelopGenerator";
public int ReminderTypeId { get; init; } = 202377;
public string EmailAttmt1 { get; init; } = string.Empty;
}
}

View File

@@ -1,27 +0,0 @@
namespace EnvelopeGenerator.Application.Configurations;
/// <summary>
///
/// </summary>
public class DispatcherParams
{
/// <summary>
/// Default value is 1
/// </summary>
public int SendingProfile { get; init; } = 1;
/// <summary>
/// Defalt value is 'DDEnvelopGenerator'
/// </summary>
public string AddedWho { get; init; } = "DDEnvelopGenerator";
/// <summary>
/// Default value is 202377
/// </summary>
public int ReminderTypeId { get; init; } = 202377;
/// <summary>
/// Default value is string.Empty
/// </summary>
public string EmailAttmt1 { get; init; } = string.Empty;
}

View File

@@ -1,37 +0,0 @@
using DigitalData.Core.Client.Interface;
namespace EnvelopeGenerator.Application.Configurations;
/// <summary>
/// https://www.gtx-messaging.com/en/api-docs/sms-rest-api/
/// </summary>
public class GtxMessagingParams : IHttpClientOptions
{
/// <summary>
///
/// </summary>
public required string Uri { get; init; }
/// <summary>
///
/// </summary>
public string? Path { get; init; }
/// <summary>
///
/// </summary>
public Dictionary<string, object>? Headers { get; init; }
/// <summary>
///
/// </summary>
public Dictionary<string, object?>? QueryParams { get; init; }
/// <summary>
/// Default value is 'to'
/// </summary>
public string RecipientQueryParamName { get; init; } = "to";
/// <summary>
/// Default value is 'text'
/// </summary>
public string MessageQueryParamName { get; init; } = "text";
}

View File

@@ -0,0 +1,7 @@
namespace EnvelopeGenerator.Application.Configurations
{
public class MailConfig
{
public required Dictionary<string, string> Placeholders { get; init; }
}
}

View File

@@ -1,12 +0,0 @@
namespace EnvelopeGenerator.Application.Configurations;
/// <summary>
///
/// </summary>
public class MailParams
{
/// <summary>
///
/// </summary>
public required Dictionary<string, string> Placeholders { get; init; }
}

View File

@@ -1,81 +0,0 @@
using OtpNet;
using System.Globalization;
namespace EnvelopeGenerator.Application.Configurations;
/// <summary>
///
/// </summary>
public class TotpSmsParams
{
/// <summary>
/// The unit is second.
/// </summary>
public int TotpStep { get; init; } = 90;
/// <summary>
/// Default value is 'Ihr 2FA-Passwort lautet {0}. Gültig bis {1}'
/// </summary>
public string Format { get; init; } = "Ihr 2FA-Passwort lautet {0}. Gültig bis {1}";
/// <summary>
///
/// </summary>
public ExpirationHandler Expiration { get; init; } = new();
/// <summary>
/// Default value is VerificationWindow.RfcSpecifiedNetworkDelay
/// </summary>
public VerificationWindow? TotpVerificationWindow { get; private init; } = VerificationWindow.RfcSpecifiedNetworkDelay;
private IEnumerable<int>? _tvwParams;
/// <summary>
///
/// </summary>
public IEnumerable<int>? TotpVerificationWindowParams
{
get => _tvwParams;
init
{
_tvwParams = value;
if(_tvwParams is not null)
TotpVerificationWindow = new(previous: _tvwParams.ElementAtOrDefault(0), future: _tvwParams.ElementAtOrDefault(0));
}
}
/// <summary>
///
/// </summary>
public class ExpirationHandler
{
/// <summary>
/// Default value is 'e{0}_r{1}_sms_code_expiration'
/// </summary>
public string CacheKeyFormat { get; init; } = "e{0}_r{1}_sms_code_expiration";
/// <summary>
/// Default value is HH:mm:ss
/// </summary>
public string Format { get; init; } = "HH:mm:ss";
/// <summary>
/// Default value is 'de-DE'
/// </summary>
public string CultureName
{
get => _cultureInfo.Name;
init => _cultureInfo = new(value);
}
/// <summary>
/// Default value is 'de-DE'
/// </summary>
private CultureInfo _cultureInfo = new("de-DE");
/// <summary>
/// Default value is 'de-DE'
/// </summary>
public CultureInfo CultureInfo => _cultureInfo;
}
}

View File

@@ -0,0 +1,17 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IConfigService : IReadService<ConfigDto, Config, int>
{
Task<DataResult<ConfigDto>> ReadFirstAsync();
Task<ConfigDto> ReadDefaultAsync();
Task<string> ReadDefaultSignatureHost();
}
}

View File

@@ -0,0 +1,10 @@
using DigitalData.Core.Abstractions.Application;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IDocumentReceiverElementService : IBasicCRUDService<DocumentReceiverElementDto, DocumentReceiverElement, int>
{
}
}

View File

@@ -0,0 +1,9 @@
using DigitalData.Core.Abstractions.Application;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IDocumentStatusService : IBasicCRUDService<DocumentStatusDto, DocumentStatus, int>
{
}
}

View File

@@ -0,0 +1,13 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IEmailTemplateService : IBasicCRUDService<EmailTemplateDto, EmailTemplate, int>
{
Task<DataResult<EmailTemplateDto>> ReadByNameAsync(EmailTemplateType type);
}
}

View File

@@ -0,0 +1,10 @@
using DigitalData.Core.Abstractions.Application;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeCertificateService : IBasicCRUDService<EnvelopeCertificateDto, EnvelopeCertificate, int>
{
}
}

View File

@@ -0,0 +1,10 @@
using DigitalData.Core.Abstractions.Application;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeDocumentService : IBasicCRUDService<EnvelopeDocumentDto, EnvelopeDocument, int>
{
}
}

View File

@@ -0,0 +1,28 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.DTOs.EnvelopeHistory;
using EnvelopeGenerator.Application.DTOs.Receiver;
using EnvelopeGenerator.Domain.Entities;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeHistoryService : ICRUDService<EnvelopeHistoryCreateDto, EnvelopeHistoryDto, EnvelopeHistoryDto, EnvelopeHistory, long>
{
Task<int> CountAsync(int? envelopeId = null, string? userReference = null, int? status = null);
Task<bool> AccessCodeAlreadyRequested(int envelopeId, string userReference);
Task<bool> IsSigned(int envelopeId, string userReference);
Task<bool> IsRejected(int envelopeId, string? userReference = null);
Task<IEnumerable<EnvelopeHistoryDto>> ReadAsync(int? envelopeId = null, string? userReference = null, ReferenceType? referenceType = null, int? status = null, bool withSender = false, bool withReceiver = false);
Task<IEnumerable<EnvelopeHistoryDto>> ReadRejectedAsync(int envelopeId, string? userReference = null);
Task<IEnumerable<ReceiverReadDto>> ReadRejectingReceivers(int envelopeId);
Task<DataResult<long>> RecordAsync(int envelopeId, string userReference, EnvelopeStatus status, string? comment = null);
}
}

View File

@@ -0,0 +1,17 @@
using DigitalData.Core.DTO;
using DigitalData.EmailProfilerDispatcher.Abstraction.Contracts;
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiver;
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiverReadOnly;
using EnvelopeGenerator.Common;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeMailService : IEmailOutService
{
Task<DataResult<int>> SendAsync(EnvelopeReceiverDto envelopeReceiverDto, Constants.EmailTemplateType tempType);
Task<DataResult<int>> SendAsync(EnvelopeReceiverReadOnlyDto dto);
Task<DataResult<int>> SendAccessCodeAsync(EnvelopeReceiverDto envelopeReceiverDto);
}
}

View File

@@ -0,0 +1,10 @@
using DigitalData.Core.Abstractions.Application;
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiverReadOnly;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeReceiverReadOnlyService : ICRUDService<EnvelopeReceiverReadOnlyCreateDto, EnvelopeReceiverReadOnlyDto, EnvelopeReceiverReadOnlyUpdateDto, EnvelopeReceiverReadOnly, long>
{
}
}

View File

@@ -0,0 +1,34 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiver;
using EnvelopeGenerator.Application.DTOs.Receiver;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeReceiverService : IBasicCRUDService<EnvelopeReceiverDto, EnvelopeReceiver, (int Envelope, int Receiver)>
{
Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUuidAsync(string uuid, bool withEnvelope = true, bool withReceiver = false);
Task<DataResult<IEnumerable<EnvelopeReceiverSecretDto>>> ReadSecretByUuidAsync(string uuid, bool withEnvelope = false, bool withReceiver = true);
Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadBySignatureAsync(string signature, bool withEnvelope = false, bool withReceiver = true);
Task<DataResult<EnvelopeReceiverDto>> ReadByUuidSignatureAsync(string uuid, string signature, bool withEnvelope = true, bool withReceiver = true);
Task<DataResult<EnvelopeReceiverDto>> ReadByEnvelopeReceiverIdAsync(string envelopeReceiverId, bool withEnvelope = true, bool withReceiver = true);
Task<DataResult<string>> ReadAccessCodeByIdAsync(int envelopeId, int receiverId);
Task<DataResult<bool>> VerifyAccessCodeAsync(string uuid, string signature, string accessCode);
Task<DataResult<bool>> VerifyAccessCodeAsync(string envelopeReceiverId, string accessCode);
Task<DataResult<bool>> IsExisting(string envelopeReceiverId);
Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, params int[] ignore_statuses);
Task<DataResult<string?>> ReadLastUsedReceiverNameByMail(string mail);
}
}

View File

@@ -0,0 +1,16 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeService : IBasicCRUDService<EnvelopeDto, Envelope, int>
{
Task<DataResult<IEnumerable<EnvelopeDto>>> ReadAllWithAsync(bool documents = false, bool history = false, bool documentReceiverElement = false);
Task<DataResult<EnvelopeDto>> ReadByUuidAsync(string uuid, bool withDocuments = false, bool withHistory = false, bool withDocumentReceiverElement = false, bool withUser = false, bool withAll = false);
Task<DataResult<IEnumerable<EnvelopeDto>>> ReadByUserAsync(int userId, int? min_status = null, int? max_status = null, params int[]ignore_statuses);
}
}

View File

@@ -0,0 +1,10 @@
using DigitalData.Core.Abstractions.Application;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeTypeService : IBasicCRUDService<EnvelopeTypeDto, EnvelopeType, int>
{
}
}

View File

@@ -0,0 +1,24 @@
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IJWTService<TClaimValue>
{
public static SymmetricSecurityKey GenerateSecurityKey(int byteSize = 32)
{
using var rng = RandomNumberGenerator.Create();
var randomBytes = new byte[byteSize];
rng.GetBytes(randomBytes);
var securityKey = new SymmetricSecurityKey(randomBytes);
return securityKey;
}
string GenerateToken(TClaimValue claimValue);
JwtSecurityToken? ReadSecurityToken(string token);
}
}

View File

@@ -0,0 +1,14 @@
using DigitalData.Core.Abstractions.Application;
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.DTOs.Receiver;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IReceiverService : ICRUDService<ReceiverCreateDto, ReceiverReadDto, ReceiverUpdateDto, Receiver, int>
{
public Task<DataResult<ReceiverReadDto>> ReadByAsync(string? emailAddress = null, string? signature = null);
public Task<Result> DeleteByAsync(string? emailAddress = null, string? signature = null);
}
}

View File

@@ -0,0 +1,10 @@
using DigitalData.Core.Abstractions.Application;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IUserReceiverService : IBasicCRUDService<UserReceiverDto, UserReceiver, int>
{
}
}

View File

@@ -0,0 +1,63 @@
using DigitalData.UserManager.Application.MappingProfiles;
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.MappingProfiles;
using EnvelopeGenerator.Application.Configurations;
using EnvelopeGenerator.Application.Services;
using EnvelopeGenerator.Infrastructure.Contracts;
using EnvelopeGenerator.Infrastructure.Repositories;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace EnvelopeGenerator.Application
{
public static class DIExtensions
{
public static IServiceCollection AddEnvelopeGenerator(this IServiceCollection services, IConfiguration dispatcherConfigSection, IConfiguration mailConfigSection)
{
//Inject CRUD Service and repositoriesad
services.AddScoped<IConfigRepository, ConfigRepository>();
services.AddScoped<IDocumentReceiverElementRepository, DocumentReceiverElementRepository>();
services.AddScoped<IEnvelopeDocumentRepository, EnvelopeDocumentRepository>();
services.AddScoped<IConfigRepository, ConfigRepository>();
services.AddScoped<IDocumentReceiverElementRepository, DocumentReceiverElementRepository>();
services.AddScoped<IDocumentStatusRepository, DocumentStatusRepository>();
services.AddScoped<IEmailTemplateRepository, EmailTemplateRepository>();
services.AddScoped<IEnvelopeRepository, EnvelopeRepository>();
services.AddScoped<IEnvelopeCertificateRepository, EnvelopeCertificateRepository>();
services.AddScoped<IEnvelopeDocumentRepository, EnvelopeDocumentRepository>();
services.AddScoped<IEnvelopeHistoryRepository, EnvelopeHistoryRepository>();
services.AddScoped<IEnvelopeReceiverRepository, EnvelopeReceiverRepository>();
services.AddScoped<IEnvelopeTypeRepository, EnvelopeTypeRepository>();
services.AddScoped<IReceiverRepository, ReceiverRepository>();
services.AddScoped<IUserReceiverRepository, UserReceiverRepository>();
services.AddScoped<IEnvelopeReceiverReadOnlyRepository, EnvelopeReceiverReadOnlyRepository>();
services.AddScoped<IConfigService, ConfigService>();
services.AddScoped<IDocumentReceiverElementService, DocumentReceiverElementService>();
services.AddScoped<IEnvelopeDocumentService, EnvelopeDocumentService>();
services.AddScoped<IEnvelopeHistoryService, EnvelopeHistoryService>();
services.AddScoped<IDocumentStatusService, DocumentStatusService>();
services.AddScoped<IEmailTemplateService, EmailTemplateService>();
services.AddScoped<IEnvelopeService, EnvelopeService>();
services.AddScoped<IEnvelopeCertificateService, EnvelopeCertificateService>();
services.AddScoped<IEnvelopeDocumentService, EnvelopeDocumentService>();
services.AddScoped<IEnvelopeReceiverService, EnvelopeReceiverService>();
services.AddScoped<IEnvelopeTypeService, EnvelopeTypeService>();
services.AddScoped<IReceiverService, ReceiverService>();
services.AddScoped<IUserReceiverService, UserReceiverService>();
services.AddScoped<IEnvelopeReceiverReadOnlyService, EnvelopeReceiverReadOnlyService>();
//Auto mapping profiles
services.AddAutoMapper(typeof(BasicDtoMappingProfile).Assembly);
services.AddAutoMapper(typeof(UserMappingProfile).Assembly);
services.Configure<DispatcherConfig>(dispatcherConfigSection);
services.Configure<MailConfig>(mailConfigSection);
return services;
}
public static IServiceCollection AddEnvelopeGenerator(this IServiceCollection services, IConfiguration config) => services.AddEnvelopeGenerator(
dispatcherConfigSection: config.GetSection("DispatcherConfig"),
mailConfigSection: config.GetSection("MailConfig"));
}
}

View File

@@ -0,0 +1,20 @@
using DigitalData.Core.Abstractions;
using System.Text.Json.Serialization;
namespace EnvelopeGenerator.Application.DTOs
{
public record ConfigDto(
string DocumentPath,
int SendingProfile,
string SignatureHost,
string ExternalProgramName,
string ExportPath,
string DocumentPathDmz,
string ExportPathDmz,
string DocumentPathMoveAftsend) : IUnique<int>
{
[JsonIgnore]
[Obsolete("Configuration does not have an ID; it represents a single table in the database.")]
public int Id => throw new InvalidOperationException("This configuration does not support an ID as it represents a single row in the database.");
};
}

View File

@@ -0,0 +1,24 @@
using DigitalData.Core.Abstractions;
namespace EnvelopeGenerator.Application.DTOs
{
public record DocumentReceiverElementDto(
int Id,
int DocumentId,
int ReceiverId,
int ElementType,
double X,
double Y,
double Width,
double Height,
int Page,
bool Required,
string? Tooltip,
bool ReadOnly,
int AnnotationIndex,
DateTime AddedWhen,
DateTime? ChangedWhen,
double Top,
double Left
): IUnique<int>;
}

View File

@@ -0,0 +1,14 @@
using DigitalData.Core.Abstractions;
namespace EnvelopeGenerator.Application.DTOs
{
public record DocumentStatusDto(
int Id,
int EnvelopeId,
int ReceiverId,
int Status,
DateTime? StatusChangedWhen,
string Value,
DateTime AddedWhen,
DateTime? ChangedWhen) : IUnique<int>;
}

View File

@@ -0,0 +1,10 @@
using DigitalData.Core.Abstractions;
namespace EnvelopeGenerator.Application.DTOs
{
public record EmailTemplateDto(
int Id,
string Name,
string Body,
string Subject) : IUnique<int>;
}

View File

@@ -0,0 +1,14 @@
using DigitalData.Core.Abstractions;
namespace EnvelopeGenerator.Application.DTOs
{
public record EnvelopeCertificateDto(
int Id,
int EnvelopeId,
string EnvelopeUuid,
string EnvelopeSubject,
int CreatorId,
string CreatorName,
string CreatorEmail,
int EnvelopeStatus) : IUnique<int>;
}

View File

@@ -0,0 +1,13 @@
using DigitalData.Core.Abstractions;
namespace EnvelopeGenerator.Application.DTOs
{
public record EnvelopeDocumentDto
(
int Id,
int EnvelopeId,
DateTime AddedWhen,
byte[]? ByteData = null,
IEnumerable<DocumentReceiverElementDto>? Elements = null
) : IUnique<int>;
}

View File

@@ -0,0 +1,67 @@
using DigitalData.Core.Abstractions;
using DigitalData.EmailProfilerDispatcher.Abstraction.Attributes;
using DigitalData.UserManager.Application.DTOs.User;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.DTOs
{
public record EnvelopeDto() : IUnique<int>
{
public int Id { get; set; }
public int UserId { get; set; }
public int Status { get; set; }
public string StatusName { get; set; }
public string Uuid { get; set; }
[TemplatePlaceholder("[MESSAGE]")]
public string Message { get; set; }
public DateTime? ExpiresWhen { get; set; }
public DateTime? ExpiresWarningWhen { get; set; }
public DateTime AddedWhen { get; set; }
public DateTime? ChangedWhen { get; set; }
[TemplatePlaceholder("[DOCUMENT_TITLE]")]
public string Title { get; set; }
public int? ContractType { get; set; }
public string Language { get; set; }
public bool? SendReminderEmails { get; set; }
public int? FirstReminderDays { get; set; }
public int? ReminderIntervalDays { get; set; }
public int? EnvelopeTypeId { get; set; }
public int? CertificationType { get; set; }
public bool? UseAccessCode { get; set; }
public int? FinalEmailToCreator { get; set; }
public int? FinalEmailToReceivers { get; set; }
public int? ExpiresWhenDays { get; set; }
public int? ExpiresWarningWhenDays { get; set; }
public bool DmzMoved { get; set; }
public UserReadDto? User { get; set; }
public EnvelopeType? EnvelopeType { get; set; }
public string? EnvelopeTypeTitle { get; set; }
public bool IsAlreadySent { get; set; }
public string? StatusTranslated { get; set; }
public string? ContractTypeTranslated { get; set; }
public IEnumerable<EnvelopeDocumentDto>? Documents { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace EnvelopeGenerator.Application.DTOs.EnvelopeHistory
{
public record EnvelopeHistoryCreateDto(
int EnvelopeId,
string UserReference,
int Status,
DateTime? ActionDate,
string? Comment = null);
}

View File

@@ -0,0 +1,21 @@
using DigitalData.Core.Abstractions;
using DigitalData.Core.DTO;
using DigitalData.UserManager.Application.DTOs.User;
using EnvelopeGenerator.Application.DTOs.Receiver;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Application.DTOs.EnvelopeHistory
{
public record EnvelopeHistoryDto(
long Id,
int EnvelopeId,
string UserReference,
int Status,
string? StatusName,
DateTime AddedWhen,
DateTime? ActionDate,
UserCreateDto? Sender,
ReceiverReadDto? Receiver,
ReferenceType ReferenceType,
string? Comment = null) : BaseDTO<long>(Id), IUnique<long>;
}

View File

@@ -0,0 +1,29 @@
using DigitalData.Core.Abstractions;
using DigitalData.EmailProfilerDispatcher.Abstraction.Attributes;
namespace EnvelopeGenerator.Application.DTOs.EnvelopeReceiver
{
public record EnvelopeReceiverBasicDto() : IUnique<(int Envelope, int Receiver)>
{
public (int Envelope, int Receiver) Id => (Envelope: EnvelopeId, Receiver: ReceiverId);
public int EnvelopeId { get; init; }
public int ReceiverId { get; init; }
public int Sequence { get; init; }
[TemplatePlaceholder("[NAME_RECEIVER]")]
public string? Name { get; init; }
public string? JobTitle { get; init; }
public string? CompanyName { get; init; }
public string? PrivateMessage { get; init; }
public DateTime AddedWhen { get; init; }
public DateTime? ChangedWhen { get; init; }
}
}

View File

@@ -0,0 +1,11 @@
using EnvelopeGenerator.Application.DTOs.Receiver;
namespace EnvelopeGenerator.Application.DTOs.EnvelopeReceiver
{
public record EnvelopeReceiverDto() : EnvelopeReceiverBasicDto()
{
public EnvelopeDto? Envelope { get; set; }
public ReceiverReadDto? Receiver { get; set; }
}
}

View File

@@ -0,0 +1,4 @@
namespace EnvelopeGenerator.Application.DTOs.EnvelopeReceiver
{
public record EnvelopeReceiverSecretDto(string? AccessCode) : EnvelopeReceiverDto;
}

View File

@@ -0,0 +1,22 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace EnvelopeGenerator.Application.DTOs.EnvelopeReceiverReadOnly
{
public record EnvelopeReceiverReadOnlyCreateDto(
DateTime DateValid)
{
[EmailAddress]
[Required]
public required string ReceiverMail { get; init; }
[JsonIgnore]
public long? EnvelopeId { get; set; } = null;
[JsonIgnore]
public string? AddedWho { get; set; }
[JsonIgnore]
public DateTime AddedWhen { get; } = DateTime.Now;
};
}

View File

@@ -0,0 +1,16 @@
using EnvelopeGenerator.Application.DTOs.Receiver;
namespace EnvelopeGenerator.Application.DTOs.EnvelopeReceiverReadOnly
{
public record EnvelopeReceiverReadOnlyDto(
long Id,
long EnvelopeId,
string ReceiverMail,
DateTime DateValid,
DateTime AddedWhen,
string AddedWho,
EnvelopeDto? Envelope = null,
string? ChangedWho = null,
DateTime? ChangedWhen = null,
ReceiverReadDto? Receiver = null);
}

View File

@@ -0,0 +1,12 @@
using DigitalData.Core.Abstractions;
namespace EnvelopeGenerator.Application.DTOs.EnvelopeReceiverReadOnly
{
public record EnvelopeReceiverReadOnlyUpdateDto(
long Id,
DateTime DateValid,
string ChangedWho) : IUnique<long>
{
public DateTime ChangedWhen { get; } = DateTime.Now;
};
}

View File

@@ -0,0 +1,21 @@
using DigitalData.Core.Abstractions;
namespace EnvelopeGenerator.Application.DTOs
{
public record EnvelopeTypeDto(
int Id,
string Title,
string Language,
int? ExpiresDays,
int? CertificationType,
bool? UseAccessCode,
int? FinalEmailToCreator,
int? FinalEmailToReceivers,
DateTime AddedWhen,
DateTime? ChangedWhen,
int? ExpiresWarningDays,
bool? SendReminderEmails,
int? FirstReminderDays,
int? ReminderIntervalDays,
int? ContractType) : IUnique<int>;
}

View File

@@ -0,0 +1,21 @@
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Text;
namespace EnvelopeGenerator.Application.DTOs.Receiver
{
public record ReceiverCreateDto([EmailAddress] string EmailAddress)
{
public string Signature => sha256HexOfMail.Value;
private readonly Lazy<string> sha256HexOfMail = new(() =>
{
var bytes_arr = Encoding.UTF8.GetBytes(EmailAddress.ToUpper());
var hash_arr = SHA256.HashData(bytes_arr);
var hexa_str = BitConverter.ToString(hash_arr);
return hexa_str.Replace("-", string.Empty);
});
public DateTime AddedWhen { get; } = DateTime.Now;
};
}

View File

@@ -0,0 +1,19 @@
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.DTOs.EnvelopeReceiver;
using System.Text.Json.Serialization;
namespace EnvelopeGenerator.Application.DTOs.Receiver
{
public record ReceiverReadDto(
int Id,
string EmailAddress,
string Signature,
DateTime AddedWhen
) : BaseDTO<int>(Id)
{
[JsonIgnore]
public IEnumerable<EnvelopeReceiverBasicDto>? EnvelopeReceivers { get; init; }
public string? LastUsedName => EnvelopeReceivers?.LastOrDefault()?.Name;
};
}

View File

@@ -0,0 +1,6 @@
using DigitalData.Core.Abstractions;
namespace EnvelopeGenerator.Application.DTOs.Receiver
{
public record ReceiverUpdateDto(int Id) : IUnique<int>;
}

View File

@@ -0,0 +1,13 @@
using DigitalData.Core.Abstractions;
namespace EnvelopeGenerator.Application.DTOs
{
public record UserReceiverDto(
int Id,
int UserId,
int ReceiverId,
string Name,
string CompanyName,
string JobTitle,
DateTime AddedWhen) : IUnique<int>;
}

View File

@@ -1,66 +0,0 @@
using EnvelopeGenerator.Application.Configurations;
using EnvelopeGenerator.Application.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using DigitalData.Core.Client;
using QRCoder;
using EnvelopeGenerator.Application.Interfaces.Services;
using System.Reflection;
using EnvelopeGenerator.Application.EnvelopeReceivers.Commands;
namespace EnvelopeGenerator.Application;
/// <summary>
/// Extensions method for dependency injection
/// </summary>
public static class DependencyInjection
{
/// <summary>
/// Adds all required services for envelope generator application
/// </summary>
/// <param name="services"></param>
/// <param name="config"></param>
/// <returns></returns>
[Obsolete("Use MediatR")]
public static IServiceCollection AddEnvelopeGeneratorServices(this IServiceCollection services, IConfiguration config)
{
//Inject CRUD Service and repositoriesad
services.TryAddScoped<IConfigService, ConfigService>();
services.TryAddScoped<IDocumentReceiverElementService, DocumentReceiverElementService>();
services.TryAddScoped<IEnvelopeDocumentService, EnvelopeDocumentService>();
services.TryAddScoped<IEnvelopeHistoryService, EnvelopeHistoryService>();
services.TryAddScoped<IDocumentStatusService, DocumentStatusService>();
services.TryAddScoped<IEmailTemplateService, EmailTemplateService>();
services.TryAddScoped<IEnvelopeService, EnvelopeService>();
services.TryAddScoped<IEnvelopeDocumentService, EnvelopeDocumentService>();
services.TryAddScoped<IEnvelopeReceiverService, EnvelopeReceiverService>();
services.TryAddScoped<IEnvelopeTypeService, EnvelopeTypeService>();
services.TryAddScoped<IReceiverService, ReceiverService>();
services.TryAddScoped<IEnvelopeReceiverReadOnlyService, EnvelopeReceiverReadOnlyService>();
//Auto mapping profiles
services.AddAutoMapper(Assembly.GetExecutingAssembly());
services.AddAutoMapper(typeof(DigitalData.UserManager.Application.DIExtensions));
services.Configure<DispatcherParams>(config.GetSection(nameof(DispatcherParams)));
services.Configure<MailParams>(config.GetSection(nameof(MailParams)));
services.Configure<AuthenticatorParams>(config.GetSection(nameof(AuthenticatorParams)));
services.Configure<TotpSmsParams>(config.GetSection(nameof(TotpSmsParams)));
services.Configure<DbTriggerParams>(config.GetSection(nameof(DbTriggerParams)));
services.AddHttpClientService<GtxMessagingParams>(config.GetSection(nameof(GtxMessagingParams)));
services.TryAddSingleton<ISmsSender, GTXSmsSender>();
services.TryAddSingleton<IEnvelopeSmsHandler, EnvelopeSmsHandler>();
services.TryAddSingleton<IAuthenticator, Authenticator>();
services.TryAddSingleton<QRCodeGenerator>();
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(Assembly.GetExecutingAssembly());
cfg.RegisterServicesFromAssembly(typeof(CreateEnvelopeReceiverCommandHandler).Assembly);
});
return services;
}
}

View File

@@ -1,19 +0,0 @@
using EnvelopeGenerator.Domain;
namespace EnvelopeGenerator.Application.DocStatus.Commands;
/// <summary>
///
/// </summary>
public class CreateDocStatusCommand : UpdateDocStatusCommand
{
/// <summary>
/// Gets timestamp when this record was added. Returns the current date and time.
/// </summary>
public DateTime AddedWhen => StatusChangedWhen;
/// <summary>
/// Gets timestamp when this record was added. Returns the current date and time.
/// </summary>
public override DateTime? ChangedWhen { get; } = null;
}

View File

@@ -1,39 +0,0 @@
using EnvelopeGenerator.Domain;
namespace EnvelopeGenerator.Application.DocStatus.Commands;
/// <summary>
///
/// </summary>
public class UpdateDocStatusCommand
{
/// <summary>
/// Gets or sets the ID of the associated envelope.
/// </summary>
public int EnvelopeId { get; set; }
/// <summary>
/// Gets or sets the ID of the receiver associated with this status.
/// </summary>
public int ReceiverId { get; set; }
/// <summary>
/// Gets the current status code.
/// </summary>
public Constants.DocumentStatus Status => Value is null ? Constants.DocumentStatus.Created : Constants.DocumentStatus.Signed;
/// <summary>
/// Gets the timestamp when the status was changed. Retrns the AddedWhen value.
/// </summary>
public DateTime StatusChangedWhen { get; } = DateTime.Now;
/// <summary>
/// Gets or sets the display value associated with the status.
/// </summary>
public string? Value { get; set; }
/// <summary>
/// Gets timestamp when this record was added. Returns the current date and time.
/// </summary>
public virtual DateTime? ChangedWhen { get; } = DateTime.Now;
}

View File

@@ -1,69 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using EnvelopeGenerator.Application.Dto;
using MediatR;
using EnvelopeGenerator.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using AutoMapper;
namespace EnvelopeGenerator.Application.Documents.Queries;
/// <summary>
/// Represents a query to read a document based on its unique identifier or associated envelope identifier.
/// </summary>
/// <param name="Id">The unique identifier of the document. Optional.</param>
/// <param name="EnvelopeId">The identifier of the envelope associated with the document. Optional.</param>
public record ReadDocumentQuery(int? Id = null, int? EnvelopeId = null) : IRequest<EnvelopeDocumentDto?>
{
}
/// <summary>
/// Handles queries for reading <see cref="EnvelopeDocument"/> data based on either the document ID or the envelope ID.
/// </summary>
public class ReadDocumentQueryHandler : IRequestHandler<ReadDocumentQuery, EnvelopeDocumentDto?>
{
/// <summary>
/// Repository for accessing <see cref="EnvelopeDocument"/> entities.
/// </summary>
private readonly IRepository<EnvelopeDocument> _repo;
private readonly IMapper _mapper;
/// <summary>
/// Initializes a new instance of the <see cref="ReadDocumentQueryHandler"/> class.
/// </summary>
/// <param name="envelopeDocumentRepository">The repository used to access <see cref="EnvelopeDocument"/> entities.</param>
public ReadDocumentQueryHandler(IRepository<EnvelopeDocument> envelopeDocumentRepository, IMapper mapper)
{
_repo = envelopeDocumentRepository;
_mapper = mapper;
}
/// <summary>
/// Handles the <see cref="ReadDocumentQuery"/> and returns a <see cref="EnvelopeDocumentDto"/> based on the provided identifiers.
/// </summary>
/// <param name="query">The query containing the document ID or envelope ID to search for.</param>
/// <param name="cancellationToken">A token to monitor for cancellation requests.</param>
/// <returns>
/// A <see cref="EnvelopeDocumentDto"/> if a matching document is found; otherwise, <c>null</c>.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Thrown when neither <see cref="ReadDocumentQuery.Id"/> nor <see cref="ReadDocumentQuery.EnvelopeId"/> is provided.
/// </exception>
[Obsolete("Use MediatR")]
public async Task<EnvelopeDocumentDto?> Handle(ReadDocumentQuery query, CancellationToken cancellationToken)
{
if (query.Id is not null)
{
var doc = await _repo.ReadOnly().Where(d => d.Id == query.Id).FirstOrDefaultAsync();
return _mapper.Map<EnvelopeDocumentDto>(doc);
}
else if (query.EnvelopeId is not null)
{
var doc = await _repo.ReadOnly().Where(d => d.EnvelopeId == query.EnvelopeId).FirstOrDefaultAsync();
return _mapper.Map<EnvelopeDocumentDto>(doc);
}
throw new InvalidOperationException(
$"Invalid {nameof(ReadDocumentQuery)}: either {nameof(query.Id)} or {nameof(query.EnvelopeId)} must be provided.");
}
}

View File

@@ -1,30 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto;
/// <summary>
/// Data Transfer Object representing configuration settings.
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public class ConfigDto
{
/// <summary>
/// Gets or sets the sending profile identifier.
/// </summary>
public int SendingProfile { get; set; }
/// <summary>
/// Gets or sets the signature host URL or name.
/// </summary>
public required string SignatureHost { get; set; }
/// <summary>
/// Gets or sets the name of the external program.
/// </summary>
public string? ExternalProgramName { get; set; }
/// <summary>
/// Gets or sets the path where exports will be saved.
/// </summary>
public string? ExportPath { get; set; }
}

View File

@@ -1,95 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto;
/// <summary>
/// Data Transfer Object representing a positioned element assigned to a document receiver.
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public class DocumentReceiverElementDto
{
/// <summary>
/// Gets or sets the unique identifier of the element.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the identifier of the associated document.
/// </summary>
public int DocumentId { get; set; }
/// <summary>
/// Gets or sets the identifier of the receiver.
/// </summary>
public int ReceiverId { get; set; }
/// <summary>
/// Gets or sets the type of the element.
/// </summary>
public int ElementType { get; set; }
/// <summary>
/// Gets or sets the X coordinate of the element.
/// </summary>
public double X { get; set; }
/// <summary>
/// Gets or sets the Y coordinate of the element.
/// </summary>
public double Y { get; set; }
/// <summary>
/// Gets or sets the width of the element.
/// </summary>
public double Width { get; set; }
/// <summary>
/// Gets or sets the height of the element.
/// </summary>
public double Height { get; set; }
/// <summary>
/// Gets or sets the page number where the element appears.
/// </summary>
public int Page { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the element is required.
/// </summary>
public bool Required { get; set; }
/// <summary>
/// Gets or sets the tooltip text for the element.
/// </summary>
public string? Tooltip { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the element is read-only.
/// </summary>
public bool ReadOnly { get; set; }
/// <summary>
/// Gets or sets the annotation index for ordering or reference.
/// </summary>
public int AnnotationIndex { get; set; }
/// <summary>
/// Gets or sets the timestamp when the element was added.
/// </summary>
public DateTime AddedWhen { get; set; }
/// <summary>
/// Gets or sets the timestamp when the element was last changed, if applicable.
/// </summary>
public DateTime? ChangedWhen { get; set; }
/// <summary>
/// Gets or sets the top position of the element (in layout terms).
/// </summary>
public double Top { get; set; }
/// <summary>
/// Gets or sets the left position of the element (in layout terms).
/// </summary>
public double Left { get; set; }
}

View File

@@ -1,50 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto;
/// <summary>
/// Data Transfer Object representing the status of a document for a specific receiver.
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public class DocumentStatusDto
{
/// <summary>
/// Gets or sets the unique identifier of the document status entry.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the ID of the associated envelope.
/// </summary>
public int EnvelopeId { get; set; }
/// <summary>
/// Gets or sets the ID of the receiver associated with this status.
/// </summary>
public int ReceiverId { get; set; }
/// <summary>
/// Gets or sets the current status code.
/// </summary>
public int Status { get; set; }
/// <summary>
/// Gets or sets the timestamp when the status was changed.
/// </summary>
public DateTime? StatusChangedWhen { get; set; }
/// <summary>
/// Gets or sets the timestamp when this record was added.
/// </summary>
public DateTime AddedWhen { get; set; }
/// <summary>
/// Gets or sets the timestamp when this record was last changed.
/// </summary>
public DateTime? ChangedWhen { get; set; }
/// <summary>
/// Gets or sets the display value associated with the status.
/// </summary>
public string? Value { get; set; }
}

View File

@@ -1,31 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto
{
/// <summary>
///
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public record EmailTemplateDto
{
/// <summary>
///
/// </summary>
public int Id{ get; init; }
/// <summary>
///
/// </summary>
public required string Name { get; init; }
/// <summary>
///
/// </summary>
public required string Body { get; set; }
/// <summary>
///
/// </summary>
public required string Subject { get; set; }
};
}

View File

@@ -1,35 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto;
/// <summary>
/// Data Transfer Object representing a document within an envelope, including optional binary data and form elements.
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public class EnvelopeDocumentDto
{
/// <summary>
/// Gets or sets the unique identifier of the document.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the envelope ID to which the document belongs.
/// </summary>
public int EnvelopeId { get; set; }
/// <summary>
/// Gets or sets the date and time when the document was added.
/// </summary>
public DateTime AddedWhen { get; set; }
/// <summary>
/// Gets or sets the binary data of the document, if available.
/// </summary>
public byte[]? ByteData { get; set; }
/// <summary>
/// Gets or sets the collection of elements associated with the document for receiver interactions, if any.
/// </summary>
public IEnumerable<DocumentReceiverElementDto>? Elements { get; set; }
}

View File

@@ -1,120 +0,0 @@
using DigitalData.EmailProfilerDispatcher.Abstraction.Attributes;
using DigitalData.UserManager.Application.DTOs.User;
using EnvelopeGenerator.Domain.Entities;
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto;
/// <summary>
///
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public record EnvelopeDto
{
/// <summary>
///
/// </summary>
public int Id { get; set; }
/// <summary>
///
/// </summary>
public int UserId { get; set; }
/// <summary>
///
/// </summary>
public int Status { get; set; }
/// <summary>
/// Default value is string.Empty
/// </summary>
public string StatusName { get; set; } = string.Empty;
/// <summary>
/// Default value is string.Empty
/// </summary>
public string Uuid { get; set; } = string.Empty;
/// <summary>
/// Default value is string.Empty
/// </summary>
[TemplatePlaceholder("[MESSAGE]")]
public string Message { get; set; } = string.Empty;
/// <summary>
///
/// </summary>
public DateTime AddedWhen { get; set; }
/// <summary>
///
/// </summary>
public DateTime? ChangedWhen { get; set; }
/// <summary>
/// Default value is string.Empty
/// </summary>
[TemplatePlaceholder("[DOCUMENT_TITLE]")]
public string Title { get; set; } = string.Empty;
/// <summary>
///
/// </summary>
public int? ContractType { get; set; }
/// <summary>
/// Default value is 'de-DE'
/// </summary>
public string Language { get; set; } = "de-DE";
/// <summary>
///
/// </summary>
public int? EnvelopeTypeId { get; set; }
/// <summary>
///
/// </summary>
public int? CertificationType { get; set; }
/// <summary>
///
/// </summary>
public bool? UseAccessCode { get; set; }
/// <summary>
///
/// </summary>
public bool TFAEnabled { get; init; }
/// <summary>
///
/// </summary>
public UserReadDto? User { get; set; }
/// <summary>
///
/// </summary>
public EnvelopeType? EnvelopeType { get; set; }
/// <summary>
///
/// </summary>
public string? EnvelopeTypeTitle { get; set; }
/// <summary>
///
/// </summary>
public bool IsAlreadySent { get; set; }
/// <summary>
///
/// </summary>
public byte[]? DocResult { get; init; }
/// <summary>
///
/// </summary>
public IEnumerable<EnvelopeDocumentDto>? Documents { get; set; }
}

View File

@@ -1,34 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto.EnvelopeHistory;
/// <summary>
/// Data Transfer Object for creating a new envelope history record.
/// </summary>
public class EnvelopeHistoryCreateDto
{
/// <summary>
/// Gets or sets the identifier of the envelope.
/// </summary>
public int EnvelopeId { get; set; }
/// <summary>
/// Gets or sets the user reference associated with the action.
/// </summary>
public required string UserReference { get; set; }
/// <summary>
/// Gets or sets the status of the envelope at the time of the action.
/// </summary>
public int Status { get; set; }
/// <summary>
/// Gets or sets the date and time when the action occurred.
/// </summary>
public DateTime? ActionDate { get; set; }
/// <summary>
/// Gets or sets an optional comment related to the action.
/// </summary>
public string? Comment { get; set; }
}

View File

@@ -1,69 +0,0 @@
using DigitalData.UserManager.Application.DTOs.User;
using EnvelopeGenerator.Application.Dto.Receiver;
using static EnvelopeGenerator.Domain.Constants;
namespace EnvelopeGenerator.Application.Dto.EnvelopeHistory;
/// <summary>
/// Data Transfer Object representing the history of an envelope, including status, sender, receiver, and related metadata.
/// </summary>
public record EnvelopeHistoryDto
{
/// <summary>
/// Unique identifier for the envelope history entry.
/// </summary>
public long Id { get; set; }
/// <summary>
/// Identifier of the associated envelope.
/// </summary>
public int EnvelopeId { get; set; }
/// <summary>
/// Reference string for the user related to this history entry.
/// </summary>
public required string UserReference { get; set; }
/// <summary>
/// Include code of the envelope at this history point.
/// </summary>
public int Status { get; set; }
/// <summary>
/// Human-readable name of the status.
/// </summary>
public string? StatusName { get; set; }
/// <summary>
/// Date and time when this history entry was added.
/// </summary>
public DateTime AddedWhen { get; set; }
/// <summary>
/// Date and time when an action was performed, if applicable.
/// </summary>
public DateTime? ActionDate { get; set; }
/// <summary>
/// Information about the sender associated with this history entry.
/// </summary>
public UserCreateDto? Sender { get; set; }
/// <summary>
/// Information about the receiver associated with this history entry.
/// </summary>
public ReceiverReadDto? Receiver { get; set; }
/// <summary>
/// Type of reference for this history entry.
/// </summary>
public ReferenceType ReferenceType { get; set; }
/// <summary>
/// Optional comment related to this history entry.
/// </summary>
public string? Comment { get; set; }
/// <inheritdoc/>
public override int GetHashCode() => Id.GetHashCode();
};

View File

@@ -1,67 +0,0 @@
using DigitalData.EmailProfilerDispatcher.Abstraction.Attributes;
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto.EnvelopeReceiver;
/// <summary>
///
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public record EnvelopeReceiverBasicDto
{
/// <summary>
///
/// </summary>
public (int Envelope, int Receiver) Id => (Envelope: EnvelopeId, Receiver: ReceiverId);
/// <summary>
///
/// </summary>
public int EnvelopeId { get; init; }
/// <summary>
///
/// </summary>
public int ReceiverId { get; init; }
/// <summary>
///
/// </summary>
public int Sequence { get; init; }
/// <summary>
///
/// </summary>
[TemplatePlaceholder("[NAME_RECEIVER]")]
public string? Name { get; init; }
/// <summary>
///
/// </summary>
public string? JobTitle { get; init; }
/// <summary>
///
/// </summary>
public string? CompanyName { get; init; }
/// <summary>
///
/// </summary>
public string? PrivateMessage { get; init; }
/// <summary>
///
/// </summary>
public DateTime AddedWhen { get; init; }
/// <summary>
///
/// </summary>
public DateTime? ChangedWhen { get; init; }
/// <summary>
///
/// </summary>
public bool HasPhoneNumber { get; init; }
}

View File

@@ -1,22 +0,0 @@
using EnvelopeGenerator.Application.Dto.Receiver;
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto.EnvelopeReceiver;
/// <summary>
///
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public record EnvelopeReceiverDto() : EnvelopeReceiverBasicDto()
{
/// <summary>
///
/// </summary>
public EnvelopeDto? Envelope { get; set; }
/// <summary>
///
/// </summary>
public ReceiverReadDto? Receiver { get; set; }
}

View File

@@ -1,20 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto.EnvelopeReceiver;
/// <summary>
///
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public record EnvelopeReceiverSecretDto : EnvelopeReceiverDto
{
/// <summary>
///
/// </summary>
public string? AccessCode { get; init; }
/// <summary>
///
/// </summary>
public string? PhoneNumber { get; init; }
}

View File

@@ -1,39 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace EnvelopeGenerator.Application.Dto.EnvelopeReceiverReadOnly;
/// <summary>
///
/// </summary>
/// <param name="DateValid"></param>
[ApiExplorerSettings(IgnoreApi = true)]
public record EnvelopeReceiverReadOnlyCreateDto(
DateTime DateValid)
{
/// <summary>
///
/// </summary>
[EmailAddress]
[Required]
public required string ReceiverMail { get; init; }
/// <summary>
/// Default value is null
/// </summary>
[JsonIgnore]
public long? EnvelopeId { get; set; } = null;
/// <summary>
///
/// </summary>
[JsonIgnore]
public string? AddedWho { get; set; }
/// <summary>
/// Default value is DateTime.Now
/// </summary>
[JsonIgnore]
public DateTime AddedWhen { get; } = DateTime.Now;
};

View File

@@ -1,63 +0,0 @@
using EnvelopeGenerator.Application.Dto.Receiver;
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto.EnvelopeReceiverReadOnly;
/// <summary>
/// Represents a read-only Data Transfer Object (DTO) for an envelope receiver.
/// Contains information about the receiver, associated envelope, and audit details.
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public class EnvelopeReceiverReadOnlyDto
{
/// <summary>
/// Gets or inits the unique identifier of the envelope receiver.
/// </summary>
public long Id { get; init; }
/// <summary>
/// Gets or inits the identifier of the associated envelope.
/// </summary>
public long EnvelopeId { get; init; }
/// <summary>
/// Gets or inits the email address of the receiver.
/// </summary>
public required string ReceiverMail { get; set; }
/// <summary>
/// Gets or inits the date until which the receiver is valid.
/// </summary>
public DateTime DateValid { get; set; }
/// <summary>
/// Gets or inits the date and time when the receiver was added.
/// </summary>
public DateTime AddedWhen { get; init; }
/// <summary>
/// Gets or inits the user who added the receiver.
/// Default value is 'unknown'.
/// </summary>
public string AddedWho { get; init; } = "Unknown";
/// <summary>
/// Gets or inits the associated envelope details.
/// </summary>
public EnvelopeDto? Envelope { get; set; }
/// <summary>
/// Gets or inits the user who last changed the receiver, if any.
/// </summary>
public string? ChangedWho { get; set; }
/// <summary>
/// Gets or inits the date and time when the receiver was last changed, if any.
/// </summary>
public DateTime? ChangedWhen { get; set; }
/// <summary>
/// Gets or inits the associated receiver details.
/// </summary>
public ReceiverReadDto? Receiver { get; set; }
}

View File

@@ -1,31 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto.EnvelopeReceiverReadOnly;
/// <summary>
/// Data Transfer Object for updating a read-only envelope receiver.
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public class EnvelopeReceiverReadOnlyUpdateDto
{
/// <summary>
/// Gets or sets the unique identifier of the envelope receiver.
/// </summary>
public long Id { get; init; }
/// <summary>
/// Gets or sets the date when the envelope receiver becomes valid.
/// </summary>
public DateTime DateValid { get; set; }
/// <summary>
/// Gets or sets the name of the user who made the change.
/// Default value is unknown.
/// </summary>
public string ChangedWho { get; set; } = "Unknown";
/// <summary>
/// Gets or sets the date and time when the change was made.
/// </summary>
public DateTime ChangedWhen { get; set; } = DateTime.Now;
}

View File

@@ -1,85 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto;
/// <summary>
/// Data Transfer Object representing a type of envelope with its configuration settings.
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public class EnvelopeTypeDto
{
/// <summary>
/// Gets or sets the unique identifier of the envelope type.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the title of the envelope type.
/// </summary>
public string Title { get; set; } = string.Empty;
/// <summary>
/// Gets or sets the language code used in this envelope type.
/// </summary>
public string Language { get; set; } = "de-DE";
/// <summary>
/// Gets or sets the number of days after which the envelope expires.
/// </summary>
public int? ExpiresDays { get; set; }
/// <summary>
/// Gets or sets the certification type identifier.
/// </summary>
public int? CertificationType { get; set; }
/// <summary>
/// Gets or sets a value indicating whether an access code is required.
/// </summary>
public bool? UseAccessCode { get; set; }
/// <summary>
/// Gets or sets the final email template ID to be sent to the creator.
/// </summary>
public int? FinalEmailToCreator { get; set; }
/// <summary>
/// Gets or sets the final email template ID to be sent to the receivers.
/// </summary>
public int? FinalEmailToReceivers { get; set; }
/// <summary>
/// Gets or sets the timestamp when this envelope type was added.
/// </summary>
public DateTime AddedWhen { get; set; }
/// <summary>
/// Gets or sets the timestamp when this envelope type was last changed.
/// </summary>
public DateTime? ChangedWhen { get; set; }
/// <summary>
/// Gets or sets the number of days before expiry when a warning should be sent.
/// </summary>
public int? ExpiresWarningDays { get; set; }
/// <summary>
/// Gets or sets a value indicating whether reminder emails should be sent.
/// </summary>
public bool? SendReminderEmails { get; set; }
/// <summary>
/// Gets or sets the number of days before the first reminder is sent.
/// </summary>
public int? FirstReminderDays { get; set; }
/// <summary>
/// Gets or sets the interval in days between reminder emails.
/// </summary>
public int? ReminderIntervalDays { get; set; }
/// <summary>
/// Gets or sets the contract type associated with the envelope type.
/// </summary>
public int? ContractType { get; set; }
}

View File

@@ -1,66 +0,0 @@
using AutoMapper;
using EnvelopeGenerator.Application.Dto.EnvelopeHistory;
using EnvelopeGenerator.Application.Dto.EnvelopeReceiver;
using EnvelopeGenerator.Application.Dto.EnvelopeReceiverReadOnly;
using EnvelopeGenerator.Application.Dto.Messaging;
using EnvelopeGenerator.Application.Dto.Receiver;
using EnvelopeGenerator.Application.Extensions;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Dto;
/// <summary>
/// Represents the AutoMapper profile configuration for mapping between
/// domain entities and data transfer objects (DTOs) used within the EnvelopeGenerator application.
/// </summary>
public class MappingProfile : Profile
{
/// <summary>
/// Initializes a new instance of the <see cref="MappingProfile"/> class.
/// Configures the mappings between entities and DTOs used throughout the application.
/// </summary>
public MappingProfile()
{
// Entity to DTO mappings
CreateMap<Config, ConfigDto>();
CreateMap<DocumentReceiverElement, DocumentReceiverElementDto>();
CreateMap<DocumentStatus, DocumentStatusDto>();
CreateMap<EmailTemplate, EmailTemplateDto>();
CreateMap<Envelope, EnvelopeDto>();
CreateMap<EnvelopeDocument, EnvelopeDocumentDto>();
CreateMap<Domain.Entities.EnvelopeHistory, EnvelopeHistoryDto>();
CreateMap<Domain.Entities.EnvelopeHistory, EnvelopeHistoryCreateDto>();
CreateMap<Domain.Entities.EnvelopeReceiver, EnvelopeReceiverDto>();
CreateMap<Domain.Entities.EnvelopeReceiver, EnvelopeReceiverSecretDto>();
CreateMap<EnvelopeType, EnvelopeTypeDto>();
CreateMap<Domain.Entities.Receiver, ReceiverReadDto>();
CreateMap<Domain.Entities.Receiver, ReceiverCreateDto>();
CreateMap<Domain.Entities.Receiver, ReceiverUpdateDto>();
CreateMap<Domain.Entities.EnvelopeReceiverReadOnly, EnvelopeReceiverReadOnlyDto>();
// DTO to Entity mappings
CreateMap<ConfigDto, Config>();
CreateMap<DocumentReceiverElementDto, DocumentReceiverElement>();
CreateMap<DocumentStatusDto, DocumentStatus>();
CreateMap<EmailTemplateDto, EmailTemplate>();
CreateMap<EnvelopeDto, Envelope>();
CreateMap<EnvelopeDocumentDto, EnvelopeDocument>();
CreateMap<EnvelopeHistoryDto, Domain.Entities.EnvelopeHistory>();
CreateMap<EnvelopeHistoryCreateDto, Domain.Entities.EnvelopeHistory>();
CreateMap<EnvelopeReceiverDto, Domain.Entities.EnvelopeReceiver>();
CreateMap<EnvelopeTypeDto, EnvelopeType>();
CreateMap<ReceiverReadDto, Domain.Entities.Receiver>().ForMember(rcv => rcv.EnvelopeReceivers, rcvReadDto => rcvReadDto.Ignore());
CreateMap<ReceiverCreateDto, Domain.Entities.Receiver>();
CreateMap<ReceiverUpdateDto, Domain.Entities.Receiver>();
CreateMap<Domain.Entities.EnvelopeReceiver, EnvelopeReceiverBasicDto>();
CreateMap<EnvelopeReceiverReadOnlyCreateDto, Domain.Entities.EnvelopeReceiverReadOnly>();
CreateMap<EnvelopeReceiverReadOnlyUpdateDto, Domain.Entities.EnvelopeReceiverReadOnly>();
// Messaging mappings
// for GTX messaging
CreateMap<GtxMessagingResponse, SmsResponse>()
.ConstructUsing(gtxRes => gtxRes.Ok()
? new SmsResponse() { Ok = true }
: new SmsResponse() { Ok = false, Errors = gtxRes });
}
}

View File

@@ -1,9 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto.Messaging;
/// <summary>
///
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public class GtxMessagingResponse : Dictionary<string, object?> { }

View File

@@ -1,25 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto.Messaging;
/// <summary>
///
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public record SmsResponse
{
/// <summary>
///
/// </summary>
public required bool Ok { get; init; }
/// <summary>
/// Returns !Ok
/// </summary>
public bool Failed => !Ok;
/// <summary>
///
/// </summary>
public dynamic? Errors { get; init; }
}

View File

@@ -1,53 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;
using System.Security.Cryptography;
using System.Text;
namespace EnvelopeGenerator.Application.Dto.Receiver;
/// <summary>
///
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public record ReceiverCreateDto
{
/// <summary>
///
/// </summary>
public ReceiverCreateDto()
{
_sha256HexOfMail = new(() =>
{
var bytes_arr = Encoding.UTF8.GetBytes(EmailAddress!.ToUpper());
var hash_arr = SHA256.HashData(bytes_arr);
var hexa_str = BitConverter.ToString(hash_arr);
return hexa_str.Replace("-", string.Empty);
});
}
/// <summary>
///
/// </summary>
[EmailAddress]
public required string EmailAddress { get; init; }
/// <summary>
///
/// </summary>
public string? TotpSecretkey { get; init; }
/// <summary>
/// var bytes_arr = Encoding.UTF8.GetBytes(EmailAddress.ToUpper());<br/>
/// var hash_arr = SHA256.HashData(bytes_arr);
/// var hexa_str = BitConverter.ToString(hash_arr);
/// return hexa_str.Replace("-", string.Empty);
/// </summary>
public string Signature => _sha256HexOfMail.Value;
private readonly Lazy<string> _sha256HexOfMail;
/// <summary>
/// Default value is DateTime.Now
/// </summary>
public DateTime AddedWhen { get; } = DateTime.Now;
};

View File

@@ -1,62 +0,0 @@
using EnvelopeGenerator.Application.Dto.EnvelopeReceiver;
using Microsoft.AspNetCore.Mvc;
using System.Text.Json.Serialization;
namespace EnvelopeGenerator.Application.Dto.Receiver;
/// <summary>
///
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public class ReceiverReadDto
{
/// <summary>
///
/// </summary>
public int Id { get; set; }
/// <summary>
///
/// </summary>
public required string EmailAddress { get; set; }
/// <summary>
///
/// </summary>
public required string Signature { get; set; }
/// <summary>
///
/// </summary>
public DateTime AddedWhen { get; set; }
/// <summary>
///
/// </summary>
[JsonIgnore]
public IEnumerable<EnvelopeReceiverBasicDto>? EnvelopeReceivers { get; set; }
/// <summary>
///
/// </summary>
public string? LastUsedName => EnvelopeReceivers?.LastOrDefault()?.Name;
/// <summary>
///
/// </summary>
public string? TotpSecretkey { get; set; } = null;
/// <summary>
///
/// </summary>
public DateTime? TfaRegDeadline { get; set; }
/// <summary>
///
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
return Id.GetHashCode();
}
}

View File

@@ -1,27 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Application.Dto.Receiver;
/// <summary>
/// Data Transfer Object for updating a receiver's information.
/// </summary>
[ApiExplorerSettings(IgnoreApi = true)]
public class ReceiverUpdateDto
{
/// <summary>
/// Gets or sets the unique identifier of the receiver.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Gets or sets the TOTP (Time-based One-Time Password) secret key.
/// Optional.
/// </summary>
public string? TotpSecretkey { get; set; }
/// <summary>
/// Gets or sets the deadline for two-factor authentication registration.
/// Optional.
/// </summary>
public DateTime? TfaRegDeadline { get; set; }
}

View File

@@ -1,40 +0,0 @@
using EnvelopeGenerator.Domain;
using MediatR;
namespace EnvelopeGenerator.Application.EmailTemplates.Commands.Reset;
/// <summary>
/// Ein Befehl zum Zurücksetzen einer E-Mail-Vorlage auf die Standardwerte.
/// Erbt von <see cref="EmailTemplateQuery"/> und ermöglicht die Angabe einer optionalen ID und eines Typs der E-Mail-Vorlage.<br/><br/>
/// Beispiele:<br/>
/// 0 - DocumentReceived: Benachrichtigung über den Empfang eines Dokuments.<br/>
/// 1 - DocumentSigned: Benachrichtigung über die Unterzeichnung eines Dokuments.<br/>
/// 2 - DocumentDeleted: Benachrichtigung über das Löschen eines Dokuments.<br/>
/// 3 - DocumentCompleted: Benachrichtigung über den Abschluss eines Dokuments.<br/>
/// 4 - DocumentAccessCodeReceived: Benachrichtigung über den Erhalt eines Zugangscodes.<br/>
/// 5 - DocumentShared: Benachrichtigung über das Teilen eines Dokuments.<br/>
/// 6 - TotpSecret: Benachrichtigung über ein TOTP-Geheimnis.<br/>
/// 7 - DocumentRejected_ADM (Für den Absender): Mail an den Absender, wenn das Dokument abgelehnt wird.<br/>
/// 8 - DocumentRejected_REC (Für den ablehnenden Empfänger): Mail an den ablehnenden Empfänger, wenn das Dokument abgelehnt wird.<br/>
/// 9 - DocumentRejected_REC_2 (Für sonstige Empfänger): Mail an andere Empfänger (Brief), wenn das Dokument abgelehnt wird.<br/>
/// </summary>
public record ResetEmailTemplateCommand : EmailTemplateQuery, IRequest
{
/// <summary>
///
/// </summary>
/// <param name="orginal"></param>
public ResetEmailTemplateCommand(EmailTemplateQuery? orginal = null) : base(orginal ?? new())
{
}
/// <summary>
///
/// </summary>
/// <param name="Id">Die optionale ID der E-Mail-Vorlage, die zurückgesetzt werden soll.</param>
/// <param name="Type">Der Typ der E-Mail-Vorlage, z. B. <see cref="Constants.EmailTemplateType"/> (optional).</param>
public ResetEmailTemplateCommand(int? Id = null, Constants.EmailTemplateType? Type = null) : base(Id, Type)
{
}
};

View File

@@ -1,116 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using EnvelopeGenerator.Application.Dto;
using EnvelopeGenerator.Domain.Entities;
using MediatR;
using Microsoft.EntityFrameworkCore;
using System.Linq;
namespace EnvelopeGenerator.Application.EmailTemplates.Commands.Reset;
/// <summary>
///
/// </summary>
public class ResetEmailTemplateCommandHandler : IRequestHandler<ResetEmailTemplateCommand>
{
private readonly IRepository<EmailTemplate> _repository;
/// <summary>
///
/// </summary>
/// <param name="repository"></param>
public ResetEmailTemplateCommandHandler(IRepository<EmailTemplate> repository)
{
_repository = repository;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancel"></param>
/// <returns></returns>
[Obsolete("Use Read-method returning IReadQuery<TEntity> instead.")]
public async Task Handle(ResetEmailTemplateCommand request, CancellationToken cancel)
{
var temps = request.Id is not null
? await _repository.ReadOnly().Where(t => t.Id == request.Id).ToListAsync(cancel)
: request.Type is not null
? await _repository.ReadOnly().Where(t => t.Name == request.Type.ToString()).ToListAsync(cancel)
: await _repository.ReadOnly().ToListAsync(cancel);
foreach (var temp in temps)
{
var def = Defaults.Where(t => t.Name == temp.Name).FirstOrDefault();
if(def is not null)
await _repository.UpdateAsync(def, t => t.Id == temp.Id, cancel);
}
}
/// <summary>
///
/// </summary>
public static readonly IEnumerable<EmailTemplateDto> Defaults = new List<EmailTemplateDto>()
{
new(){
Id = 1,
Name = "DocumentReceived",
Body = "Guten Tag [NAME_RECEIVER],<br />\r\n<br /><B><I>\r\n[NAME_SENDER]</I></B> hat Ihnen ein Dokument zum [SIGNATURE_TYPE] gesendet.<br />\r\n<br />\r\nÜber den folgenden Link können Sie das Dokument einsehen und elektronisch unterschreiben: <a href=\"[LINK_TO_DOCUMENT]\">[LINK_TO_DOCUMENT_TEXT]</a><br />\r\n<br />\r\n[MESSAGE]<br />\r\n<br />\r\nMit freundlichen Grüßen<br />\r\n<br />\r\n[NAME_PORTAL]",
Subject = "Dokument erhalten: '[DOCUMENT_TITLE]'"
},
new(){
Id = 2,
Name = "DocumentDeleted",
Body = "Guten Tag [NAME_RECEIVER],<br />\r\n<br /><B><I>\r\n[NAME_SENDER]</I></B> hat den Umschlag <B><I>'[DOCUMENT_TITLE]'</I></B> gelöscht/zurückgezogen.<br /><p>\rBegründung: <br /> <I>[REASON]</I> <p>\r\n<br />\r\nMit freundlichen Grüßen<br />\r\n<br />\r\n[NAME_PORTAL]",
Subject = "Umschlag zurückgezogen: '[DOCUMENT_TITLE]'"
},
new(){
Id = 3,
Name = "DocumentSigned",
Body = "Guten Tag [NAME_RECEIVER],<br />\r\n<br />\r\nhiermit bestätigen wir Ihnen die erfolgreiche Signatur für den Vorgang <B><I>'[DOCUMENT_TITLE]'</I></B>.<br />\r\nWenn alle Vertragspartner unterzeichnet haben, erhalten Sie ebenfalls per email ein unterschriebenes Exemplar mit dem Signierungszertifikat!\r\n<br />\r\nMit freundlichen Grüßen<br />\r\n<br />\r\n[NAME_PORTAL]",
Subject = "Dokument unterschrieben: '[DOCUMENT_TITLE]'"
},
new(){
Id = 4,
Name = "DocumentCompleted",
Body = "Guten Tag [NAME_RECEIVER],<br />\r\n<br />\r\nDer Signaturvorgang <B><I>'[DOCUMENT_TITLE]'</I></B> wurde erfolgreich abgeschlossen.<br />\r\n<br />\r\nSie erhalten das Dokument mit einem detaillierten Ergebnisbericht als Anhang zu dieser EmailAddress.<br />\r\n<br />\r\nMit freundlichen Grüßen<br />\r\n<br />\r\n[NAME_PORTAL]",
Subject = "Umschlag abgeschlossen: '[DOCUMENT_TITLE]'"
},
new(){
Id = 5,
Name = "DocumentAccessCodeReceived",
Body = "Guten Tag [NAME_RECEIVER],<br />\r\n<br /><B><I>\r\n[NAME_SENDER]</I></B> hat Ihnen ein Dokument zum [SIGNATURE_TYPE] gesendet. <br />\r\n<br />\r\nVerwenden Sie den folgenden Zugriffscode, um das Dokument einzusehen:<br />\r\n<br />\r\n[DOCUMENT_ACCESS_CODE]<br />\r\n<br />\r\nMit freundlichen Grüßen<br />\r\n<br />\r\n[NAME_PORTAL]",
Subject = "Zugriffscode für Dokument erhalten: '[DOCUMENT_TITLE]'"
},
new(){
Id = 6,
Name = "DocumentRejected_ADM",
Body = "Guten Tag [NAME_SENDER],<p><B><I>[NAME_RECEIVER]</I></B> hat den Umschlag <B><I>'[DOCUMENT_TITLE]'</I></B> mit folgendem Grund abgelehnt: <p>\r\n[REASON] \r\n<p>Der Umschlag wurde auf den Include Rejected gesetzt. <p> \r\nMit freundlichen Grüßen<br />\r\n<br />\r\n[NAME_PORTAL]",
Subject = "'[DOCUMENT_TITLE]' - Unterzeichnungsvorgang zurückgezogen"
},
new(){
Id = 9,
Name = "DocumentRejected_REC",
Body = "Guten Tag [NAME_RECEIVER],\r\n<p>Hiermit bestätigen wir Ihnen die Ablehnung des Unterzeichnungsvorganges <B><I>'[DOCUMENT_TITLE]'</I></B>!<p>Der Vertragsinhaber <B><I>[NAME_SENDER]</I></B> wurde über die Ablehnung informiert. <p> \r\nMit freundlichen Grüßen<br />\r\n<br />\r\n[NAME_PORTAL]",
Subject = "'[DOCUMENT_TITLE]' - Bestätigung Ablehnung"
},
new(){
Id = 10,
Name = "DocumentRejected_REC_2",
Body = "Guten Tag [NAME_RECEIVER],\r\n<p>Der Unterzeichnungsvorganges <B><I>'[DOCUMENT_TITLE]'</I></B> wurde durch einen anderen Vertragspartner abgelehnt! Ihre notwendige Unterzeichnung wurde verworfen.<p> Der Vertragsinhaber <B><I>[NAME_SENDER]</I></B> wird sich bei Bedarf mit Ihnen in Verbindung setzen. <p> \r\nMit freundlichen Grüßen<br />\r\n<br />\r\n[NAME_PORTAL]",
Subject = "'[DOCUMENT_TITLE]' - Unterzeichnungsvorgang abgelehnt."
},
new(){
Id = 11,
Name = "DocumentShared",
Body = "Guten Tag,<br /> <br /><B><I> [NAME_RECEIVER]</I></B> hat Ihnen ein Dokument zum Ansehen gesendet.<br /> <br /> Über den folgenden Link können Sie das Dokument einsehen: <a href=\"[LINK_TO_DOCUMENT]\">[LINK_TO_DOCUMENT_TEXT]</a><br /> <br /> <br /> Mit freundlichen Grüßen<br /> <br /> [NAME_PORTAL]",
Subject = "Dokument geteilt: '[DOCUMENT_TITLE]'"
},
new(){
Id = 12,
Name = "TotpSecret",
Body = "Guten Tag,<br /> <br />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.<br /> <br /> <img src=\"data:image/png;base64,[TFA_QR_CODE]\" style=\"width: 13rem; height: 13rem;\"><br /> <br />\r\n<br /> Mit freundlichen Grüßen<br /> <br /> [NAME_PORTAL]",
Subject = "2-Faktor-Verifizierung QR-Code"
}
};
}

View File

@@ -1,29 +0,0 @@
using MediatR;
using System.Text.Json.Serialization;
namespace EnvelopeGenerator.Application.EmailTemplates.Commands.Update;
/// <summary>
/// Befehl zum Aktualisieren einer E-Mail-Vorlage.
/// </summary>
/// <param name="Body">
/// (Optional)Der neue Inhalt des E-Mail-Textkörpers. Wenn null, bleibt der vorhandene Inhalt unverändert.
/// </param>
/// <param name="Subject">
/// (Optional) Der neue Betreff der E-Mail. Wenn null, bleibt der vorhandene Betreff unverändert.
/// </param>
public record UpdateEmailTemplateCommand(string? Body = null, string? Subject = null) : IRequest
{
/// <param>
/// Die Abfrage, die die E-Mail-Vorlage darstellt, die aktualisiert werden soll.
/// </param>
[JsonIgnore]
public EmailTemplateQuery? EmailTemplateQuery { get; set; }
/// <summary>
///
/// </summary>
[JsonIgnore]
public DateTime ChangedWhen { get; init; } = DateTime.Now;
}

View File

@@ -1,72 +0,0 @@
using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using EnvelopeGenerator.Application.Dto;
using EnvelopeGenerator.Application.Exceptions;
using EnvelopeGenerator.Domain;
using EnvelopeGenerator.Domain.Entities;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace EnvelopeGenerator.Application.EmailTemplates.Commands.Update;
/// <summary>
///
/// </summary>
public class UpdateEmailTemplateCommandHandler : IRequestHandler<UpdateEmailTemplateCommand>
{
private readonly IRepository<EmailTemplate> _repository;
private readonly IMapper _mapper;
/// <summary>
///
/// </summary>
/// <param name="repository"></param>
public UpdateEmailTemplateCommandHandler(IRepository<EmailTemplate> repository, IMapper mapper)
{
_repository = repository;
_mapper = mapper;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancel"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="NotFoundException"></exception>
[Obsolete("Use Read-method returning IReadQuery<TEntity> instead.")]
public async Task Handle(UpdateEmailTemplateCommand request, CancellationToken cancel)
{
EmailTemplateDto? tempDto;
if (request.EmailTemplateQuery?.Id is int id)
{
var temp = await _repository.ReadOnly().Where(t => t.Id == id).FirstOrDefaultAsync(cancel);
tempDto = _mapper.Map<EmailTemplateDto>(temp);
}
else if (request!.EmailTemplateQuery!.Type is Constants.EmailTemplateType type)
{
var temp = await _repository.ReadOnly().Where(t => t.Name == type.ToString()).FirstOrDefaultAsync(cancel);
tempDto = _mapper.Map<EmailTemplateDto>(temp);
}
else
{
throw new InvalidOperationException("Both id and type is null. Id: " + request.EmailTemplateQuery.Id +". Type: " + request.EmailTemplateQuery.Type.ToString());
}
if (tempDto == null)
{
throw new NotFoundException();
}
if (request.Body is not null)
tempDto.Body = request.Body;
if (request.Subject is not null)
tempDto.Subject = request.Subject;
await _repository.UpdateAsync(tempDto, t => t.Id == tempDto.Id, cancel);
}
}

View File

@@ -1,24 +0,0 @@
using EnvelopeGenerator.Domain;
namespace EnvelopeGenerator.Application.EmailTemplates;
/// <summary>
/// Repräsentiert eine Abfrage für E-Mail-Vorlagen, die für Absender und Empfänger von Umschlägen verwendet werden.
/// Die Standardkultur ist "de-DE".
/// </summary>
/// <param name="Id">Die eindeutige Kennung der E-Mail-Vorlage (optional).</param>
/// <param name="Type">Der Typ der E-Mail-Vorlage, z. B. <see cref="Constants.EmailTemplateType"/> (optional). Beispiele:
/// 0 - DocumentReceived: Benachrichtigung über den Empfang eines Dokuments.
/// 1 - DocumentSigned: Benachrichtigung über die Unterzeichnung eines Dokuments.
/// 2 - DocumentDeleted: Benachrichtigung über das Löschen eines Dokuments.
/// 3 - DocumentCompleted: Benachrichtigung über den Abschluss eines Dokuments.
/// 4 - DocumentAccessCodeReceived: Benachrichtigung über den Erhalt eines Zugangscodes.
/// 5 - DocumentShared: Benachrichtigung über das Teilen eines Dokuments.
/// 6 - TotpSecret: Benachrichtigung über ein TOTP-Geheimnis.
/// 7 - DocumentRejected_ADM (Für den Absender): Mail an den Absender, wenn das Dokument abgelehnt wird.
/// 8 - DocumentRejected_REC (Für den ablehnenden Empfänger): Mail an den ablehnenden Empfänger, wenn das Dokument abgelehnt wird.
/// 9 - DocumentRejected_REC_2 (Für sonstige Empfänger): Mail an andere Empfänger (Brief), wenn das Dokument abgelehnt wird.
/// </param>
public record EmailTemplateQuery(int? Id = null, Constants.EmailTemplateType? Type = null)
{
}

View File

@@ -1,24 +0,0 @@
using AutoMapper;
using EnvelopeGenerator.Application.EmailTemplates.Queries.Read;
using EnvelopeGenerator.Domain.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EnvelopeGenerator.Application.EmailTemplates;
/// <summary>
///
/// </summary>
public class MappingProfile : Profile
{
/// <summary>
///
/// </summary>
public MappingProfile()
{
CreateMap<EmailTemplate, ReadEmailTemplateResponse>();
}
}

View File

@@ -1,12 +0,0 @@
using MediatR;
namespace EnvelopeGenerator.Application.EmailTemplates.Queries.Read;
/// <summary>
/// Stellt eine Abfrage dar, um eine E-Mail-Vorlage zu lesen.
/// Diese Klasse erbt von <see cref="EmailTemplateQuery"/>.
/// </summary>
public record ReadEmailTemplateQuery : EmailTemplateQuery, IRequest<ReadEmailTemplateResponse?>
{
}

View File

@@ -1,52 +0,0 @@
using AutoMapper;
using EnvelopeGenerator.Application.Interfaces.Repositories;
using EnvelopeGenerator.Domain;
using MediatR;
namespace EnvelopeGenerator.Application.EmailTemplates.Queries.Read;
/// <summary>
///
/// </summary>
public class ReadEmailTemplateQueryHandler : IRequestHandler<ReadEmailTemplateQuery, ReadEmailTemplateResponse?>
{
private readonly IMapper _mapper;
[Obsolete("Use Read-method returning IReadQuery<TEntity> instead.")]
private readonly IEmailTemplateRepository _repository;
/// <summary>
/// Initialisiert eine neue Instanz der <see cref="EmailTemplateController"/>-Klasse.
/// </summary>
/// <param name="mapper">
/// <param name="repository">
/// Die AutoMapper-Instanz, die zum Zuordnen von Objekten verwendet wird.
/// </param>
[Obsolete("Use Read-method returning IReadQuery<TEntity> instead.")]
public ReadEmailTemplateQueryHandler(IMapper mapper, IEmailTemplateRepository repository)
{
_mapper = mapper;
_repository = repository;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
[Obsolete("Use IRepository")]
public async Task<ReadEmailTemplateResponse?> 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<ReadEmailTemplateResponse>(temp);
return res;
}
}

View File

@@ -1,37 +0,0 @@
namespace EnvelopeGenerator.Application.EmailTemplates.Queries.Read;
/// <summary>
/// Stellt die Antwort für eine Abfrage von E-Mail-Vorlagen bereit.
/// </summary>
public class ReadEmailTemplateResponse
{
/// <summary>
/// Die eindeutige Kennung der E-Mail-Vorlage.
/// </summary>
public int Id { get; set; }
/// <summary>
/// Name des Typs
/// </summary>
public required string Name { get; set; }
/// <summary>
/// Das Datum und die Uhrzeit, wann die Vorlage hinzugefügt wurde.
/// </summary>
public DateTime AddedWhen { get; set; }
/// <summary>
/// Der Inhalt (Body) der E-Mail-Vorlage. Kann null sein.
/// </summary>
public string? Body { get; set; }
/// <summary>
/// Der Betreff der E-Mail-Vorlage. Kann null sein.
/// </summary>
public string? Subject { get; set; }
/// <summary>
/// Das Datum und die Uhrzeit, wann die Vorlage zuletzt geändert wurde. Kann null sein.
/// </summary>
public DateTime? ChangedWhen { get; set; }
}

View File

@@ -1,97 +1,56 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<None Remove="Resources\Model.Designer.vb" />
</ItemGroup>
<ItemGroup>
<None Remove="Resources\Model.Designer.vb" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.1.0" />
<PackageReference Include="DigitalData.Core.Application" Version="3.4.0" />
<PackageReference Include="DigitalData.Core.Client" Version="2.1.0" />
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="3.1.1" />
<PackageReference Include="MediatR" Version="12.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.18" />
<PackageReference Include="Otp.NET" Version="1.4.0" />
<PackageReference Include="QRCoder" Version="1.6.0" />
<PackageReference Include="QRCoder-ImageSharp" Version="0.10.0" />
<PackageReference Include="UserManager" Version="1.1.3" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="DigitalData.Core.Abstractions" Version="2.0.0" />
<PackageReference Include="DigitalData.Core.Application" Version="2.0.0" />
<PackageReference Include="DigitalData.Core.DTO" Version="2.0.0" />
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="2.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.18" />
<PackageReference Include="UserManager.Application" Version="2.0.0" />
<PackageReference Include="UserManager.Infrastructure" Version="2.0.0" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EnvelopeGenerator.Extensions\EnvelopeGenerator.Extensions.csproj" />
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Model.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Model.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
<ProjectReference Include="..\EnvelopeGenerator.Extensions\EnvelopeGenerator.Extensions.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Update="Resources\Model.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Model.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Model.en.resx">
<CustomToolNamespace>My.Resources</CustomToolNamespace>
<LastGenOutput>Model.en.Designer.vb</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\Model.resx">
<CustomToolNamespace>My.Resources</CustomToolNamespace>
<LastGenOutput>Model.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\Resource.de-DE.resx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Update="Resources\Resource.en-US.resx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="AutoMapper" Version="13.0.1" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.3" />
<PackageReference Include="CommandDotNet">
<Version>7.0.5</Version>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
<PackageReference Include="CommandDotNet">
<Version>8.1.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
<PackageReference Include="AutoMapper" Version="14.0.0" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
<PackageReference Include="CommandDotNet">
<Version>8.1.1</Version>
</PackageReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Resources\Model.en.resx">
<CustomToolNamespace>My.Resources</CustomToolNamespace>
<LastGenOutput>Model.en.Designer.vb</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\Model.resx">
<CustomToolNamespace>My.Resources</CustomToolNamespace>
<LastGenOutput>Model.Designer.cs</LastGenOutput>
<Generator>PublicResXFileCodeGenerator</Generator>
</EmbeddedResource>
<EmbeddedResource Update="Resources\Resource.de-DE.resx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
<EmbeddedResource Update="Resources\Resource.en-US.resx">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
</Project>

View File

@@ -1,61 +0,0 @@
using EnvelopeGenerator.Application.Envelopes.Commands;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace EnvelopeGenerator.Application.EnvelopeReceivers.Commands;
/// <summary>
/// Befehl zur Erstellung eines Umschlags.
/// </summary>
/// <param name="Title">Der Titel des Umschlags. Dies ist ein Pflichtfeld.</param>
/// <param name="Message">Die Nachricht, die im Umschlag enthalten sein soll. Dies ist ein Pflichtfeld.</param>
/// <param name="Document">Das mit dem Umschlag verknüpfte Dokument. Dies ist ein Pflichtfeld.</param>
/// <param name="Receivers">Eine Sammlung von Empfängern, die den Umschlag erhalten. Dies ist ein Pflichtfeld.</param>
/// <param name="TFAEnabled">Gibt an, ob die Zwei-Faktor-Authentifizierung für den Umschlag aktiviert ist. Standardmäßig false.</param>
public record CreateEnvelopeReceiverCommand(
[Required] string Title,
[Required] string Message,
[Required] DocumentCreateCommand Document,
[Required] IEnumerable<ReceiverGetOrCreateCommand> Receivers,
bool TFAEnabled = false
) : CreateEnvelopeCommand(Title, Message, TFAEnabled), IRequest<CreateEnvelopeReceiverResponse>;
#region Subcommands
/// <summary>
/// Signaturposition auf einem Dokument.
/// </summary>
/// <param name="X">X-Position</param>
/// <param name="Y">Y-Position</param>
/// <param name="Page">Seite, auf der sie sich befindet</param>
public record Signature([Required] double X, [Required] double Y, [Required] int Page);
/// <summary>
/// DTO für Empfänger, die erstellt oder abgerufen werden sollen.
/// Wenn nicht, wird sie erstellt und mit einer Signatur versehen.
/// </summary>
/// <param name="Signatures">Unterschriften auf Dokumenten.</param>
/// <param name="Salution">Der Name, mit dem der Empfänger angesprochen werden soll. Bei Null oder keinem Wert wird der zuletzt verwendete Name verwendet.</param>
/// <param name="PhoneNumber">Sollte mit Vorwahl geschrieben werden</param>
public record ReceiverGetOrCreateCommand([Required] IEnumerable<Signature> Signatures, string? Salution = null, string? PhoneNumber = null)
{
private string _emailAddress = string.Empty;
/// <summary>
/// E-Mail-Adresse des Empfängers.
/// </summary>
[Required]
public string EmailAddress { get => _emailAddress.ToLower(); init => _emailAddress = value.ToLower(); }
};
/// <summary>
/// DTO zum Erstellen eines Dokuments.
/// </summary>
public record DocumentCreateCommand()
{
/// <summary>
/// Die Dokumentdaten im Base64-String-Format. Wird verwendet, wenn das Dokument als Base64-codierter String bereitgestellt wird.
/// </summary>
[Required]
public required string DataAsBase64 { get; init; }
};
#endregion

View File

@@ -1,67 +0,0 @@
using AutoMapper;
using EnvelopeGenerator.Application.Interfaces.SQLExecutor;
using EnvelopeGenerator.Application.Dto.Receiver;
using EnvelopeGenerator.Domain.Entities;
using MediatR;
namespace EnvelopeGenerator.Application.EnvelopeReceivers.Commands;
/// <summary>
/// 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.
/// </summary>
public class CreateEnvelopeReceiverCommandHandler : IRequestHandler<CreateEnvelopeReceiverCommand, CreateEnvelopeReceiverResponse>
{
private readonly IMapper _mapper;
private readonly IEnvelopeExecutor _envelopeExecutor;
private readonly IEnvelopeReceiverExecutor _erExecutor;
/// <summary>
///
/// </summary>
/// <param name="mapper"></param>
/// <param name="envelopeExecutor"></param>
/// <param name="erExecutor"></param>
public CreateEnvelopeReceiverCommandHandler(IMapper mapper, IEnvelopeExecutor envelopeExecutor, IEnvelopeReceiverExecutor erExecutor)
{
_mapper = mapper;
_envelopeExecutor = envelopeExecutor;
_erExecutor = erExecutor;
}
/// <summary>
/// Handles the execution of the <see cref="CreateEnvelopeReceiverCommand"/>.
/// Responsible for validating input data, creating or retrieving recipients, associating signatures,
/// and storing the envelope and document details.
/// </summary>
/// <param name="request">The command containing all necessary information to create an envelope.</param>
/// <param name="cancel">Token to observe while waiting for the task to complete.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task<CreateEnvelopeReceiverResponse> 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<EnvelopeReceiver> sentRecipients = new();
List<ReceiverGetOrCreateCommand> 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<CreateEnvelopeReceiverResponse>(envelope);
res.UnsentReceivers = unsentRecipients;
res.SentReceiver = _mapper.Map<IEnumerable<ReceiverReadDto>>(sentRecipients);
return res;
}
}

View File

@@ -1,20 +0,0 @@
using EnvelopeGenerator.Application.Dto;
using EnvelopeGenerator.Application.Dto.Receiver;
namespace EnvelopeGenerator.Application.EnvelopeReceivers.Commands;
/// <summary>
///
/// </summary>
public record CreateEnvelopeReceiverResponse : EnvelopeDto
{
/// <summary>
///
/// </summary>
public IEnumerable<ReceiverReadDto> SentReceiver { get; set; } = new List<ReceiverReadDto>();
/// <summary>
///
/// </summary>
public IEnumerable<ReceiverGetOrCreateCommand> UnsentReceivers { get; set; } = new List<ReceiverGetOrCreateCommand>();
}

View File

@@ -1,20 +0,0 @@
using AutoMapper;
using EnvelopeGenerator.Application.Dto.Receiver;
using EnvelopeGenerator.Application.Envelopes.Commands;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.EnvelopeReceivers;
/// <summary>
///
/// </summary>
public class MappingProfile : Profile
{
/// <summary>
///
/// </summary>
public MappingProfile()
{
CreateMap<Receiver, ReceiverReadDto>();
}
}

View File

@@ -1,105 +0,0 @@
using EnvelopeGenerator.Application.Dto.EnvelopeReceiver;
using EnvelopeGenerator.Application.Envelopes.Queries;
using EnvelopeGenerator.Application.Exceptions;
using EnvelopeGenerator.Application.Extensions;
using EnvelopeGenerator.Application.Receivers.Queries;
using EnvelopeGenerator.Extensions;
using MediatR;
namespace EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
/// <summary>
/// Repräsentiert eine Abfrage zum Lesen eines Envelope-Empfängers.
/// Invalid (0): Ungültiger Include.
/// EnvelopeCreated (1001): Der Umschlag wurde erstellt.
/// EnvelopeSaved (1002): Der Umschlag wurde gespeichert.
/// EnvelopeQueued (1003): Der Umschlag wurde zur Verarbeitung eingeplant.
/// EnvelopeSent (1004): Der Umschlag wurde versendet. (Nicht verwendet)
/// EnvelopePartlySigned (1005): Der Umschlag wurde teilweise unterschrieben.
/// EnvelopeCompletelySigned (1006): Der Umschlag wurde vollständig unterschrieben.
/// EnvelopeReportCreated (1007): Ein Abschlussbericht wurde für den Umschlag erstellt.
/// EnvelopeArchived (1008): Der Umschlag wurde archiviert.
/// EnvelopeDeleted (1009): Der Umschlag wurde gelöscht.
/// AccessCodeRequested (2001): Der Zugriffscode wurde angefordert.
/// AccessCodeCorrect (2002): Der Zugriffscode war korrekt.
/// AccessCodeIncorrect (2003): Der Zugriffscode war falsch.
/// DocumentOpened (2004): Das Dokument wurde geöffnet.
/// DocumentSigned (2005): Ein Dokument wurde unterschrieben.
/// SignatureConfirmed (2006): Die Signatur wurde bestätigt.
/// DocumentRejected (2007): Ein Dokument wurde abgelehnt.
/// EnvelopeShared (2008): Der Umschlag wurde geteilt.
/// EnvelopeViewed (2009): Der Umschlag wurde angesehen.
/// DocumentForwarded (4001): Das Dokument wurde weitergeleitet.
/// MessageInvitationSent (3001): Einladung wurde gesendet (vom Trigger verwendet).
/// MessageAccessCodeSent (3002): Zugriffscode wurde gesendet.
/// MessageConfirmationSent (3003): Bestätigungsnachricht wurde gesendet.
/// MessageDeletionSent (3004): Löschbenachrichtigung wurde gesendet.
/// MessageCompletionSent (3005): Abschlussbenachrichtigung wurde gesendet.
/// </summary>
/// <remarks>
/// Diese Abfrage kombiniert Informationen über einen Umschlag (<see cref="ReadEnvelopeQuery"/>)
/// und einen Empfänger (<see cref="ReadReceiverQuery"/>), um eine vollständige Antwort
/// (<see cref="EnvelopeReceiverDto"/>) zu generieren.
/// Die Antwort enthält Details wie den Include, die Zuordnung zwischen Umschlag und Empfänger
/// sowie zusätzliche Metadaten.
/// </remarks>
public record ReadEnvelopeReceiverQuery : IRequest<IEnumerable<EnvelopeReceiverDto>>
{
/// <summary>
///
/// </summary>
public string? Key
{
get => Envelope?.Uuid is string uuid && Receiver?.Signature is string signature
? (uuid, signature).EncodeEnvelopeReceiverId()
: null;
init
{
if (value is null)
return;
(string? EnvelopeUuid, string? ReceiverSignature) = value.DecodeEnvelopeReceiverId();
if(string.IsNullOrEmpty(EnvelopeUuid) || string.IsNullOrEmpty(ReceiverSignature))
{
throw new BadRequestException("Der EnvelopeReceiverKey muss ein gültiger Base64-kodierter String sein, der die EnvelopeUuid und die ReceiverSignature enthält.");
}
Envelope = new ReadEnvelopeQuery()
{
Uuid = EnvelopeUuid
};
Receiver = new ReadReceiverQuery()
{
Signature = ReceiverSignature
};
}
}
/// <summary>
/// Der Umschlag, der mit dem Empfänger verknüpft ist.
/// </summary>
public ReadEnvelopeQuery? Envelope { get; set; }
/// <summary>
/// Der Empfänger, der mit dem Umschlag verknüpft ist.
/// </summary>
public ReadReceiverQuery? Receiver { get; set; }
};
/// <summary>
///
/// </summary>
public static class Extensions
{
/// <summary>
///
/// </summary>
/// <param name="mediator"></param>
/// <param name="key"></param>
/// <param name="cancel"></param>
/// <returns></returns>
public static Task<EnvelopeReceiverDto?> ReadEnvelopeReceiverAsync(this IMediator mediator, string key, CancellationToken cancel = default)
{
var q = new ReadEnvelopeReceiverQuery() { Key = key };
return mediator.Send(q, cancel).Then(envRcvs => envRcvs.FirstOrDefault());
}
}

View File

@@ -1,87 +0,0 @@
using AutoMapper;
using DigitalData.Core.Abstraction.Application.Repository;
using EnvelopeGenerator.Application.Dto.EnvelopeReceiver;
using EnvelopeGenerator.Domain.Entities;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
/// <summary>
///
/// </summary>
public class ReadEnvelopeReceiverQueryHandler : IRequestHandler<ReadEnvelopeReceiverQuery, IEnumerable<EnvelopeReceiverDto>>
{
private readonly IRepository<EnvelopeReceiver> _repo;
private readonly IMapper _mapper;
/// <summary>
///
/// </summary>
/// <param name="envelopeReceiver"></param>
/// <param name="mapper"></param>
public ReadEnvelopeReceiverQueryHandler(IRepository<EnvelopeReceiver> envelopeReceiver, IMapper mapper)
{
_repo = envelopeReceiver;
_mapper = mapper;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancel"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public async Task<IEnumerable<EnvelopeReceiverDto>> Handle(ReadEnvelopeReceiverQuery request, CancellationToken cancel)
{
var q = _repo.Read();
if(request.Envelope is not null)
{
var env = request.Envelope;
if (env.Id is not null)
q = q.Where(er => er.EnvelopeId == env.Id);
if (env.Status is not null)
{
if(env.Status.Min is not null)
q = q.Where(er => er.Envelope.Status >= env.Status.Min);
if(env.Status.Max is not null)
q = q.Where(er => er.Envelope.Status <= env.Status.Max);
if(env.Status .Include?.Length > 0)
q = q.Where(er => env.Status.Include.Contains(er.Envelope.Status));
if(env.Status.Ignore is not null)
q = q.Where(er => !env.Status.Ignore.Contains(er.Envelope.Status));
}
if (env.Uuid is not null)
q = q.Where(er => er.Envelope.Uuid == env.Uuid);
}
if (request.Receiver is not null)
{
var rcv = request.Receiver;
if (rcv.Id is not null)
q = q.Where(r => r.ReceiverId == rcv.Id);
if (rcv.EmailAddress is not null)
q = q.Where(r => r.Receiver.EmailAddress == rcv.EmailAddress);
if (rcv.Signature is not null)
q = q.Where(er => er.Receiver.Signature == rcv.Signature);
}
var envRcvs = await q.Include(er => er.Envelope).ThenInclude(e => e.Documents).ThenInclude(d => d.Elements)
.Include(er => er.Envelope).ThenInclude(e => e.History)
.Include(er => er.Envelope).ThenInclude(e => e.User)
.Include(er => er.Receiver)
.ToListAsync(cancel);
return _mapper.Map<IEnumerable<EnvelopeReceiverDto>>(envRcvs);
}
}

View File

@@ -1,83 +0,0 @@
using DigitalData.Core.Abstraction.Application.Repository;
using EnvelopeGenerator.Application.Exceptions;
using EnvelopeGenerator.Domain;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Extensions;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
/// <summary>
///
/// </summary>
public record ReceiverAlreadySignedQuery : IRequest<bool>
{
/// <summary>
///
/// </summary>
public required string Key
{
get => (EnvelopeUuid, ReceiverSignature).EncodeEnvelopeReceiverId();
init
{
(string? EnvelopeUuid, string? ReceiverSignature) = value.DecodeEnvelopeReceiverId();
if (string.IsNullOrEmpty(EnvelopeUuid) || string.IsNullOrEmpty(ReceiverSignature))
{
throw new BadRequestException("Der EnvelopeReceiverKey muss ein gültiger Base64-kodierter String sein, der die EnvelopeUuid und die ReceiverSignature enthält.");
}
}
}
internal string EnvelopeUuid { get; set; } = null!;
internal string ReceiverSignature { get; set; } = null!;
}
/// <summary>
///
/// </summary>
public static class ReceiverAlreadySignedQueryExtensions
{
/// <summary>
///
/// </summary>
/// <param name="mediator"></param>
/// <param name="key"></param>
/// <param name="cancel"></param>
/// <returns></returns>
public static Task<bool> ReceiverAlreadySigned(this IMediator mediator, string key, CancellationToken cancel = default)
=> mediator.Send(new ReceiverAlreadySignedQuery { Key = key }, cancel);
}
/// <summary>
///
/// </summary>
public class ReceiverAlreadySignedQueryHandler : IRequestHandler<ReceiverAlreadySignedQuery, bool>
{
private readonly IRepository<EnvelopeReceiver> _repo;
/// <summary>
///
/// </summary>
/// <param name="repo"></param>
public ReceiverAlreadySignedQueryHandler(IRepository<EnvelopeReceiver> repo)
{
_repo = repo;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancel"></param>
/// <returns></returns>
public async Task<bool> Handle(ReceiverAlreadySignedQuery request, CancellationToken cancel = default)
{
return await _repo.Read()
.Where(er => er.Envelope.Uuid == request.EnvelopeUuid)
.Where(er => er.Receiver.Signature == request.ReceiverSignature)
.Where(er => er.Envelope.History.Any(hist => hist.Status == Constants.EnvelopeStatus.DocumentSigned))
.AnyAsync(cancel);
}
}

View File

@@ -1,28 +0,0 @@
using EnvelopeGenerator.Application.Dto;
using EnvelopeGenerator.Application.Envelopes.Queries;
using MediatR;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace EnvelopeGenerator.Application.Envelopes.Commands;
/// <summary>
/// Befehl zur Erstellung eines Umschlags.
/// </summary>
/// <param name="Title">Der Titel des Umschlags. Dies ist ein Pflichtfeld.</param>
/// <param name="Message">Die Nachricht, die im Umschlag enthalten sein soll. Dies ist ein Pflichtfeld.</param>
/// <param name="TFAEnabled">Gibt an, ob die Zwei-Faktor-Authentifizierung für den Umschlag aktiviert ist. Standardmäßig false.</param>
public record CreateEnvelopeCommand(
[Required] string Title,
[Required] string Message,
bool TFAEnabled = false
) : IRequest<EnvelopeDto?>
{
/// <summary>
/// Id of receiver
/// </summary>
[JsonIgnore]
[BindNever]
public int? UserId { get; set; }
};

View File

@@ -1,42 +0,0 @@
using AutoMapper;
using EnvelopeGenerator.Application.Interfaces.SQLExecutor;
using EnvelopeGenerator.Application.Dto;
using MediatR;
namespace EnvelopeGenerator.Application.Envelopes.Commands;
/// <summary>
///
/// </summary>
public class CreateEnvelopeCommandHandler : IRequestHandler<CreateEnvelopeCommand, EnvelopeDto?>
{
private readonly IEnvelopeExecutor _envelopeExecutor;
private readonly IMapper _mapper;
/// <summary>
///
/// </summary>
/// <param name="envelopeExecutor"></param>
/// <param name="mapper"></param>
public CreateEnvelopeCommandHandler(IEnvelopeExecutor envelopeExecutor, IMapper mapper)
{
_envelopeExecutor = envelopeExecutor;
_mapper = mapper;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public async Task<EnvelopeDto?> 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<EnvelopeDto>(envelope);
}
}

View File

@@ -1,19 +0,0 @@
using AutoMapper;
using EnvelopeGenerator.Application.EnvelopeReceivers.Commands;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Envelopes;
/// <summary>
///
/// </summary>
public class MappingProfile : Profile
{
/// <summary>
///
/// </summary>
public MappingProfile()
{
CreateMap<Envelope, CreateEnvelopeReceiverResponse>();
}
}

View File

@@ -1,76 +0,0 @@
using MediatR;
using EnvelopeGenerator.Domain;
namespace EnvelopeGenerator.Application.Envelopes.Queries;
/// <summary>
/// Repräsentiert eine Abfrage für Umschläge.
/// </summary>
public class ReadEnvelopeQuery : IRequest
{
/// <summary>
/// Die eindeutige Kennung des Umschlags.
/// </summary>
public int? Id { get; init; }
/// <summary>
/// Die universell eindeutige Kennung des Umschlags.
/// </summary>
public string? Uuid { get; init; }
/// <summary>
/// Abfrage des Include des Umschlags
/// </summary>
public EnvelopeStatus? Status { get; init; }
}
/// <summary>
/// Repräsentiert den Include eines Umschlags und dessen Beziehung zum Empfänger. (vgl. auch <see cref="Constants.EnvelopeStatus"/>
/// Invalid (0): Ungültiger Include.
/// EnvelopeCreated (1001): Der Umschlag wurde erstellt.
/// EnvelopeSaved (1002): Der Umschlag wurde gespeichert.
/// EnvelopeQueued (1003): Der Umschlag wurde zur Verarbeitung eingeplant.
/// EnvelopeSent (1004): Der Umschlag wurde versendet. (Nicht verwendet)
/// EnvelopePartlySigned (1005): Der Umschlag wurde teilweise unterschrieben.
/// EnvelopeCompletelySigned (1006): Der Umschlag wurde vollständig unterschrieben.
/// EnvelopeReportCreated (1007): Ein Abschlussbericht wurde für den Umschlag erstellt.
/// EnvelopeArchived (1008): Der Umschlag wurde archiviert.
/// EnvelopeDeleted (1009): Der Umschlag wurde gelöscht.
/// AccessCodeRequested (2001): Der Zugriffscode wurde angefordert.
/// AccessCodeCorrect (2002): Der Zugriffscode war korrekt.
/// AccessCodeIncorrect (2003): Der Zugriffscode war falsch.
/// DocumentOpened (2004): Das Dokument wurde geöffnet.
/// DocumentSigned (2005): Ein Dokument wurde unterschrieben.
/// SignatureConfirmed (2006): Die Signatur wurde bestätigt.
/// DocumentRejected (2007): Ein Dokument wurde abgelehnt.
/// EnvelopeShared (2008): Der Umschlag wurde geteilt.
/// EnvelopeViewed (2009): Der Umschlag wurde angesehen.
/// DocumentForwarded (4001): Das Dokument wurde weitergeleitet.
/// MessageInvitationSent (3001): Einladung wurde gesendet (vom Trigger verwendet).
/// MessageAccessCodeSent (3002): Zugriffscode wurde gesendet.
/// MessageConfirmationSent (3003): Bestätigungsnachricht wurde gesendet.
/// MessageDeletionSent (3004): Löschbenachrichtigung wurde gesendet.
/// MessageCompletionSent (3005): Abschlussbenachrichtigung wurde gesendet.
/// </summary>
public record EnvelopeStatus
{
/// <summary>
/// Der minimale Statuswert, der berücksichtigt werden.
/// </summary>
public Constants.EnvelopeStatus? Min { get; init; }
/// <summary>
/// Der maximale Statuswert, der berücksichtigt werden.
/// </summary>
public Constants.EnvelopeStatus? Max { get; init; }
/// <summary>
/// Eine Liste von Statuswerten, die einbezogen werden.
/// </summary>
public Constants.EnvelopeStatus[]? Include { get; init; }
/// <summary>
/// Eine Liste von Statuswerten, die ignoriert werden werden.
/// </summary>
public Constants.EnvelopeStatus[]? Ignore { get; init; }
}

View File

@@ -1,11 +0,0 @@
using EnvelopeGenerator.Application.Receivers.Queries;
namespace EnvelopeGenerator.Application.Envelopes.Queries;
/// <summary>
/// Eine Abfrage, um die zuletzt verwendete Anrede eines Empfängers zu ermitteln,
/// damit diese für zukünftige Umschläge wiederverwendet werden kann.
/// </summary>
public record ReadReceiverNameQuery() : ReadReceiverQuery
{
}

View File

@@ -1,22 +0,0 @@
namespace EnvelopeGenerator.Application.Exceptions;
/// <summary>
/// Represents an exception that is thrown when a bad request is encountered.
/// </summary>
public class BadRequestException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="BadRequestException"/> class.
/// </summary>
public BadRequestException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="BadRequestException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public BadRequestException(string? message) : base(message)
{
}
}

View File

@@ -1,22 +0,0 @@
namespace EnvelopeGenerator.Application.Exceptions;
/// <summary>
/// Represents an exception that is thrown when a requested resource is not found.
/// </summary>
public class NotFoundException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="NotFoundException"/> class.
/// </summary>
public NotFoundException()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NotFoundException"/> class with a specified error message.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public NotFoundException(string? message) : base(message)
{
}
}

View File

@@ -1,222 +0,0 @@
using Microsoft.Extensions.Caching.Distributed;
namespace EnvelopeGenerator.Application.Extensions;
/// <summary>
///
/// </summary>
public static class CacheExtensions
{
/// <summary>
///
/// </summary>
/// <param name="cache"></param>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="options"></param>
/// <param name="cToken"></param>
/// <returns></returns>
public static Task SetLongAsync(this IDistributedCache cache, string key, long value, DistributedCacheEntryOptions? options = null, CancellationToken cToken = default)
=> options is null
? cache.SetAsync(key, BitConverter.GetBytes(value), token: cToken)
: cache.SetAsync(key, BitConverter.GetBytes(value), options: options, token: cToken);
/// <summary>
///
/// </summary>
/// <param name="cache"></param>
/// <param name="key"></param>
/// <param name="cToken"></param>
/// <returns></returns>
public static async Task<long?> GetLongAsync(this IDistributedCache cache, string key, CancellationToken cToken = default)
{
var value = await cache.GetAsync(key, cToken);
return value is null ? null : BitConverter.ToInt64(value, 0);
}
/// <summary>
///
/// </summary>
/// <param name="cache"></param>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="options"></param>
/// <param name="cToken"></param>
/// <returns></returns>
public static Task SetDateTimeAsync(this IDistributedCache cache, string key, DateTime value, DistributedCacheEntryOptions? options = null, CancellationToken cToken = default)
=> cache.SetLongAsync(key: key, value: value.Ticks, options: options, cToken: cToken);
/// <summary>
///
/// </summary>
/// <param name="cache"></param>
/// <param name="key"></param>
/// <param name="cToken"></param>
/// <returns></returns>
public static async Task<DateTime?> GetDateTimeAsync(this IDistributedCache cache, string key, CancellationToken cToken = default)
{
var value = await cache.GetAsync(key, cToken);
return value is null ? null : new(BitConverter.ToInt64(value, 0));
}
/// <summary>
///
/// </summary>
/// <param name="cache"></param>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="options"></param>
/// <param name="cToken"></param>
/// <returns></returns>
public static Task SetTimeSpanAsync(this IDistributedCache cache, string key, TimeSpan value, DistributedCacheEntryOptions? options = null, CancellationToken cToken = default)
=> cache.SetLongAsync(key: key, value: value.Ticks, options: options, cToken);
/// <summary>
///
/// </summary>
/// <param name="cache"></param>
/// <param name="key"></param>
/// <param name="cToken"></param>
/// <returns></returns>
public static async Task<TimeSpan?> GetTimeSpanAsync(this IDistributedCache cache, string key, CancellationToken cToken = default)
{
var value = await cache.GetAsync(key, cToken);
return value is null ? null : new(BitConverter.ToInt64(value, 0));
}
//TODO: use code generator
#region GetOrSetAsync
#region string
/// <summary>
///
/// </summary>
/// <param name="cache"></param>
/// <param name="key"></param>
/// <param name="factory"></param>
/// <param name="options"></param>
/// <param name="cacheInBackground"></param>
/// <param name="cToken"></param>
/// <returns></returns>
public static async Task<string> GetOrSetAsync(this IDistributedCache cache, string key, Func<string> factory, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken cToken = default)
{
var value = await cache.GetStringAsync(key, cToken);
if (value is null)
{
// create new and save
value = factory();
Task CacheAsync() => options is null
? cache.SetStringAsync(key, value, cToken)
: cache.SetStringAsync(key, value, options, cToken);
if (cacheInBackground)
_ = Task.Run(async () => await CacheAsync(), cToken);
else
await CacheAsync();
}
return value;
}
/// <summary>
///
/// </summary>
/// <param name="cache"></param>
/// <param name="key"></param>
/// <param name="factoryAsync"></param>
/// <param name="options"></param>
/// <param name="cacheInBackground"></param>
/// <param name="cToken"></param>
/// <returns></returns>
public static async Task<string> GetOrSetAsync(this IDistributedCache cache, string key, Func<Task<string>> factoryAsync, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken cToken = default)
{
var value = await cache.GetStringAsync(key, cToken);
if(value is null)
{
// create new and save
value = await factoryAsync();
Task CacheAsync() => options is null
? cache.SetStringAsync(key: key, value: value, token: cToken)
: cache.SetStringAsync(key: key, value: value, options: options, token: cToken);
if (cacheInBackground)
_ = Task.Run(async () => await CacheAsync(), cToken);
else
await CacheAsync();
}
return value;
}
#endregion
#region DateTime
/// <summary>
///
/// </summary>
/// <param name="cache"></param>
/// <param name="key"></param>
/// <param name="factory"></param>
/// <param name="options"></param>
/// <param name="cacheInBackground"></param>
/// <param name="cToken"></param>
/// <returns></returns>
public static async Task<DateTime> GetOrSetAsync(this IDistributedCache cache, string key, Func<DateTime> factory, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken cToken = default)
{
if (await cache.GetDateTimeAsync(key, cToken) is DateTime dateTimeValue)
return dateTimeValue;
else
{
// create new and save
var newValue = factory();
Task CacheAsync() => options is null
? cache.SetDateTimeAsync(key, newValue, cToken: cToken)
: cache.SetDateTimeAsync(key, newValue, options, cToken);
if (cacheInBackground)
_ = Task.Run(async () => await CacheAsync(), cToken);
else
await CacheAsync();
return newValue;
}
}
/// <summary>
///
/// </summary>
/// <param name="cache"></param>
/// <param name="key"></param>
/// <param name="factory"></param>
/// <param name="options"></param>
/// <param name="cacheInBackground"></param>
/// <param name="cToken"></param>
/// <returns></returns>
public static async Task<DateTime> GetOrSetAsync(this IDistributedCache cache, string key, Func<Task<DateTime>> factory, DistributedCacheEntryOptions? options = null, bool cacheInBackground = false, CancellationToken cToken = default)
{
if (await cache.GetDateTimeAsync(key, cToken) is DateTime dateTimeValue)
return dateTimeValue;
else
{
// create new and save
var newValue = await factory();
Task CacheAsync() => options is null
? cache.SetDateTimeAsync(key, newValue, cToken: cToken)
: cache.SetDateTimeAsync(key, newValue, options, cToken);
if (cacheInBackground)
_ = Task.Run(async () => await CacheAsync(), cToken);
else
await CacheAsync();
return newValue;
}
}
#endregion
#endregion
}

View File

@@ -1,27 +0,0 @@
using EnvelopeGenerator.Application.Dto.Messaging;
namespace EnvelopeGenerator.Application.Extensions;
/// <summary>
/// Provides extension methods for common mapping and conversion operations.
/// </summary>
public static class MappingExtensions
{
/// <summary>
/// Determines whether the response indicates a successful "OK" message status.
/// </summary>
/// <param name="gtxMessagingResponse">The response object to evaluate.</param>
/// <returns><see langword="true"/> if the response contains a "message-status" key with a value of "ok" (case-insensitive);
/// otherwise, <see langword="false"/>.</returns>
public static bool Ok(this GtxMessagingResponse gtxMessagingResponse)
=> gtxMessagingResponse.TryGetValue("message-status", out var status)
&& status?.ToString()?.ToLower() == "ok";
/// <summary>
/// Converts the specified byte array to its equivalent string representation encoded in base-64.
/// </summary>
/// <param name="bytes">The byte array to encode.</param>
/// <returns>A base-64 encoded string representation of the input byte array.</returns>
public static string ToBase64String(this byte[] bytes)
=> Convert.ToBase64String(bytes);
}

View File

@@ -1,53 +0,0 @@
using EnvelopeGenerator.Application.Exceptions;
namespace EnvelopeGenerator.Application.Extensions;
/// <summary>
/// Extension methods for tasks
/// </summary>
public static class TaskExtensions
{
/// <summary>
/// Awaits the specified task and ensures that the result is not <c>null</c>.
/// If the result is <c>null</c>, a <see cref="NotFoundException"/> is thrown.
/// </summary>
/// <typeparam name="T">The type of the result.</typeparam>
/// <param name="task">The task to await.</param>
/// <param name="exceptionMessage">Optional custom exception message.</param>
/// <returns>The awaited result if not <c>null</c>.</returns>
/// <exception cref="NotFoundException">Thrown if the result is <c>null</c>.</exception>
public static async Task<T> ThrowIfNull<T>(this Task<T?> task, string? exceptionMessage = null)
{
var result = await task;
return result ?? throw new NotFoundException(exceptionMessage);
}
/// <summary>
/// Awaits the specified task and ensures that the result is not <c>empty</c>.
/// If the result contains no elements, a <see cref="NotFoundException"/> is thrown.
/// </summary>
/// <typeparam name="T">The element type of the collection.</typeparam>
/// <param name="task">The task to await.</param>
/// <param name="exceptionMessage">Optional custom exception message.</param>
/// <returns>The awaited collection if it is not <c>null</c> or empty.</returns>
/// <exception cref="NotFoundException">Thrown if the result is <c>null</c> or empty.</exception>
public static async Task<IEnumerable<T>> ThrowIfNull<T>(this Task<IEnumerable<T>> task, string? exceptionMessage = null)
{
var result = await task;
return result?.Any() ?? false ? result : throw new NotFoundException(exceptionMessage);
}
/// <summary>
///
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="I"></typeparam>
/// <param name="task"></param>
/// <param name="act"></param>
/// <returns></returns>
public static async Task<I> Then<T, I>(this Task<T> task, Func<T, I> act)
{
var res = await task;
return act(res);
}
}

View File

@@ -1,19 +0,0 @@
using AutoMapper;
using EnvelopeGenerator.Application.Histories.Queries.Read;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.Histories;
/// <summary>
///
/// </summary>
public class MappingProfile: Profile
{
/// <summary>
///
/// </summary>
public MappingProfile()
{
CreateMap<EnvelopeHistory, ReadHistoryResponse>();
}
}

View File

@@ -1,20 +0,0 @@
using EnvelopeGenerator.Domain;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace EnvelopeGenerator.Application.Histories.Queries.Read;
//TODO: Add sender query
/// <summary>
/// Repräsentiert eine Abfrage für die Verlaufshistorie eines Umschlags.
/// </summary>
/// <param name="EnvelopeId">Die eindeutige Kennung des Umschlags.</param>
/// <param name="Status">Der Include des Umschlags, der abgefragt werden soll. Kann optional angegeben werden, um die Ergebnisse zu filtern.</param>
/// <param name="OnlyLast">Abfrage zur Steuerung, ob nur der aktuelle Include oder der gesamte Datensatz zurückgegeben wird.</param>
public record ReadHistoryQuery(
[Required]
int EnvelopeId,
Constants.EnvelopeStatus? Status = null,
bool? OnlyLast = true) : IRequest<IEnumerable<ReadHistoryResponse>>
{
};

View File

@@ -1,46 +0,0 @@
using AutoMapper;
using EnvelopeGenerator.Application.Interfaces.Repositories;
using EnvelopeGenerator.Application.Exceptions;
using MediatR;
namespace EnvelopeGenerator.Application.Histories.Queries.Read;
/// <summary>
///
/// </summary>
public class ReadHistoryQueryHandler : IRequestHandler<ReadHistoryQuery, IEnumerable<ReadHistoryResponse>>
{
[Obsolete("Use IRepository")]
private readonly IEnvelopeHistoryRepository _repository;
private readonly IMapper _mapper;
/// <summary>
///
/// </summary>
/// <param name="repository"></param>
/// <param name="mapper"></param>
[Obsolete("Use IRepository")]
public ReadHistoryQueryHandler(IEnvelopeHistoryRepository repository, IMapper mapper)
{
_repository = repository;
_mapper = mapper;
}
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="NotFoundException"></exception>
public async Task<IEnumerable<ReadHistoryResponse>> Handle(ReadHistoryQuery request, CancellationToken cancellationToken)
{
var hists = await _repository.ReadAsync(request.EnvelopeId, status: request.Status is null ? null : request.Status);
if (!hists.Any())
throw new NotFoundException();
return _mapper.Map<IEnumerable<ReadHistoryResponse>>(hists);
}
}

Some files were not shown because too many files have changed in this diff Show More