Compare commits

...

8 Commits

Author SHA1 Message Date
95c8e15887 Add EnvelopeDto and EnvelopeService for API integration
Introduced the `EnvelopeDto` class to represent envelope data with JSON property mappings. Added the `EnvelopeService` class to handle API interactions, including fetching envelopes with optional filters and query string construction using `Microsoft.AspNetCore.WebUtilities`. Updated the project file to include the required package reference for query string manipulation.
2026-06-15 15:40:59 +02:00
561b844e59 Add filtering for active and completed envelopes
Added `OnlyActive` and `OnlyCompleted` properties to the `ReadEnvelopeQuery` class to enable filtering envelopes by their active or completed status. Updated the `ReadEnvelopeQueryHandler` to apply these filters when the properties are set.

Enhanced the `EnvelopeStatus` class by introducing `Active` and `Completed` status lists and adding extension methods (`IsActive` and `IsCompleted`) to determine status categories. Included necessary `using` directives for `System` and `System.Linq`.
2026-06-15 15:07:12 +02:00
011960be75 Add EnvelopeStatus extensions and update documentation URL
The `System` namespace was added to `EnvelopeStatus.cs` to enable additional functionality. A documentation URL in the comments was updated to point to a new location, replacing the outdated link.

Introduced a new static class `EnvelopeStatusExtensions` with two extension methods for the `EnvelopeStatus` enum:
- `IsActive`: Checks if the status is active (between `EnvelopeCreated` and `EnvelopePartlySigned`).
- `IsCompleted`: Checks if the status is completed (between `EnvelopeCompletelySigned` and `EnvelopeWithdrawn`).
2026-06-15 14:55:01 +02:00
151c785af9 Enhance JSON options and authorization policies
Added JSON serialization options to ignore reference cycles in
the `AddControllers` method by configuring `ReferenceHandler`
to `IgnoreCycles`. Updated the `AddAuthorizationBuilder` to
include authentication schemes for the `SenderOrReceiver`
policy, requiring roles and schemes for enhanced security.
2026-06-12 15:21:50 +02:00
fa354a05cc Update authorization policy in ConfigController
Replaced the generic [Authorize] attribute with a more specific
[Authorize(Policy = AuthPolicy.SenderOrReceiver)] to enforce
a stricter authorization policy. Added a `using` directive for
`EnvelopeGenerator.Domain.Constants` to support the new policy.
2026-06-12 15:21:29 +02:00
1326407462 Update AuthController to use specific auth scheme
The `[Authorize]` attribute on the `Check` method was updated to specify the `AuthScheme.Sender` authentication scheme. This change ensures that the `Check` endpoint now requires authentication using this specific scheme, enhancing security and supporting multiple authentication schemes within the application.
2026-06-12 15:15:44 +02:00
a3c653ddb3 Simplify Envelope to EnvelopeDto mapping
Removed custom mapping logic for the `Receivers` property in the
`Envelope` to `EnvelopeDto` mapping within the `MappingProfile`
class. The mapping now uses default behavior without projecting
`EnvelopeReceivers` to `Receivers`.
2026-06-12 15:15:09 +02:00
8d736cdc5e Refactor EnvelopeDto property for receiver handling
Replaced the `Receivers` property with `EnvelopeReceivers` in the `EnvelopeDto` class to improve clarity and better align with the updated data model. The new property uses `IEnumerable<EnvelopeReceiverDto>?` instead of `IEnumerable<ReceiverDto>?`.
2026-06-12 15:14:51 +02:00
11 changed files with 154 additions and 10 deletions

View File

@@ -69,7 +69,7 @@ public partial class AuthController(IOptions<AuthTokenKeys> authTokenKeyOptions,
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)] [ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)] [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[HttpGet("check")] [HttpGet("check")]
[Authorize] [Authorize(AuthenticationSchemes = AuthScheme.Sender)]
public IActionResult Check(string? role = null) public IActionResult Check(string? role = null)
=> role is not null && !User.IsInRole(role) => role is not null && !User.IsInRole(role)
? Unauthorized() ? Unauthorized()

View File

@@ -1,4 +1,5 @@
using EnvelopeGenerator.API.Models.PsPdfKitAnnotation; using EnvelopeGenerator.API.Models.PsPdfKitAnnotation;
using EnvelopeGenerator.Domain.Constants;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@@ -13,7 +14,7 @@ namespace EnvelopeGenerator.API.Controllers;
/// </remarks> /// </remarks>
[Route("api/[controller]")] [Route("api/[controller]")]
[ApiController] [ApiController]
[Authorize] [Authorize(Policy = AuthPolicy.SenderOrReceiver)]
public class ConfigController(IOptionsMonitor<AnnotationParams> annotationParamsOptions) : ControllerBase public class ConfigController(IOptionsMonitor<AnnotationParams> annotationParamsOptions) : ControllerBase
{ {
private readonly AnnotationParams _annotationParams = annotationParamsOptions.CurrentValue; private readonly AnnotationParams _annotationParams = annotationParamsOptions.CurrentValue;

View File

@@ -44,7 +44,11 @@ try
var deferredProvider = new DeferredServiceProvider(); var deferredProvider = new DeferredServiceProvider();
builder.Services.AddControllers(); builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.ReferenceHandler = System.Text.Json.Serialization.ReferenceHandler.IgnoreCycles;
});
builder.Services.AddHttpClient(); builder.Services.AddHttpClient();
builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy")); builder.Services.AddReverseProxy().LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
@@ -238,8 +242,9 @@ try
}); });
builder.Services.AddAuthorizationBuilder() builder.Services.AddAuthorizationBuilder()
.AddPolicy(AuthPolicy.SenderOrReceiver, policy => policy.RequireRole(Role.Sender, Role.Receiver.Full)) .AddPolicy(AuthPolicy.SenderOrReceiver, policy => policy
.RequireRole(Role.Sender, Role.Receiver.Full)
.AddAuthenticationSchemes(AuthScheme.Sender, AuthScheme.Receiver))
.AddPolicy(AuthPolicy.Sender, policy => policy .AddPolicy(AuthPolicy.Sender, policy => policy
.RequireRole(Role.Sender) .RequireRole(Role.Sender)
.AddAuthenticationSchemes(AuthScheme.Sender)) .AddAuthenticationSchemes(AuthScheme.Sender))

View File

@@ -22,7 +22,7 @@
"https": { "https": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": false, "launchBrowser": true,
"launchUrl": "swagger", "launchUrl": "swagger",
"applicationUrl": "https://localhost:8088;http://localhost:5131", "applicationUrl": "https://localhost:8088;http://localhost:5131",
"environmentVariables": { "environmentVariables": {

View File

@@ -133,5 +133,5 @@ public record EnvelopeDto : IEnvelope
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
public IEnumerable<ReceiverDto>? Receivers { get; set; } public IEnumerable<EnvelopeReceiverDto>? EnvelopeReceivers { get; set; }
} }

View File

@@ -26,7 +26,7 @@ public class MappingProfile : Profile
CreateMap<DocReceiverElement, DocReceiverElementDto>(); CreateMap<DocReceiverElement, DocReceiverElementDto>();
CreateMap<DocumentStatus, DocumentStatusDto>(); CreateMap<DocumentStatus, DocumentStatusDto>();
CreateMap<EmailTemplate, EmailTemplateDto>(); CreateMap<EmailTemplate, EmailTemplateDto>();
CreateMap<Envelope, EnvelopeDto>().ForMember(dest => dest.Receivers, opt => opt.MapFrom(src => src.EnvelopeReceivers.Select(er => er.Receiver))); CreateMap<Envelope, EnvelopeDto>();
CreateMap<Document, DocumentDto>(); CreateMap<Document, DocumentDto>();
CreateMap<Domain.Entities.History, HistoryDto>().ForMember(dest => dest.ActionDate, opt => opt.MapFrom(src => src.ChangedWhen)); CreateMap<Domain.Entities.History, HistoryDto>().ForMember(dest => dest.ActionDate, opt => opt.MapFrom(src => src.ChangedWhen));
CreateMap<Domain.Entities.History, HistoryCreateDto>().ForMember(dest => dest.ActionDate, opt => opt.MapFrom(src => src.ChangedWhen)); CreateMap<Domain.Entities.History, HistoryCreateDto>().ForMember(dest => dest.ActionDate, opt => opt.MapFrom(src => src.ChangedWhen));

View File

@@ -14,6 +14,16 @@ namespace EnvelopeGenerator.Application.Envelopes.Queries;
/// </summary> /// </summary>
public record ReadEnvelopeQuery : EnvelopeQueryBase, IRequest<IEnumerable<EnvelopeDto>> public record ReadEnvelopeQuery : EnvelopeQueryBase, IRequest<IEnumerable<EnvelopeDto>>
{ {
/// <summary>
///
/// </summary>
public bool OnlyActive { get; init; } = false;
/// <summary>
///
/// </summary>
public bool OnlyCompleted { get; init; } = false;
/// <summary> /// <summary>
/// Abfrage des Include des Umschlags /// Abfrage des Include des Umschlags
/// </summary> /// </summary>
@@ -132,6 +142,12 @@ public class ReadEnvelopeQueryHandler : IRequestHandler<ReadEnvelopeQuery, IEnum
query = query.Where(e => !status.Ignore.Contains(e.Status)); query = query.Where(e => !status.Ignore.Contains(e.Status));
} }
if(request is { OnlyActive: true })
query = query.Where(e => Status.Active.Contains(e.Status));
if (request is { OnlyCompleted: true })
query = query.Where(e => Status.Completed.Contains(e.Status));
var envelopes = await query var envelopes = await query
.Include(e => e.EnvelopeReceivers).ThenInclude(er => er.Receiver) .Include(e => e.EnvelopeReceivers).ThenInclude(er => er.Receiver)
.ToListAsync(cancel); .ToListAsync(cancel);

View File

@@ -1,8 +1,10 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Linq;
namespace EnvelopeGenerator.Domain.Constants namespace EnvelopeGenerator.Domain.Constants
{ {
// http://wiki.dd/xwiki13/bin/view/Anwendungen/Produkt-Handbuch/Sonstiges/SignFlow/Envelope%20Status/ // http://wiki.dd/xwiki_prod/bin/view/Anwendungen/Produkt-Handbuch/Sonstiges/signFLOW/signFLOW%20-%20Enwickler-Handbuch/4.%20Anhang/4.3%20Historie%20und%20Status%20der%20Umschl%C3%A4ge/
public enum EnvelopeStatus public enum EnvelopeStatus
{ {
Invalid = 0, Invalid = 0,
@@ -49,5 +51,28 @@ namespace EnvelopeGenerator.Domain.Constants
EnvelopeStatus.EnvelopeCreated, EnvelopeStatus.EnvelopeCreated,
EnvelopeStatus.DocumentMod_Rotation EnvelopeStatus.DocumentMod_Rotation
}; };
public static readonly List<EnvelopeStatus> Active = Enum.GetValues(typeof(EnvelopeStatus))
.Cast<EnvelopeStatus>()
.Where(status => status.IsActive())
.ToList();
public static readonly List<EnvelopeStatus> Completed = Enum.GetValues(typeof(EnvelopeStatus))
.Cast<EnvelopeStatus>()
.Where(status => status.IsCompleted())
.ToList();
}
public static class EnvelopeStatusExtensions
{
public static bool IsActive(this EnvelopeStatus status)
{
return status >= EnvelopeStatus.EnvelopeCreated && status < EnvelopeStatus.EnvelopePartlySigned;
}
public static bool IsCompleted(this EnvelopeStatus status)
{
return status >= EnvelopeStatus.EnvelopeCompletelySigned && status <= EnvelopeStatus.EnvelopeWithdrawn;
}
} }
} }

View File

@@ -29,6 +29,7 @@
<PackageReference Include="DevExpress.Blazor.Reporting.Viewer" Version="25.2.3" /> <PackageReference Include="DevExpress.Blazor.Reporting.Viewer" Version="25.2.3" />
<PackageReference Include="DevExpress.Drawing.Skia" Version="25.2.3" /> <PackageReference Include="DevExpress.Drawing.Skia" Version="25.2.3" />
<PackageReference Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="8.3.1.2" /> <PackageReference Include="HarfBuzzSharp.NativeAssets.WebAssembly" Version="8.3.1.2" />
<PackageReference Include="Microsoft.AspNetCore.WebUtilities" Version="8.0.28" />
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" Version="3.119.1" /> <PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" Version="3.119.1" />
<PackageReference Include="SkiaSharp.Views.Blazor" Version="3.119.1" /> <PackageReference Include="SkiaSharp.Views.Blazor" Version="3.119.1" />
<NativeFileReference Include="$(HarfBuzzSharpStaticLibraryPath)\2.0.23\*.a" /> <NativeFileReference Include="$(HarfBuzzSharpStaticLibraryPath)\2.0.23\*.a" />

View File

@@ -0,0 +1,24 @@
using System.Text.Json.Serialization;
namespace EnvelopeGenerator.ReceiverUI.Models;
public class EnvelopeDto
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("uuid")]
public string? Uuid { get; set; }
[JsonPropertyName("title")]
public string? Title { get; set; }
[JsonPropertyName("status")]
public int Status { get; set; }
[JsonPropertyName("docResult")]
public byte[]? DocResult { get; set; }
[JsonPropertyName("envelopeReceivers")]
public List<EnvelopeReceiverDto> EnvelopeReceivers { get; set; } = new();
}

View File

@@ -0,0 +1,72 @@
using System.Net.Http.Json;
using System.Text.Json;
using EnvelopeGenerator.ReceiverUI.Models;
using EnvelopeGenerator.ReceiverUI.Options;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Extensions.Options;
namespace EnvelopeGenerator.ReceiverUI.Services;
/// <summary>
/// Retrieves <see cref="EnvelopeDto"/>s from the API.
/// </summary>
public class EnvelopeService
{
private readonly HttpClient _http;
private readonly ApiOptions _apiOptions;
private static readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web);
public EnvelopeService(HttpClient http, IOptions<ApiOptions> apiOptions)
{
_http = http;
_apiOptions = apiOptions.Value;
}
/// <summary>
/// Fetches envelopes from the API with optional filters.
/// </summary>
/// <exception cref="HttpRequestException">Thrown when the API request fails.</exception>
public async Task<IEnumerable<EnvelopeDto>?> GetAsync(
int? id = null,
string? uuid = null,
bool? onlyActive = null,
bool? onlyCompleted = null,
CancellationToken cancel = default)
{
var baseUrl = $"{_apiOptions.BaseUrl}/api/Envelope";
var queryParams = new Dictionary<string, string?>();
if (id.HasValue)
{
queryParams["Id"] = id.Value.ToString();
}
if (!string.IsNullOrEmpty(uuid))
{
queryParams["Uuid"] = uuid;
}
if (onlyActive.HasValue)
{
queryParams["OnlyActive"] = onlyActive.Value.ToString();
}
if (onlyCompleted.HasValue)
{
queryParams["OnlyCompleted"] = onlyCompleted.Value.ToString();
}
var url = QueryHelpers.AddQueryString(baseUrl, queryParams);
var response = await _http.GetAsync(url, cancel);
if (!response.IsSuccessStatusCode)
{
var statusCode = (int)response.StatusCode;
var reasonPhrase = response.ReasonPhrase ?? "Unknown error";
throw new HttpRequestException(
$"Failed to load envelopes. Status: {statusCode} ({reasonPhrase})",
null,
response.StatusCode);
}
return await response.Content.ReadFromJsonAsync<IEnumerable<EnvelopeDto>>(_jsonOptions, cancel);
}
}