Compare commits

..

20 Commits

Author SHA1 Message Date
Developer 02
456c591fae Bump version to 1.2.3 for EnvelopeGenerator.GeneratorAPI
Updated the version information in the project file,
incrementing `<Version>`, `<FileVersion>`, and `<AssemblyVersion>`
from 1.2.2 to 1.2.3.
2025-05-12 11:14:56 +02:00
Developer 02
41b2841c25 Add validation to ReadHistoryQuery and remove StatusName
Updated ReadHistoryQuery to include a [Required] attribute for the EnvelopeId property, enforcing mandatory validation. Removed the StatusName property from ReadHistoryResponse, which may impact how status is accessed in the application.
2025-05-12 11:07:53 +02:00
Developer 02
6a6da4a876 Update mapping in ReadHistoryMappingProfile constructor
Changed mapping from EnvelopeHistory to ReadHistoryResponse.
2025-05-12 10:58:21 +02:00
Developer 02
af280ee64e Add MediatR support for envelope history queries
Updated project references and introduced MediatR for handling
envelope history queries. Added new mapping profile and response
class, and refactored the HistoryController to utilize the
mediator pattern for improved query handling.
2025-05-12 10:55:19 +02:00
Developer 02
9b945ce232 Add error handling and update history query logic
- Introduced a using directive for exceptions in DocumentCreateReadSQL.cs.
- Enhanced CreateParmas method with try-catch for base64 conversion errors, throwing BadRequestException on failure.
- Added switch statement in HistoryController.cs to manage Related property in ReadHistoryQuery, setting flags for receiver and sender.
2025-05-12 10:04:33 +02:00
Developer 02
8b1199bc71 Add Related property to ReadHistoryQuery and update controller
The `ReadHistoryQuery` record now includes a computed `Related` property that determines the context of the record based on the `Status` value. The constructor has been updated to remove the `Related` parameter.

Additionally, the `GetAllAsync` method in `HistoryController` has been modified to handle the `Related` property, setting the `withReceiver` boolean when the value is `ReferenceType.Receiver`.
2025-05-12 09:57:14 +02:00
Developer 02
1d74b7ca06 Remove null check for EmailAddress in GetReceiverName
This commit removes the conditional check for the `EmailAddress` property in the `GetReceiverName` method. As a result, the method will no longer return a `BadRequest` if `EmailAddress` is null, which may lead to unvalidated null values being passed to the `_erService.ReadLastUsedReceiverNameByMailAsync` method.
2025-05-12 09:46:59 +02:00
Developer 02
c1bce7c639 Refactor receiver methods for async support
Updated repository and service interfaces to use async methods for retrieving receiver information. Changed method signatures to include optional parameters for `id` and `signature`, and made existing parameters nullable. Adjusted related service and controller implementations to ensure consistent usage of the new async methods. Improved error handling in the repository to enforce parameter requirements. Updated using directives in the repository for necessary dependencies.
2025-05-12 09:38:29 +02:00
Developer 02
4401a70217 Refactor EnvelopeDto and improve response handling
Updated the `EnvelopeDto` class by removing several properties to simplify the data structure and added `AddedWhen`, `ChangedWhen`, and a nullable `EnvelopeType`.

Modified the return statement in `EnvelopeController.cs` to return `NotFound()` when no envelopes are present, enhancing the accuracy of HTTP responses.
2025-05-12 09:19:12 +02:00
Developer 02
fd53f5bfd6 Enhance DTOs and controller with documentation and checks
- Added XML documentation comments to properties in `EnvelopeHistoryDto.cs`.
- Changed `EnvelopeId` to non-nullable in `ReadHistoryQuery.cs` and added `Status` parameter for filtering.
- Removed unused `using` directives in `HistoryController.cs` and updated `GetAllAsync` to filter histories by `Status`, returning `NotFound` if no results are found.
2025-05-11 20:54:04 +02:00
Developer 02
6126fce24d Refactor MemoryCacheExtensions and HistoryController
Updated MemoryCacheExtensions to accept more flexible parameters in GetEnumAsDictionary and improved the merging logic for ignored values. Simplified HistoryController by removing the logger dependency and adjusted GetReferenceTypes to include a key parameter for cache retrieval.
2025-05-11 15:19:37 +02:00
Developer 02
ce41090979 Enhance MemoryCacheExtensions and update HistoryController
- Modified `GetEnumAsDictionary` to accept a key and ignores for better caching and filtering of enum values.
- Updated XML documentation in `HistoryController.cs` to correct status code descriptions.
- Adjusted `GetEnvelopeStatus` to use the new parameters for improved control over cached enum values.
2025-05-11 15:01:12 +02:00
Developer 02
3fa113003c Update ReferenceType enum and clean up status handling
Modified the `ReferenceType` enum in `Constants.vb` to change the values for `Sender` and `Receiver`. Removed the `ReferenceType` and `StatusName` properties from `EnvelopeHistory.cs`. Updated status code comments in `HistoryController.cs`, adding new codes for `EnvelopeRejected` and `EnvelopeWithdrawn`, and adjusted parameter descriptions to align with the new enum values.
2025-05-11 14:27:28 +02:00
Developer 02
5504093591 Update access code for DocumentForwarded constant
Changed the value of the constant `DocumentForwarded` from `4001` to `2006` to align it with the other access codes, which are now consistently in the range of `2001` to `2009`.
2025-05-11 13:15:25 +02:00
Developer 02
040cf8641d Configure logging for non-development environments
Conditionally clear logging providers and set up NLog only when the application is not in the development environment. This change improves logging management based on the environment, enhancing production logging behavior.
2025-05-11 11:13:30 +02:00
Developer 02
09df86000b Bump version to 3.1.4 in project file
Updated package, assembly, and file versions from 3.1.3 to 3.1.4 to reflect the new release.
2025-05-10 03:49:46 +02:00
Developer 02
406ddfb226 Update package references and improve code readability
- Added `Microsoft.Data.SqlClient` v5.1.1 to `EnvelopeGenerator.Application.csproj` for enhanced database compatibility.
- Refactored `ReadByUuidSignatureAsync` in `EnvelopeReceiverRepository` to improve query construction and execution clarity.
- Simplified error handling in `HomeController` by replacing `ThenAsync` with a more direct approach for processing results.
- Removed `System.Data.SqlClient` v4.8.5 and added `System.Diagnostics.PerformanceCounter` v7.0.0 in `EnvelopeGenerator.Web.csproj` to focus on performance monitoring.
2025-05-10 03:47:55 +02:00
Developer 02
b01f13fb5f Add CommandDotNet and update logging configuration
- Included `CommandDotNet.Execution` namespace in `Program.cs`.
- Adjusted logging setup to clear providers and use NLog only in non-development environments.
- Added `AddHttpContextAccessor` to the service collection.
- Introduced a new `ConnectionStrings` section in `appsettings.Development.json` with a database connection string.
2025-05-10 02:39:32 +02:00
Developer 02
00fedc7c4c Enhance logging and refactor configuration in API
Updated `EnvelopeGenerator.GeneratorAPI.csproj` to include NLog packages for improved logging.
2025-05-10 01:25:32 +02:00
Developer 02
171de98552 Update version and enhance logging configuration
- Bump `EnvelopeGenerator.Web` version to `3.1.3`.
- Improve logging setup in `Program.cs` with NLog and Trace level.
- Modify `ServiceContainer` to accept `MSSQLServer` parameter.
- Update `ModelContainer` to utilize new database service.
- Add `warningLogs` section in `appsettings.json` for logging.
- Streamline logging rules by consolidating level properties.
2025-05-09 23:27:10 +02:00
27 changed files with 483 additions and 314 deletions

View File

@@ -21,5 +21,5 @@ public interface IEnvelopeReceiverRepository : ICRUDRepository<EnvelopeReceiver,
Task<IEnumerable<EnvelopeReceiver>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, params int[] ignore_statuses);
Task<EnvelopeReceiver?> ReadLastByReceiver(string email);
Task<EnvelopeReceiver?> ReadLastByReceiverAsync(string? email = null, int? id = null, string? signature = null);
}

View File

@@ -35,7 +35,7 @@ public interface IEnvelopeReceiverService : IBasicCRUDService<EnvelopeReceiverDt
Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, EnvelopeQuery? envelopeQuery = null, ReadReceiverQuery? receiverQuery = null, params int[] ignore_statuses);
Task<DataResult<string?>> ReadLastUsedReceiverNameByMail(string mail);
Task<DataResult<string?>> ReadLastUsedReceiverNameByMailAsync(string? mail = null, int? id = null, string? signature = null);
Task<DataResult<SmsResponse>> SendSmsAsync(string envelopeReceiverId, string message);
Task<DataResult<IEnumerable<EnvelopeReceiverSecretDto>>> ReadWithSecretByUuidAsync(string uuid);

View File

@@ -21,9 +21,9 @@ namespace EnvelopeGenerator.Application.DTOs
[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]")]
@@ -33,40 +33,22 @@ namespace EnvelopeGenerator.Application.DTOs
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 TFAEnabled { get; init; }
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 byte[]? DocResult { get; init; }
public IEnumerable<EnvelopeDocumentDto>? Documents { get; set; }

View File

@@ -5,19 +5,32 @@ using EnvelopeGenerator.Application.DTOs.Receiver;
using Microsoft.AspNetCore.Mvc;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Application.DTOs.EnvelopeHistory
{
[ApiExplorerSettings(IgnoreApi = true)]
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>;
}
namespace EnvelopeGenerator.Application.DTOs.EnvelopeHistory;
/// <summary>
///
/// </summary>
/// <param name="Id"></param>
/// <param name="EnvelopeId"></param>
/// <param name="UserReference"></param>
/// <param name="Status"></param>
/// <param name="StatusName"></param>
/// <param name="AddedWhen"></param>
/// <param name="ActionDate"></param>
/// <param name="Sender"></param>
/// <param name="Receiver"></param>
/// <param name="ReferenceType"></param>
/// <param name="Comment"></param>
[ApiExplorerSettings(IgnoreApi = true)]
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

@@ -21,6 +21,7 @@
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="3.0.0" />
<PackageReference Include="MediatR" Version="12.5.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.18" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.1.1" />
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.0.2" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.4" />
<PackageReference Include="Otp.NET" Version="1.4.0" />
@@ -30,6 +31,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\EnvelopeGenerator.Common\EnvelopeGenerator.Common.vbproj" />
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
<ProjectReference Include="..\EnvelopeGenerator.Extensions\EnvelopeGenerator.Extensions.csproj" />
</ItemGroup>

View File

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

View File

@@ -1,6 +1,6 @@
using EnvelopeGenerator.Application.Envelopes.Queries.Read;
using EnvelopeGenerator.Application.Receivers.Queries.Read;
using EnvelopeGenerator.Common;
using EnvelopeGenerator.Common;
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace EnvelopeGenerator.Application.Histories.Queries.Read;
@@ -9,9 +9,12 @@ namespace EnvelopeGenerator.Application.Histories.Queries.Read;
/// Repräsentiert eine Abfrage für die Verlaufshistorie eines Umschlags.
/// </summary>
/// <param name="EnvelopeId">Die eindeutige Kennung des Umschlags.</param>
/// <param name="Related">Abfrage, die angibt, worauf sich der Datensatz bezieht. Ob er sich auf den Empfänger, den Sender oder das System bezieht, wird durch 0, 1 bzw. 2 dargestellt.</param>
/// <param name="Status">Der Status 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 Status oder der gesamte Datensatz zurückgegeben wird.</param>
public record ReadHistoryQuery(
int? EnvelopeId = null,
Constants.ReferenceType? Related = null,
bool? OnlyLast = true);
[Required]
int EnvelopeId,
Constants.EnvelopeStatus? Status = null,
bool? OnlyLast = true) : IRequest<IEnumerable<ReadHistoryResponse>>
{
};

View File

@@ -0,0 +1,44 @@
using AutoMapper;
using EnvelopeGenerator.Application.Contracts.Repositories;
using EnvelopeGenerator.Application.Exceptions;
using MediatR;
namespace EnvelopeGenerator.Application.Histories.Queries.Read;
/// <summary>
///
/// </summary>
public class ReadHistoryQueryHandler : IRequestHandler<ReadHistoryQuery, IEnumerable<ReadHistoryResponse>>
{
private readonly IEnvelopeHistoryRepository _repository;
private readonly IMapper _mapper;
/// <summary>
///
/// </summary>
/// <param name="repository"></param>
/// <param name="mapper"></param>
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 : (int) request.Status);
if (!hists.Any())
throw new NotFoundException();
return _mapper.Map<IEnumerable<ReadHistoryResponse>>(hists);
}
}

View File

@@ -0,0 +1,60 @@
using EnvelopeGenerator.Common;
namespace EnvelopeGenerator.Application.Histories.Queries.Read;
/// <summary>
/// Represents the history of an envelope, including its status, user actions, and references.
/// </summary>
public class ReadHistoryResponse
{
/// <summary>
/// Gets or sets the unique identifier of the envelope history record.
/// </summary>
public long Id { get; set; }
/// <summary>
/// Gets or sets the identifier of the associated envelope.
/// </summary>
public int EnvelopeId { get; set; }
/// <summary>
/// Gets or sets the reference identifier of the user who performed the action.
/// </summary>
public string UserReference { get; set; }
/// <summary>
/// Gets or sets the status code of the envelope.
/// </summary>
public int Status { get; set; }
/// <summary>
///
/// </summary>
public Common.Constants.ReferenceType ReferenceType => Status.ToString().FirstOrDefault() switch
{
'1' => Constants.ReferenceType.Sender,
'2' => Constants.ReferenceType.Receiver,
_ => Constants.ReferenceType.System,
};
/// <summary>
/// Gets or sets the date and time when the record was added.
/// </summary>
public DateTime AddedWhen { get; set; }
/// <summary>
/// Gets or sets the date and time when the action occurred.
/// </summary>
public DateTime? ActionDate { get; set; }
/// <summary>
/// Gets or sets the optional comment about the envelope history record.
/// </summary>
public string? Comment { get; set; }
/// <inheritdoc/>
public override int GetHashCode()
{
return Id.GetHashCode();
}
}

View File

@@ -1,5 +1,6 @@
using Dapper;
using EnvelopeGenerator.Application.Contracts.SQLExecutor;
using EnvelopeGenerator.Application.Exceptions;
using EnvelopeGenerator.Domain.Entities;
namespace EnvelopeGenerator.Application.SQL;
@@ -34,9 +35,16 @@ public class DocumentCreateReadSQL : ISQL<EnvelopeDocument>
/// <returns></returns>
public static DynamicParameters CreateParmas(string base64)
{
var parameters = new DynamicParameters();
byte[] byteData = Convert.FromBase64String(base64);
parameters.Add("ByteData", byteData, System.Data.DbType.Binary);
return parameters;
try
{
var parameters = new DynamicParameters();
byte[] byteData = Convert.FromBase64String(base64);
parameters.Add("ByteData", byteData, System.Data.DbType.Binary);
return parameters;
}
catch(FormatException ex)
{
throw new BadRequestException(ex.Message.Replace("input", "dataAsBase64"));
}
}
}

View File

@@ -57,7 +57,7 @@ namespace EnvelopeGenerator.Application.Services
{
if (readOnlyDto?.Envelope is not null && readOnlyDto.Receiver is not null)
{
_placeholders["[NAME_RECEIVER]"] = await _envRcvService.ReadLastUsedReceiverNameByMail(readOnlyDto.AddedWho).ThenAsync(res => res, (msg, ntc) => string.Empty) ?? string.Empty;
_placeholders["[NAME_RECEIVER]"] = await _envRcvService.ReadLastUsedReceiverNameByMailAsync(readOnlyDto.AddedWho).ThenAsync(res => res, (msg, ntc) => string.Empty) ?? string.Empty;
var erReadOnlyId = (readOnlyDto.Id).EncodeEnvelopeReceiverId();
var sigHost = await _configService.ReadDefaultSignatureHost();
var linkToDoc = $"{sigHost}/EnvelopeKey/{erReadOnlyId}";

View File

@@ -162,9 +162,9 @@ public class EnvelopeReceiverService : BasicCRUDService<IEnvelopeReceiverReposit
return Result.Success(dto_list);
}
public async Task<DataResult<string?>> ReadLastUsedReceiverNameByMail(string mail)
public async Task<DataResult<string?>> ReadLastUsedReceiverNameByMailAsync(string? mail = null, int? id = null, string? signature = null)
{
var er = await _repository.ReadLastByReceiver(mail);
var er = await _repository.ReadLastByReceiverAsync(mail, id, signature);
return er is null ? Result.Fail<string?>().Notice(LogLevel.None, Flag.NotFound) : Result.Success(er.Name);
}

View File

@@ -20,7 +20,7 @@
AccessCodeIncorrect = 2003
DocumentOpened = 2004
DocumentSigned = 2005
DocumentForwarded = 4001
DocumentForwarded = 2006
DocumentRejected = 2007
EnvelopeShared = 2008
EnvelopeViewed = 2009
@@ -48,8 +48,8 @@
'TODO: standardize in xwiki
Public Enum ReferenceType
Receiver = 0
Sender
Sender = 1
Receiver
System
Unknown
End Enum

View File

@@ -2,7 +2,6 @@
using DigitalData.UserManager.Domain.Entities;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.Domain.Entities
{
@@ -42,19 +41,5 @@ namespace EnvelopeGenerator.Domain.Entities
[ForeignKey("UserReference")]
public virtual Receiver? Receiver { get; set; }
[NotMapped]
public ReferenceType ReferenceType => (Status / 1000) switch
{
1 => ReferenceType.Sender,
2 or 3 => ReferenceType.Receiver,
_ => ReferenceType.Unknown,
};
[NotMapped]
public string? StatusName
=> (Enum.IsDefined(typeof(EnvelopeStatus), Status))
? Enum.GetName(typeof(EnvelopeStatus), Status)
: null;
}
}

View File

@@ -6,11 +6,24 @@ public static class MemoryCacheExtensions
{
private static readonly Guid BaseId = Guid.NewGuid();
public static IDictionary<string, int> GetEnumAsDictionary<TEnum>(this IMemoryCache memoryCache)
public static IDictionary<string, int> GetEnumAsDictionary<TEnum>(this IMemoryCache memoryCache, string key = "", params object[] ignores)
where TEnum : Enum
=> memoryCache.GetOrCreate(BaseId + typeof(TEnum).FullName, _ =>
Enum.GetValues(typeof(TEnum))
=> memoryCache.GetOrCreate(BaseId + typeof(TEnum).FullName + key, _ =>
{
var mergedIgnores = new List<TEnum>();
foreach (var ignore in ignores)
{
if (ignore is IEnumerable<TEnum> ignoreList)
mergedIgnores.AddRange(ignoreList);
else if (ignore is TEnum ignoreVal)
mergedIgnores.Add(ignoreVal);
}
return Enum.GetValues(typeof(TEnum))
.Cast<TEnum>()
.ToDictionary(e => e.ToString(), e => Convert.ToInt32(e)))
.Where(e => !mergedIgnores.Contains(e))
.ToDictionary(e => e.ToString(), e => Convert.ToInt32(e));
})
?? throw new InvalidOperationException($"Failed to cache or retrieve enum dictionary for type '{typeof(TEnum).FullName}'.");
}

View File

@@ -74,7 +74,7 @@ public class EnvelopeController : ControllerBase
if (envelope.Uuid is string uuid)
envelopes = envelopes.Where(e => e.Uuid == uuid);
return Ok(envelopes);
return envelopes.Any() ? Ok(envelopes) : NotFound();
},
Fail: IActionResult (msg, ntc) =>
{

View File

@@ -112,7 +112,7 @@ public class EnvelopeReceiverController : ControllerBase
/// <summary>
/// Ruft den Namen des zuletzt verwendeten Empfängers basierend auf der angegebenen E-Mail-Adresse ab.
/// </summary>
/// <param name="receiverName">Die Abfrage, die die E-Mail-Adresse des Empfängers enthält.</param>
/// <param name="receiver">Abfrage, bei der nur eine der Angaben ID, Signatur oder E-Mail-Adresse des Empfängers eingegeben werden muss.</param>
/// <returns>Eine HTTP-Antwort mit dem Namen des Empfängers oder einem Fehlerstatus.</returns>
/// <remarks>
/// Dieser Endpunkt ermöglicht es, den Namen des zuletzt verwendeten Empfängers basierend auf der E-Mail-Adresse abzurufen.
@@ -123,13 +123,10 @@ public class EnvelopeReceiverController : ControllerBase
/// <response code="500">Ein unerwarteter Fehler ist aufgetreten.</response>
[Authorize]
[HttpGet("salute")]
public async Task<IActionResult> GetReceiverName([FromQuery] ReadReceiverNameQuery receiverName)
public async Task<IActionResult> GetReceiverName([FromQuery] ReadReceiverNameQuery receiver)
{
if (receiverName.EmailAddress is null)
return BadRequest();
return await _erService.ReadLastUsedReceiverNameByMail(receiverName.EmailAddress).ThenAsync(
Success: res => res is null ? Ok(string.Empty) : Ok(res),
return await _erService.ReadLastUsedReceiverNameByMailAsync(receiver.EmailAddress, receiver.Id, receiver.Signature).ThenAsync(
Success: res => res is null ? NotFound() : Ok(res),
Fail: IActionResult (msg, ntc) =>
{
if (ntc.HasFlag(Flag.NotFound))

View File

@@ -1,13 +1,12 @@
using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
using EnvelopeGenerator.Application.Contracts.Services;
using EnvelopeGenerator.Application.Contracts.Services;
using EnvelopeGenerator.Application.Histories.Queries.Read;
using EnvelopeGenerator.Extensions;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using static EnvelopeGenerator.Common.Constants;
namespace EnvelopeGenerator.GeneratorAPI.Controllers;
/// <summary>
@@ -18,34 +17,35 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers;
[Authorize]
public class HistoryController : ControllerBase
{
private readonly ILogger<HistoryController> _logger;
private readonly IEnvelopeHistoryService _service;
private readonly IMemoryCache _memoryCache;
private readonly IMediator _mediator;
/// <summary>
/// Konstruktor für den HistoryController.
/// </summary>
/// <param name="logger">Der Logger, der für das Protokollieren von Informationen verwendet wird.</param>
/// <param name="service">Der Dienst, der für die Verarbeitung der Umschlaghistorie verantwortlich ist.</param>
public HistoryController(ILogger<HistoryController> logger, IEnvelopeHistoryService service, IMemoryCache memoryCache)
/// <param name="memoryCache"></param>
/// param name="mediator"
public HistoryController(IEnvelopeHistoryService service, IMemoryCache memoryCache, IMediator mediator)
{
_logger = logger;
_service = service;
_memoryCache = memoryCache;
_mediator = mediator;
}
/// <summary>
/// Gibt alle möglichen Verweise auf alle möglichen Status in einem Verlaufsdatensatz zurück. (z. B. DocumentSigned bezieht sich auf Receiver.)
/// Dies wird hinzugefügt, damit Client-Anwendungen sich selbst auf dem neuesten Stand halten können.
/// 0 - Receiver:
/// Historische Datensätze, die sich auf den Status des Absenders beziehen. Sie haben Statuscodes, die mit 1* beginnen.
/// 1 - Sender:
/// Historische Datensätze über den Status der Empfänger. Diese haben Statuscodes, die mit 2* beginnen.
/// 2 - System:
/// Historische Datensätze über den Status der Empfänger. Diese haben Statuscodes, die mit 1* beginnen.
/// 2 - Receiver:
/// Historische Datensätze, die sich auf den Status des Absenders beziehen. Sie haben Statuscodes, die mit 2* beginnen.
/// 3 - System:
/// Historische Datensätze, die sich auf den allgemeinen Zustand des Umschlags beziehen. Diese haben Statuscodes, die mit 3* beginnen.
/// 3 - Unknown:
/// 4 - Unknown:
/// Ein unbekannter Datensatz weist auf einen möglichen Mangel oder eine Unstimmigkeit im Aktualisierungsprozess der Anwendung hin.
/// </summary>
/// <returns></returns>
@@ -54,29 +54,25 @@ public class HistoryController : ControllerBase
[Authorize]
public IActionResult GetReferenceTypes(ReferenceType? referenceType = null)
{
return referenceType is null ? Ok(_memoryCache.GetEnumAsDictionary<ReferenceType>()) : Ok(referenceType.ToString());
return referenceType is null ? Ok(_memoryCache.GetEnumAsDictionary<ReferenceType>("gen.api", ReferenceType.Unknown)) : Ok(referenceType.ToString());
}
/// <summary>
/// Gibt alle möglichen Status in einem Verlaufsdatensatz zurück.
/// Dies wird hinzugefügt, damit Client-Anwendungen sich selbst auf dem neuesten Stand halten können.
/// 0: Invalid
/// 1001: EnvelopeCreated
/// 1002: EnvelopeSaved
/// 1003: EnvelopeQueued
/// 1004: EnvelopeSent (Nicht verwendet)
/// 1005: EnvelopePartlySigned
/// 1006: EnvelopeCompletelySigned
/// 1007: EnvelopeReportCreated
/// 1008: EnvelopeArchived
/// 1009: EnvelopeDeleted
/// 10007: EnvelopeRejected
/// 10009: EnvelopeWithdrawn
/// 2001: AccessCodeRequested
/// 2002: AccessCodeCorrect
/// 2003: AccessCodeIncorrect
/// 2004: DocumentOpened
/// 2005: DocumentSigned
/// 4001: DocumentForwarded
/// 2006: SignatureConfirmed
/// 2006: DocumentForwarded
/// 2007: DocumentRejected
/// 2008: EnvelopeShared
/// 2009: EnvelopeViewed
@@ -88,9 +84,9 @@ public class HistoryController : ControllerBase
/// </summary>
/// <param name="status">
/// Abfrageparameter, der angibt, auf welche Referenz sich der Status bezieht.
/// 0 - Sender: Historische Datensätze, die sich auf den Status des Absenders beziehen. Sie haben Statuscodes, die mit 1* beginnen.
/// 1 - Receiver: Historische Datensätze über den Status der Empfänger. Diese haben Statuscodes, die mit 2* beginnen.
/// 2 - System: Diese werden durch Datenbank-Trigger aktualisiert und sind in den Tabellen EnvelopeHistory und EmailOut zu finden.Sie arbeiten
/// 1 - Sender: Historische Datensätze, die sich auf den Status des Absenders beziehen. Sie haben Statuscodes, die mit 1* beginnen.
/// 2 - Receiver: Historische Datensätze über den Status der Empfänger. Diese haben Statuscodes, die mit 2* beginnen.
/// 3 - System: Diese werden durch Datenbank-Trigger aktualisiert und sind in den Tabellen EnvelopeHistory und EmailOut zu finden.Sie arbeiten
/// integriert mit der Anwendung EmailProfiler, um E-Mails zu versenden und haben die Codes, die mit 3* beginnen.
/// </param>
/// <returns>Gibt die HTTP-Antwort zurück.</returns>
@@ -99,13 +95,13 @@ public class HistoryController : ControllerBase
[Authorize]
public IActionResult GetEnvelopeStatus([FromQuery] EnvelopeStatus? status = null)
{
return status is null ? Ok(_memoryCache.GetEnumAsDictionary<EnvelopeStatus>()) : Ok(status.ToString());
return status is null ? Ok(_memoryCache.GetEnumAsDictionary<EnvelopeStatus>("gen.api", Status.NonHist, Status.RelatedToFormApp)) : Ok(status.ToString());
}
/// <summary>
/// Ruft die gesamte Umschlaghistorie basierend auf den angegebenen Abfrageparametern ab.
/// </summary>
/// <param name="history">Die Abfrageparameter, die die Filterkriterien für die Umschlaghistorie definieren.</param>
/// <param name="historyQuery">Die Abfrageparameter, die die Filterkriterien für die Umschlaghistorie definieren.</param>
/// <returns>Eine Liste von Historieneinträgen, die den angegebenen Kriterien entsprechen, oder nur der letzte Eintrag.</returns>
/// <response code="200">Die Anfrage war erfolgreich, und die Umschlaghistorie wird zurückgegeben.</response>
/// <response code="400">Die Anfrage war ungültig oder unvollständig.</response>
@@ -114,29 +110,10 @@ public class HistoryController : ControllerBase
/// <response code="500">Ein unerwarteter Fehler ist aufgetreten.</response>
[HttpGet]
[Authorize]
public async Task<IActionResult> GetAllAsync([FromQuery] ReadHistoryQuery history)
public async Task<IActionResult> GetAllAsync([FromQuery] ReadHistoryQuery historyQuery)
{
var history = await _mediator.Send(historyQuery);
bool withReceiver = false;
bool withSender = false;
switch (history.Related)
{
case ReferenceType.Receiver:
withReceiver = true;
break;
case ReferenceType.Sender:
withSender = true;
break;
}
var histories = await _service.ReadAsync(
envelopeId: history.EnvelopeId,
referenceType: history.Related,
withSender: withSender,
withReceiver: withReceiver);
return Ok(histories);
return Ok((historyQuery.OnlyLast ?? false) ? history.MaxBy(h => h.AddedWhen) : history);
}
}

View File

@@ -10,9 +10,9 @@
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>EnvelopeGenerator.GeneratorAPI</Product>
<Version>1.2.2</Version>
<FileVersion>1.2.2</FileVersion>
<AssemblyVersion>1.2.2</AssemblyVersion>
<Version>1.2.3</Version>
<FileVersion>1.2.3</FileVersion>
<AssemblyVersion>1.2.3</AssemblyVersion>
<PackageOutputPath>Copyright © 2025 Digital Data GmbH. All rights reserved.</PackageOutputPath>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
@@ -23,6 +23,8 @@
<PackageReference Include="DigitalData.Core.API" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.4" />
<PackageReference Include="NLog" Version="5.2.5" />
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
<PackageReference Include="Scalar.AspNetCore" Version="2.2.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.1" />
<PackageReference Include="DigitalData.Core.Abstractions" Version="3.6.0" />

View File

@@ -16,19 +16,34 @@ using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using DigitalData.Core.Abstractions.Security.Extensions;
using EnvelopeGenerator.GeneratorAPI.Middleware;
using NLog.Web;
using NLog;
var builder = WebApplication.CreateBuilder(args);
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
logger.Info("Logging initialized!");
var config = builder.Configuration;
try
{
var builder = WebApplication.CreateBuilder(args);
var deferredProvider = new DeferredServiceProvider();
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
builder.Services.AddControllers();
if (!builder.Environment.IsDevelopment())
{
builder.Logging.ClearProviders();
builder.Host.UseNLog();
}
//CORS Policy
var allowedOrigins = config.GetSection("AllowedOrigins").Get<string[]>() ??
throw new InvalidOperationException("AllowedOrigins section is missing in the configuration.");
builder.Services.AddCors(options =>
var config = builder.Configuration;
var deferredProvider = new DeferredServiceProvider();
builder.Services.AddControllers();
//CORS Policy
var allowedOrigins = config.GetSection("AllowedOrigins").Get<string[]>() ??
throw new InvalidOperationException("AllowedOrigins section is missing in the configuration.");
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOriginsPolicy", builder =>
{
@@ -40,34 +55,34 @@ builder.Services.AddCors(options =>
});
});
// Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
// Swagger
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
Version = "v1",
Title = "signFLOW Absender-API",
Description = "Eine API zur Verwaltung der Erstellung, des Versands und der Nachverfolgung von Umschlägen in der signFLOW-Anwendung.",
Contact = new OpenApiContact
options.SwaggerDoc("v1", new OpenApiInfo
{
Name = "Digital Data GmbH",
Url = new Uri("https://digitaldata.works/digitale-signatur#kontakt"),
Email = "info-flow@digitaldata.works"
},
});
Version = "v1",
Title = "signFLOW Absender-API",
Description = "Eine API zur Verwaltung der Erstellung, des Versands und der Nachverfolgung von Umschlägen in der signFLOW-Anwendung.",
Contact = new OpenApiContact
{
Name = "Digital Data GmbH",
Url = new Uri("https://digitaldata.works/digitale-signatur#kontakt"),
Email = "info-flow@digitaldata.works"
},
});
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT-Autorisierungs-Header unter Verwendung des Bearer-Schemas.",
});
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Name = "Authorization",
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT",
In = ParameterLocation.Header,
Description = "JWT-Autorisierungs-Header unter Verwendung des Bearer-Schemas.",
});
options.AddSecurityRequirement(new OpenApiSecurityRequirement
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
@@ -82,130 +97,136 @@ builder.Services.AddSwaggerGen(options =>
}
});
var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml");
foreach (var xmlFile in xmlFiles)
{
options.IncludeXmlComments(xmlFile);
}
});
builder.Services.AddOpenApi();
// DbContext
var connStr = config.GetConnectionString("Default") ?? throw new InvalidOperationException("There is no default connection string in appsettings.json.");
builder.Services.Configure<ConnectionString>(cs => cs.Value = connStr);
builder.Services.AddDbContext<EGDbContext>(options => options.UseSqlServer(connStr));
builder.Services.AddAuthHubClient(config.GetSection("AuthClientParams"));
var authTokenKeys = config.GetOrDefault<AuthTokenKeys>();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
var xmlFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.xml");
foreach (var xmlFile in xmlFiles)
{
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
{
var clientParams = deferredProvider.GetOptions<ClientParams>();
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
return new List<SecurityKey>() { publicKey.SecurityKey };
},
ValidateIssuer = true,
ValidIssuer = authTokenKeys.Issuer,
ValidateAudience = true,
ValidAudience = authTokenKeys.Audience,
};
options.IncludeXmlComments(xmlFile);
}
});
builder.Services.AddOpenApi();
// DbContext
var connStr = config.GetConnectionString("Default") ?? throw new InvalidOperationException("There is no default connection string in appsettings.json.");
opt.Events = new JwtBearerEvents
builder.Services.Configure<ConnectionString>(cs => cs.Value = connStr);
builder.Services.AddDbContext<EGDbContext>(options => options.UseSqlServer(connStr));
builder.Services.AddAuthHubClient(config.GetSection("AuthClientParams"));
var authTokenKeys = config.GetOrDefault<AuthTokenKeys>();
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(opt =>
{
OnMessageReceived = context =>
opt.TokenValidationParameters = new TokenValidationParameters
{
// if there is no token read related cookie or query string
if (context.Token is null) // if there is no token
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
{
if (context.Request.Cookies.TryGetValue(authTokenKeys.Cookie, out var cookieToken) && cookieToken is not null)
context.Token = cookieToken;
else if (context.Request.Query.TryGetValue(authTokenKeys.QueryString, out var queryStrToken))
context.Token = queryStrToken;
var clientParams = deferredProvider.GetOptions<ClientParams>();
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
return new List<SecurityKey>() { publicKey.SecurityKey };
},
ValidateIssuer = true,
ValidIssuer = authTokenKeys.Issuer,
ValidateAudience = true,
ValidAudience = authTokenKeys.Audience,
};
opt.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
// if there is no token read related cookie or query string
if (context.Token is null) // if there is no token
{
if (context.Request.Cookies.TryGetValue(authTokenKeys.Cookie, out var cookieToken) && cookieToken is not null)
context.Token = cookieToken;
else if (context.Request.Query.TryGetValue(authTokenKeys.QueryString, out var queryStrToken))
context.Token = queryStrToken;
}
return Task.CompletedTask;
}
return Task.CompletedTask;
}
};
};
});
// Authentication
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
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.LoginPath = "/api/auth/login";
options.LogoutPath = "/api/auth/logout";
options.SlidingExpiration = true;
});
// Authentication
builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
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.LoginPath = "/api/auth/login";
options.LogoutPath = "/api/auth/logout";
options.SlidingExpiration = true;
});
// User manager
builder.Services.AddUserManager<EGDbContext>();
// User manager
builder.Services.AddUserManager<EGDbContext>();
// LDAP
builder.ConfigureBySection<DirectorySearchOptions>();
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
// LDAP
builder.ConfigureBySection<DirectorySearchOptions>();
builder.Services.AddDirectorySearchService(config.GetSection("DirectorySearchOptions"));
// Localizer
builder.Services.AddCookieBasedLocalizer();
// Localizer
builder.Services.AddCookieBasedLocalizer() ;
// Envelope generator serives
builder.Services
.AddEnvelopeGeneratorInfrastructureServices(sqlExecutorConfigureOptions: executor => executor.ConnectionString = connStr)
.AddEnvelopeGeneratorServices(config);
// Envelope generator serives
builder.Services
.AddEnvelopeGeneratorInfrastructureServices(sqlExecutorConfigureOptions: executor => executor.ConnectionString = connStr)
.AddEnvelopeGeneratorServices(config);
var app = builder.Build();
var app = builder.Build();
deferredProvider.Factory = () => app.Services;
deferredProvider.Factory = () => app.Services;
app.UseMiddleware<ExceptionHandlingMiddleware>();
app.UseMiddleware<ExceptionHandlingMiddleware>();
app.MapOpenApi();
app.MapOpenApi();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment() || (app.IsDevOrDiP() && config.GetValue<bool>("UseSwagger")))
{
app.UseSwagger();
app.UseSwaggerUI();
app.MapScalarApiReference();
}
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment() || (app.IsDevOrDiP() && config.GetValue<bool>("UseSwagger")))
{
app.UseSwagger();
app.UseSwaggerUI();
app.MapScalarApiReference();
// Set CORS policy
app.UseCors("AllowSpecificOriginsPolicy");
// Localizer
string[] supportedCultureNames = { "de-DE", "en-US" };
IList<CultureInfo> list = supportedCultureNames.Select((string cn) => new CultureInfo(cn)).ToList();
CultureInfo cultureInfo = list.FirstOrDefault() ?? throw new ArgumentNullException("supportedCultureNames", "Supported cultures cannot be empty.");
RequestLocalizationOptions requestLocalizationOptions = new RequestLocalizationOptions
{
SupportedCultures = list,
SupportedUICultures = list
};
requestLocalizationOptions.RequestCultureProviders.Add(new QueryStringRequestCultureProvider());
app.UseRequestLocalization(requestLocalizationOptions);
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
}
// Set CORS policy
app.UseCors("AllowSpecificOriginsPolicy");
// Localizer
string[] supportedCultureNames = { "de-DE", "en-US" };
IList<CultureInfo> list = supportedCultureNames.Select((string cn) => new CultureInfo(cn)).ToList();
CultureInfo cultureInfo = list.FirstOrDefault() ?? throw new ArgumentNullException("supportedCultureNames", "Supported cultures cannot be empty.");
RequestLocalizationOptions requestLocalizationOptions = new RequestLocalizationOptions
catch (Exception ex)
{
SupportedCultures = list,
SupportedUICultures = list
};
requestLocalizationOptions.RequestCultureProviders.Add(new QueryStringRequestCultureProvider());
app.UseRequestLocalization(requestLocalizationOptions);
app.UseHttpsRedirection();
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
logger.Error(ex, "Stopped program because of exception");
throw;
}

View File

@@ -2,6 +2,9 @@
using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Application.Contracts.Repositories;
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Http.HttpResults;
using EnvelopeGenerator.Application.Exceptions;
using EnvelopeGenerator.Common.My.Resources;
namespace EnvelopeGenerator.Infrastructure.Repositories;
@@ -39,7 +42,11 @@ public class EnvelopeReceiverRepository : CRUDRepository<EnvelopeReceiver, (int
=> await ReadWhere(signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver, readOnly: readOnly).ToListAsync();
public async Task<EnvelopeReceiver?> ReadByUuidSignatureAsync(string uuid, string signature, bool withEnvelope = true, bool withReceiver = true, bool readOnly = true)
=> await ReadWhere(uuid: uuid, signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver, readOnly: readOnly).FirstOrDefaultAsync();
{
var query = ReadWhere(uuid: uuid, signature: signature, withEnvelope: withEnvelope, withReceiver: withReceiver, readOnly: readOnly);
return await query.FirstOrDefaultAsync();
}
public async Task<string?> ReadAccessCodeAsync(string uuid, string signature, bool readOnly = true)
=> await ReadWhere(uuid: uuid, signature: signature, readOnly: readOnly)
@@ -89,8 +96,26 @@ public class EnvelopeReceiverRepository : CRUDRepository<EnvelopeReceiver, (int
return await query.Include(er => er.Envelope).Include(er => er.Receiver).ToListAsync();
}
public async Task<EnvelopeReceiver?> ReadLastByReceiver(string email)
public async Task<EnvelopeReceiver?> ReadLastByReceiverAsync(string? email = null, int? id = null, string? signature = null)
{
return await _dbSet.Where(er => er.Receiver!.EmailAddress == email).OrderBy(er => er.EnvelopeId).LastOrDefaultAsync();
var parameters = new[] { email, id?.ToString(), signature }.Count(p => p != null);
if (parameters == 0)
throw new BadRequestException("You must provide either 'email', 'id', or 'signature' for the query.");
if (parameters > 1)
throw new BadRequestException("Please provide only one parameter: either 'email', 'id', or 'signature'.");
var query = _dbSet.AsNoTracking();
if(email is not null)
query = query.Where(er => er.Receiver!.EmailAddress == email);
if (id is not null)
query = query.Where(er => er.Receiver!.Id == id);
if (signature is not null)
query = query.Where(er => er.Receiver!.Signature == signature);
return await query.OrderBy(er => er.EnvelopeId).LastOrDefaultAsync();
}
}

View File

@@ -96,31 +96,32 @@ public class HomeController : ViewControllerBase
ViewData["EnvelopeKey"] = envelopeReceiverId;
return await _envRcvService.ReadByEnvelopeReceiverIdAsync(envelopeReceiverId: envelopeReceiverId).ThenAsync<EnvelopeReceiverDto, IActionResult>(
SuccessAsync: async er =>
var er_res = await _envRcvService.ReadByEnvelopeReceiverIdAsync(envelopeReceiverId: envelopeReceiverId);
if (er_res.IsFailed)
{
_logger.LogNotice(er_res.Notices);
return this.ViewEnvelopeNotFound();
}
var er = er_res.Data;
EnvelopeResponse response = await envelopeOldService.LoadEnvelope(envelopeReceiverId);
bool accessCodeAlreadyRequested = await _historyService.AccessCodeAlreadyRequested(envelopeId: er.Envelope!.Id, userReference: er.Receiver!.EmailAddress);
if (!accessCodeAlreadyRequested)
{
await _historyService.RecordAsync(er.EnvelopeId, er.Receiver.EmailAddress, EnvelopeStatus.AccessCodeRequested);
var mailRes = await _mailService.SendAccessCodeAsync(envelopeReceiverDto: er);
if (mailRes.IsFailed)
{
EnvelopeResponse response = await envelopeOldService.LoadEnvelope(envelopeReceiverId);
_logger.LogNotice(mailRes);
return this.ViewAccessCodeNotSent();
}
}
bool accessCodeAlreadyRequested = await _historyService.AccessCodeAlreadyRequested(envelopeId: er.Envelope!.Id, userReference: er.Receiver!.EmailAddress);
if (!accessCodeAlreadyRequested)
{
await _historyService.RecordAsync(er.EnvelopeId, er.Receiver.EmailAddress, EnvelopeStatus.AccessCodeRequested);
var mailRes = await _mailService.SendAccessCodeAsync(envelopeReceiverDto: er);
if (mailRes.IsFailed)
{
_logger.LogNotice(mailRes);
return this.ViewAccessCodeNotSent();
}
}
return Redirect($"{envelopeReceiverId}/Locked");
},
Fail: (messages, notices) =>
{
_logger.LogNotice(notices);
return this.ViewEnvelopeNotFound();
});
return Redirect($"{envelopeReceiverId}/Locked");
}
catch(Exception ex)
{

View File

@@ -5,7 +5,7 @@
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<PackageId>EnvelopeGenerator.Web</PackageId>
<Version>3.1.2</Version>
<Version>3.1.4</Version>
<Authors>Digital Data GmbH</Authors>
<Company>Digital Data GmbH</Company>
<Product>EnvelopeGenerator.Web</Product>
@@ -13,8 +13,8 @@
<PackageTags>digital data envelope generator web</PackageTags>
<Description>EnvelopeGenerator.Web is an ASP.NET MVC application developed to manage signing processes. It uses Entity Framework Core (EF Core) for database operations. The user interface for signing processes is developed with Razor View Engine (.cshtml files) and JavaScript under wwwroot, integrated with PSPDFKit. This integration allows users to view and sign documents seamlessly.</Description>
<ApplicationIcon>Assets\icon.ico</ApplicationIcon>
<AssemblyVersion>3.1.2</AssemblyVersion>
<FileVersion>3.1.2</FileVersion>
<AssemblyVersion>3.1.4</AssemblyVersion>
<FileVersion>3.1.4</FileVersion>
<Copyright>Copyright © 2025 Digital Data GmbH. All rights reserved.</Copyright>
</PropertyGroup>
@@ -2126,7 +2126,7 @@
<PackageReference Include="Quartz.Serialization.Json" Version="3.8.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="System.Configuration.ConfigurationManager" Version="7.0.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.5" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="7.0.0" />
<PackageReference Include="System.DirectoryServices" Version="7.0.1" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="7.0.1" />
<PackageReference Include="System.DirectoryServices.Protocols" Version="7.0.1" />

View File

@@ -24,6 +24,14 @@ try
{
var builder = WebApplication.CreateBuilder(args);
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
if (!builder.Environment.IsDevelopment())
{
builder.Logging.ClearProviders();
builder.Host.UseNLog();
}
var config = builder.Configuration;
var allowedOrigins = config.GetSection("AllowedOrigins").Get<string[]>() ??
@@ -41,15 +49,14 @@ try
});
});
builder.Logging.ClearProviders();
builder.Host.UseNLog();
// Add base services
builder.Services.AddScoped<DatabaseService>();
// Add higher order services
builder.Services.AddScoped<EnvelopeOldService>();
builder.Services.AddHttpContextAccessor();
builder.ConfigureBySection<TFARegParams>();
// Add controllers and razor views

View File

@@ -18,9 +18,9 @@ namespace EnvelopeGenerator.Web.Services
public ActionService actionService;
public EmailService emailService;
public ServiceContainer(State state)
public ServiceContainer(State state, MSSQLServer MSSQL)
{
actionService = new(state);
actionService = new(state, MSSQL);
emailService = new(state);
}
}
@@ -82,7 +82,7 @@ namespace EnvelopeGenerator.Web.Services
State.DbConfig = configModel.LoadConfiguration();
Models = new(State);
Services = new(State);
Services = new(State, MSSQL);
}
else
{

View File

@@ -7,5 +7,8 @@
}
},
"AdminPassword": "dd",
"UseCSPInDev": false
"UseCSPInDev": false,
"ConnectionStrings": {
"Default": "Server=sDD-VMP04-SQL19\\CURSORAG;Database=DD_ECM;User Id=sa;Password=dd;Encrypt=false;TrustServerCertificate=True;"
}
}

View File

@@ -39,6 +39,11 @@
"fileName": "${logDirectory}\\${logFileNamePrefix}-Info.log",
"maxArchiveDays": 30
},
"warningLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Warning.log",
"maxArchiveDays": 30
},
"errorLogs": {
"type": "File",
"fileName": "${logDirectory}\\${logFileNamePrefix}-Error.log",
@@ -50,14 +55,17 @@
"maxArchiveDays": 30
}
},
// Trace, Debug, Info, Warn, Error and *Fatal*
"rules": [
{
"logger": "*",
"minLevel": "Info",
"maxLevel": "Warn",
"level": "Info",
"writeTo": "infoLogs"
},
{
"logger": "*",
"level": "Warn",
"writeTo": "warningLogs"
},
{
"logger": "*",
"level": "Error",