This commit is contained in:
SchreiberM 2024-05-29 12:58:21 +02:00
commit 297ecfab7b
56 changed files with 649 additions and 952 deletions

View File

@ -1,11 +0,0 @@
using DigitalData.Core.Contracts.Application;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IEmailOutService : IBasicCRUDService<IEmailOutRepository, EmailOutDto, EmailOut, int>
{
}
}

View File

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

View File

@ -1,17 +1,20 @@
using DigitalData.Core.Contracts.Application;
using EnvelopeGenerator.Application.DTOs;
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.DTOs.EnvelopeHistory;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Application.Contracts
{
public interface IEnvelopeHistoryService : IBasicCRUDService<IEnvelopeHistoryRepository, EnvelopeHistoryDto, EnvelopeHistory, long>
public interface IEnvelopeHistoryService : ICRUDService<IEnvelopeHistoryRepository, 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<DataResult<long>> RecordAsync(int envelopeId, string userReference, EnvelopeStatus status);
}
}

View File

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

View File

@ -1,25 +0,0 @@
namespace EnvelopeGenerator.Application.DTOs
{
public record EmailOutDto(
int Guid,
int ReminderTypeId,
int SendingProfile,
int ReferenceId,
string? ReferenceString,
int? EntityId,
int WfId,
string? WfReference,
string EmailAdress,
string EmailSubj,
string EmailBody,
string? EmailAttmt1,
DateTime? EmailSent,
string? Comment,
string AddedWho,
DateTime? AddedWhen,
string? ChangedWho,
DateTime? ChangedWhen,
DateTime? ErrorTimestamp,
string? ErrorMsg
);
}

View File

@ -1,4 +1,5 @@
using DigitalData.UserManager.Domain.Entities;
using EnvelopeGenerator.Application.DTOs.EnvelopeHistory;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.DTOs

View File

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

View File

@ -1,4 +1,4 @@
namespace EnvelopeGenerator.Application.DTOs
namespace EnvelopeGenerator.Application.DTOs.EnvelopeHistory
{
public record EnvelopeHistoryDto(
long Id,

View File

@ -28,6 +28,15 @@
<Reference Include="DigitalData.Core.Infrastructure">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.Core.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="DigitalData.EmailProfilerDispatcher.Application">
<HintPath>..\..\EmailProfilerDispatcher\DigitalData.EmailProfilerDispatcher.Application\bin\Debug\net7.0\DigitalData.EmailProfilerDispatcher.Application.dll</HintPath>
</Reference>
<Reference Include="DigitalData.EmailProfilerDispatcher.Domain">
<HintPath>..\..\EmailProfilerDispatcher\DigitalData.EmailProfilerDispatcher.Application\bin\Debug\net7.0\DigitalData.EmailProfilerDispatcher.Domain.dll</HintPath>
</Reference>
<Reference Include="DigitalData.EmailProfilerDispatcher.Infrastructure">
<HintPath>..\..\EmailProfilerDispatcher\DigitalData.EmailProfilerDispatcher.Application\bin\Debug\net7.0\DigitalData.EmailProfilerDispatcher.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="DigitalData.UserManager.Application">
<HintPath>..\..\WebUserManager\DigitalData.UserManager.Application\bin\Debug\net7.0\DigitalData.UserManager.Application.dll</HintPath>
</Reference>

View File

@ -1,11 +1,107 @@
using EnvelopeGenerator.Application.Services;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging;
using System.Text;
namespace EnvelopeGenerator.Application
{
/// <summary>
/// Provides extension methods for decoding and extracting information from an envelope receiver ID.
/// </summary>
public static class EnvelopeGeneratorExtensions
{
/// <summary>
/// Validates whether a given string is a correctly formatted Base-64 encoded string.
/// </summary>
/// <remarks>
/// This method checks the string for proper Base-64 formatting, which includes validating
/// the length of the string (must be divisible by 4). It also checks each character to ensure
/// it belongs to the Base-64 character set (A-Z, a-z, 0-9, '+', '/', and '=' for padding).
/// The method ensures that padding characters ('=') only appear at the end of the string and
/// are in a valid configuration (either one '=' at the end if the string's length % 4 is 3,
/// or two '==' if the length % 4 is 2).
/// </remarks>
/// <param name="input">The Base-64 encoded string to validate.</param>
/// <returns>
/// <c>true</c> if the string is a valid Base-64 encoded string; otherwise, <c>false</c>.
/// </returns>
/// <example>
/// <code>
/// string testString = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnk=";
/// bool isValid = IsValidBase64String(testString);
/// Console.WriteLine(isValid); // Output: true
/// </code>
/// </example>
public static bool IsBase64String(this string input)
{
// Check if the string is null or empty
if (string.IsNullOrEmpty(input))
{
return false;
}
// Replace valid base-64 padding
input = input.Trim();
int mod4 = input.Length % 4;
if (mod4 > 0)
{
// Base-64 string lengths should be divisible by 4
return false;
}
// Check each character to ensure it is valid base-64
foreach (char c in input)
{
if (!char.IsLetterOrDigit(c) && c != '+' && c != '/' && c != '=')
{
// Invalid character detected
return false;
}
}
// Ensure no invalid padding scenarios exist
if (input.EndsWith("==") && (input.Length % 4 == 0) ||
input.EndsWith("=") && (input.Length % 4 == 3))
{
return true;
}
return input.IndexOf('=') == -1; // No padding allowed except at the end
}
/// <summary>
/// Decodes the envelope receiver ID and extracts the envelope UUID and receiver signature.
/// </summary>
/// <param name="envelopeReceiverId">The base64 encoded string containing the envelope UUID and receiver signature.</param>
/// <returns>A tuple containing the envelope UUID and receiver signature.</returns>
public static (string? EnvelopeUuid, string? ReceiverSignature) DecodeEnvelopeReceiverId(this string envelopeReceiverId)
{
if (!envelopeReceiverId.IsBase64String())
{
return (null, null);
}
byte[] bytes = Convert.FromBase64String(envelopeReceiverId);
string decodedString = System.Text.Encoding.UTF8.GetString(bytes);
string[] parts = decodedString.Split(new string[] { "::" }, StringSplitOptions.None);
if (parts.Length > 1)
return (EnvelopeUuid: parts[0], ReceiverSignature: parts[1]);
else
return (string.Empty, string.Empty);
}
/// <summary>
/// Gets the envelope UUID from the decoded envelope receiver ID.
/// </summary>
/// <param name="envelopeReceiverId">The base64 encoded string to decode.</param>
/// <returns>The envelope UUID.</returns>
public static string? GetEnvelopeUuid(this string envelopeReceiverId) => envelopeReceiverId.DecodeEnvelopeReceiverId().EnvelopeUuid;
/// <summary>
/// Gets the receiver signature from the decoded envelope receiver ID.
/// </summary>
/// <param name="envelopeReceiverId">The base64 encoded string to decode.</param>
/// <returns>The receiver signature.</returns>
public static string? GetReceiverSignature(this string envelopeReceiverId) => envelopeReceiverId.DecodeEnvelopeReceiverId().ReceiverSignature;
public static void LogEnvelopeError(this ILogger logger, string envelopeEeceiverId, Exception? exception = null, string? message = null, params object?[] args)
{
var sb = new StringBuilder().AppendLine(envelopeEeceiverId.DecodeEnvelopeReceiverId().ToTitle());

View File

@ -2,16 +2,18 @@
{
public static class Key
{
public static readonly string EnvelopeNotFound = "EnvelopeNotFound";
public static readonly string EnvelopeReceiverNotFound = "EnvelopeReceiverNotFound";
public static readonly string AccessCodeNull = "AccessCodeNull";
public static readonly string WrongAccessCode = "WrongAccessCode";
public static readonly string DataIntegrityIssue = "DataIntegrityIssue";
public static readonly string SecurityBreachOrDataIntegrity = "SecurityBreachOrDataIntegrity";
public static readonly string PossibleDataIntegrityIssue = "PossibleDataIntegrityIssue";
public static readonly string SecurityBreach = "SecurityBreach";
public static readonly string PossibleSecurityBreach = "PossibleSecurityBreach";
public static readonly string WrongEnvelopeReceiverId = "WrongEnvelopeReceiverId";
public static readonly string EnvelopeOrReceiverNonexists = "EnvelopeOrReceiverNonexists";
public static readonly string InnerServiceError = nameof(InnerServiceError);
public static readonly string EnvelopeNotFound = nameof(EnvelopeNotFound);
public static readonly string EnvelopeReceiverNotFound = nameof(EnvelopeReceiverNotFound);
public static readonly string AccessCodeNull = nameof(AccessCodeNull);
public static readonly string WrongAccessCode = nameof(WrongAccessCode);
public static readonly string DataIntegrityIssue = nameof(DataIntegrityIssue);
public static readonly string SecurityBreachOrDataIntegrity = nameof(SecurityBreachOrDataIntegrity);
public static readonly string PossibleDataIntegrityIssue = nameof(PossibleDataIntegrityIssue);
public static readonly string SecurityBreach = nameof(SecurityBreach);
public static readonly string PossibleSecurityBreach = nameof(PossibleSecurityBreach);
public static readonly string WrongEnvelopeReceiverId = nameof(WrongEnvelopeReceiverId);
public static readonly string EnvelopeOrReceiverNonexists = nameof(EnvelopeOrReceiverNonexists);
public static readonly string Default = nameof(Default);
}
}

View File

@ -1,5 +1,6 @@
using AutoMapper;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Application.DTOs.EnvelopeHistory;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.MappingProfiles
@ -17,11 +18,11 @@ namespace EnvelopeGenerator.Application.MappingProfiles
CreateMap<EnvelopeCertificate, EnvelopeCertificateDto>();
CreateMap<EnvelopeDocument, EnvelopeDocumentDto>();
CreateMap<EnvelopeHistory, EnvelopeHistoryDto>();
CreateMap<EnvelopeHistory, EnvelopeHistoryCreateDto>();
CreateMap<EnvelopeReceiver, EnvelopeReceiverDto>();
CreateMap<EnvelopeType, EnvelopeTypeDto>();
CreateMap<Receiver, ReceiverDto>();
CreateMap<UserReceiver, UserReceiverDto>();
CreateMap<EmailOut, EmailOutDto>();
// DTO to Entity mappings
CreateMap<ConfigDto, Config>();
@ -32,11 +33,11 @@ namespace EnvelopeGenerator.Application.MappingProfiles
CreateMap<EnvelopeCertificateDto, EnvelopeCertificate>();
CreateMap<EnvelopeDocumentDto, EnvelopeDocument>();
CreateMap<EnvelopeHistoryDto, EnvelopeHistory>();
CreateMap<EnvelopeHistoryCreateDto, EnvelopeHistory>();
CreateMap<EnvelopeReceiverDto, EnvelopeReceiver>();
CreateMap<EnvelopeTypeDto, EnvelopeType>();
CreateMap<ReceiverDto, Receiver>();
CreateMap<UserReceiverDto, UserReceiver>();
CreateMap<EmailOutDto, EmailOut>();
}
}
}

View File

@ -117,12 +117,24 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Complete" xml:space="preserve">
<value>Abschließen</value>
</data>
<data name="de-DE" xml:space="preserve">
<value>Deutch</value>
</data>
<data name="DocProtected" xml:space="preserve">
<value>Dokument geschützt</value>
</data>
<data name="en-US" xml:space="preserve">
<value>Englisch</value>
</data>
<data name="EnvelopeInfo1" xml:space="preserve">
<value>Sie müssen {0} Vorgang unterzeichen. Bitte prüfen Sie die Seite {1}.</value>
</data>
<data name="EnvelopeInfo2" xml:space="preserve">
<value>Erstellt am {0} von {1}. Sie können den Absender über &lt;a href="mailto:{2}?subject={3}&amp;body=Sehr%20geehrter%20{4}%20{5},%0A%0A%0A"&gt;{6}&lt;/a&gt; kontaktieren.</value>
</data>
<data name="LocakedOpen" xml:space="preserve">
<value>Öffnen</value>
</data>
@ -141,4 +153,13 @@
<data name="LockedTitle" xml:space="preserve">
<value>Dokument erfordert einen Zugriffscode</value>
</data>
<data name="SignDoc" xml:space="preserve">
<value>Dokument unterschreiben</value>
</data>
<data name="UnexpectedError" xml:space="preserve">
<value>Ein unerwarteter Fehler ist aufgetreten.</value>
</data>
<data name="WrongAccessCode" xml:space="preserve">
<value>Ungültiger Zugangscode.</value>
</data>
</root>

View File

@ -117,12 +117,24 @@
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Complete" xml:space="preserve">
<value>Complete</value>
</data>
<data name="de-DE" xml:space="preserve">
<value>German</value>
</data>
<data name="DocProtected" xml:space="preserve">
<value>Document protected</value>
</data>
<data name="en-US" xml:space="preserve">
<value>English</value>
</data>
<data name="EnvelopeInfo1" xml:space="preserve">
<value>You have to sign {0} process. Please check page {1}.</value>
</data>
<data name="EnvelopeInfo2" xml:space="preserve">
<value>Created on {0} by {1}. You can contact the sender via &lt;a href="mailto:{2}?subject={3}&amp;body=Dear%20{4}%20{5},%0A%0A%0A"&gt;{6}&lt;/a&gt;.</value>
</data>
<data name="LocakedOpen" xml:space="preserve">
<value>Open</value>
</data>
@ -141,4 +153,13 @@
<data name="LockedTitle" xml:space="preserve">
<value>Document requires an access code</value>
</data>
<data name="SignDoc" xml:space="preserve">
<value>Sign document</value>
</data>
<data name="UnexpectedError" xml:space="preserve">
<value>An unexpected error has occurred.</value>
</data>
<data name="WrongAccessCode" xml:space="preserve">
<value>Invalid access code.</value>
</data>
</root>

View File

@ -1,18 +0,0 @@
using AutoMapper;
using DigitalData.Core.Application;
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Application.Resources;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using Microsoft.Extensions.Localization;
namespace EnvelopeGenerator.Application.Services
{
public class EmailOutService : BasicCRUDService<IEmailOutRepository, EmailOutDto, EmailOut, int>, IEmailOutService
{
public EmailOutService(IEmailOutRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper) : base(repository, localizer, mapper)
{
}
}
}

View File

@ -6,6 +6,9 @@ using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using EnvelopeGenerator.Application.Resources;
using static EnvelopeGenerator.Common.Constants;
using DigitalData.Core.DTO;
using Microsoft.Extensions.Logging;
namespace EnvelopeGenerator.Application.Services
{
@ -15,5 +18,15 @@ namespace EnvelopeGenerator.Application.Services
: base(repository, localizer, mapper)
{
}
public async Task<DataResult<EmailTemplateDto>> ReadByNameAsync(EmailTemplateType type)
{
var temp = await _repository.ReadByNameAsync(type);
return temp is null
? Result.Fail<EmailTemplateDto>()
.Message(Key.InnerServiceError)
.Notice(LogLevel.Error, Flag.DataIntegrityIssue, $"EmailTemplateType '{type}' is not found in DB. Please, define required e-mail template.")
: Result.Success(_mapper.MapOrThrow<EmailTemplateDto>(temp));
}
}
}

View File

@ -1,102 +0,0 @@
namespace EnvelopeGenerator.Application.Services
{
/// <summary>
/// Provides extension methods for decoding and extracting information from an envelope receiver ID.
/// </summary>
public static class EnvelopeGeneratorExtensions
{
/// <summary>
/// Validates whether a given string is a correctly formatted Base-64 encoded string.
/// </summary>
/// <remarks>
/// This method checks the string for proper Base-64 formatting, which includes validating
/// the length of the string (must be divisible by 4). It also checks each character to ensure
/// it belongs to the Base-64 character set (A-Z, a-z, 0-9, '+', '/', and '=' for padding).
/// The method ensures that padding characters ('=') only appear at the end of the string and
/// are in a valid configuration (either one '=' at the end if the string's length % 4 is 3,
/// or two '==' if the length % 4 is 2).
/// </remarks>
/// <param name="input">The Base-64 encoded string to validate.</param>
/// <returns>
/// <c>true</c> if the string is a valid Base-64 encoded string; otherwise, <c>false</c>.
/// </returns>
/// <example>
/// <code>
/// string testString = "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlzIHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2YgdGhlIG1pbmQsIHRoYXQgYnk=";
/// bool isValid = IsValidBase64String(testString);
/// Console.WriteLine(isValid); // Output: true
/// </code>
/// </example>
public static bool IsBase64String(this string input)
{
// Check if the string is null or empty
if (string.IsNullOrEmpty(input))
{
return false;
}
// Replace valid base-64 padding
input = input.Trim();
int mod4 = input.Length % 4;
if (mod4 > 0)
{
// Base-64 string lengths should be divisible by 4
return false;
}
// Check each character to ensure it is valid base-64
foreach (char c in input)
{
if (!char.IsLetterOrDigit(c) && c != '+' && c != '/' && c != '=')
{
// Invalid character detected
return false;
}
}
// Ensure no invalid padding scenarios exist
if (input.EndsWith("==") && (input.Length % 4 == 0) ||
input.EndsWith("=") && (input.Length % 4 == 3))
{
return true;
}
return input.IndexOf('=') == -1; // No padding allowed except at the end
}
/// <summary>
/// Decodes the envelope receiver ID and extracts the envelope UUID and receiver signature.
/// </summary>
/// <param name="envelopeReceiverId">The base64 encoded string containing the envelope UUID and receiver signature.</param>
/// <returns>A tuple containing the envelope UUID and receiver signature.</returns>
public static (string? EnvelopeUuid, string? ReceiverSignature) DecodeEnvelopeReceiverId(this string envelopeReceiverId)
{
if (!envelopeReceiverId.IsBase64String())
{
return (null, null);
}
byte[] bytes = Convert.FromBase64String(envelopeReceiverId);
string decodedString = System.Text.Encoding.UTF8.GetString(bytes);
string[] parts = decodedString.Split(new string[] { "::" }, StringSplitOptions.None);
if (parts.Length > 1)
return (EnvelopeUuid: parts[0], ReceiverSignature: parts[1]);
else
return (string.Empty, string.Empty);
}
/// <summary>
/// Gets the envelope UUID from the decoded envelope receiver ID.
/// </summary>
/// <param name="envelopeReceiverId">The base64 encoded string to decode.</param>
/// <returns>The envelope UUID.</returns>
public static string GetEnvelopeUuid(this string envelopeReceiverId) => envelopeReceiverId.DecodeEnvelopeReceiverId().EnvelopeUuid;
/// <summary>
/// Gets the receiver signature from the decoded envelope receiver ID.
/// </summary>
/// <param name="envelopeReceiverId">The base64 encoded string to decode.</param>
/// <returns>The receiver signature.</returns>
public static string GetReceiverSignature(this string envelopeReceiverId) => envelopeReceiverId.DecodeEnvelopeReceiverId().ReceiverSignature;
}
}

View File

@ -2,15 +2,16 @@
using DigitalData.Core.Application;
using Microsoft.Extensions.Localization;
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using static EnvelopeGenerator.Common.Constants;
using EnvelopeGenerator.Application.Resources;
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.DTOs.EnvelopeHistory;
namespace EnvelopeGenerator.Application.Services
{
public class EnvelopeHistoryService : BasicCRUDService<IEnvelopeHistoryRepository, EnvelopeHistoryDto, EnvelopeHistory, long>, IEnvelopeHistoryService
public class EnvelopeHistoryService : CRUDService<IEnvelopeHistoryRepository, EnvelopeHistoryCreateDto, EnvelopeHistoryDto, EnvelopeHistoryDto, EnvelopeHistory, long>, IEnvelopeHistoryService
{
public EnvelopeHistoryService(IEnvelopeHistoryRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper)
: base(repository, localizer, mapper)
@ -33,5 +34,12 @@ namespace EnvelopeGenerator.Application.Services
envelopeId: envelopeId,
userReference: userReference,
status: (int) EnvelopeStatus.DocumentSigned) > 0;
public async Task<DataResult<long>> RecordAsync(int envelopeId, string userReference, EnvelopeStatus status) =>
await CreateAsync(new (EnvelopeId: envelopeId, UserReference: userReference, Status: (int)status, ActionDate: DateTime.Now))
.ThenAsync(
Success: id => Result.Success(id),
Fail: (mssg, ntc) => Result.Fail<long>().Message(mssg).Notice(ntc)
);
}
}

View File

@ -0,0 +1,36 @@
using AutoMapper;
using DigitalData.Core.DTO;
using DigitalData.EmailProfilerDispatcher.Application.DTOs.EmailOut;
using DigitalData.EmailProfilerDispatcher.Application.Services;
using DigitalData.EmailProfilerDispatcher.Infrastructure.Contracts;
using DigitalData.UserManager.Application;
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Common;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Localization;
namespace EnvelopeGenerator.Application.Services
{
public class EnvelopeMailService : EmailOutService<Resource>, IEnvelopeMailService
{
private readonly IEmailTemplateService _tempService;
private readonly IMemoryCache _cache;
public EnvelopeMailService(IEmailOutRepository repository, IStringLocalizer<Resource> localizer, IMapper mapper, IEmailTemplateService tempService, IMemoryCache cache) : base(repository, localizer, mapper)
{
_tempService = tempService;
_cache = cache;
}
public Task<DataResult<int>> SendAccessCodeAsync(EnvelopeReceiverDto envelopeReceiverDto)
{
throw new NotImplementedException();
}
public Task<DataResult<int>> SendAsync(EnvelopeReceiverDto envelopeReceiverDto, Constants.EmailTemplateType tempType)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,89 +0,0 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace EnvelopeGenerator.Domain.Entities
{
[Table("TBEMLP_EMAIL_OUT", Schema = "dbo")]
public class EmailOut
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("GUID")]
public int Id { get; set; }
[Required]
[Column("REMINDER_TYPE_ID")]
public int ReminderTypeId { get; set; } = 1; // Default value
[Required]
[Column("SENDING_PROFILE")]
public int SendingProfile { get; set; }
[Required]
[Column("REFERENCE_ID")]
public int ReferenceId { get; set; }
[StringLength(200)]
[Column("REFERENCE_STRING")]
public string ReferenceString { get; set; }
[Column("ENTITY_ID")]
public int? EntityId { get; set; }
[Required]
[Column("WF_ID")]
public int WfId { get; set; }
[StringLength(200)]
[Column("WF_REFERENCE")]
public string WfReference { get; set; }
[Required]
[StringLength(1000)]
[Column("EMAIL_ADRESS")]
public string EmailAdress { get; set; }
[Required]
[StringLength(500)]
[Column("EMAIL_SUBJ")]
public string EmailSubj { get; set; }
[Required]
[Column("EMAIL_BODY")]
public string EmailBody { get; set; }
[StringLength(512)]
[Column("EMAIL_ATTMT1")]
public string EmailAttmt1 { get; set; }
[Column("EMAIL_SENT")]
public DateTime? EmailSent { get; set; }
[StringLength(500)]
[Column("COMMENT")]
public string Comment { get; set; }
[Required]
[StringLength(50)]
[Column("ADDED_WHO")]
public string AddedWho { get; set; } = "DEFAULT"; // Default value
[Column("ADDED_WHEN")]
public DateTime? AddedWhen { get; set; } = DateTime.Now; // Default value
[StringLength(50)]
[Column("CHANGED_WHO")]
public string ChangedWho { get; set; }
[Column("CHANGED_WHEN")]
public DateTime? ChangedWhen { get; set; }
[Column("ERROR_TIMESTAMP")]
public DateTime? ErrorTimestamp { get; set; }
[StringLength(900)]
[Column("ERROR_MSG")]
public string ErrorMsg { get; set; }
}
}

View File

@ -25,6 +25,7 @@ namespace EnvelopeGenerator.Domain.Entities
[Required]
[Column("ADDED_WHEN", TypeName = "datetime")]
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime AddedWhen { get; set; }
[Column("ACTION_DATE", TypeName = "datetime")]

View File

@ -1,9 +0,0 @@
using DigitalData.Core.Contracts.Infrastructure;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Infrastructure.Contracts
{
public interface IEmailOutRepository : ICRUDRepository<EmailOut, int>
{
}
}

View File

@ -1,9 +1,11 @@
using DigitalData.Core.Contracts.Infrastructure;
using EnvelopeGenerator.Domain.Entities;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Infrastructure.Contracts
{
public interface IEmailTemplateRepository : ICRUDRepository<EmailTemplate, int>
{
Task<EmailTemplate?> ReadByNameAsync(EmailTemplateType type);
}
}

View File

@ -33,11 +33,6 @@ namespace DigitalData.UserManager.Infrastructure.Repositories
.WithOne()
.HasForeignKey(ed => ed.EnvelopeId);
//modelBuilder.Entity<Envelope>()
// .HasMany(e => e.Receivers)
// .WithOne(er => er.Envelope)
// .HasForeignKey(er => er.EnvelopeId);
modelBuilder.Entity<Envelope>()
.HasMany(e => e.History)
.WithOne()
@ -53,10 +48,9 @@ namespace DigitalData.UserManager.Infrastructure.Repositories
.WithMany(ed => ed.Elements)
.HasForeignKey(dre => dre.DocumentId);
//modelBuilder.Entity<Receiver>()
// .HasMany(e => e.EnvelopeReceivers)
// .WithOne(er => er.Receiver)
// .HasForeignKey(er => er.ReceiverId);
// Configure entities to handle database triggers
modelBuilder.Entity<Envelope>().ToTable(tb => tb.HasTrigger("TBSIG_ENVELOPE_HISTORY_AFT_INS"));
modelBuilder.Entity<EnvelopeHistory>().ToTable(tb => tb.HasTrigger("TBSIG_ENVELOPE_HISTORY_AFT_INS"));
base.OnModelCreating(modelBuilder);
}

View File

@ -1,14 +0,0 @@
using DigitalData.Core.Infrastructure;
using DigitalData.UserManager.Infrastructure.Repositories;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
namespace EnvelopeGenerator.Infrastructure.Repositories
{
public class EmailOutRepository : CRUDRepository<EmailOut, int, EGDbContext>, IEmailOutRepository
{
public EmailOutRepository(EGDbContext dbContext) : base(dbContext)
{
}
}
}

View File

@ -2,6 +2,8 @@
using DigitalData.UserManager.Infrastructure.Repositories;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using Microsoft.EntityFrameworkCore;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Infrastructure.Repositories
{
@ -10,5 +12,7 @@ namespace EnvelopeGenerator.Infrastructure.Repositories
public EmailTemplateRepository(EGDbContext dbContext) : base(dbContext)
{
}
public async Task<EmailTemplate?> ReadByNameAsync(EmailTemplateType type) => await _dbSet.Where(t => t.Name == type.ToString()).FirstOrDefaultAsync();
}
}

View File

@ -25,7 +25,7 @@ namespace EnvelopeGenerator.Infrastructure.Repositories
if (withEnvelope)
query = query
.Include(er => er.Envelope).ThenInclude(e => e!.Documents!).ThenInclude(d => d.Elements)
.Include(er => er.Envelope).ThenInclude(e => e!.Documents!).ThenInclude(d => d.Elements!.Where(e => signature == null || e.Receiver!.Signature == signature))
.Include(er => er.Envelope).ThenInclude(e => e!.History)
.Include(er => er.Envelope).ThenInclude(e => e!.User);

View File

@ -1,63 +0,0 @@
###############################################################################
# Set default behavior to automatically normalize line endings.
###############################################################################
* text=auto
###############################################################################
# Set default behavior for command prompt diff.
#
# This is need for earlier builds of msysgit that does not have it on by
# default for csharp files.
# Note: This is only used by command line
###############################################################################
#*.cs diff=csharp
###############################################################################
# Set the merge driver for project and solution files
#
# Merging from the command prompt will add diff markers to the files if there
# are conflicts (Merging from VS is not affected by the settings below, in VS
# the diff markers are never inserted). Diff markers may cause the following
# file extensions to fail to load in VS. An alternative would be to treat
# these files as binary and thus will always conflict and require user
# intervention with every merge. To do so, just uncomment the entries below
###############################################################################
#*.sln merge=binary
#*.csproj merge=binary
#*.vbproj merge=binary
#*.vcxproj merge=binary
#*.vcproj merge=binary
#*.dbproj merge=binary
#*.fsproj merge=binary
#*.lsproj merge=binary
#*.wixproj merge=binary
#*.modelproj merge=binary
#*.sqlproj merge=binary
#*.wwaproj merge=binary
###############################################################################
# behavior for image files
#
# image files are treated as binary by default.
###############################################################################
#*.jpg binary
#*.png binary
#*.gif binary
###############################################################################
# diff behavior for common document formats
#
# Convert binary document formats to text before diffing them. This feature
# is only available from the command line. Turn it on by uncommenting the
# entries below.
###############################################################################
#*.doc diff=astextplain
#*.DOC diff=astextplain
#*.docx diff=astextplain
#*.DOCX diff=astextplain
#*.dot diff=astextplain
#*.DOT diff=astextplain
#*.pdf diff=astextplain
#*.PDF diff=astextplain
#*.rtf diff=astextplain
#*.RTF diff=astextplain

View File

@ -1,363 +0,0 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd

View File

@ -43,28 +43,28 @@ namespace EnvelopeGenerator.Web.Controllers
//TODO: integrate localizer for ready-to-use views
public static ViewResult ViewError(this Controller controller, ErrorViewModel errorViewModel) => controller.View("_Error", errorViewModel);
public static ViewResult ViewError404(this Controller controller) => controller.ViewError(new ErrorViewModel()
public static ViewResult ViewError404(this Controller controller) => controller.ViewError(new()
{
Title = "404",
Subtitle = "Die von Ihnen gesuchte Seite ist nicht verfügbar",
Body = "Sie können derzeit nur an Sie gerichtete Briefe einsehen und unterschreiben.",
});
public static ViewResult ViewEnvelopeNotFound(this Controller controller) => controller.ViewError(new ErrorViewModel()
public static ViewResult ViewEnvelopeNotFound(this Controller controller) => controller.ViewError(new()
{
Title = "404",
Subtitle = "Document not found",
Body = "Wenn Sie diese URL in Ihrer E-Mail erhalten haben, wenden Sie sich bitte an das IT-Team."
});
public static ViewResult ViewDocumentNotFound(this Controller controller) => controller.ViewError(new ErrorViewModel()
public static ViewResult ViewDocumentNotFound(this Controller controller) => controller.ViewError(new()
{
Title = "404",
Subtitle = "Umschlag nicht gefunden",
Body = "Wenn Sie diese URL in Ihrer E-Mail erhalten haben, wenden Sie sich bitte an das IT-Team."
});
public static ViewResult ViewInnerServiceError(this Controller controller) => controller.ViewError(new ErrorViewModel()
public static ViewResult ViewInnerServiceError(this Controller controller) => controller.ViewError(new()
{
Title = "500",
Subtitle = "Ein unerwarteter Fehler ist aufgetreten",

View File

@ -3,7 +3,7 @@ using EnvelopeGenerator.Common;
using EnvelopeGenerator.Web.Services;
using EnvelopeGenerator.Application.Contracts;
using Microsoft.AspNetCore.Authorization;
using EnvelopeGenerator.Application.Services;
using EnvelopeGenerator.Application;
namespace EnvelopeGenerator.Web.Controllers
{

View File

@ -1,4 +1,4 @@
using EnvelopeGenerator.Application.Services;
using EnvelopeGenerator.Application;
using EnvelopeGenerator.Common;
using EnvelopeGenerator.Web.Services;
using Microsoft.AspNetCore.Authorization;

View File

@ -56,8 +56,8 @@ namespace EnvelopeGenerator.Web.Controllers
bool accessCodeAlreadyRequested = await _historyService.AccessCodeAlreadyRequested(envelopeId: er.Envelope!.Id, userReference: er.Receiver!.EmailAddress);
if (!accessCodeAlreadyRequested)
{
// Send email with password
bool actionResult = database.Services.actionService.RequestAccessCode(response.Envelope, response.Receiver);
await _historyService.RecordAsync(er.EnvelopeId, er.Receiver.EmailAddress, Constants.EnvelopeStatus.AccessCodeRequested);
bool result = database.Services.emailService.SendDocumentAccessCodeReceivedEmail(response.Envelope, response.Receiver);
}
@ -77,7 +77,7 @@ namespace EnvelopeGenerator.Web.Controllers
}
[HttpGet("EnvelopeKey/{envelopeReceiverId}/Locked")]
public async Task<IActionResult> EnvelopeLocked([FromRoute] string envelopeReceiverId, [FromQuery] string? culture)
public async Task<IActionResult> EnvelopeLocked([FromRoute] string envelopeReceiverId, [FromQuery] string? culture = null)
{
try
{
@ -89,19 +89,17 @@ namespace EnvelopeGenerator.Web.Controllers
UserLanguage = _cultures.Default.Language;
return Redirect($"{Request.Headers["Referer"]}?culture={_cultures.Default.Language}");
}
if (UserLanguage is not null && culture is not null)
{
else if (UserLanguage is not null && culture is not null)
return Redirect($"Locked");
}
ViewData["UserLanguage"] = UserLanguage;
ViewData["UserCulture"] = _cultures[UserLanguage ?? culture];
return await _envRcvService.IsExisting(envelopeReceiverId: envelopeReceiverId).ThenAsync(
Success: isExisting => isExisting ? View().WithData("EnvelopeKey", envelopeReceiverId) : this.ViewEnvelopeNotFound(),
Fail: IActionResult (messages,notices) =>
{
_logger.LogNotice(notices);
Response.StatusCode = StatusCodes.Status401Unauthorized;
return this.ViewEnvelopeNotFound();
});
}
@ -157,10 +155,19 @@ namespace EnvelopeGenerator.Web.Controllers
return this.ViewDocumentNotFound();
}
var claims = new List<Claim> { new(ClaimTypes.NameIdentifier, uuid), new(ClaimTypes.Hash, signature) };
var claims = new List<Claim> {
new(ClaimTypes.NameIdentifier, uuid),
new(ClaimTypes.Hash, signature),
new(ClaimTypes.Name, er.Name ?? string.Empty),
new(ClaimTypes.Email, er.Receiver.EmailAddress),
new(EnvelopeClaimTypes.Title, er.Envelope.Title)
};
var claimsIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
var authProperties = new AuthenticationProperties { };
var authProperties = new AuthenticationProperties {
AllowRefresh = false,
IsPersistent = false
};
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
@ -169,6 +176,7 @@ namespace EnvelopeGenerator.Web.Controllers
//add PSPDFKit licence key
ViewData["PSPDFKitLicenseKey"] = _configuration["PSPDFKitLicenseKey"];
ViewData["UserCulture"] = _cultures[UserLanguage];
return View("ShowEnvelope", er);
},
@ -182,14 +190,19 @@ namespace EnvelopeGenerator.Web.Controllers
else
{
database.Services.actionService.EnterIncorrectAccessCode(response.Envelope, response.Receiver); //for history
return Unauthorized();
Response.StatusCode = StatusCodes.Status401Unauthorized;
return View("EnvelopeLocked")
.WithData("UserLanguage", UserLanguage ?? _cultures.Default.Language)
.WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value);
}
},
Fail: (messages, notices) =>
{
_logger.LogNotice(notices);
return Unauthorized();
Response.StatusCode = StatusCodes.Status401Unauthorized;
return View("EnvelopeLocked")
.WithData("UserLanguage", UserLanguage ?? _cultures.Default.Language)
.WithData("ErrorMessage", _localizer[WebKey.WrongAccessCode].Value);
});
}
catch(Exception ex)

View File

@ -1,7 +1,11 @@
using EnvelopeGenerator.Application.Contracts;
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Web.Controllers.Test
{
@ -9,7 +13,23 @@ namespace EnvelopeGenerator.Web.Controllers.Test
{
public TestEmailTemplateController(ILogger<TestEmailTemplateController> logger, IEmailTemplateService service) : base(logger, service)
{
}
[HttpGet]
public virtual async Task<IActionResult> GetAll([FromQuery] string? tempType = null)
{
return tempType is null
? await base.GetAll()
: await _service.ReadByNameAsync((EmailTemplateType)Enum.Parse(typeof(EmailTemplateType), tempType)).ThenAsync(
Success: Ok,
Fail: IActionResult (messages, notices) =>
{
_logger.LogNotice(notices);
return NotFound(messages);
});
}
[NonAction]
public override Task<IActionResult> GetAll() => base.GetAll();
}
}

View File

@ -1,6 +1,6 @@
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application;
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Application.Services;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using Microsoft.AspNetCore.Mvc;

View File

@ -1,13 +1,13 @@
using DigitalData.Core.API;
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Application.DTOs.EnvelopeHistory;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.Web.Controllers.Test
{
public class TestEnvelopeHistoryController : TestControllerBase<IEnvelopeHistoryService, IEnvelopeHistoryRepository, EnvelopeHistoryDto, EnvelopeHistory, long>
public class TestEnvelopeHistoryController : CRUDControllerBase<IEnvelopeHistoryService, IEnvelopeHistoryRepository, EnvelopeHistoryCreateDto, EnvelopeHistoryDto, EnvelopeHistoryDto, EnvelopeHistory, long>
{
public TestEnvelopeHistoryController(ILogger<TestEnvelopeHistoryController> logger, IEnvelopeHistoryService service) : base(logger, service)
{

View File

@ -2,7 +2,7 @@
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.Contracts;
using EnvelopeGenerator.Application.DTOs;
using EnvelopeGenerator.Application.Services;
using EnvelopeGenerator.Application;
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Infrastructure.Contracts;
using Microsoft.AspNetCore.Mvc;

View File

@ -1,4 +1,5 @@
using AngleSharp.Common;
using DigitalData.Core.API;
using EnvelopeGenerator.Application.Resources;
using EnvelopeGenerator.Web.Models;
using Microsoft.AspNetCore.Mvc;
@ -23,9 +24,15 @@ namespace EnvelopeGenerator.Web.Controllers.Test
public IActionResult Localize([FromQuery] string key = "de_DE") => Ok(_localizer[key]);
[HttpGet("fi-class")]
public IActionResult GetFIClass(string? lang = null) => lang is null ? Ok(_cultures.FIClasses) : Ok(_cultures.FIClassOf(lang));
public IActionResult GetFIClass(string? lang = null) => lang is null ? Ok(_cultures.FIClasses) : Ok(_cultures[lang]?.FIClass);
[HttpGet("culture")]
public IActionResult GetCultures(string? lang = null) => lang is null ? Ok(_cultures) : Ok(_cultures.CultureOf(lang));
public IActionResult GetCultures(string? lang = null) => lang is null ? Ok(_cultures) : Ok(_cultures[lang]);
[HttpGet("to-culture-info")]
public IActionResult ToCultureInfo(string locale) => Ok(locale.ToCultureInfo());
[HttpGet("two-letter-iso-language-name")]
public IActionResult TwoLetterISOLanguageName(string locale) => Ok(locale.TwoLetterISOLanguageName());
}
}

View File

@ -27,7 +27,7 @@ namespace EnvelopeGenerator.Web.Controllers.Test
{
try
{
var passwordFromConfig = _config["Config:AdminPassword"];
var passwordFromConfig = _config["AdminPassword"];
if (passwordFromConfig == null)
{

View File

@ -0,0 +1,13 @@
namespace EnvelopeGenerator.Web
{
/// <summary>
/// Provides custom claim types for envelope-related information.
/// </summary>
public static class EnvelopeClaimTypes
{
/// <summary>
/// Claim type for the title of an envelope.
/// </summary>
public static readonly string Title = $"Envelope{nameof(Title)}";
}
}

View File

@ -62,6 +62,15 @@
<Reference Include="DigitalData.Core.Infrastructure">
<HintPath>..\..\WebCoreModules\DigitalData.Core.Infrastructure\bin\Debug\net7.0\DigitalData.Core.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="DigitalData.EmailProfilerDispatcher.Application">
<HintPath>..\..\EmailProfilerDispatcher\DigitalData.EmailProfilerDispatcher.Application\bin\Debug\net7.0\DigitalData.EmailProfilerDispatcher.Application.dll</HintPath>
</Reference>
<Reference Include="DigitalData.EmailProfilerDispatcher.Domain">
<HintPath>..\..\EmailProfilerDispatcher\DigitalData.EmailProfilerDispatcher.Application\bin\Debug\net7.0\DigitalData.EmailProfilerDispatcher.Domain.dll</HintPath>
</Reference>
<Reference Include="DigitalData.EmailProfilerDispatcher.Infrastructure">
<HintPath>..\..\EmailProfilerDispatcher\DigitalData.EmailProfilerDispatcher.Application\bin\Debug\net7.0\DigitalData.EmailProfilerDispatcher.Infrastructure.dll</HintPath>
</Reference>
<Reference Include="DigitalData.Modules.Base">
<HintPath>..\..\DDModules\Base\bin\Debug\DigitalData.Modules.Base.dll</HintPath>
</Reference>

View File

@ -1,8 +1,18 @@
namespace EnvelopeGenerator.Web.Models
using System.Globalization;
namespace EnvelopeGenerator.Web.Models
{
public class Culture
{
public string Language { get; init; } = string.Empty;
private string _language = string.Empty;
public string Language { get => _language;
init {
_language = value;
Info = new(value);
}
}
public string FIClass { get; init; } = string.Empty;
public CultureInfo? Info { get; init; }
}
}

View File

@ -6,10 +6,8 @@
public IEnumerable<string> FIClasses => this.Select(c => c.FIClass);
public Culture? CultureOf(string? language) => language is null ? null : this.Where(c => c.Language == language).FirstOrDefault();
public Culture Default => this.First();
public string FIClassOf(string? language) => language is null ? string.Empty : CultureOf(language)?.FIClass ?? string.Empty;
public Culture? this[string? language] => language is null ? null : this.Where(c => c.Language == language).FirstOrDefault();
}
}

View File

@ -17,6 +17,9 @@ using DigitalData.Core.DTO;
using System.Text.Encodings.Web;
using Ganss.Xss;
using Microsoft.Extensions.Options;
using DigitalData.EmailProfilerDispatcher.Application;
using EnvelopeGenerator.Application;
using EnvelopeGenerator.Application.Resources;
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Info("Logging initialized!");
@ -56,21 +59,21 @@ try
//remove option for Test*Controller
options.Conventions.Add(new RemoveIfControllerConvention()
.AndIf(c => c.ControllerName.StartsWith("Test"))
.AndIf(c => !config.GetValue<bool>("EnableTestControllers")));
.AndIf(_ => !builder.IsDevOrDiP() || !config.GetValue<bool>("EnableTestControllers")));
}).AddJsonOptions(q =>
{
// Prevents serialization error when serializing SvgBitmap in EnvelopeReceiver
q.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles;
});
if (config.GetValue<bool>("EnableSwagger"))
if (config.GetValue<bool>("EnableSwagger") && builder.IsDevOrDiP())
{
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
}
//AddEF Core dbcontext
var connStr = config["Config:ConnectionString"];
var connStr = config.GetConnectionString(Key.Default) ?? throw new InvalidOperationException("There is no default connection string in appsettings.json.");
builder.Services.AddDbContext<EGDbContext>(options => options.UseSqlServer(connStr));
//Inject CRUD Service and repositoriesad
@ -89,7 +92,6 @@ try
builder.Services.AddScoped<IEnvelopeTypeRepository, EnvelopeTypeRepository>();
builder.Services.AddScoped<IReceiverRepository, ReceiverRepository>();
builder.Services.AddScoped<IUserReceiverRepository, UserReceiverRepository>();
builder.Services.AddScoped<IEmailOutRepository, EmailOutRepository>();
builder.Services.AddScoped<IConfigService, ConfigService>();
builder.Services.AddScoped<IDocumentReceiverElementService, DocumentReceiverElementService>();
builder.Services.AddScoped<IEnvelopeDocumentService, EnvelopeDocumentService>();
@ -103,7 +105,6 @@ try
builder.Services.AddScoped<IEnvelopeTypeService, EnvelopeTypeService>();
builder.Services.AddScoped<IReceiverService, ReceiverService>();
builder.Services.AddScoped<IUserReceiverService, UserReceiverService>();
builder.Services.AddScoped<IEmailOutService, EmailOutService>();
//Auto mapping profiles
builder.Services.AddAutoMapper(typeof(BasicDtoMappingProfile).Assembly);
@ -127,7 +128,8 @@ try
options.Cookie.HttpOnly = true; // Makes the cookie inaccessible to client-side scripts for security
options.Cookie.SecurePolicy = CookieSecurePolicy.SameAsRequest; // Ensures cookies are sent over HTTPS only
options.Cookie.SameSite = SameSiteMode.Strict; // Protects against CSRF attacks by restricting how cookies are sent with requests from external sites
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = context =>
@ -151,7 +153,7 @@ try
};
});
builder.Services.AddSingleton(_ => config.GetSection("ContactLink").Get<ContactLink>() ?? new ContactLink());
builder.Services.AddSingleton(config.GetSection("ContactLink").Get<ContactLink>() ?? new());
builder.Services.AddCookieConsentSettings();
@ -167,9 +169,15 @@ try
});
// Register the FlagIconCssClass instance as a singleton
builder.Services.Configure<Cultures>(builder.Configuration.GetSection("Cultures"));
builder.Services.Configure<Cultures>(config.GetSection("Cultures"));
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<Cultures>>().Value);
// Register mail services
builder.Services.AddScoped<IEnvelopeMailService, EnvelopeMailService>();
builder.Services.AddDispatcher<EGDbContext, Resource>();
builder.Services.AddMemoryCache();
var app = builder.Build();
// Configure the HTTP request pipeline.
@ -181,14 +189,21 @@ try
}
//Content-Security-Policy
if (config.GetValue<bool>("TestCSP") || !app.Environment.IsDevelopment())
if (config.GetValue<bool>("UseCSPInDev") || !app.Environment.IsDevelopment())
{
var csp_list = config.GetSection("Content-Security-Policy").Get<string[]>();
if (csp_list is not null)
app.UseCSPMiddleware($"{string.Join("; ", csp_list)};");
if (csp_list is null)
logger.Warn("There is no Content-Security-Policy");
else
{
var csp = string.Join("; ", csp_list?.Where(st => st is not null) ?? Array.Empty<string>());
logger.Info($"Content-Security-Policy {csp}");
if (csp_list is not null)
app.UseCSPMiddleware(csp);
}
}
if (config.GetValue<bool>("EnableSwagger"))
if (config.GetValue<bool>("EnableSwagger") && builder.IsDevOrDiP())
{
app.UseSwagger();
app.UseSwaggerUI();

View File

@ -1,5 +1,6 @@
using DigitalData.Modules.Database;
using DigitalData.Modules.Logging;
using EnvelopeGenerator.Application;
using EnvelopeGenerator.Common;
namespace EnvelopeGenerator.Web.Services
@ -52,11 +53,11 @@ namespace EnvelopeGenerator.Web.Services
public DatabaseService(ILogger<DatabaseService> logger, IConfiguration config)
{
LogConfig logConfig = new LogConfig(LogConfig.PathType.CustomPath, config["Config:LogPath"], null, "Digital Data", "ECM.EnvelopeGenerator.Web");
LogConfig logConfig = new LogConfig(LogConfig.PathType.CustomPath, config["NLog:variables:logDirectory"], null, "Digital Data", "ECM.EnvelopeGenerator.Web");
_logger = logger;
_logger.LogInformation("Establishing MSSQL Database connection..");
MSSQL = new MSSQLServer(logConfig, config["Config:ConnectionString"]);
MSSQL = new MSSQLServer(logConfig, config.GetConnectionString(Key.Default));
if (MSSQL.DBInitialized == true)
{

View File

@ -2,8 +2,8 @@
var nonce = _accessor.HttpContext?.Items["csp-nonce"] as string;
}
@{
ViewData["Title"] = "Dokument geschützt";
var userLanguage = ViewData["UserLanguage"] as string;
ViewData["Title"] = _localizer[WebKey.DocProtected];
var userCulture = ViewData["UserCulture"] as Culture;
}
<div class="page container py-5 px-2">
<header class="text-center">
@ -24,23 +24,31 @@
<div class="input">
<label class="visually-hidden" for="access_code">@_localizer[WebKey.LockedTitle]</label>
<input type="password" id="access_code" class="form-control" name="access_code" placeholder="@_localizer[WebKey.LockedAccessCode]" required="required">
<div id="access-code-error-message" class="text-danger" style="height: 20px;">
@if (ViewData["ErrorMessage"] is string errMsg)
{
@_sanitizer.Sanitize(errMsg)
}
</div>
</div>
<div class="button">
<button type="submit" class="btn btn-primary">@_localizer[WebKey.LocakedOpen]</button>
</div>
</form>
</div>
<div class="col-4 d-flex justify-content-center align-items-center">
<div class="col-4 mb-3 d-flex justify-content-center align-items-center">
<div class="dropdown">
<button class="btn btn-outline-secondary dropdown-toggle" type="button" id="langDropdownMenuButton" data-bs-toggle="dropdown" aria-expanded="false">
<span class="fi @_cultures.FIClassOf(userLanguage).TrySanitize(_sanitizer) me-2" id="selectedFlag"></span><span id="selectedLanguage"></span>
<span class="fi @userCulture?.FIClass.TrySanitize(_sanitizer) me-2" id="selectedFlag"></span><span id="selectedLanguage"></span>
</button>
<ul class="dropdown-menu" aria-labelledby="langDropdownMenuButton">
@foreach(var lang in _cultures.Languages)
@foreach(var culture in _cultures)
{
var lang = culture.Language;
var info = culture.Info;
<li>
<a class="dropdown-item" data-language="@lang.TrySanitize(_sanitizer)" data-flag="@_cultures.FIClassOf(lang).TrySanitize(_sanitizer)">
<span class="fi @_cultures.FIClassOf(lang).TrySanitize(_sanitizer) me-2"></span>@_localizer[lang].Value.TrySanitize(_sanitizer)
<a class="dropdown-item" data-language="@lang.TrySanitize(_sanitizer)" data-flag="@_cultures[lang]?.FIClass.TrySanitize(_sanitizer)">
<span class="fi @_cultures[lang]?.FIClass.TrySanitize(_sanitizer) me-2"></span>@info?.Parent.NativeName
</a>
</li>
}

View File

@ -5,10 +5,11 @@
@using EnvelopeGenerator.Application.DTOs;
@model EnvelopeReceiverDto;
@{
ViewData["Title"] = "Dokument unterschreiben";
ViewData["Title"] = _localizer[WebKey.SignDoc];
}
<partial name="_CookieConsentPartial" />
@{
var userCulture = ViewData["UserCulture"] as Culture;
var envelope = Model.Envelope;
var document = Model.Envelope?.Documents?.FirstOrDefault();
var sender = Model.Envelope?.User;
@ -16,34 +17,56 @@
var stPageIndexes = string.Join(pages.Count() > 1 ? ", " : "", pages.Take(pages.Count() - 1))
+ (pages.Count() > 1 ? " und " : "") + pages.LastOrDefault();
}
<nav class="navbar navbar-light bg-light">
<div class="container-fluid">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarToggleExternalContent" aria-controls="navbarToggleExternalContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="d-flex flex-column min-vh-100">
<nav class="navbar navbar-light bg-light">
<div class="container-fluid">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarToggleExternalContent" aria-controls="navbarToggleExternalContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="navbar-brand me-auto ms-5 envelope-message">@($"Hallo {Model.Name.TrySanitize(_sanitizer)}, {@envelope?.Message.TrySanitize(_sanitizer)}")</div>
<div class="col-1 p-0 m-0 me-3 d-flex">
<img src="~/img/digital_data.svg" alt="...">
<div class="col-1 p-0 m-0 me-3 d-flex">
<img src="~/img/digital_data.svg" alt="...">
</div>
</div>
</div>
</nav>
<div class="collapse show" id="navbarToggleExternalContent" data-bs-theme="light">
<div class="bg-light p-1">
<div class="card sender-card mb-3">
<div class="row g-0">
<div class="col-1 p-0 m-0 ps-4 mx-auto">
<img src="~/img/default-user.svg" class="img-fluid p-0 m-0" alt="...">
</div>
<div class="col p-0 m-0">
<div class="card-body p-0 m-0">
<h5 class="card-title p-0 m-0">@($"{envelope?.Title.TrySanitize(_sanitizer)}")</h5>
<p class="card-text p-0 m-0">@($"Sie haben {(pages.Count())} Briefe zu unterschreiben. Bitte prüfen Sie die Seiten {stPageIndexes.TrySanitize(_sanitizer)}.")</p>
<p class="card-text p-0 m-0"><small class="text-body-secondary">Erstellt am @envelope?.AddedWhen von @sender?.Prename.TrySanitize(_sanitizer) @sender?.Name.TrySanitize(_sanitizer). Sie können den Absender über <a href="mailto:@(sender?.Email.TryEncode(_encoder))?subject=@(envelope?.Title.TryEncode(_encoder))&body=Sehr%20geehrter%20@(sender?.Prename.TryEncode(_encoder))%20@(sender?.Name.TryEncode(_encoder)),%0A%0A%0A">@sender?.Email.TryEncode(_encoder)</a> kontaktieren.</small></p>
</nav>
<div class="collapse show" id="navbarToggleExternalContent" data-bs-theme="light">
<div class="bg-light p-1">
<div class="card sender-card mb-3">
<div class="row g-0">
<div class="col p-0 m-0">
<div class="card-body p-0 m-0 ms-4">
<h5 class="card-title p-0 m-0">@($"{envelope?.Title.TrySanitize(_sanitizer)}")</h5>
<p class="card-text p-0 m-0">@(string.Format(_localizer[WebKey.EnvelopeInfo1], pages.Count(), stPageIndexes.TrySanitize(_sanitizer)))</p>
<p class="card-text p-0 m-0"><small class="text-body-secondary">@Html.Raw(string.Format(_localizer[WebKey.EnvelopeInfo2],
envelope?.AddedWhen.ToString(userCulture?.Info?.DateTimeFormat),
$"{sender?.Prename} {sender?.Name}".TrySanitize(_sanitizer),
sender?.Email.TryEncode(_encoder),
envelope?.Title.TryEncode(_encoder),
sender?.Prename.TryEncode(_encoder),
sender?.Name.TryEncode(_encoder),
sender?.Email.TryEncode(_encoder)))</small></p>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="btn-group btn_group position-fixed bottom-0 end-0 d-flex align-items-center" role="group" aria-label="Basic mixed styles example">
<button class="btn_complete btn btn-primary" type="button">
<svg class="icon" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="0 0 20 16">
<path d="m10.036 8.278 9.258-7.79A1.979 1.979 0 0 0 18 0H2A1.987 1.987 0 0 0 .641.541l9.395 7.737Z" />
<path d="M11.241 9.817c-.36.275-.801.425-1.255.427-.428 0-.845-.138-1.187-.395L0 2.6V14a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V2.5l-8.759 7.317Z" />
</svg>
<span>@_localizer[WebKey.Complete]</span>
</button>
<button class="btn_refresh btn btn-outline-secondary" type="button">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor" class="bi bi-arrow-counterclockwise" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M8 3a5 5 0 1 1-4.546 2.914.5.5 0 0 0-.908-.417A6 6 0 1 0 8 2v1z" />
<path d="M8 4.466V.534a.25.25 0 0 0-.41-.192L5.23 2.308a.25.25 0 0 0 0 .384l2.36 1.966A.25.25 0 0 0 8 4.466z" />
</svg>
</button>
</div>
<div id='app' class="flex-grow-1"></div>
</div>
<script nonce="@nonce">
const collapseNav = () => {
@ -57,7 +80,6 @@
}
});
}
</script>
@if (ViewData["DocumentBytes"] is byte[] documentBytes)
{
var settings = new Newtonsoft.Json.JsonSerializerSettings
@ -66,23 +88,8 @@
};
var envelopeReceiverJson = Newtonsoft.Json.JsonConvert.SerializeObject(Model, settings);
var documentBase64String = Convert.ToBase64String(documentBytes);
var envelopeKey = ViewData["EnvelopeKey"] as string;
<script nonce="@nonce">
var base64String = "@Html.Raw(documentBase64String.TrySanitize(_sanitizer))";
var byteCharacters = atob(base64String);
var byteNumbers = new Array(byteCharacters.length);
for (var i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
var byteArray = new Uint8Array(byteNumbers);
var documentArrayBuffer = byteArray.buffer;
document.addEventListener("DOMContentLoaded", async () => {
const app = new App("#app", "@envelopeKey.TrySanitize(_sanitizer)", @Html.Raw(envelopeReceiverJson.TrySanitize(_sanitizer)), documentArrayBuffer, "@ViewData["PSPDFKitLicenseKey"]");
await app.init();
})
</script>
@:document.addEventListener("DOMContentLoaded", async () => await new App("@envelopeKey.TrySanitize(_sanitizer)", @Html.Raw(envelopeReceiverJson.TrySanitize(_sanitizer)), B64ToBuff("@Html.Raw(documentBase64String.TrySanitize(_sanitizer))"), "@ViewData["PSPDFKitLicenseKey"]", "@userCulture?.Info?.TwoLetterISOLanguageName").init())
}
<div id='app'></div>
</script>

View File

@ -3,13 +3,12 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - EnvelopeGenerator.Web</title>
<title>@ViewData["Title"]</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/lib/sweetalert2/sweetalert2.min.css" />
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/EnvelopeGenerator.Web.styles.css" asp-append-version="true"/>
<link rel="stylesheet" href="~/lib/flag-icons-main/css/flag-icons.min.css" asp-append-version="true" />
<link href="~/lib/select2/dist/css/select2.min.css" rel="stylesheet"/>
</head>
<body>
<script src="~/lib/jquery/dist/jquery.min.js"></script>
@ -22,7 +21,8 @@
<script src="~/js/app.js" asp-append-version="true"></script>
<script src="~/lib/pspdfkit/pspdfkit.js" asp-append-version="true"></script>
<script src="~/lib/bootstrap-cookie-consent-settings-main/bootstrap-cookie-consent-settings.js" asp-append-version="true"></script>
<script src="~/lib/select2/dist/js/select2.min.js"></script>
<script src="~/js/util.js" asp-append-version="true"></script>
<script src="~/js/api.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
<main role="main">
<partial name="_CookieConsentPartial" />

View File

@ -1,4 +1,6 @@
namespace EnvelopeGenerator.Web
using System.Numerics;
namespace EnvelopeGenerator.Web
{
public static class WebKey
{
@ -16,5 +18,11 @@
public static readonly string LockedAccessCode = nameof(LockedAccessCode);
public static readonly string LockedFooterTitle = nameof(LockedFooterTitle);
public static readonly string LockedFooterBody = nameof(LockedFooterBody);
public static readonly string WrongAccessCode = nameof(WrongAccessCode);
public static readonly string SignDoc = nameof(SignDoc);
public static readonly string DocProtected = nameof(DocProtected);
public static readonly string Complete = nameof(Complete);
public static readonly string EnvelopeInfo1 = nameof(EnvelopeInfo1);
public static readonly string EnvelopeInfo2 = nameof(EnvelopeInfo2);
}
}

View File

@ -1,4 +1,5 @@
{
"DiPMode": false, //Please be careful when enabling Development in Production (DiP) mode. It allows Swagger and test controllers to be enabled in a production environment.
"EnableSwagger": true,
"EnableTestControllers": true,
"DetailedErrors": true,
@ -10,17 +11,13 @@
"Microsoft.AspNetCore.Hosting.Diagnostics": "Warning"
}
},
"Config": {
"ConnectionString": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;",
"LogPath": "E:\\EnvelopeGenerator\\Logs",
"LogDebug": true,
"LogJson": true,
"AdminPassword": "dd"
"ConnectionStrings": {
"Default": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
},
"PSPDFKitLicenseKey": null,
/* The first format parameter {0} will be replaced by the nonce value. */
"TestCSP": true,
"Content-Security-Policy": [
"AdminPassword": "dd",
"PSPDFKitLicenseKey_SignFlow": "y8VgCpBgUfNlpKJZC-GwjpPs-S-KFBHv4RywfHbqpBAbO0XxRuWDaMGZtIaMrXBDlndlJLk---Ve93xjI_ZR4sbFymf4Ot97yTYUMeDdL2LYXhspkEnSAtkXf9zqepNL1v_0DMibjpqXFQMkVB1y3D5xdnOg-iJuCCZEMhZ780qg04_H_nmx63uSgxjN0GJxC8YvsbnRcUZ2l_idImMvWL0HMqB5B7oEpNenasA0RK0uapFRTa7NIQok0phpTHZYKB4qvj7od2yxlytGB7qBl4-lwT70DSQ9mrLkCWbuzZ9cV9D8fDzdFXr6WoZdOYpkrUadRbsy2bhPq_ukxszDWN4JGhebo0XKUK_YfgvSlS7lFOxHNblHeC9B7gZ8T-VuQ_z1QA2JYRf1dmhSuclnW00diShIg-N0I79PWGsQE4j40XtVpyWcN9uT9hMuiRpL0LzHV4YgsgBrgKgs_moqL7f0L4-MwaS25Dx4Wcz4ttKaerLavwMM4CJHI3DNqTC5UUEG6EViFxBQtrmuAS7kiw2nWjvXO7kUA24NARtsRCphjWE4l6wSMdh7kpqhfbV7_hdb5xXYGALNPkv8En6zPpFIew8DDcOH9dgxfKMI34LLhkEWqovZW_7fXNJTEIHVpR0DSPbZrmyEwkECnbDcNzjyFk2M1fzstJj_dSotyZvS57XJK2DgojbRgXL9pncs",
"UseCSPInDev": false,
"Content-Security-Policy": [ // The first format parameter {0} will be replaced by the nonce value.
"default-src 'self'",
"script-src 'self' 'nonce-{0}' 'unsafe-inline' 'unsafe-eval' blob: data:",
"style-src 'self' 'unsafe-inline'",
@ -32,24 +29,27 @@
"object-src 'self'",
"worker-src 'self' blob: data:"
],
"AdminPassword": "dd",
"AllowedOrigins": [ "https://localhost:7202", "https://digitale.unterschrift.wisag.de/" ],
"NLog": {
"throwConfigExceptions": true,
"variables": {
"logDirectory": "E:\\EnvelopeGenerator\\Logs",
"logFileNamePrefix": "${shortdate}-ECM.EnvelopeGenerator.Web"
},
"targets": {
"infoLogs": {
"type": "File",
"fileName": "E:\\EnvelopeGenerator\\Logs\\${shortdate}-ECM.EnvelopeGenerator.Web-Info.log",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Info.log",
"maxArchiveDays": 30
},
"errorLogs": {
"type": "File",
"fileName": "E:\\EnvelopeGenerator\\Logs\\${shortdate}-ECM.EnvelopeGenerator.Web-Error.log",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Error.log",
"maxArchiveDays": 30
},
"criticalLogs": {
"type": "File",
"fileName": "E:\\EnvelopeGenerator\\Logs\\${shortdate}-ECM.EnvelopeGenerator.Web-Critical.log",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Critical.log",
"maxArchiveDays": 30
}
},

View File

@ -4,14 +4,28 @@
*/
/* Toolbar Buttons */
#app {
background: gray;
width: 100vw;
height: 100vh;
margin: 0 auto;
height: 80vh;
}
.btn-group {
margin-right: 10vw;
margin-bottom: 10vh;
}
.btn_refresh, .btn_complete {
}
.btn_complete .icon {
width: 1.1rem;
}
.btn_complete span {
vertical-align: middle;
}
.button-finish {
transition: background-color linear 300ms;
background-color: #059669; /* emerald-600 */
@ -60,46 +74,45 @@ body {
max-width: 40rem;
}
.page section {
max-width: 30rem;
margin: 0 auto;
.page section {
max-width: 30rem;
margin: 0 auto;
}
.page header .icon {
display: inline-block;
border-radius: 100px;
padding: 15px;
margin-bottom: 2rem;
}
.page header .icon.admin {
background-color: #331904;
color: #fecba1;
}
.page header .icon.locked {
background-color: #ffc107;
color: #000;
}
.page header .icon.signed {
background-color: #146c43;
color: #fff;
}
.page .form {
max-width: 30rem;
margin: 2rem auto;
display: flex;
gap: 1rem;
}
#form-access-code > .input,
#form-admin-password > .input {
flex-grow: 1;
}
.page header .icon {
display: inline-block;
border-radius: 100px;
padding: 15px;
margin-bottom: 2rem;
}
.page header .icon.admin {
background-color: #331904;
color: #fecba1;
}
.page header .icon.locked {
background-color: #ffc107;
color: #000;
}
.page header .icon.signed {
background-color: #146c43;
color: #fff;
}
.page .form {
max-width: 30rem;
margin: 2rem auto;
display: flex;
gap: 1rem;
}
#form-access-code > .input,
#form-admin-password > .input {
flex-grow: 1;
}
#page-admin header .icon {
background-color: #331904;
color: #fecba1;
@ -120,26 +133,29 @@ footer#page-footer {
font-size: 0.85rem;
}
footer#page-footer a,
footer#page-footer a:link,
footer#page-footer a:hover,
footer#page-footer a:visited,
footer#page-footer a:focus {
color: #444;
}
footer#page-footer a,
footer#page-footer a:link,
footer#page-footer a:hover,
footer#page-footer a:visited,
footer#page-footer a:focus {
color: #444;
}
.sender-card {
background-color: transparent;
border: none;
}
.sender-card .row {
height: 7vh;
}
.sender-card img{
height: 7vh;
background-color: rgb(209, 207, 207);
border-radius: 50px;
}
.sender-card .row {
height: 7vh;
}
.sender-card img {
height: 7vh;
background-color: rgb(209, 207, 207);
border-radius: 50px;
}
.envelope-message {
font-family: 'Roboto', sans-serif;
}
@ -152,32 +168,12 @@ footer#page-footer a:focus {
width: 30%;
height: 70%;
}
.dropdown-flag {
height: 75%;
width: 75%;
}
/* --- */
/* Adjusting the height of the select2 container */
.dropdown-flag .select2-container--default .select2-selection--single {
height: 40px; /* Desired height */
}
/* Adjusting the height and vertical alignment of the selected item */
.dropdown-flag .select2-container--default .select2-selection--single .select2-selection__rendered {
line-height: 38px; /* Should be 2px less than the height for internal padding */
}
/* Adjusting the height of the dropdown arrow */
.dropdown-flag .select2-container--default .select2-selection--single .select2-selection__arrow {
height: 38px; /* Again, 2px less than the height */
}
/* Adjusting the height of dropdown list items */
.dropdown-flag .select2-container--default .select2-dropdown .select2-results>.select2-results__options {
max-height: 200px; /* Optional, adjust for larger dropdown height */
}
/* CSS for custom class to increase dropdown height */
.increase-dropdown-height {
min-height: 400px; /* Optional, larger value for increased height */
@ -190,27 +186,74 @@ footer#page-footer a:focus {
max-width: 180px; /* Suitable maximum width for the form */
}
.select2-container--default .select2-search--dropdown .select2-search__field {
border-color: #86b7fe;
outline: 0;
mask-border-width: 0
}
.select2-container--default .select2-search--dropdown .select2-search__field:hover {
border-color: #86b7fe;
outline: 0;
box-shadow: 0 0 0 .25rem rgba(13, 110, 253, .25);
}
.select2-search__field {
display:none
}
.lang-item {
font-size: 0.85rem;
}
#langDropdownMenuButton{
#langDropdownMenuButton {
min-width: 4vw;
}
}
/* Additional styles for better mobile responsiveness */
@media (max-width: 767px) {
.navbar {
flex-direction: column;
align-items: flex-start;
}
.navbar-toggler {
}
.navbar-brand {
font-size: 0.5rem;
text-align: center;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
.collapse .card-text, .collapsing .card-text {
font-size: 0.6rem; /* Font size reduced */
margin: 0rem;
padding: 0rem;
}
.sender-card .card-body {
padding: 0.5rem;
}
.btn_group {
position: fixed;
flex-direction: row;
bottom: 0.5rem;
right: 0.5rem;
}
.btn_complete, .btn_refresh {
padding: 0.5rem;
font-size: 0.9rem;
height: 100%; /* Adjust height for better fit */
width: 10rem; /* Full width for better touch area */
margin-bottom: 0.5rem; /* Space between buttons */
}
.img-fluid {
width: 1.2rem;
height: 100%;
display: none;
}
img {
max-width: 4rem;
}
.page {
margin-top: 1rem;
max-width: 90%;
padding: 0.5rem;
}
.page section {
max-width: 90%;
}
}

View File

@ -3,8 +3,6 @@
const annotations = []
document.elements.forEach((element) => {
console.debug('Creating annotation for element', element.id)
const [annotation, formField] = this.createAnnotationFromElement(element)
annotations.push(annotation)
annotations.push(formField)

View File

@ -0,0 +1,7 @@
const submitForm = async form => await fetch(form.action, {
method: form.method,
body: new FormData(form),
headers: {
"X-Requested-With": "XMLHttpRequest"
}
})

View File

@ -10,8 +10,8 @@ const ActionType = {
}
class App {
constructor(container, envelopeKey, envelopeReceiver, documentBytes, licenseKey) {
this.container = container
constructor(envelopeKey, envelopeReceiver, documentBytes, licenseKey, locale, container) {
this.container = container ?? `#${this.constructor.name.toLowerCase()}`;
this.envelopeKey = envelopeKey
this.UI = new UI()
@ -25,6 +25,7 @@ class App {
this.envelopeReceiver = envelopeReceiver;
this.documentBytes = documentBytes;
this.licenseKey = licenseKey;
this.locale = locale;
}
// This function will be called from the ShowEnvelope.razor page
@ -38,7 +39,6 @@ class App {
const documentResponse = this.documentBytes
if (documentResponse.fatal || documentResponse.error) {
console.error(documentResponse.error)
return Swal.fire({
title: 'Fehler',
text: 'Dokument konnte nicht geladen werden!',
@ -49,7 +49,7 @@ class App {
const arrayBuffer = this.documentBytes
// Load PSPDFKit
this.Instance = await this.UI.loadPSPDFKit(arrayBuffer, this.container, this.licenseKey)
this.Instance = await this.UI.loadPSPDFKit(arrayBuffer, this.container, this.licenseKey, this.locale)
this.UI.configurePSPDFKit(this.Instance, this.handleClick.bind(this))
this.Instance.addEventListener(
@ -66,8 +66,6 @@ class App {
)
// Load annotations into PSPDFKit
console.debug('Loading annotations..')
try {
this.signatureCount = this.currentDocument.elements.length
const annotations = this.Annotation.createAnnotations(
@ -85,12 +83,15 @@ class App {
})
}
} catch (e) {
console.error(e)
}
//add click events of external buttons
[...document.getElementsByClassName('btn_refresh')].forEach(btn => btn.addEventListener('click', _ => this.handleClick('RESET')));
[...document.getElementsByClassName('btn_complete')].forEach(btn => btn.addEventListener('click', _ => this.handleClick('FINISH')));
}
handleAnnotationsLoad(loadedAnnotations) {
console.debug('annotations loaded', loadedAnnotations.toJS())
loadedAnnotations.toJS()
}
handleAnnotationsChange() { }
@ -168,7 +169,6 @@ class App {
}
async handleFinish(event) {
const validationResult = await this.validateAnnotations(this.signatureCount)
if (validationResult === false) {
Swal.fire({
@ -184,7 +184,6 @@ class App {
try {
await this.Instance.save()
} catch (e) {
console.error(e)
Swal.fire({
title: 'Fehler',
text: 'Umschlag konnte nicht signiert werden!',
@ -196,7 +195,6 @@ class App {
// Export annotation data and save to database
try {
const json = await this.Instance.exportInstantJSON()
console.log(json)
const postEnvelopeResult = await this.Network.postEnvelope(
this.envelopeKey,
this.currentDocument.id,
@ -223,7 +221,6 @@ class App {
return true
} catch (e) {
console.error(e)
return false
}
}

View File

@ -14,8 +14,9 @@
// Load the PSPDFKit UI by setting a target element as the container to render in
// and a arraybuffer which represents the document that should be displayed.
loadPSPDFKit(arrayBuffer, container, licenseKey) {
loadPSPDFKit(arrayBuffer, container, licenseKey, locale) {
return PSPDFKit.load({
locale: locale,
licenseKey: licenseKey,
styleSheets: ['/css/site.css'],
container: container,
@ -47,8 +48,6 @@
configurePSPDFKit(instance, handler) {
const toolbarItems = this.getToolbarItems(instance, handler)
instance.setToolbarItems(toolbarItems)
console.debug('PSPDFKit configured!')
}
annotationRenderer(data) {
@ -70,6 +69,7 @@
}
getCustomItems = function (callback) {
return []
return [
{
type: 'custom',
@ -124,4 +124,4 @@
return annotationPresets
}
}
}

View File

@ -0,0 +1 @@
const B64ToBuff = (base64String) => new Uint8Array(Array.from(atob(base64String), char => char.charCodeAt(0))).buffer;