Compare commits

...

5 Commits

Author SHA1 Message Date
Developer 02
261d1b3db9 Verbesserte Funktionen zur Erstellung von Umschlägen und Aktualisierung von Projekten
Neue DTOs und Befehle zur Erstellung von Umschlägen in CreateEnvelope.cs hinzugefügt. Aktualisierte Projektdateien, um net7.0, net8.0 und net9.0 zu unterstützen. Refactored EnvelopeController für bessere Struktur und Fehlerbehandlung. Einführung einer Methode zur Erstellung von Umschlägen in EnvelopeReceiverController unter Verwendung von IMediator. Allgemeine Verbesserungen der Funktionalität und Kompatibilität.
2025-04-04 15:36:03 +02:00
Developer 02
401d03aac2 Verbesserung der Umschlagserstellung mit neuen DTOs und Befehlen
Einführung neuer Datenübertragungsobjekte für Signaturen und Empfänger.
Aktualisiert `CreateEnvelopeCommand` um benötigte Felder wie
`Title`, `Message`, `Document` und `Receivers`, zusammen mit optionalen
Parametern. Entfernt `EnvelopeCreateDto` für einen besser strukturierten
Ansatz zur Verwaltung von Umschlagserstellungsdaten.
2025-04-04 14:02:30 +02:00
Developer 02
7871bf72f6 Add CreateEnvelope command and DTO for envelope creation
Führt den `CreateEnvelopeCommand` Datensatz und seinen Handler ein, der die `IRequest` Schnittstelle von MediatR implementiert. Der Handler wirft derzeit eine `NotImplementedException`.

Fügt die Klasse „EnvelopeCreateDto“ mit Eigenschaften für Titel, Nachricht, Sprache, Verfallsdaten, Vertragstyp und TFA-Flag hinzu. Erforderliche Felder werden mit Datenanmerkungen validiert, und für bestimmte Eigenschaften werden Standardwerte festgelegt.
2025-04-03 17:48:49 +02:00
Developer 02
7e07afa384 Add MediatR package reference to project
This commit adds a new package reference for "MediatR"
with version "11.1.0" to the project file
`EnvelopeGenerator.Application.csproj`.
2025-04-03 13:21:08 +02:00
Developer 02
251420134a Verbesserung der Authentifizierungsmethoden und der Dokumentation
Die XML-Dokumentation für die Methode „Login“ wurde aktualisiert, um die Antwortcodes zu verdeutlichen, und es wurden Beispielanfragen hinzugefügt. Einführung einer neuen „Logout“-Methode mit entsprechender Dokumentation und Autorisierung. Hinzufügen einer Methode `IsAuthenticated` zur Überprüfung auf gültige Token, mit aktualisierter Antwortbehandlung und Dokumentation. Das `AllowAnonymous`-Attribut von `IsAuthenticated` wurde entfernt, um die Autorisierung zu erzwingen.
2025-04-03 11:12:33 +02:00
7 changed files with 216 additions and 47 deletions

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -17,6 +17,7 @@
<PackageReference Include="DigitalData.Core.Client" Version="2.0.3" />
<PackageReference Include="DigitalData.Core.DTO" Version="2.0.0" />
<PackageReference Include="DigitalData.EmailProfilerDispatcher" Version="2.0.0" />
<PackageReference Include="MediatR" Version="11.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.18" />
<PackageReference Include="Otp.NET" Version="1.4.0" />
<PackageReference Include="QRCoder" Version="1.6.0" />

View File

@@ -0,0 +1,63 @@
using MediatR;
using System.ComponentModel.DataAnnotations;
namespace EnvelopeGenerator.Application.EnvelopeReceivers.Commands;
#region DTOs
/// <summary>
/// Signaturposition auf einem Dokument.
/// </summary>
/// <param name="X">X-Position</param>
/// <param name="Y">Y-Position</param>
/// <param name="Page">Seite, auf der sie sich befindet</param>
public record Signature([Required] int X, [Required] int Y, [Required] int Page);
/// <summary>
/// DTO für Empfänger, die erstellt oder abgerufen werden sollen.
/// Wenn nicht, wird sie erstellt und mit einer Signatur versehen.
/// </summary>
/// <param name="Signatures">Unterschriften auf Dokumenten.</param>
/// <param name="Name">Der Name, mit dem der Käufer angesprochen werden soll. Bei Null oder keinem Wert wird der zuletzt verwendete Name verwendet.</param>
/// <param name="PhoneNumber">Sollte mit Vorwahl geschrieben werden</param>
public record ReceiverGetOrCreateDto([Required] IEnumerable<Signature> Signatures, string? Name = null, string? PhoneNumber = null)
{
private string _emailAddress;
/// <summary>
/// E-Mail-Adresse des Empfängers.
/// </summary>
[Required]
public required string EmailAddress { get => _emailAddress.ToLower(); init => _emailAddress.ToLower(); }
};
/// <summary>
/// DTO für die Erstellung eines Dokuments.
/// </summary>
public record DocumentCreateDto(byte[]? DataAsByte = null, string? DataAsBase64 = null);
#endregion
/// <summary>
/// Befehl zur Erstellung eines Umschlags.
/// </summary>
public record CreateEnvelopeCommand(
[Required] string Title,
[Required] string Message,
[Required] DocumentCreateDto Document,
[Required] IEnumerable<ReceiverGetOrCreateDto> Receivers,
string Language = "de-DE",
DateTime? ExpiresWhen = null,
DateTime? ExpiresWarningWhen = null,
int ContractType = (int)Common.Constants.ContractType.Contract,
bool TFAEnabled = false
) : IRequest;
/// <summary>
/// Handler für den CreateEnvelopeCommand.
/// </summary>
public class CreateEnvelopeCommandHandler : IRequestHandler<CreateEnvelopeCommand>
{
public Task<Unit> Handle(CreateEnvelopeCommand request, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

View File

@@ -41,12 +41,22 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers
/// <param name="login">Benutzeranmeldedaten (Benutzername und Passwort).</param>
/// <param name="cookie">Wenn wahr, wird das JWT-Token auch als HTTP-Only-Cookie gesendet.</param>
/// <returns>
/// Gibt eine HTTP 200 OK-Antwort mit dem JWT-Token im Antwortkörper oder als HTTP-Only-Cookie zurück, wenn 'cookie' wahr ist.
/// Gibt eine HTTP 200 oder 401.
/// </returns>
/// <remarks>
/// Sample request:
///
/// POST /api/auth?cookie=true
/// {
/// "username": "MaxMustermann",
/// "password": "Geheim123!"
/// }
///
/// </remarks>
/// <response code="200">Erfolgreiche Anmeldung. Gibt das JWT-Token im Antwortkörper oder als Cookie zurück, wenn 'cookie' wahr ist.</response>
/// <response code="401">Unbefugt. Ungültiger Benutzername oder Passwort.</response>
[ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[AllowAnonymous]
[HttpPost]
public async Task<IActionResult> Login([FromBody] Login login, [FromQuery] bool cookie = false)
@@ -112,12 +122,22 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers
/// </summary>
/// <param name="login">Benutzeranmeldedaten (Benutzername und Passwort).</param>
/// <returns>
/// Gibt eine HTTP 200 OK-Antwort als HTTP-Only-Cookie zurück.
/// Gibt eine HTTP 200 oder 401.
/// </returns>
/// <remarks>
/// Sample request:
///
/// POST /api/auth/form
/// {
/// "username": "MaxMustermann",
/// "password": "Geheim123!"
/// }
///
/// </remarks>
/// <response code="200">Erfolgreiche Anmeldung. Gibt das JWT-Token im Antwortkörper oder als Cookie zurück, wenn 'cookie' wahr ist.</response>
/// <response code="401">Unbefugt. Ungültiger Benutzername oder Passwort.</response>
[ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[AllowAnonymous]
[HttpPost]
[Route("form")]
@@ -126,6 +146,22 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers
return await Login(login, true);
}
/// <summary>
/// Entfernt das Authentifizierungs-Cookie des Benutzers (AuthCookie)
/// </summary>
/// <returns>
/// Gibt eine HTTP 200 oder 401.
/// </returns>
/// <remarks>
/// Sample request:
///
/// POST /api/auth/logout
///
/// </remarks>
/// <response code="200">Erfolgreich gelöscht, wenn der Benutzer ein berechtigtes Cookie hat.</response>
/// <response code="401">Wenn es kein zugelassenes Cookie gibt, wird „nicht zugelassen“ zurückgegeben.</response>
[ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[Authorize]
[HttpPost("logout")]
public async Task<IActionResult> Logout()
@@ -142,8 +178,22 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers
}
}
[AllowAnonymous]
/// <summary>
/// Prüft, ob der Benutzer ein autorisiertes Token hat.
/// </summary>
/// <returns>Wenn ein autorisiertes Token vorhanden ist HTTP 200 asynchron 401</returns>
/// <remarks>
/// Sample request:
///
/// GET /api/auth
///
/// </remarks>
/// <response code="200">Wenn es einen autorisierten Cookie gibt.</response>
/// <response code="401">Wenn kein Cookie vorhanden ist oder nicht autorisierte.</response>
[ProducesResponseType(typeof(string), StatusCodes.Status200OK, "text/javascript")]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[Authorize]
[HttpGet]
public IActionResult IsAuthenticated() => Ok(User.Identity?.IsAuthenticated ?? false);
public IActionResult IsAuthenticated() => Ok();
}
}

View File

@@ -3,50 +3,49 @@ using EnvelopeGenerator.Application.Contracts.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace EnvelopeGenerator.GeneratorAPI.Controllers
namespace EnvelopeGenerator.GeneratorAPI.Controllers;
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class EnvelopeController : ControllerBase
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
public class EnvelopeController : ControllerBase
private readonly ILogger<EnvelopeController> _logger;
private readonly IEnvelopeService _envelopeService;
public EnvelopeController(ILogger<EnvelopeController> logger, IEnvelopeService envelopeService)
{
private readonly ILogger<EnvelopeController> _logger;
private readonly IEnvelopeService _envelopeService;
_logger = logger;
_envelopeService = envelopeService;
}
public EnvelopeController(ILogger<EnvelopeController> logger, IEnvelopeService envelopeService)
[Authorize]
[HttpGet]
public async Task<IActionResult> GetAsync(
[FromQuery] int? min_status = null,
[FromQuery] int? max_status = null,
[FromQuery] params int[] ignore_statuses)
{
try
{
_logger = logger;
_envelopeService = envelopeService;
}
[Authorize]
[HttpGet]
public async Task<IActionResult> GetCurrentAsync(
[FromQuery] int? min_status = null,
[FromQuery] int? max_status = null,
[FromQuery] params int[] ignore_statuses)
{
try
if (User.GetId() is int intId)
return await _envelopeService.ReadByUserAsync(intId, min_status: min_status, max_status: max_status, ignore_statuses: ignore_statuses).ThenAsync(
Success: Ok,
Fail: IActionResult (msg, ntc) =>
{
_logger.LogNotice(ntc);
return StatusCode(StatusCodes.Status500InternalServerError);
});
else
{
if (User.GetId() is int intId)
return await _envelopeService.ReadByUserAsync(intId, min_status: min_status, max_status: max_status, ignore_statuses: ignore_statuses).ThenAsync(
Success: Ok,
Fail: IActionResult (msg, ntc) =>
{
_logger.LogNotice(ntc);
return StatusCode(StatusCodes.Status500InternalServerError);
});
else
{
_logger.LogError("Despite successful authorization, the 'api/envelope' route encountered an issue: the user ID is not recognized as an integer. This may be due to the removal of the ID during the creation of the claims list.");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "{Message}", ex.Message);
_logger.LogError("Despite successful authorization, the 'api/envelope' route encountered an issue: the user ID is not recognized as an integer. This may be due to the removal of the ID during the creation of the claims list.");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
catch (Exception ex)
{
_logger.LogError(ex, "{Message}", ex.Message);
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}

View File

@@ -1,6 +1,8 @@
using DigitalData.Core.DTO;
using EnvelopeGenerator.Application.Contracts.Services;
using EnvelopeGenerator.Application.EnvelopeReceivers.Commands;
using EnvelopeGenerator.Common.My.Resources;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
@@ -12,12 +14,16 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers
public class EnvelopeReceiverController : ControllerBase
{
private readonly ILogger<EnvelopeReceiverController> _logger;
private readonly IEnvelopeReceiverService _erService;
public EnvelopeReceiverController(ILogger<EnvelopeReceiverController> logger, IEnvelopeReceiverService envelopeReceiverService)
private readonly IMediator _mediator;
public EnvelopeReceiverController(ILogger<EnvelopeReceiverController> logger, IEnvelopeReceiverService envelopeReceiverService, IMediator mediator)
{
_logger = logger;
_erService = envelopeReceiverService;
_mediator = mediator;
}
[HttpGet]
@@ -94,5 +100,55 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
/// <summary>
/// Datenübertragungsobjekt mit Informationen zu Umschlägen, Empfängern und Unterschriften.
/// </summary>
/// <param name="envelope"></param>
/// <param name="cancellationToken">Token to cancel the operation</param>
/// <returns>HTTP-Antwort</returns>
/// <remarks>
/// Sample request:
///
/// POST /api/envelope
/// {
/// "title": "Vertragsdokument",
/// "message": "Bitte unterschreiben Sie dieses Dokument.",
/// "document": {
/// "dataAsBase64": "dGVzdC1iYXNlNjQtZGF0YQ=="
/// },
/// "receivers": [
/// {
/// "emailAddress": "example@example.com",
/// "signatures": [
/// {
/// "x": 100,
/// "y": 200,
/// "page": 1
/// }
/// ],
/// "name": "Max Mustermann",
/// "phoneNumber": "+49123456789"
/// }
/// ],
/// "language": "de-DE",
/// "expiresWhen": "2025-12-31T23:59:59Z",
/// "expiresWarningWhen": "2025-12-24T23:59:59Z",
/// "contractType": 1,
/// "tfaEnabled": false
/// }
///
/// </remarks>
/// <response code="202">Envelope-Erstellung und Sendeprozessbefehl erfolgreich</response>
/// <response code="400">Wenn ein Fehler im HTTP-Body auftritt</response>
/// <response code="401">Wenn kein autorisierter Token vorhanden ist</response>
/// <response code="500">Es handelt sich um einen unerwarteten Fehler. Die Protokolle sollten überprüft werden.</response>
[Authorize]
[HttpPost]
public async Task<IActionResult> CreateAsync([FromBody] CreateEnvelopeCommand envelope, CancellationToken cancellationToken)
{
await _mediator.Send(envelope, cancellationToken);
return Accepted();
}
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
<TargetFrameworks>net7.0;net8.0;net9.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>