Compare commits

..

9 Commits

Author SHA1 Message Date
a3b33637fd Update authorization scheme for GetAsync method
Modified the `[Authorize]` attribute on the `GetAsync` method in the `EnvelopeController` class to specify `AuthenticationSchemes = AuthScheme.Sender`. This change enforces a more specific authentication requirement, allowing access only to users authenticated under the `Sender` scheme.
2026-06-11 23:04:53 +02:00
bc3134a033 Update Envelope to EnvelopeDto mapping configuration
The mapping for the `Envelope` entity to the `EnvelopeDto` was
modified to include a custom mapping for the `Receivers` property.
The `Receivers` property in `EnvelopeDto` is now populated by
mapping from the `EnvelopeReceivers` collection in the `Envelope`
entity, specifically selecting the `Receiver` property from each
`EnvelopeReceiver` object.
2026-06-11 23:04:43 +02:00
f106255c6b Add Receivers property to EnvelopeDto
Enhanced the EnvelopeDto class by introducing a new `Receivers` property of type `IEnumerable<ReceiverDto>?` to support including recipient information in the envelope DTO.

Added necessary `using` directives for `EnvelopeReceiver` and `Receiver` DTOs to ensure proper namespace references. Updated the class to accommodate this new functionality.
2026-06-11 23:04:31 +02:00
cb103dcb69 Restrict UserId visibility and update query includes
The `UserId` property in `ReadEnvelopeQuery` was changed from `public` to `internal` to improve encapsulation. A new `Authorize` method was added to set the `UserId` property using the `with` expression.

In `ReadEnvelopeQueryHandler`, the LINQ query was updated to replace the inclusion of `Documents` with `EnvelopeReceivers` and their associated `Receiver`, reflecting a shift in the data being eagerly loaded to support updated functionality.
2026-06-11 23:04:05 +02:00
8c1dd9c40d Make boolean properties nullable in Envelope class
Changed `SendReminderEmails`, `UseAccessCode`, and `TfaEnabled`
properties from non-nullable `bool` to nullable `bool?` to allow
representation of `null` values. Added conditional initialization
of `TfaEnabled` to `false` for `NETFRAMEWORK` target.
2026-06-11 23:03:35 +02:00
ee358ffaab Update project version to 1.4.2
Bump the project version from 1.4.1 to 1.4.2 in the `<Version>` tag for the NuGet package. Updated `<AssemblyVersion>` and `<FileVersion>` to `1.4.2.0` to maintain consistency with the new version. This reflects a minor version update for API compatibility and file versioning.
2026-06-11 17:14:20 +02:00
0780dbdd94 Enhance PDF viewer and add embed page with file upload
- Added CSS styles for `.pdf-viewer` and its child elements
  to ensure proper dimensions and layout for PDF display.
- Enhanced `EnvelopeReceiverPage_DxPdfViewer.razor` with
  conditional rendering for improved user feedback.
- Introduced `EnvelopeReceiverPage_embed.razor` with a new
  route `/envelope/Embed`, drag-and-drop file upload, and
  embedded PDF viewer using `<embed>`.
- Implemented default PDF loading from embedded resources
  and Base64 encoding for embedding.
- Refactored file upload handling with `OnFilesUploading`
  and centralized allowed file types logic.
- Improved user experience with success and informational
  messages for file upload and PDF viewing.
2026-06-11 17:13:17 +02:00
d722742fe8 Remove unused DevExpress Reporting CSS file
The `dx-blazor-reporting-components.bs5.css` file reference was removed from `EnvelopeReceiverPage_DxPdfViewer.razor`. This CSS file was likely used for styling DevExpress Blazor Reporting Viewer components, which are no longer needed or have been replaced.
2026-06-11 15:55:26 +02:00
8c42105f58 Add PDF viewer with drag-and-drop file upload support
Added a new Razor page `EnvelopeReceiverPage_DxPdfViewer.razor` with a route `/envelope/DxPdfViewer`. Integrated DevExpress components, including `DxPdfViewer` for displaying PDF documents and `DxFileInput` for drag-and-drop file uploads. Styled the drag-and-drop zone with custom CSS. Initialized the viewer with a default embedded PDF file and implemented logic to handle file uploads dynamically.
2026-06-11 15:33:22 +02:00
8 changed files with 253 additions and 10 deletions

View File

@@ -51,7 +51,7 @@ public class EnvelopeController : ControllerBase
/// <response code="401">Der Benutzer ist nicht authentifiziert.</response> /// <response code="401">Der Benutzer ist nicht authentifiziert.</response>
/// <response code="403">Der Benutzer hat keine Berechtigung, auf die Ressource zuzugreifen.</response> /// <response code="403">Der Benutzer hat keine Berechtigung, auf die Ressource zuzugreifen.</response>
/// <response code="500">Ein unerwarteter Fehler ist aufgetreten.</response> /// <response code="500">Ein unerwarteter Fehler ist aufgetreten.</response>
[Authorize] [Authorize(AuthenticationSchemes = AuthScheme.Sender)]
[HttpGet] [HttpGet]
public async Task<IActionResult> GetAsync([FromQuery] ReadEnvelopeQuery envelope) public async Task<IActionResult> GetAsync([FromQuery] ReadEnvelopeQuery envelope)
{ {

View File

@@ -1,9 +1,12 @@
using DigitalData.EmailProfilerDispatcher.Abstraction.Attributes; using DigitalData.EmailProfilerDispatcher.Abstraction.Attributes;
using DigitalData.UserManager.Application.DTOs.User; using DigitalData.UserManager.Application.DTOs.User;
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
using EnvelopeGenerator.Application.Common.Dto.Receiver;
using EnvelopeGenerator.Domain.Constants; using EnvelopeGenerator.Domain.Constants;
using EnvelopeGenerator.Domain.Entities; using EnvelopeGenerator.Domain.Entities;
using EnvelopeGenerator.Domain.Interfaces; using EnvelopeGenerator.Domain.Interfaces;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Text.Json.Serialization;
namespace EnvelopeGenerator.Application.Common.Dto; namespace EnvelopeGenerator.Application.Common.Dto;
@@ -126,4 +129,9 @@ public record EnvelopeDto : IEnvelope
/// ///
/// </summary> /// </summary>
public IEnumerable<DocumentDto>? Documents { get; set; } public IEnumerable<DocumentDto>? Documents { get; set; }
/// <summary>
///
/// </summary>
public IEnumerable<ReceiverDto>? Receivers { 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>(); CreateMap<Envelope, EnvelopeDto>().ForMember(dest => dest.Receivers, opt => opt.MapFrom(src => src.EnvelopeReceivers.Select(er => er.Receiver)));
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

@@ -22,7 +22,7 @@ public record ReadEnvelopeQuery : EnvelopeQueryBase, IRequest<IEnumerable<Envelo
/// <summary> /// <summary>
/// Optionaler Benutzerfilter; wenn gesetzt, werden nur Umschläge des Benutzers geladen. /// Optionaler Benutzerfilter; wenn gesetzt, werden nur Umschläge des Benutzers geladen.
/// </summary> /// </summary>
public int? UserId { get; init; } internal int? UserId { get; init; }
/// <summary> /// <summary>
/// Setzt den Benutzerkontext für die Abfrage. /// Setzt den Benutzerkontext für die Abfrage.
@@ -133,7 +133,7 @@ public class ReadEnvelopeQueryHandler : IRequestHandler<ReadEnvelopeQuery, IEnum
} }
var envelopes = await query var envelopes = await query
.Include(e => e.Documents) .Include(e => e.EnvelopeReceivers).ThenInclude(er => er.Receiver)
.ToListAsync(cancel); .ToListAsync(cancel);
return _mapper.Map<IEnumerable<EnvelopeDto>>(envelopes); return _mapper.Map<IEnumerable<EnvelopeDto>>(envelopes);

View File

@@ -94,7 +94,7 @@ namespace EnvelopeGenerator.Domain.Entities
public string Language { get; set; } public string Language { get; set; }
[Column("SEND_REMINDER_EMAILS")] [Column("SEND_REMINDER_EMAILS")]
public bool SendReminderEmails { get; set; } public bool? SendReminderEmails { get; set; }
[Column("FIRST_REMINDER_DAYS")] [Column("FIRST_REMINDER_DAYS")]
public int? FirstReminderDays { get; set; } public int? FirstReminderDays { get; set; }
@@ -114,7 +114,7 @@ namespace EnvelopeGenerator.Domain.Entities
public int? CertificationType { get; set; } public int? CertificationType { get; set; }
[Column("USE_ACCESS_CODE")] [Column("USE_ACCESS_CODE")]
public bool UseAccessCode { get; set; } public bool? UseAccessCode { get; set; }
[Column("FINAL_EMAIL_TO_CREATOR")] [Column("FINAL_EMAIL_TO_CREATOR")]
public int? FinalEmailToCreator { get; set; } public int? FinalEmailToCreator { get; set; }
@@ -132,7 +132,7 @@ namespace EnvelopeGenerator.Domain.Entities
public User User { get; set; } public User User { get; set; }
[Column("TFA_ENABLED")] [Column("TFA_ENABLED")]
public bool TfaEnabled { get; set; } public bool? TfaEnabled { get; set; }
#if NETFRAMEWORK #if NETFRAMEWORK
= false; = false;
#endif #endif

View File

@@ -14,11 +14,11 @@
<PackageIcon>Assets\icon.ico</PackageIcon> <PackageIcon>Assets\icon.ico</PackageIcon>
<PackageTags>digital data envelope generator web</PackageTags> <PackageTags>digital data envelope generator web</PackageTags>
<Description>EnvelopeGenerator.ReceiverUI is a Blazor WebAssembly 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> <Description>EnvelopeGenerator.ReceiverUI is a Blazor WebAssembly 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>
<Version>1.4.1</Version> <Version>1.4.2</Version>
<!-- NuGet package version --> <!-- NuGet package version -->
<AssemblyVersion>1.4.1.0</AssemblyVersion> <AssemblyVersion>1.4.2.0</AssemblyVersion>
<!-- Assembly version for API compatibility --> <!-- Assembly version for API compatibility -->
<FileVersion>1.4.1.0</FileVersion> <FileVersion>1.4.2.0</FileVersion>
<!-- Windows file version --> <!-- Windows file version -->
<Copyright>Copyright © 2026 Digital Data GmbH. All rights reserved.</Copyright> <Copyright>Copyright © 2026 Digital Data GmbH. All rights reserved.</Copyright>

View File

@@ -0,0 +1,112 @@
@page "/envelope/DxPdfViewer"
@using System.IO
@using DevExpress.Blazor
@using System.Reflection
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
<style>
.custom-drop-zone {
padding: 0 !important;
border-style: dashed;
border-width: 2px !important;
height: 230px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(183, 183, 183, 0.1);
}
.custom-drop-zone.custom-drop-zone-hover {
border-style: solid;
}
.custom-drop-zone svg {
width: 42px;
height: 42px;
}
.custom-drop-zone > *:not(#overviewDemoSelectButton) {
pointer-events: none;
}
.pdf-viewer {
height: 800px !important;
min-height: 800px !important;
}
.pdf-viewer .dxbrv-surface-wrapper,
.pdf-viewer .dxbrv-document-surface {
height: 100% !important;
min-height: 750px !important;
}
.pdf-viewer .dxbrv-report-preview-content {
width: auto !important;
height: auto !important;
min-width: 200px !important;
min-height: 200px !important;
}
</style>
<div id="overviewDemoDropZone" class="card custom-drop-zone rounded-3 w-100 m-0">
<span class="drop-file-icon mb-3"></span>
<span class="drop-file-label">Drag and Drop File Here</span><span class="m-1">or</span>
<DxButton Id="overviewDemoSelectButton"
CssClass="m-1"
RenderStyle="ButtonRenderStyle.Primary"
Text="Select File" />
</div>
<DxFileInput @ref="fileInput"
AcceptedFileTypes="@ALLOWED_FILE_TYPES"
AllowedFileExtensions="@ALLOWED_FILE_TYPES"
CssClass="w-100"
ExternalDropZoneCssSelector="#overviewDemoDropZone"
ExternalDropZoneDragOverCssClass="custom-drop-zone-hover"
ExternalSelectButtonCssSelector="#overviewDemoSelectButton"
FilesUploading="OnFilesUploading"
MaxFileSize="2000000">
</DxFileInput>
@if (DocumentContent != null && DocumentContent.Length > 0)
{
<div class="alert alert-success mt-3">
PDF loaded: @DocumentContent.Length bytes
</div>
<DxPdfViewer CssClass="w-100 pdf-viewer" DocumentContent="@DocumentContent" />
}
else
{
<div class="alert alert-info mt-3">
Please upload a PDF file to view it.
</div>
}
@code {
readonly List<string> ALLOWED_FILE_TYPES = new List<string> { ".pdf" };
DxFileInput fileInput;
byte[] DocumentContent { get; set; }
protected override void OnInitialized()
{
Assembly assembly = Assembly.GetExecutingAssembly();
Stream stream = assembly.GetManifestResourceStream("EnvelopeGenerator.ReceiverUI.Resources.Invoice.pdf");
if (stream != null)
{
using (stream)
using (var binaryReader = new BinaryReader(stream))
DocumentContent = binaryReader.ReadBytes((int)stream.Length);
}
}
protected async Task OnFilesUploading(FilesUploadingEventArgs args)
{
using (MemoryStream stream = new MemoryStream())
{
IFileInputSelectedFile file = args.Files[0];
await file.OpenReadStream(file.Size).CopyToAsync(stream);
DocumentContent = stream.ToArray();
await InvokeAsync(StateHasChanged);
}
}
}

View File

@@ -0,0 +1,123 @@
@page "/envelope/Embed"
@using System.IO
@using DevExpress.Blazor
@using System.Reflection
<link href="_content/DevExpress.Blazor.Themes/blazing-berry.bs5.min.css" rel="stylesheet" />
<style>
.custom-drop-zone {
padding: 0 !important;
border-style: dashed;
border-width: 2px !important;
height: 230px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(183, 183, 183, 0.1);
}
.custom-drop-zone.custom-drop-zone-hover {
border-style: solid;
}
.custom-drop-zone svg {
width: 42px;
height: 42px;
}
.custom-drop-zone > *:not(#overviewDemoSelectButton) {
pointer-events: none;
}
.pdf-viewer {
height: 800px !important;
min-height: 800px !important;
}
.pdf-viewer .dxbrv-surface-wrapper,
.pdf-viewer .dxbrv-document-surface {
height: 100% !important;
min-height: 750px !important;
}
.pdf-viewer .dxbrv-report-preview-content {
width: auto !important;
height: auto !important;
min-width: 200px !important;
min-height: 200px !important;
}
</style>
<div id="overviewDemoDropZone" class="card custom-drop-zone rounded-3 w-100 m-0">
<span class="drop-file-icon mb-3"></span>
<span class="drop-file-label">Drag and Drop File Here</span><span class="m-1">or</span>
<DxButton Id="overviewDemoSelectButton"
CssClass="m-1"
RenderStyle="ButtonRenderStyle.Primary"
Text="Select File" />
</div>
<DxFileInput @ref="fileInput"
AcceptedFileTypes="@ALLOWED_FILE_TYPES"
AllowedFileExtensions="@ALLOWED_FILE_TYPES"
CssClass="w-100"
ExternalDropZoneCssSelector="#overviewDemoDropZone"
ExternalDropZoneDragOverCssClass="custom-drop-zone-hover"
ExternalSelectButtonCssSelector="#overviewDemoSelectButton"
FilesUploading="OnFilesUploading"
MaxFileSize="2000000">
</DxFileInput>
@if (DocumentContent != null && DocumentContent.Length > 0)
{
<div class="alert alert-success mt-3">
PDF loaded: @DocumentContent.Length bytes
</div>
<embed src="@GetPdfDataUrl()" type="application/pdf" class="w-100 pdf-viewer" />
}
else
{
<div class="alert alert-info mt-3">
Please upload a PDF file to view it.
</div>
}
@code {
readonly List<string> ALLOWED_FILE_TYPES = new List<string> { ".pdf" };
DxFileInput fileInput;
byte[] DocumentContent { get; set; }
protected override void OnInitialized()
{
Assembly assembly = Assembly.GetExecutingAssembly();
Stream stream = assembly.GetManifestResourceStream("EnvelopeGenerator.ReceiverUI.Resources.Invoice.pdf");
if (stream != null)
{
using (stream)
using (var binaryReader = new BinaryReader(stream))
DocumentContent = binaryReader.ReadBytes((int)stream.Length);
}
}
protected async Task OnFilesUploading(FilesUploadingEventArgs args)
{
using (MemoryStream stream = new MemoryStream())
{
IFileInputSelectedFile file = args.Files[0];
await file.OpenReadStream(file.Size).CopyToAsync(stream);
DocumentContent = stream.ToArray();
await InvokeAsync(StateHasChanged);
}
}
private string GetPdfDataUrl()
{
if (DocumentContent == null || DocumentContent.Length == 0)
return string.Empty;
string base64 = Convert.ToBase64String(DocumentContent);
return $"data:application/pdf;base64,{base64}#toolbar=0&navpanes=0&scrollbar=1";
}
}