Compare commits
45 Commits
feat/blazo
...
b708343db0
| Author | SHA1 | Date | |
|---|---|---|---|
| b708343db0 | |||
| b416823f38 | |||
| 5bdc552492 | |||
| cdf34a262b | |||
| 4c84b28034 | |||
| b70f902190 | |||
| 094c87eb88 | |||
| 9b2539e378 | |||
| 0b73a90b15 | |||
| 76cfe4dc46 | |||
| c1a10cc0fa | |||
| b6b5ca52f2 | |||
| 5279731281 | |||
| 27ed3689f2 | |||
| d4f23e0e82 | |||
| 618f899440 | |||
| 2eb258d236 | |||
| 28df3f4ec1 | |||
| 3e37dc1eff | |||
| 5fd8637913 | |||
| 31db160fba | |||
| b6e63841cd | |||
| f051896296 | |||
| 92b93e862e | |||
| 8876f5c286 | |||
| e93c7e8bc1 | |||
| 16493b4594 | |||
| 938504b2d1 | |||
| 3eb718f6ac | |||
| 99781aeb8a | |||
| ffcd41f4dc | |||
| a7ed9be1de | |||
| 32fbf782fa | |||
| bfae72529c | |||
| 67e6f288eb | |||
| 823bafeeb9 | |||
| 750b9f1b57 | |||
| 0a4daccc0f | |||
| bc4905d2f4 | |||
| 7c737ee6ad | |||
| 8a796a2eec | |||
| 83957d28e9 | |||
| fe3f1347d5 | |||
| 1e35e0447f | |||
| 7828ed237d |
@@ -1,3 +1,4 @@
|
||||
using DigitalData.Auth.Claims;
|
||||
using EnvelopeGenerator.API.Controllers.Interfaces;
|
||||
using EnvelopeGenerator.API.Models;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
@@ -73,4 +74,44 @@ public partial class AuthController(IOptions<AuthTokenKeys> authTokenKeyOptions,
|
||||
=> role is not null && !User.IsInRole(role)
|
||||
? Unauthorized()
|
||||
: Ok();
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the caller holds a valid per-envelope receiver token for the given envelope key.
|
||||
/// The request must carry a cookie named <c>AuthTokenSignFLOWReceiver.{envelopeKey}</c>.
|
||||
/// </summary>
|
||||
/// <param name="envelopeKey">The unique envelope key extracted from the route.</param>
|
||||
/// <response code="200">Valid per-envelope token found.</response>
|
||||
/// <response code="401">Token is missing, expired or invalid.</response>
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
|
||||
[Authorize(Policy = AuthPolicy.Receiver)]
|
||||
[HttpGet("check/envelope/{envelopeKey}")]
|
||||
public IActionResult CheckEnvelopeReceiver([FromRoute] string envelopeKey) => Ok();
|
||||
|
||||
/// <summary>
|
||||
/// Removes the per-envelope receiver cookie for the given envelope key.
|
||||
/// </summary>
|
||||
/// <param name="envelopeKey">The unique envelope key whose cookie should be deleted.</param>
|
||||
/// <response code="200">Cookie successfully deleted.</response>
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
|
||||
[HttpPost("logout/envelope/{envelopeKey}")]
|
||||
public IActionResult LogoutEnvelopeReceiver([FromRoute] string envelopeKey)
|
||||
{
|
||||
var cookieName = CookieNames.GetEnvelopeReceiverCookieName(authTokenKeys.Cookie, envelopeKey);
|
||||
Response.Cookies.Delete(cookieName);
|
||||
return Ok();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all per-envelope receiver cookies from the current request.
|
||||
/// </summary>
|
||||
/// <response code="200">All envelope receiver cookies successfully deleted.</response>
|
||||
[ProducesResponseType(typeof(void), StatusCodes.Status200OK)]
|
||||
[HttpPost("logout/envelope")]
|
||||
public IActionResult LogoutAllEnvelopeReceivers()
|
||||
{
|
||||
foreach (var cookieName in Request.Cookies.Keys.Where(k => CookieNames.IsEnvelopeReceiverCookie(k, authTokenKeys.Cookie)))
|
||||
Response.Cookies.Delete(cookieName);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using DigitalData.Auth.Claims;
|
||||
using EnvelopeGenerator.API.Controllers.Interfaces;
|
||||
using EnvelopeGenerator.API.Extensions;
|
||||
using EnvelopeGenerator.Application.Documents.Queries;
|
||||
@@ -14,10 +15,9 @@ namespace EnvelopeGenerator.API.Controllers;
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="DocumentController"/> class.
|
||||
/// </remarks>
|
||||
[Authorize]
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class DocumentController(IMediator mediator, IAuthorizationService authService) : ControllerBase, IAuthController
|
||||
public class DocumentController(IMediator mediator, IAuthorizationService authService, ILogger<DocumentController> logger) : ControllerBase, IAuthController
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
@@ -60,4 +60,36 @@ public class DocumentController(IMediator mediator, IAuthorizationService authSe
|
||||
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="envelopeKey"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
[HttpGet("{envelopeKey}")]
|
||||
public async Task<IActionResult> GetDocumentOfReceiver(string envelopeKey, CancellationToken cancel)
|
||||
{
|
||||
var envelopeId = 2071;
|
||||
|
||||
//var envelopeIdStr = User.FindFirst(EnvelopeClaimNames.EnvelopeId)?.Value;
|
||||
|
||||
//if (!int.TryParse(envelopeIdStr, out int envelopeId))
|
||||
//{
|
||||
// logger.LogError(
|
||||
// "Inner service error: Failed to parse Envelope ID from claims. Claim '{ClaimName}' had an invalid or missing value: '{ClaimValue}'.",
|
||||
// EnvelopeClaimNames.EnvelopeId,
|
||||
// envelopeIdStr ?? "null");
|
||||
|
||||
// return StatusCode(StatusCodes.Status500InternalServerError, "Inner service error: Invalid envelope claim.");
|
||||
//}
|
||||
|
||||
var senderDoc = await mediator.Send(new ReadDocumentQuery() { EnvelopeId = envelopeId }, cancel);
|
||||
|
||||
if (senderDoc.ByteData is not byte[] senderDocByte)
|
||||
return NotFound("Document is empty.");
|
||||
|
||||
Response.Headers.ContentDisposition = $"inline; filename=\"{envelopeKey}.pdf\"";
|
||||
return File(senderDocByte, "application/pdf");
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ public class EnvelopeController : ControllerBase
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> CreateAsync([FromBody] CreateEnvelopeCommand command)
|
||||
{
|
||||
var res = await _mediator.Send(command.Authorize(User.GetId()));
|
||||
var res = await _mediator.Send(command.WithAuth(User.GetId()));
|
||||
|
||||
if (res is null)
|
||||
{
|
||||
|
||||
@@ -14,6 +14,7 @@ using EnvelopeGenerator.Application.Common.SQL;
|
||||
using EnvelopeGenerator.Application.Common.Dto.Receiver;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
|
||||
using EnvelopeGenerator.API.Extensions;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
|
||||
namespace EnvelopeGenerator.API.Controllers;
|
||||
|
||||
@@ -73,6 +74,24 @@ public class EnvelopeReceiverController : ControllerBase
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="envelopeKey"></param>
|
||||
/// <param name="cancel"></param>
|
||||
/// <returns></returns>
|
||||
[Authorize(Policy = AuthPolicy.Receiver)]
|
||||
[HttpGet("{envelopeKey}")]
|
||||
public async Task<IActionResult> GetEnvelopeReceiverOfReceiver([FromRoute] string envelopeKey, CancellationToken cancel)
|
||||
{
|
||||
var er = await _mediator.Send(new ReadEnvelopeReceiverQuery()
|
||||
{
|
||||
Key = envelopeKey
|
||||
}, cancel);
|
||||
|
||||
return Ok(er);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ruft den Namen des zuletzt verwendeten Empfängers basierend auf der angegebenen E-Mail-Adresse ab.
|
||||
/// </summary>
|
||||
|
||||
@@ -16,6 +16,12 @@ public sealed class AuthProxyDocumentFilter : IDocumentFilter
|
||||
/// <param name="swaggerDoc"></param>
|
||||
/// <param name="context"></param>
|
||||
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
{
|
||||
AddLoginOperation(swaggerDoc, context);
|
||||
AddEnvelopeReceiverLoginOperation(swaggerDoc, context);
|
||||
}
|
||||
|
||||
private static void AddLoginOperation(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
{
|
||||
const string path = "/api/auth";
|
||||
|
||||
@@ -67,4 +73,51 @@ public sealed class AuthProxyDocumentFilter : IDocumentFilter
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static void AddEnvelopeReceiverLoginOperation(OpenApiDocument swaggerDoc, DocumentFilterContext context)
|
||||
{
|
||||
const string path = "/api/Auth/envelope-receiver/{key}";
|
||||
|
||||
var bodySchema = context.SchemaGenerator.GenerateSchema(typeof(EnvelopeReceiverLogin), context.SchemaRepository);
|
||||
|
||||
var operation = new OpenApiOperation
|
||||
{
|
||||
Summary = "Envelope receiver login (auth-hub proxy)",
|
||||
Description = "Proxies the envelope receiver login to the auth service. " +
|
||||
"The `cookie` query parameter is always forwarded as `true` so the auth service sets the per-envelope cookie automatically.",
|
||||
Tags = [new() { Name = "Auth" }],
|
||||
Parameters =
|
||||
{
|
||||
new OpenApiParameter
|
||||
{
|
||||
Name = "key",
|
||||
In = ParameterLocation.Path,
|
||||
Required = true,
|
||||
Schema = new OpenApiSchema { Type = "string" },
|
||||
Description = "The unique envelope receiver key."
|
||||
}
|
||||
},
|
||||
RequestBody = new OpenApiRequestBody
|
||||
{
|
||||
Required = false,
|
||||
Content =
|
||||
{
|
||||
["multipart/form-data"] = new OpenApiMediaType { Schema = bodySchema }
|
||||
}
|
||||
},
|
||||
Responses =
|
||||
{
|
||||
["200"] = new OpenApiResponse { Description = "OK – per-envelope cookie set by auth service." },
|
||||
["401"] = new OpenApiResponse { Description = "Unauthorized – invalid or missing access code." }
|
||||
}
|
||||
};
|
||||
|
||||
swaggerDoc.Paths[path] = new OpenApiPathItem
|
||||
{
|
||||
Operations =
|
||||
{
|
||||
[OperationType.Post] = operation
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net9.0</TargetFrameworks>
|
||||
<TargetFrameworks>net8.0</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
@@ -30,11 +30,16 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AspNetCore.Scalar" Version="1.1.8" />
|
||||
<PackageReference Include="DigitalData.Auth.Claims" Version="1.0.3" />
|
||||
<PackageReference Include="DigitalData.Auth.Client" Version="1.3.7" />
|
||||
<PackageReference Include="DigitalData.Core.API" Version="2.2.1" />
|
||||
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.6" />
|
||||
<PackageReference Include="itext" Version="8.0.5" />
|
||||
<PackageReference Include="itext.bouncy-castle-adapter" Version="8.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.4" Condition="'$(TargetFramework)' == 'net9.0'" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="8.0.11" Condition="'$(TargetFramework)' == 'net8.0'" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.6" Condition="'$(TargetFramework)' == 'net9.0'" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.17" Condition="'$(TargetFramework)' == 'net8.0'" />
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.82.1" />
|
||||
<PackageReference Include="NLog" Version="5.2.5" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" Version="5.3.0" />
|
||||
@@ -71,6 +76,7 @@
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.PdfEditor\EnvelopeGenerator.PdfEditor.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
7
EnvelopeGenerator.API/Models/EnvelopeReceiverLogin.cs
Normal file
7
EnvelopeGenerator.API/Models/EnvelopeReceiverLogin.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace EnvelopeGenerator.API.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Request body for the envelope-receiver login endpoint.
|
||||
/// </summary>
|
||||
/// <param name="AccessCode">The access code sent to the receiver.</param>
|
||||
public record EnvelopeReceiverLogin(string? AccessCode = null);
|
||||
@@ -19,6 +19,7 @@ using DigitalData.Core.Abstractions.Security.Extensions;
|
||||
using EnvelopeGenerator.API.Middleware;
|
||||
using NLog.Web;
|
||||
using NLog;
|
||||
using DigitalData.Auth.Claims;
|
||||
|
||||
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
||||
logger.Info("Logging initialized!");
|
||||
@@ -110,7 +111,9 @@ try
|
||||
|
||||
options.DocumentFilter<EnvelopeGenerator.API.Documentation.AuthProxyDocumentFilter>();
|
||||
});
|
||||
#if NET9_0_OR_GREATER
|
||||
builder.Services.AddOpenApi();
|
||||
#endif
|
||||
|
||||
//Add EF Core dbcontext
|
||||
var useDbMigration = Environment.GetEnvironmentVariable("MIGRATION_TEST_MODE") == true.ToString() || config.GetValue<bool>("UseDbMigration");
|
||||
@@ -126,6 +129,9 @@ try
|
||||
|
||||
var authTokenKeys = config.GetOrDefault<AuthTokenKeys>();
|
||||
|
||||
// Scheme name used for per-envelope receiver JWT authentication.
|
||||
const string EnvelopeReceiverScheme = "EnvelopeReceiverJwt";
|
||||
|
||||
builder.Services.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
@@ -163,6 +169,61 @@ try
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
})
|
||||
// Per-envelope receiver scheme: reads the JWT from the cookie named
|
||||
// AuthTokenSignFLOWReceiver.{envelope_key} where envelope_key is the
|
||||
// last path segment of the request URL.
|
||||
// This enables simultaneous authentication for multiple envelopes
|
||||
// within the same browser session.
|
||||
.AddJwtBearer(EnvelopeReceiverScheme, opt =>
|
||||
{
|
||||
opt.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
|
||||
{
|
||||
var clientParams = deferredProvider.GetOptions<ClientParams>();
|
||||
var publicKey = clientParams!.PublicKeys.Get(authTokenKeys.Issuer, authTokenKeys.Audience);
|
||||
return [publicKey.SecurityKey];
|
||||
},
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = authTokenKeys.Issuer,
|
||||
ValidateAudience = true,
|
||||
ValidAudience = authTokenKeys.Audience,
|
||||
};
|
||||
|
||||
opt.Events = new JwtBearerEvents
|
||||
{
|
||||
OnMessageReceived = context =>
|
||||
{
|
||||
var paths = context.Request.Path.Value?.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
// Derive the envelope key from the last route segment: /{envelope_key}
|
||||
var envelopeKey = paths?.LastOrDefault();
|
||||
|
||||
if (envelopeKey is not null)
|
||||
{
|
||||
var cookieName = CookieNames.GetEnvelopeReceiverCookieName(authTokenKeys.Cookie, envelopeKey);
|
||||
if (context.Request.Cookies.TryGetValue(cookieName, out var cookieToken) && cookieToken is not null)
|
||||
context.Token = cookieToken;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
OnTokenValidated = context =>
|
||||
{
|
||||
var paths = context.Request.Path.Value?.Split('/', StringSplitOptions.RemoveEmptyEntries);
|
||||
var envelopeKey = paths?.LastOrDefault();
|
||||
|
||||
var sub = context.Principal?.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value
|
||||
?? context.Principal?.FindFirst("sub")?.Value;
|
||||
|
||||
if (envelopeKey is null || sub != envelopeKey)
|
||||
context.Fail("Envelope key in the path does not match the token subject.");
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Authentication
|
||||
@@ -182,8 +243,13 @@ try
|
||||
policy.RequireRole(Role.Sender, Role.Receiver.Full))
|
||||
.AddPolicy(AuthPolicy.Sender, policy =>
|
||||
policy.RequireRole(Role.Sender))
|
||||
// Per-envelope policy: uses the dedicated EnvelopeReceiverJwt scheme so it
|
||||
// never conflicts with the default JwtBearer scheme.
|
||||
.AddPolicy(AuthPolicy.Receiver, policy =>
|
||||
policy.RequireRole(Role.Receiver.Full))
|
||||
policy
|
||||
.AddAuthenticationSchemes(EnvelopeReceiverScheme)
|
||||
.RequireAuthenticatedUser()
|
||||
.RequireRole(Role.Receiver.Full, "receiver"))
|
||||
.AddPolicy(AuthPolicy.ReceiverTFA, policy =>
|
||||
policy.RequireRole(Role.Receiver.TFA));
|
||||
|
||||
@@ -224,7 +290,9 @@ try
|
||||
|
||||
app.UseMiddleware<ExceptionHandlingMiddleware>();
|
||||
|
||||
#if NET9_0_OR_GREATER
|
||||
app.MapOpenApi();
|
||||
#endif
|
||||
|
||||
// Configure the HTTP request pipeline.
|
||||
if (app.Environment.IsDevelopment() || (app.IsDevOrDiP() && config.GetValue<bool>("UseSwagger")))
|
||||
@@ -258,9 +326,19 @@ try
|
||||
app.UseAuthentication();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.MapReverseProxy();
|
||||
app.MapControllers();
|
||||
|
||||
// Catch-all YARP proxy — only forward requests that are not swagger/scalar/openapi paths.
|
||||
app.MapWhen(
|
||||
ctx =>
|
||||
{
|
||||
var path = ctx.Request.Path.Value ?? string.Empty;
|
||||
return !path.StartsWith("/swagger", StringComparison.OrdinalIgnoreCase) &&
|
||||
!path.StartsWith("/scalar", StringComparison.OrdinalIgnoreCase) &&
|
||||
!path.StartsWith("/openapi", StringComparison.OrdinalIgnoreCase);
|
||||
},
|
||||
branch => branch.UseRouting().UseEndpoints(e => e.MapReverseProxy()));
|
||||
|
||||
app.Run();
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"UseSwagger": true,
|
||||
"UseDbMigration": true,
|
||||
"UseDbMigration": false,
|
||||
"DiPMode": true,
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
|
||||
@@ -1,6 +1,38 @@
|
||||
{
|
||||
"ReverseProxy": {
|
||||
"Routes": {
|
||||
"receiver-ui-receiver": {
|
||||
"ClusterId": "receiver-ui",
|
||||
"Order": 100,
|
||||
"Match": {
|
||||
"Path": "/receiver/{**catch-all}",
|
||||
"Methods": [ "GET", "HEAD" ]
|
||||
}
|
||||
},
|
||||
"receiver-ui-sender": {
|
||||
"ClusterId": "receiver-ui",
|
||||
"Order": 100,
|
||||
"Match": {
|
||||
"Path": "/sender/{**catch-all}",
|
||||
"Methods": [ "GET", "HEAD" ]
|
||||
}
|
||||
},
|
||||
"receiver-ui-envelope": {
|
||||
"ClusterId": "receiver-ui",
|
||||
"Order": 100,
|
||||
"Match": {
|
||||
"Path": "/envelope/{**catch-all}",
|
||||
"Methods": [ "GET", "HEAD" ]
|
||||
}
|
||||
},
|
||||
"receiver-ui-static-assets": {
|
||||
"ClusterId": "receiver-ui",
|
||||
"Order": 999,
|
||||
"Match": {
|
||||
"Path": "{**catch-all}",
|
||||
"Methods": [ "GET", "HEAD" ]
|
||||
}
|
||||
},
|
||||
"auth-login": {
|
||||
"ClusterId": "auth-hub",
|
||||
"Match": {
|
||||
@@ -10,9 +42,27 @@
|
||||
"Transforms": [
|
||||
{ "PathSet": "/api/auth/sign-flow" }
|
||||
]
|
||||
},
|
||||
"auth-envelope-receiver-login": {
|
||||
"ClusterId": "auth-hub",
|
||||
"Match": {
|
||||
"Path": "/api/Auth/envelope-receiver/{key}",
|
||||
"Methods": [ "POST" ]
|
||||
},
|
||||
"Transforms": [
|
||||
{ "PathPattern": "/api/auth/envelope-receiver/{key}" },
|
||||
{ "QueryValueParameter": "cookie", "Set": "true" }
|
||||
]
|
||||
}
|
||||
},
|
||||
"Clusters": {
|
||||
"receiver-ui": {
|
||||
"Destinations": {
|
||||
"primary": {
|
||||
"Address": "https://localhost:52936"
|
||||
}
|
||||
}
|
||||
},
|
||||
"auth-hub": {
|
||||
"Destinations": {
|
||||
"primary": {
|
||||
@@ -23,3 +73,4 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
using AutoMapper;
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Application.Envelopes.Queries;
|
||||
using EnvelopeGenerator.Application.Receivers.Queries;
|
||||
using MediatR;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
||||
using EnvelopeGenerator.Application.Common.Query;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
|
||||
namespace EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a query for reading an envelope receiver including sensitive fields
|
||||
/// (access code, phone number) that are excluded from the standard <see cref="ReadEnvelopeReceiverQuery"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Returns a single <see cref="EnvelopeReceiverSecretDto"/> matched by UUID and receiver signature.
|
||||
/// Equivalent to the legacy <c>ReadWithSecretByUuidSignatureAsync</c> service method.
|
||||
/// </remarks>
|
||||
public record ReadEnvelopeReceiverSecretQuery
|
||||
: EnvelopeReceiverQueryBase<ReadEnvelopeQuery, ReadReceiverQuery>,
|
||||
IRequest<EnvelopeReceiverSecretDto?>;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for dispatching <see cref="ReadEnvelopeReceiverSecretQuery"/> via <see cref="IMediator"/>.
|
||||
/// </summary>
|
||||
public static class ReadEnvelopeReceiverSecretQueryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sends a <see cref="ReadEnvelopeReceiverSecretQuery"/> using the composite key (uuid::signature).
|
||||
/// </summary>
|
||||
/// <param name="mediator">The mediator instance.</param>
|
||||
/// <param name="key">Composite key in the format <c>uuid::signature</c>.</param>
|
||||
/// <param name="cancel">Cancellation token.</param>
|
||||
/// <returns>The matching <see cref="EnvelopeReceiverSecretDto"/>, or <c>null</c> if not found.</returns>
|
||||
public static Task<EnvelopeReceiverSecretDto?> ReadEnvelopeReceiverSecretAsync(
|
||||
this IMediator mediator,
|
||||
string key,
|
||||
CancellationToken cancel = default)
|
||||
=> mediator.Send(new ReadEnvelopeReceiverSecretQuery { Key = key }, cancel);
|
||||
|
||||
/// <summary>
|
||||
/// Sends a <see cref="ReadEnvelopeReceiverSecretQuery"/> using UUID and receiver signature.
|
||||
/// </summary>
|
||||
/// <param name="mediator">The mediator instance.</param>
|
||||
/// <param name="uuid">Envelope UUID.</param>
|
||||
/// <param name="signature">Receiver signature.</param>
|
||||
/// <param name="cancel">Cancellation token.</param>
|
||||
/// <returns>The matching <see cref="EnvelopeReceiverSecretDto"/>, or <c>null</c> if not found.</returns>
|
||||
public static Task<EnvelopeReceiverSecretDto?> ReadEnvelopeReceiverSecretAsync(
|
||||
this IMediator mediator,
|
||||
string uuid,
|
||||
string signature,
|
||||
CancellationToken cancel = default)
|
||||
{
|
||||
var q = new ReadEnvelopeReceiverSecretQuery();
|
||||
q.Envelope.Uuid = uuid;
|
||||
q.Receiver.Signature = signature;
|
||||
return mediator.Send(q, cancel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles <see cref="ReadEnvelopeReceiverSecretQuery"/> and returns a
|
||||
/// <see cref="EnvelopeReceiverSecretDto"/> containing sensitive fields.
|
||||
/// </summary>
|
||||
public class ReadEnvelopeReceiverSecretQueryHandler
|
||||
: IRequestHandler<ReadEnvelopeReceiverSecretQuery, EnvelopeReceiverSecretDto?>
|
||||
{
|
||||
private readonly IRepository<EnvelopeReceiver> _repo;
|
||||
private readonly IRepository<Receiver> _rcvRepo;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of <see cref="ReadEnvelopeReceiverSecretQueryHandler"/>.
|
||||
/// </summary>
|
||||
/// <param name="envelopeReceiver">Repository for <see cref="EnvelopeReceiver"/>.</param>
|
||||
/// <param name="rcvRepo">Repository for <see cref="Receiver"/>.</param>
|
||||
/// <param name="mapper">AutoMapper instance.</param>
|
||||
public ReadEnvelopeReceiverSecretQueryHandler(
|
||||
IRepository<EnvelopeReceiver> envelopeReceiver,
|
||||
IRepository<Receiver> rcvRepo,
|
||||
IMapper mapper)
|
||||
{
|
||||
_repo = envelopeReceiver;
|
||||
_rcvRepo = rcvRepo;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the query and returns the matching <see cref="EnvelopeReceiverSecretDto"/>.
|
||||
/// </summary>
|
||||
/// <param name="request">The query containing filter criteria.</param>
|
||||
/// <param name="cancel">Cancellation token.</param>
|
||||
/// <returns>
|
||||
/// The matched <see cref="EnvelopeReceiverSecretDto"/>, or <c>null</c> if no record is found.
|
||||
/// </returns>
|
||||
public async Task<EnvelopeReceiverSecretDto?> Handle(
|
||||
ReadEnvelopeReceiverSecretQuery request,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var q = _repo.Query.Where(request, notnull: false);
|
||||
|
||||
var envRcvs = await q
|
||||
.Include(er => er.Envelope).ThenInclude(e => e!.Documents!).ThenInclude(d => d.Elements)
|
||||
.Include(er => er.Envelope).ThenInclude(e => e!.Histories)
|
||||
.Include(er => er.Envelope).ThenInclude(e => e!.User)
|
||||
.Include(er => er.Receiver)
|
||||
.ToListAsync(cancel);
|
||||
|
||||
if (request.Receiver.HasAnyCriteria && envRcvs.Count != 0)
|
||||
{
|
||||
var receiver = await _rcvRepo.Query.Where(request.Receiver).FirstAsync(cancel);
|
||||
|
||||
foreach (var item in envRcvs)
|
||||
item.Envelope?.Documents?.FirstOrDefault()?.Elements?.RemoveAll(s => s.ReceiverId != receiver.Id);
|
||||
}
|
||||
|
||||
var envRcv = envRcvs.FirstOrDefault();
|
||||
if (envRcv is null)
|
||||
return null;
|
||||
|
||||
return _mapper.Map<EnvelopeReceiverSecretDto>(envRcv);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -40,10 +40,10 @@ public record CreateEnvelopeCommand : IRequest<EnvelopeDto?>
|
||||
/// </summary>
|
||||
/// <param name="userId"></param>
|
||||
/// <returns></returns>
|
||||
public bool Authorize(int userId)
|
||||
public CreateEnvelopeCommand WithAuth(int userId)
|
||||
{
|
||||
UserId = userId;
|
||||
return true;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
145
EnvelopeGenerator.DependencyInjection/DependencyInjection.cs
Normal file
145
EnvelopeGenerator.DependencyInjection/DependencyInjection.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using EnvelopeGenerator.Application;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
using EnvelopeGenerator.Application.Services;
|
||||
using EnvelopeGenerator.Infrastructure;
|
||||
using DigitalData.EmailProfilerDispatcher;
|
||||
using DigitalData.UserManager.DependencyInjection;
|
||||
|
||||
namespace EnvelopeGenerator.DependencyInjection;
|
||||
|
||||
/// <summary>
|
||||
/// Controls which optional services are registered by <see cref="DependencyInjection.AddEnvelopeGenerator"/>.
|
||||
/// All flags default to <c>true</c>. Set a flag to <c>false</c> if the consuming project
|
||||
/// already registers that service itself or simply does not need it.
|
||||
/// </summary>
|
||||
public sealed class EnvelopeGeneratorOptions
|
||||
{
|
||||
/// <summary>Calls <c>AddHttpContextAccessor()</c>. Default: <c>true</c>.</summary>
|
||||
public bool AddHttpContextAccessor { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Calls <c>AddDistributedSqlServerCache()</c> with the supplied <see cref="SqlCacheOptions"/>.
|
||||
/// Requires <see cref="SqlCacheOptions"/> to be configured. Default: <c>true</c>.
|
||||
/// </summary>
|
||||
public bool AddDistributedSqlServerCache { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Options for the distributed SQL Server cache.
|
||||
/// Required when <see cref="AddDistributedSqlServerCache"/> is <c>true</c>.
|
||||
/// </summary>
|
||||
public SqlCacheOptions? SqlCacheOptions { get; set; }
|
||||
|
||||
/// <summary>Calls <c>AddDispatcher<TDbContext>()</c>. Default: <c>true</c>.</summary>
|
||||
public bool AddDispatcher { get; set; } = true;
|
||||
|
||||
/// <summary>Calls <c>AddMemoryCache()</c>. Default: <c>true</c>.</summary>
|
||||
public bool AddMemoryCache { get; set; } = true;
|
||||
|
||||
/// <summary>Calls <c>AddUserManager<TDbContext>()</c>. Default: <c>true</c>.</summary>
|
||||
public bool AddUserManager { get; set; } = true;
|
||||
}
|
||||
|
||||
/// <summary>Options for <c>AddDistributedSqlServerCache</c>.</summary>
|
||||
public sealed class SqlCacheOptions
|
||||
{
|
||||
/// <summary>SQL Server connection string.</summary>
|
||||
public string ConnectionString { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Schema name. Default: <c>dbo</c>.</summary>
|
||||
public string SchemaName { get; set; } = "dbo";
|
||||
|
||||
/// <summary>Table name. Default: <c>TBDD_CACHE</c>.</summary>
|
||||
public string TableName { get; set; } = "TBDD_CACHE";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for registering EnvelopeGenerator services into an <see cref="IServiceCollection"/>.
|
||||
/// Use <see cref="AddEnvelopeGenerator{TDbContext}"/> as the single entry-point for projects that need both the
|
||||
/// application layer (MediatR, AutoMapper, CRUD services, configuration sections) and the infrastructure
|
||||
/// layer (repositories, DbContext, SQL executors).
|
||||
/// For projects that do not need a database (e.g. lightweight API gateways or unit-test hosts), use
|
||||
/// <see cref="AddEnvelopeGeneratorCore"/> to register only the application layer.
|
||||
/// </summary>
|
||||
public static class DependencyInjection
|
||||
{
|
||||
/// <summary>
|
||||
/// Registers the full EnvelopeGenerator stack using <see cref="EGDbContext"/> as the DbContext type.
|
||||
/// </summary>
|
||||
public static IServiceCollection AddEnvelopeGenerator(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
Action<EnvelopeGenerator.Infrastructure.DependencyInjection.Config>? infrastructureOptions = null,
|
||||
Action<EnvelopeGeneratorOptions>? options = null)
|
||||
{
|
||||
var opt = new EnvelopeGeneratorOptions();
|
||||
options?.Invoke(opt);
|
||||
|
||||
#pragma warning disable CS0618
|
||||
// Application layer: CRUD services, MediatR, AutoMapper, configuration sections.
|
||||
services.AddEnvelopeGeneratorServices(configuration);
|
||||
|
||||
// Infrastructure layer: repositories, DbContext, Dapper type maps, SQL executors.
|
||||
services.AddEnvelopeGeneratorInfrastructureServices(cfg =>
|
||||
{
|
||||
infrastructureOptions?.Invoke(cfg);
|
||||
});
|
||||
#pragma warning restore CS0618
|
||||
|
||||
if (opt.AddHttpContextAccessor)
|
||||
services.AddHttpContextAccessor();
|
||||
|
||||
if (opt.AddDistributedSqlServerCache && opt.SqlCacheOptions is { } cacheOpts)
|
||||
services.AddDistributedSqlServerCache(o =>
|
||||
{
|
||||
o.ConnectionString = cacheOpts.ConnectionString;
|
||||
o.SchemaName = cacheOpts.SchemaName;
|
||||
o.TableName = cacheOpts.TableName;
|
||||
});
|
||||
|
||||
if (opt.AddDispatcher)
|
||||
services.AddDispatcher<EGDbContext>();
|
||||
|
||||
if (opt.AddMemoryCache)
|
||||
services.AddMemoryCache();
|
||||
|
||||
#pragma warning disable CS0618
|
||||
if (opt.AddUserManager)
|
||||
services.AddUserManager<EGDbContext>();
|
||||
#pragma warning restore CS0618
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers only the <em>application</em> layer services (MediatR handlers, AutoMapper profiles,
|
||||
/// CRUD services, configuration sections) without any infrastructure / database dependencies.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection to register services into.</param>
|
||||
/// <param name="configuration">Application configuration used to bind application-level option sections.</param>
|
||||
/// <returns>The updated <see cref="IServiceCollection"/>.</returns>
|
||||
#pragma warning disable CS0618
|
||||
public static IServiceCollection AddEnvelopeGeneratorCore(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration)
|
||||
{
|
||||
services.AddEnvelopeGeneratorServices(configuration);
|
||||
return services;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
|
||||
/// <summary>
|
||||
/// Registers <see cref="EnvelopeMailService"/> as the <see cref="IEnvelopeMailService"/> scoped
|
||||
/// implementation.
|
||||
/// </summary>
|
||||
/// <param name="services">Service collection to register services into.</param>
|
||||
/// <returns>The updated <see cref="IServiceCollection"/>.</returns>
|
||||
#pragma warning disable CS0618
|
||||
public static IServiceCollection AddEnvelopeMailService(this IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IEnvelopeMailService, EnvelopeMailService>();
|
||||
return services;
|
||||
}
|
||||
#pragma warning restore CS0618
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
||||
<!-- NuGet package metadata -->
|
||||
<PackageId>EnvelopeGenerator</PackageId>
|
||||
<Authors>Digital Data GmbH</Authors>
|
||||
<Company>Digital Data GmbH</Company>
|
||||
<Product>EnvelopeGenerator</Product>
|
||||
<Description>
|
||||
Envelope Generator ist eine Bibliothek zur Verwaltung und Verarbeitung digitaler Umschläge (Envelopes).
|
||||
Dieses Paket enthält die Dependency-Injection-Erweiterungsmethoden und bündelt die Application-
|
||||
sowie Infrastructure-Schicht in einer einzigen NuGet-Referenz.
|
||||
</Description>
|
||||
<Copyright>Copyright 2024 Digital Data GmbH</Copyright>
|
||||
<RepositoryUrl>http://git.dd:3000/AppStd/EnvelopeGenerator.git</RepositoryUrl>
|
||||
<PackageTags>digital data envelope generator di dependency injection</PackageTags>
|
||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||
<Version>1.2.0.3</Version>
|
||||
<AssemblyVersion>1.2.0.3</AssemblyVersion>
|
||||
<FileVersion>1.2.0.3</FileVersion>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
</PropertyGroup>
|
||||
|
||||
<!-- ASP.NET Core shared framework (AddHttpContextAccessor, AddMemoryCache, etc.) -->
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
</ItemGroup>
|
||||
|
||||
<!--
|
||||
All dependencies are declared here.
|
||||
Because Application and Infrastructure DLLs are bundled via PrivateAssets=all,
|
||||
their PackageReferences have been moved to this project so that the correct
|
||||
dependencies are written to the nuspec and a consuming project works with
|
||||
a single package install.
|
||||
-->
|
||||
<ItemGroup>
|
||||
<!-- DigitalData BaGet packages -->
|
||||
<PackageReference Include="DigitalData.Core.Abstraction.Application" Version="1.6.0" />
|
||||
<PackageReference Include="DigitalData.Core.Application" Version="3.4.0" />
|
||||
<PackageReference Include="DigitalData.Core.Client" Version="2.1.0" />
|
||||
<PackageReference Include="DigitalData.Core.Exceptions" Version="1.1.0" />
|
||||
<PackageReference Include="DigitalData.Core.Infrastructure" Version="2.6.1" />
|
||||
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="3.1.1" />
|
||||
<PackageReference Include="UserManager" Version="1.1.3" />
|
||||
|
||||
<!-- ORM / Database -->
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="5.2.2" />
|
||||
|
||||
<!-- Application services -->
|
||||
<PackageReference Include="MediatR" Version="12.5.0" />
|
||||
<PackageReference Include="QRCoder" Version="1.6.0" />
|
||||
<PackageReference Include="QRCoder-ImageSharp" Version="0.10.0" />
|
||||
<PackageReference Include="QuestPDF" Version="2025.7.1" />
|
||||
<PackageReference Include="Otp.NET" Version="1.4.0" />
|
||||
|
||||
<!-- Security / Identity -->
|
||||
<PackageReference Include="Microsoft.Identity.Client" Version="4.82.1" />
|
||||
|
||||
<!-- Utilities -->
|
||||
<PackageReference Include="HtmlSanitizer" Version="9.0.892" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
<PackageReference Include="System.Formats.Asn1" Version="10.0.3" />
|
||||
<PackageReference Include="System.Security.AccessControl" Version="6.0.1" />
|
||||
|
||||
<!-- DI abstractions -->
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="9.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- TFM-specific packages -->
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
<PackageReference Include="CommandDotNet" Version="7.0.5" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="7.0.20" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="7.0.20" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
|
||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||
<PackageReference Include="CommandDotNet" Version="8.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.17" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="8.0.17" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.17" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="8.0.17" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.17" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net9.0'">
|
||||
<PackageReference Include="AutoMapper" Version="14.0.0" />
|
||||
<PackageReference Include="CommandDotNet" Version="8.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.SqlServer" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="9.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Bundle dependency DLLs into the package lib folder -->
|
||||
<Target Name="IncludeDependencyDlls" BeforeTargets="_GetPackageFiles">
|
||||
<ItemGroup>
|
||||
<_DepDlls Include="
 ..\EnvelopeGenerator.Application\bin\$(Configuration)\%(ProjectReference.TargetFramework)\EnvelopeGenerator.Application.dll;
 ..\EnvelopeGenerator.Domain\bin\$(Configuration)\%(ProjectReference.TargetFramework)\EnvelopeGenerator.Domain.dll;
 ..\EnvelopeGenerator.Infrastructure\bin\$(Configuration)\%(ProjectReference.TargetFramework)\EnvelopeGenerator.Infrastructure.dll" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
<!--
|
||||
Rebuild all dependency projects for every TFM before packing so that
|
||||
the DLLs bundled into the package are always up-to-date.
|
||||
Run with: dotnet pack -c Release
|
||||
-->
|
||||
<Target Name="RebuildDependenciesBeforePack" BeforeTargets="GenerateNuspec">
|
||||
<MSBuild Projects="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj"
|
||||
Targets="Build"
|
||||
Properties="Configuration=$(Configuration);TargetFramework=net7.0" />
|
||||
<MSBuild Projects="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj"
|
||||
Targets="Build"
|
||||
Properties="Configuration=$(Configuration);TargetFramework=net8.0" />
|
||||
<MSBuild Projects="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj"
|
||||
Targets="Build"
|
||||
Properties="Configuration=$(Configuration);TargetFramework=net9.0" />
|
||||
|
||||
<MSBuild Projects="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj"
|
||||
Targets="Build"
|
||||
Properties="Configuration=$(Configuration);TargetFramework=net7.0" />
|
||||
<MSBuild Projects="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj"
|
||||
Targets="Build"
|
||||
Properties="Configuration=$(Configuration);TargetFramework=net8.0" />
|
||||
<MSBuild Projects="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj"
|
||||
Targets="Build"
|
||||
Properties="Configuration=$(Configuration);TargetFramework=net9.0" />
|
||||
|
||||
<MSBuild Projects="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj"
|
||||
Targets="Build"
|
||||
Properties="Configuration=$(Configuration);TargetFramework=net7.0" />
|
||||
<MSBuild Projects="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj"
|
||||
Targets="Build"
|
||||
Properties="Configuration=$(Configuration);TargetFramework=net8.0" />
|
||||
<MSBuild Projects="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj"
|
||||
Targets="Build"
|
||||
Properties="Configuration=$(Configuration);TargetFramework=net9.0" />
|
||||
</Target>
|
||||
|
||||
<Target Name="BundleReferencedDlls" AfterTargets="Build">
|
||||
<ItemGroup>
|
||||
<BuildOutputInPackage Include="..\EnvelopeGenerator.Application\bin\$(Configuration)\net7.0\EnvelopeGenerator.Application.dll" TargetFramework="net7.0" />
|
||||
<BuildOutputInPackage Include="..\EnvelopeGenerator.Application\bin\$(Configuration)\net8.0\EnvelopeGenerator.Application.dll" TargetFramework="net8.0" />
|
||||
<BuildOutputInPackage Include="..\EnvelopeGenerator.Application\bin\$(Configuration)\net9.0\EnvelopeGenerator.Application.dll" TargetFramework="net9.0" />
|
||||
<BuildOutputInPackage Include="..\EnvelopeGenerator.Domain\bin\$(Configuration)\net7.0\EnvelopeGenerator.Domain.dll" TargetFramework="net7.0" />
|
||||
<BuildOutputInPackage Include="..\EnvelopeGenerator.Domain\bin\$(Configuration)\net8.0\EnvelopeGenerator.Domain.dll" TargetFramework="net8.0" />
|
||||
<BuildOutputInPackage Include="..\EnvelopeGenerator.Domain\bin\$(Configuration)\net9.0\EnvelopeGenerator.Domain.dll" TargetFramework="net9.0" />
|
||||
<BuildOutputInPackage Include="..\EnvelopeGenerator.Infrastructure\bin\$(Configuration)\net7.0\EnvelopeGenerator.Infrastructure.dll" TargetFramework="net7.0" />
|
||||
<BuildOutputInPackage Include="..\EnvelopeGenerator.Infrastructure\bin\$(Configuration)\net8.0\EnvelopeGenerator.Infrastructure.dll" TargetFramework="net8.0" />
|
||||
<BuildOutputInPackage Include="..\EnvelopeGenerator.Infrastructure\bin\$(Configuration)\net9.0\EnvelopeGenerator.Infrastructure.dll" TargetFramework="net9.0" />
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
|
||||
</Project>
|
||||
@@ -24,6 +24,10 @@
|
||||
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DevExpress.Blazor.PdfViewer" Version="25.2.3" />
|
||||
<PackageReference Include="DevExpress.Blazor.Reporting.JSBasedControls" 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="HarfBuzzSharp.NativeAssets.WebAssembly" Version="8.3.1.2" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.WebAssembly" Version="3.119.1" />
|
||||
<PackageReference Include="SkiaSharp.Views.Blazor" Version="3.119.1" />
|
||||
@@ -32,9 +36,6 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.11" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.11" PrivateAssets="all" />
|
||||
<PackageReference Include="DevExpress.Drawing.Skia" Version="25.2.3" />
|
||||
<PackageReference Include="DevExpress.Blazor.Reporting.JSBasedControls" Version="25.2.3" />
|
||||
<PackageReference Include="DevExpress.Blazor.Reporting.Viewer" Version="25.2.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="Properties\PublishProfiles\" />
|
||||
|
||||
8
EnvelopeGenerator.ReceiverUI/Options/ApiOptions.cs
Normal file
8
EnvelopeGenerator.ReceiverUI/Options/ApiOptions.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace EnvelopeGenerator.ReceiverUI.Options;
|
||||
|
||||
public class ApiOptions
|
||||
{
|
||||
public const string SectionName = "Api";
|
||||
|
||||
public string BaseUrl { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
@page "/receiver"
|
||||
@page "/receiver/{EnvelopeKey}"
|
||||
@using System.Drawing
|
||||
@using DevExpress.Drawing
|
||||
@using DevExpress.Utils
|
||||
@@ -10,9 +11,11 @@
|
||||
@using XRPictureBox = DevExpress.XtraReports.UI.XRPictureBox
|
||||
@using XRControl = DevExpress.XtraReports.UI.XRControl
|
||||
@using ImageSizeMode = DevExpress.XtraPrinting.ImageSizeMode
|
||||
@using EnvelopeGenerator.ReceiverUI.Services;
|
||||
@using EnvelopeGenerator.ReceiverUI.Services
|
||||
@using DevExpress.Blazor.Reporting
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject InMemoryReportStorageWebExtension ReportStorage
|
||||
@inject EnvelopeGenerator.ReceiverUI.Services.DocumentService DocumentService
|
||||
|
||||
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||
<link href="_content/DevExpress.Blazor.Reporting.Viewer/css/dx-blazor-reporting-components.bs5.css" rel="stylesheet" />
|
||||
@@ -119,7 +122,9 @@
|
||||
</DxPopup>
|
||||
|
||||
<div class="receiver-viewer-wrapper">
|
||||
@if(Report is not null) {
|
||||
@if(!string.IsNullOrWhiteSpace(EnvelopeKey) && PdfBytes is { Length: > 0 }) {
|
||||
<DxPdfViewer @key="ViewerKey" CssClass="w-100 h-100" DocumentContent="PdfBytes" />
|
||||
} else if(Report is not null) {
|
||||
<DxReportViewer @key="ViewerKey" @ref="reportViewer" Report="Report" RootCssClasses="w-100 h-100" />
|
||||
}
|
||||
</div>
|
||||
@@ -127,6 +132,7 @@
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
const string SignatureTabDraw = "draw";
|
||||
const string SignatureTabText = "text";
|
||||
const string SignatureTabImage = "image";
|
||||
@@ -143,8 +149,13 @@
|
||||
("Cursive", "cursive")
|
||||
};
|
||||
|
||||
DxReportViewer reportViewer;
|
||||
[Parameter] public string? EnvelopeKey { get; set; }
|
||||
|
||||
DxReportViewer? reportViewer;
|
||||
XtraReport? Report;
|
||||
string PdfViewerUrl = string.Empty;
|
||||
byte[]? PdfBytes;
|
||||
byte[]? SignedPdfBytes;
|
||||
bool SignatureApplied;
|
||||
bool SignaturePopupVisible;
|
||||
string? SignatureValidationMessage;
|
||||
@@ -158,9 +169,35 @@
|
||||
int ViewerKey;
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
Report = CreateReportInstance();
|
||||
|
||||
await Task.CompletedTask;
|
||||
EnvelopeKey = null; // Force report generation for testing. Remove this line to enable EnvelopeKey-based loading.
|
||||
if (!string.IsNullOrWhiteSpace(EnvelopeKey)) {
|
||||
(PdfBytes, _) = await DocumentService.GetDocumentAsync(EnvelopeKey);
|
||||
return;
|
||||
}
|
||||
|
||||
Report = CreateReportInstance();
|
||||
}
|
||||
|
||||
async Task<XtraReport?> BuildPdfReportAsync(string key) {
|
||||
Console.WriteLine("BuildPdfReportAsync is invoked..");
|
||||
var (pdfBytes, _) = await DocumentService.GetDocumentAsync(key);
|
||||
Console.WriteLine($"[BuildPdfReport] key={key}, pdfBytes={pdfBytes?.Length ?? 0}");
|
||||
|
||||
if (pdfBytes is not { Length: > 0 })
|
||||
return CreateReportInstance();
|
||||
|
||||
var report = new XtraReport();
|
||||
var detail = new DevExpress.XtraReports.UI.DetailBand();
|
||||
report.Bands.Add(detail);
|
||||
var pdfContent = new DevExpress.XtraReports.UI.XRPdfContent { Source = pdfBytes, GenerateOwnPages = true };
|
||||
detail.Controls.Add(pdfContent);
|
||||
Console.WriteLine($"[BuildPdfReport] XRPdfContent added, Source length={pdfContent.Source?.Length ?? 0}");
|
||||
|
||||
ReportStorage.SetData(report, key);
|
||||
var result = ReportStorage.TryGetReport(key, out var stored) ? stored : report;
|
||||
Console.WriteLine($"[BuildPdfReport] TryGetReport success={stored is not null}, bands={result?.Bands?.Count}");
|
||||
return result;
|
||||
}
|
||||
|
||||
async Task OpenSignaturePopupAsync() {
|
||||
@@ -244,10 +281,8 @@
|
||||
|
||||
PopupValidationMessage = null;
|
||||
SignatureValidationMessage = null;
|
||||
Report = CreateSignedReportInstance(signatureDataUrl, SignerFullName.Trim(), SignerPosition.Trim(), SignaturePlace.Trim());
|
||||
SignatureApplied = true;
|
||||
SignaturePopupVisible = false;
|
||||
ViewerKey++;
|
||||
}
|
||||
|
||||
async Task<string?> GetActiveSignatureDataUrlAsync() {
|
||||
@@ -263,13 +298,30 @@
|
||||
}
|
||||
|
||||
async Task ExportSignedPdfAsync() {
|
||||
if(!SignatureApplied || Report is null) {
|
||||
if(!SignatureApplied) {
|
||||
SignatureValidationMessage = "Bitte fuegen Sie die Unterschrift zuerst zum Bericht hinzu.";
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SignatureValidationMessage = null;
|
||||
|
||||
var signatureDataUrl = await GetActiveSignatureDataUrlAsync();
|
||||
if(string.IsNullOrWhiteSpace(signatureDataUrl)) {
|
||||
SignatureValidationMessage = "Die Unterschrift konnte nicht gelesen werden.";
|
||||
return;
|
||||
}
|
||||
|
||||
var signedKey = $"{EnvelopeKey}_signed";
|
||||
var signedReport = new XtraReport();
|
||||
var detail = new DevExpress.XtraReports.UI.DetailBand();
|
||||
signedReport.Bands.Add(detail);
|
||||
detail.Controls.Add(new DevExpress.XtraReports.UI.XRPdfContent { Source = PdfBytes });
|
||||
ReportStorage.SetData(signedReport, signedKey);
|
||||
Report = ReportStorage.TryGetReport(signedKey, out var stored) ? stored : signedReport;
|
||||
ViewerKey++;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
await Task.Delay(300);
|
||||
await reportViewer.ExportToAsync(ExportFormat.Pdf);
|
||||
} catch(Exception) {
|
||||
SignatureValidationMessage = "Das signierte PDF konnte nicht exportiert werden. Bitte laden Sie die Seite neu und versuchen Sie es erneut.";
|
||||
@@ -283,7 +335,8 @@
|
||||
}
|
||||
|
||||
XtraReport CreateSignedReportInstance(string signatureDataUrl, string signerFullName, string signerPosition, string signaturePlace) {
|
||||
var report = CreateReportInstance();
|
||||
var baseReportName = string.IsNullOrWhiteSpace(EnvelopeKey) ? "LargeDatasetReport" : EnvelopeKey;
|
||||
var report = ReportStorage.TryGetReport(baseReportName, out var stored) ? stored : CreateReportInstance();
|
||||
AddSignature(report, signatureDataUrl, signerFullName, signerPosition, signaturePlace);
|
||||
return report;
|
||||
}
|
||||
@@ -358,3 +411,4 @@
|
||||
bottomMargin.Controls.Remove(control);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
81
EnvelopeGenerator.ReceiverUI/Pages/TestViewer.razor
Normal file
81
EnvelopeGenerator.ReceiverUI/Pages/TestViewer.razor
Normal file
@@ -0,0 +1,81 @@
|
||||
@page "/test"
|
||||
@using System.Drawing
|
||||
@using DevExpress.Drawing
|
||||
@using DevExpress.Utils
|
||||
@using DevExpress.XtraPrinting
|
||||
@using DevExpress.XtraPrinting.Drawing
|
||||
@using XtraReport = DevExpress.XtraReports.UI.XtraReport
|
||||
@using BottomMarginBand = DevExpress.XtraReports.UI.BottomMarginBand
|
||||
@using XRLabel = DevExpress.XtraReports.UI.XRLabel
|
||||
@using XRPictureBox = DevExpress.XtraReports.UI.XRPictureBox
|
||||
@using XRControl = DevExpress.XtraReports.UI.XRControl
|
||||
@using ImageSizeMode = DevExpress.XtraPrinting.ImageSizeMode
|
||||
@using EnvelopeGenerator.ReceiverUI.Services
|
||||
@using DevExpress.Blazor.Reporting
|
||||
@inject IJSRuntime JSRuntime
|
||||
@inject InMemoryReportStorageWebExtension ReportStorage
|
||||
@inject EnvelopeGenerator.ReceiverUI.Services.DocumentService DocumentService
|
||||
@inject Microsoft.Extensions.Configuration.IConfiguration Configuration
|
||||
@inject HttpClient Http
|
||||
@using System;
|
||||
|
||||
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
|
||||
<link href="_content/DevExpress.Blazor.Reporting.Viewer/css/dx-blazor-reporting-components.bs5.css" rel="stylesheet" />
|
||||
|
||||
<div class="receiver-page-layout">
|
||||
|
||||
<div class="receiver-viewer-wrapper">
|
||||
<embed class="w-100 h-50" src="/docs/Document.pdf" type="application/pdf" />
|
||||
@if (PdfBytes is { Length: > 0 }) {
|
||||
<DxPdfViewer DocumentContent="PdfBytes" CssClass="w-100 h-50" />
|
||||
}
|
||||
else{
|
||||
<div>Not found</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
const string SignatureTabDraw = "draw";
|
||||
const string SignatureTabText = "text";
|
||||
const string SignatureTabImage = "image";
|
||||
const string DrawCanvasId = "receiver-signature-pad";
|
||||
const string TypedCanvasId = "receiver-typed-signature-pad";
|
||||
const string ImageInputId = "receiver-signature-image-input";
|
||||
const string ImageCanvasId = "receiver-image-signature-pad";
|
||||
|
||||
readonly (string Text, string Value)[] TypedSignatureFonts = {
|
||||
("Brush Script", "'Brush Script MT', cursive"),
|
||||
("Segoe Script", "'Segoe Script', cursive"),
|
||||
("Lucida Handwriting", "'Lucida Handwriting', cursive"),
|
||||
("Comic Sans", "'Comic Sans MS', cursive"),
|
||||
("Cursive", "cursive")
|
||||
};
|
||||
|
||||
[Parameter] public string? EnvelopeKey { get; set; }
|
||||
|
||||
DxReportViewer? reportViewer;
|
||||
XtraReport? Report;
|
||||
string PdfViewerUrl = string.Empty;
|
||||
byte[]? PdfBytes;
|
||||
byte[]? SignedPdfBytes;
|
||||
bool SignatureApplied;
|
||||
bool SignaturePopupVisible;
|
||||
string? PopupValidationMessage;
|
||||
string ActiveSignatureTab = SignatureTabDraw;
|
||||
string TypedSignatureText = string.Empty;
|
||||
string TypedSignatureFont = "'Brush Script MT', cursive";
|
||||
string SignerFullName = string.Empty;
|
||||
string SignerPosition = string.Empty;
|
||||
string SignaturePlace = string.Empty;
|
||||
int ViewerKey;
|
||||
|
||||
protected override async Task OnInitializedAsync() {
|
||||
PdfBytes = await Http.GetByteArrayAsync("/docs/Document.pdf");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Components.Web;
|
||||
using EnvelopeGenerator.ReceiverUI;
|
||||
using DevExpress.DataAccess.Web;
|
||||
using EnvelopeGenerator.ReceiverUI.Services;
|
||||
using EnvelopeGenerator.ReceiverUI.Options;
|
||||
using DevExpress.XtraReports.Services;
|
||||
using DevExpress.Blazor.Reporting;
|
||||
using DevExpress.XtraReports.Web.Extensions;
|
||||
@@ -11,8 +12,12 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args);
|
||||
builder.RootComponents.Add<App>("#app");
|
||||
builder.RootComponents.Add<HeadOutlet>("head::after");
|
||||
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
|
||||
builder.Services.Configure<ApiOptions>(opts =>
|
||||
builder.Configuration.GetSection(ApiOptions.SectionName).Bind(opts));
|
||||
builder.Services.AddScoped<EnvelopeGenerator.ReceiverUI.Services.DocumentService>();
|
||||
|
||||
builder.Services.AddDevExpressWebAssemblyBlazorReportViewer();
|
||||
builder.Services.AddDevExpressWebAssemblyBlazorPdfViewer();
|
||||
|
||||
builder.Services.AddDevExpressBlazorReportingWebAssembly(configure => {
|
||||
configure.UseDevelopmentMode();
|
||||
|
||||
27
EnvelopeGenerator.ReceiverUI/Services/DocumentService.cs
Normal file
27
EnvelopeGenerator.ReceiverUI/Services/DocumentService.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using Microsoft.Extensions.Options;
|
||||
using EnvelopeGenerator.ReceiverUI.Options;
|
||||
|
||||
namespace EnvelopeGenerator.ReceiverUI.Services;
|
||||
|
||||
public class DocumentService(HttpClient http, IOptions<ApiOptions> apiOptions)
|
||||
{
|
||||
private readonly ApiOptions _api = apiOptions.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the PDF bytes for the given envelope key from the API.
|
||||
/// Returns null bytes with the HTTP status code on failure.
|
||||
/// </summary>
|
||||
public async Task<(byte[]? Bytes, HttpStatusCode StatusCode)> GetDocumentAsync(string envelopeKey, CancellationToken cancel = default)
|
||||
{
|
||||
var response = await http.GetAsync($"{_api.BaseUrl}/api/Document/{Uri.EscapeDataString(envelopeKey)}", cancel);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return (null, response.StatusCode);
|
||||
|
||||
var bytes = await response.Content.ReadAsByteArrayAsync(cancel);
|
||||
return (bytes, response.StatusCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,4 +6,5 @@
|
||||
@using EnvelopeGenerator.ReceiverUI
|
||||
@using EnvelopeGenerator.ReceiverUI.Shared
|
||||
@using DevExpress.Blazor.Reporting
|
||||
@using DevExpress.Blazor.PdfViewer
|
||||
@using DevExpress.Blazor
|
||||
5
EnvelopeGenerator.ReceiverUI/wwwroot/appsettings.json
Normal file
5
EnvelopeGenerator.ReceiverUI/wwwroot/appsettings.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"Api": {
|
||||
"BaseUrl": "https://localhost:8088"
|
||||
}
|
||||
}
|
||||
BIN
EnvelopeGenerator.ReceiverUI/wwwroot/docs/Document.pdf
Normal file
BIN
EnvelopeGenerator.ReceiverUI/wwwroot/docs/Document.pdf
Normal file
Binary file not shown.
@@ -193,13 +193,12 @@ public static class Extensions
|
||||
#endregion
|
||||
|
||||
#region Envelope
|
||||
public static CreateEnvelopeCommand CreateEnvelopeCommand(this Faker fake, int userId) => new()
|
||||
public static CreateEnvelopeCommand CreateEnvelopeCommand(this Faker fake, int userId) => new CreateEnvelopeCommand()
|
||||
{
|
||||
Message = fake.Lorem.Paragraph(fake.Random.Number(2, 5)),
|
||||
Title = fake.Lorem.Words(fake.Random.Number(3, 4)).Join(" "),
|
||||
UserId = userId,
|
||||
UseSQLExecutor = false
|
||||
};
|
||||
}.WithAuth(userId);
|
||||
|
||||
public static List<CreateEnvelopeCommand> CreateEnvelopeCommands(this Faker fake, params int[] userIDs)
|
||||
=> Enumerable.Range(0, userIDs.Length)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace EnvelopeGenerator.Tests.Domain;
|
||||
|
||||
public class ConstantsTests
|
||||
{
|
||||
[TestCase(EnvelopeSigningType.ReadAndSign, EnvelopeSigningType.ReadAndSign)]
|
||||
[TestCase(EnvelopeSigningType.WetSignature, EnvelopeSigningType.WetSignature)]
|
||||
[TestCase((EnvelopeSigningType)5, EnvelopeSigningType.WetSignature)]
|
||||
public void Normalize_ReturnsExpectedValue(EnvelopeSigningType input, EnvelopeSigningType expected)
|
||||
{
|
||||
var normalized = input.Normalize();
|
||||
|
||||
Assert.That(normalized, Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
@@ -438,4 +438,10 @@ public class EnvelopeController : ViewControllerBase
|
||||
return this.ViewDocumentNotFound();
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("EnvelopeReceiverWithSecretByMediatr")]
|
||||
public async Task<IActionResult> EnvelopeReceiverWithSecretByMediatr([FromQuery] ReadEnvelopeReceiverSecretQuery q, CancellationToken cancel)
|
||||
{
|
||||
return await _mediator.Send(q, cancel) is EnvelopeReceiverSecretDto dto ? Ok(dto) : NotFound();
|
||||
}
|
||||
}
|
||||
@@ -2175,6 +2175,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Application\EnvelopeGenerator.Application.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.DependencyInjection\EnvelopeGenerator.DependencyInjection.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.Infrastructure\EnvelopeGenerator.Infrastructure.csproj" />
|
||||
<ProjectReference Include="..\EnvelopeGenerator.PdfEditor\EnvelopeGenerator.PdfEditor.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
using EnvelopeGenerator.Application.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NLog;
|
||||
using Quartz;
|
||||
@@ -9,14 +8,12 @@ using EnvelopeGenerator.Web.Models;
|
||||
using System.Text.Encodings.Web;
|
||||
using Ganss.Xss;
|
||||
using Microsoft.Extensions.Options;
|
||||
using EnvelopeGenerator.Application;
|
||||
using DigitalData.EmailProfilerDispatcher;
|
||||
using EnvelopeGenerator.Infrastructure;
|
||||
using EnvelopeGenerator.Web.Sanitizers;
|
||||
using EnvelopeGenerator.Web.Models.Annotation;
|
||||
using DigitalData.UserManager.DependencyInjection;
|
||||
using EnvelopeGenerator.Web.Middleware;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
using EnvelopeGenerator.DependencyInjection;
|
||||
using EnvelopeGenerator.Web;
|
||||
|
||||
var logger = LogManager.Setup().LoadConfigurationFromAppSettings().GetCurrentClassLogger();
|
||||
@@ -57,8 +54,6 @@ try
|
||||
});
|
||||
});
|
||||
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
|
||||
builder.ConfigureBySection<TFARegParams>();
|
||||
|
||||
// Add controllers and razor views
|
||||
@@ -95,17 +90,9 @@ try
|
||||
var connStr = config.GetConnectionString(cnnStrName)
|
||||
?? throw new InvalidOperationException($"Connection string '{cnnStrName}' is missing in the application configuration.");
|
||||
|
||||
builder.Services.AddDistributedSqlServerCache(options =>
|
||||
{
|
||||
options.ConnectionString = connStr;
|
||||
options.SchemaName = "dbo";
|
||||
options.TableName = "TBDD_CACHE";
|
||||
});
|
||||
|
||||
// Add envelope generator services
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
builder.Services.AddEnvelopeGeneratorInfrastructureServices(
|
||||
opt =>
|
||||
builder.Services.AddEnvelopeGenerator(config,
|
||||
infrastructureOptions: opt =>
|
||||
{
|
||||
opt.AddDbTriggerParams(config);
|
||||
opt.AddDbContext((provider, options) =>
|
||||
@@ -116,12 +103,16 @@ try
|
||||
.EnableSensitiveDataLogging()
|
||||
.EnableDetailedErrors();
|
||||
});
|
||||
},
|
||||
options: opt =>
|
||||
{
|
||||
opt.SqlCacheOptions = new()
|
||||
{
|
||||
ConnectionString = connStr,
|
||||
SchemaName = "dbo",
|
||||
TableName = "TBDD_CACHE"
|
||||
};
|
||||
});
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
builder.Services.AddEnvelopeGeneratorServices(config);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
builder.Services.Configure<CookiePolicyOptions>(options =>
|
||||
{
|
||||
@@ -169,22 +160,12 @@ try
|
||||
builder.Services.AddSingleton(sp => sp.GetRequiredService<IOptions<MultiCulture>>().Value);
|
||||
|
||||
// Register mail services
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
builder.Services.AddScoped<IEnvelopeMailService, EnvelopeMailService>();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
builder.Services.AddDispatcher<EGDbContext>();
|
||||
|
||||
builder.Services.AddMemoryCache();
|
||||
builder.Services.AddEnvelopeMailService();
|
||||
|
||||
builder.ConfigureBySection<CustomImages>();
|
||||
|
||||
builder.ConfigureBySection<AnnotationParams>();
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
builder.Services.AddUserManager<EGDbContext>();
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseMiddleware<ExceptionHandlingMiddleware>();
|
||||
|
||||
2
EnvelopeGenerator.Web/wwwroot/js/util.min.js
vendored
2
EnvelopeGenerator.Web/wwwroot/js/util.min.js
vendored
@@ -1 +1 @@
|
||||
function detailedCurrentDate(){return new Intl.DateTimeFormat(getCurrentCulture(),{day:"2-digit",month:"2-digit",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:"shortOffset"}).format()}function findNearest(e,t,o,n){const r=n.reduce(((n,r)=>{const i=Math.sqrt((t(e)-t(r))**2+(o(e)-o(r))**2);return i<n.dist?{dist:i,dest:r}:n}),{dist:1/0,dest:null});return r.dest}const getCurrentCulture=()=>("undefined"!=typeof localized&&localized.culture)?localized.culture:navigator.language||"en-US";const B64ToBuff=e=>new Uint8Array(Array.from(atob(e),e=>e.charCodeAt(0))).buffer;const getLocaleDateString=e=>new Date().toLocaleDateString(getCurrentCulture());
|
||||
function detailedCurrentDate(){return new Intl.DateTimeFormat(getCurrentCulture(),{day:"2-digit",month:"2-digit",year:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit",timeZoneName:"shortOffset"}).format()}function findNearest(n,t,i,r){const u=r=>Math.sqrt((t(n)-t(r))**2+(i(n)-i(r))**2);return r.reduce((n,t)=>{const i=u(t);return i<n.dist?{dist:i,dest:t}:n},{dist:Infinity,dest:null}).dest}const getCurrentCulture=()=>typeof localized!="undefined"&&localized.culture?localized.culture:navigator.language||"en-US",B64ToBuff=n=>new Uint8Array(Array.from(atob(n),n=>n.charCodeAt(0))).buffer,getLocaleDateString=()=>(new Date).toLocaleDateString(getCurrentCulture());
|
||||
@@ -35,6 +35,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.Tests", "
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.API", "EnvelopeGenerator.API\EnvelopeGenerator.API.csproj", "{EC768913-6270-14F4-1DD3-69C87A659462}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.DependencyInjection", "EnvelopeGenerator.DependencyInjection\EnvelopeGenerator.DependencyInjection.csproj", "{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EnvelopeGenerator.ReceiverUI", "EnvelopeGenerator.ReceiverUI\EnvelopeGenerator.ReceiverUI.csproj", "{FB2D306B-1042-4A70-31ED-F991A1599371}"
|
||||
EndProject
|
||||
Global
|
||||
@@ -87,6 +89,10 @@ Global
|
||||
{EC768913-6270-14F4-1DD3-69C87A659462}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{EC768913-6270-14F4-1DD3-69C87A659462}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{FB2D306B-1042-4A70-31ED-F991A1599371}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{FB2D306B-1042-4A70-31ED-F991A1599371}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FB2D306B-1042-4A70-31ED-F991A1599371}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -110,6 +116,7 @@ Global
|
||||
{211619F5-AE25-4BA5-A552-BACAFE0632D3} = {9943209E-1744-4944-B1BA-4F87FC1A0EEB}
|
||||
{224C4845-1CDE-22B7-F3A9-1FF9297F70E8} = {0CBC2432-A561-4440-89BC-671B66A24146}
|
||||
{EC768913-6270-14F4-1DD3-69C87A659462} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||
{5DCCF9A1-C03F-90E6-87D3-E96DB25250C2} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||
{FB2D306B-1042-4A70-31ED-F991A1599371} = {E3C758DC-914D-4B7E-8457-0813F1FDB0CB}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
||||
Reference in New Issue
Block a user