Compare commits
15 Commits
refactor/a
...
d24049cc02
| Author | SHA1 | Date | |
|---|---|---|---|
| d24049cc02 | |||
|
|
8655d84ed5 | ||
|
|
caae986b2d | ||
|
|
af28844714 | ||
|
|
8257ed573d | ||
|
|
93488ba83e | ||
|
|
4aa889f178 | ||
|
|
60d7043164 | ||
|
|
7aa9853756 | ||
|
|
0a544cfe85 | ||
|
|
4f3c66b4f7 | ||
|
|
7271a92d32 | ||
|
|
c7275ad966 | ||
|
|
bf8115259a | ||
|
|
590ab9bf02 |
407
EnvelopeGenerator.API/Controllers/ReceiverAuthController.cs
Normal file
407
EnvelopeGenerator.API/Controllers/ReceiverAuthController.cs
Normal file
@@ -0,0 +1,407 @@
|
||||
using EnvelopeGenerator.API.Models;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Application.Common.Interfaces.Services;
|
||||
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
||||
using EnvelopeGenerator.Domain.Constants;
|
||||
using EnvelopeGenerator.API.Extensions;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using OtpNet;
|
||||
|
||||
namespace EnvelopeGenerator.API.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// REST-API für den Empfänger-Authentifizierungs-Flow.
|
||||
///
|
||||
/// Entspricht der Logik in EnvelopeGenerator.Web.Controllers.EnvelopeController
|
||||
/// (Main + LogInEnvelope), aber gibt JSON statt Views zurück.
|
||||
///
|
||||
/// Der Blazor-Client (ReceiverUI) ruft diese Endpunkte auf.
|
||||
///
|
||||
/// FLOW:
|
||||
/// 1. Client ruft GET /api/receiverauth/{key}/status → Prüft Status
|
||||
/// 2. Client ruft POST /api/receiverauth/{key}/access-code → Sendet AccessCode
|
||||
/// 3. Client ruft POST /api/receiverauth/{key}/tfa → Sendet TFA-Code
|
||||
///
|
||||
/// Nach erfolgreicher Authentifizierung wird ein Cookie gesetzt (SignInEnvelopeAsync).
|
||||
/// Danach kann der Client die Dokument-Daten über die bestehenden Envelope-Endpunkte laden.
|
||||
/// </summary>
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class ReceiverAuthController : ControllerBase
|
||||
{
|
||||
private readonly ILogger<ReceiverAuthController> _logger;
|
||||
private readonly IMediator _mediator;
|
||||
private readonly IEnvelopeReceiverService _envRcvService;
|
||||
private readonly IEnvelopeHistoryService _historyService;
|
||||
private readonly IAuthenticator _authenticator;
|
||||
private readonly IReceiverService _rcvService;
|
||||
private readonly IEnvelopeSmsHandler _envSmsHandler;
|
||||
|
||||
public ReceiverAuthController(
|
||||
ILogger<ReceiverAuthController> logger,
|
||||
IMediator mediator,
|
||||
IEnvelopeReceiverService envRcvService,
|
||||
IEnvelopeHistoryService historyService,
|
||||
IAuthenticator authenticator,
|
||||
IReceiverService rcvService,
|
||||
IEnvelopeSmsHandler envSmsHandler)
|
||||
{
|
||||
_logger = logger;
|
||||
_mediator = mediator;
|
||||
_envRcvService = envRcvService;
|
||||
_historyService = historyService;
|
||||
_authenticator = authenticator;
|
||||
_rcvService = rcvService;
|
||||
_envSmsHandler = envSmsHandler;
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// ENDPUNKT 1: STATUS PRÜFEN
|
||||
// Entspricht: Web.EnvelopeController.Main()
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>
|
||||
/// Prüft den aktuellen Status eines Umschlags für den Empfänger.
|
||||
/// Entscheidet ob: NotFound, Rejected, Signed, AccessCode nötig, oder direkt anzeigen.
|
||||
/// </summary>
|
||||
/// <param name="key">Der EnvelopeReceiver-Key aus der URL (Base64-kodiert)</param>
|
||||
/// <param name="cancel">Cancellation-Token</param>
|
||||
/// <returns>ReceiverAuthResponse mit dem aktuellen Status</returns>
|
||||
[HttpGet("{key}/status")]
|
||||
public async Task<IActionResult> GetStatus([FromRoute] string key, CancellationToken cancel)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ── Key dekodieren ──
|
||||
if (!key.TryDecode(out var decoded))
|
||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
||||
|
||||
// ── ReadOnly-Links ──
|
||||
if (decoded.GetEncodeType() == EncodeType.EnvelopeReceiverReadOnly)
|
||||
{
|
||||
return Ok(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "show_document",
|
||||
ReadOnly = true
|
||||
});
|
||||
}
|
||||
|
||||
// ── EnvelopeReceiver laden (Light-Query: ohne Documents/Elements) ──
|
||||
var er = await _mediator.Send(
|
||||
new ReadEnvelopeReceiverLightQuery { Key = key }, cancel);
|
||||
if (er is null)
|
||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
||||
|
||||
// ── Abgelehnt? ──
|
||||
var rejRcvrs = await _historyService.ReadRejectingReceivers(er.Envelope!.Id);
|
||||
if (rejRcvrs.Any())
|
||||
{
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return Ok(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "rejected",
|
||||
Title = er.Envelope.Title,
|
||||
SenderEmail = er.Envelope.User?.Email
|
||||
});
|
||||
}
|
||||
|
||||
// ── Bereits signiert? ──
|
||||
if (await _historyService.IsSigned(
|
||||
envelopeId: er.Envelope.Id,
|
||||
userReference: er.Receiver!.EmailAddress))
|
||||
{
|
||||
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return Ok(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "already_signed",
|
||||
Title = er.Envelope.Title,
|
||||
SenderEmail = er.Envelope.User?.Email
|
||||
});
|
||||
}
|
||||
|
||||
// ── Kein AccessCode nötig? → Direkt SignIn ──
|
||||
if (!er.Envelope.UseAccessCode)
|
||||
{
|
||||
(string? uuid, string? signature) = decoded.ParseEnvelopeReceiverId();
|
||||
var erSecretRes = await _envRcvService.ReadWithSecretByUuidSignatureAsync(
|
||||
uuid: uuid!, signature: signature!);
|
||||
|
||||
if (erSecretRes.IsFailed)
|
||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
||||
|
||||
await HttpContext.SignInEnvelopeAsync(erSecretRes.Data, Role.ReceiverFull);
|
||||
|
||||
return Ok(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "show_document",
|
||||
Title = er.Envelope.Title,
|
||||
Message = er.Envelope.Message,
|
||||
SenderEmail = er.Envelope.User?.Email,
|
||||
ReadOnly = er.Envelope.ReadOnly
|
||||
});
|
||||
}
|
||||
|
||||
// ── AccessCode nötig ──
|
||||
// HINWEIS: Die E-Mail mit dem AccessCode wird NICHT hier gesendet.
|
||||
// Das passiert bereits im Web-Projekt, wenn der Link generiert wird.
|
||||
// Der Blazor-Flow übernimmt erst NACH dem E-Mail-Versand.
|
||||
bool accessCodeAlreadyRequested = await _historyService.AccessCodeAlreadyRequested(
|
||||
envelopeId: er.Envelope.Id,
|
||||
userReference: er.Receiver.EmailAddress);
|
||||
|
||||
if (!accessCodeAlreadyRequested)
|
||||
{
|
||||
// AccessCode wurde noch nie angefordert — das bedeutet der Empfänger
|
||||
// kommt zum ersten Mal. Wir zeichnen es auf, aber die E-Mail
|
||||
// wurde bereits vom Web-Projekt gesendet.
|
||||
await _historyService.RecordAsync(
|
||||
er.EnvelopeId, er.Receiver.EmailAddress, EnvelopeStatus.AccessCodeRequested);
|
||||
}
|
||||
|
||||
// ── Prüfe ob der Nutzer bereits eingeloggt ist ──
|
||||
if (User.IsInRole(Role.ReceiverFull))
|
||||
{
|
||||
return Ok(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "show_document",
|
||||
Title = er.Envelope.Title,
|
||||
Message = er.Envelope.Message,
|
||||
SenderEmail = er.Envelope.User?.Email,
|
||||
ReadOnly = er.Envelope.ReadOnly
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "requires_access_code",
|
||||
Title = er.Envelope.Title,
|
||||
SenderEmail = er.Envelope.User?.Email,
|
||||
TfaEnabled = er.Envelope.TFAEnabled,
|
||||
HasPhoneNumber = er.HasPhoneNumber
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error checking status for key {Key}", key);
|
||||
return StatusCode(500, new ReceiverAuthResponse
|
||||
{
|
||||
Status = "error",
|
||||
ErrorMessage = "Ein unerwarteter Fehler ist aufgetreten."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// ENDPUNKT 2: ACCESS-CODE PRÜFEN
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>
|
||||
/// Prüft den eingegebenen Zugangscode.
|
||||
/// Bei Erfolg: SignIn oder TFA-Weiterleitung.
|
||||
/// Bei Fehler: Fehlermeldung zurückgeben.
|
||||
/// </summary>
|
||||
[HttpPost("{key}/access-code")]
|
||||
public async Task<IActionResult> SubmitAccessCode(
|
||||
[FromRoute] string key,
|
||||
[FromBody] AccessCodeRequest request,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
try
|
||||
{
|
||||
// ── Key dekodieren + Daten laden ──
|
||||
(string? uuid, string? signature) = key.DecodeEnvelopeReceiverId();
|
||||
if (uuid is null || signature is null)
|
||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
||||
|
||||
var erSecretRes = await _envRcvService.ReadWithSecretByUuidSignatureAsync(
|
||||
uuid: uuid, signature: signature);
|
||||
|
||||
if (erSecretRes.IsFailed)
|
||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
||||
|
||||
var erSecret = erSecretRes.Data;
|
||||
|
||||
// ── AccessCode prüfen ──
|
||||
if (erSecret.AccessCode != request.AccessCode)
|
||||
{
|
||||
await _historyService.RecordAsync(
|
||||
erSecret.EnvelopeId,
|
||||
erSecret.Receiver!.EmailAddress,
|
||||
EnvelopeStatus.AccessCodeIncorrect);
|
||||
|
||||
return Unauthorized(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "requires_access_code",
|
||||
Title = erSecret.Envelope!.Title,
|
||||
SenderEmail = erSecret.Envelope.User?.Email,
|
||||
TfaEnabled = erSecret.Envelope.TFAEnabled,
|
||||
HasPhoneNumber = erSecret.HasPhoneNumber,
|
||||
ErrorMessage = "Falscher Zugangscode."
|
||||
});
|
||||
}
|
||||
|
||||
// ── AccessCode korrekt ──
|
||||
await _historyService.RecordAsync(
|
||||
erSecret.EnvelopeId,
|
||||
erSecret.Receiver!.EmailAddress,
|
||||
EnvelopeStatus.AccessCodeCorrect);
|
||||
|
||||
// ── TFA erforderlich? ──
|
||||
if (erSecret.Envelope!.TFAEnabled)
|
||||
{
|
||||
var rcv = erSecret.Receiver;
|
||||
if (rcv.TotpSecretkey is null)
|
||||
{
|
||||
rcv.TotpSecretkey = _authenticator.GenerateTotpSecretKey();
|
||||
await _rcvService.UpdateAsync(rcv);
|
||||
}
|
||||
|
||||
await HttpContext.SignInEnvelopeAsync(erSecret, Role.ReceiverTFA);
|
||||
|
||||
if (request.PreferSms)
|
||||
{
|
||||
var (smsRes, expiration) = await _envSmsHandler.SendTotpAsync(erSecret);
|
||||
|
||||
return Ok(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "requires_tfa",
|
||||
TfaType = "sms",
|
||||
TfaExpiration = expiration,
|
||||
Title = erSecret.Envelope.Title,
|
||||
SenderEmail = erSecret.Envelope.User?.Email,
|
||||
HasPhoneNumber = erSecret.HasPhoneNumber
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
return Ok(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "requires_tfa",
|
||||
TfaType = "authenticator",
|
||||
Title = erSecret.Envelope.Title,
|
||||
SenderEmail = erSecret.Envelope.User?.Email,
|
||||
HasPhoneNumber = erSecret.HasPhoneNumber
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Kein TFA → Direkt SignIn ──
|
||||
await HttpContext.SignInEnvelopeAsync(erSecret, Role.ReceiverFull);
|
||||
|
||||
return Ok(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "show_document",
|
||||
Title = erSecret.Envelope.Title,
|
||||
Message = erSecret.Envelope.Message,
|
||||
SenderEmail = erSecret.Envelope.User?.Email,
|
||||
ReadOnly = erSecret.Envelope.ReadOnly
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error submitting access code for key {Key}", key);
|
||||
return StatusCode(500, new ReceiverAuthResponse
|
||||
{
|
||||
Status = "error",
|
||||
ErrorMessage = "Ein unerwarteter Fehler ist aufgetreten."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
// ENDPUNKT 3: TFA-CODE PRÜFEN
|
||||
// ══════════════════════════════════════════════════════════════
|
||||
|
||||
/// <summary>
|
||||
/// Prüft den TFA-Code (SMS oder Authenticator).
|
||||
/// Setzt voraus, dass der Nutzer bereits mit ReceiverTFA-Rolle eingeloggt ist.
|
||||
/// </summary>
|
||||
[HttpPost("{key}/tfa")]
|
||||
public async Task<IActionResult> SubmitTfaCode(
|
||||
[FromRoute] string key,
|
||||
[FromBody] TfaCodeRequest request,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!User.IsInRole(Role.ReceiverTFA))
|
||||
return Unauthorized(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "requires_access_code",
|
||||
ErrorMessage = "Bitte zuerst den Zugangscode eingeben."
|
||||
});
|
||||
|
||||
(string? uuid, string? signature) = key.DecodeEnvelopeReceiverId();
|
||||
if (uuid is null || signature is null)
|
||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
||||
|
||||
var erSecretRes = await _envRcvService.ReadWithSecretByUuidSignatureAsync(
|
||||
uuid: uuid, signature: signature);
|
||||
|
||||
if (erSecretRes.IsFailed)
|
||||
return NotFound(new ReceiverAuthResponse { Status = "not_found" });
|
||||
|
||||
var erSecret = erSecretRes.Data;
|
||||
|
||||
if (erSecret.Receiver!.TotpSecretkey is null)
|
||||
{
|
||||
_logger.LogError("TotpSecretkey is null for receiver {Signature}", signature);
|
||||
return StatusCode(500, new ReceiverAuthResponse
|
||||
{
|
||||
Status = "error",
|
||||
ErrorMessage = "TFA-Konfiguration fehlt."
|
||||
});
|
||||
}
|
||||
|
||||
bool codeValid;
|
||||
|
||||
if (request.Type == "sms")
|
||||
{
|
||||
codeValid = _envSmsHandler.VerifyTotp(request.Code, erSecret.Receiver.TotpSecretkey);
|
||||
}
|
||||
else
|
||||
{
|
||||
codeValid = _authenticator.VerifyTotp(
|
||||
request.Code,
|
||||
erSecret.Receiver.TotpSecretkey,
|
||||
window: VerificationWindow.RfcSpecifiedNetworkDelay);
|
||||
}
|
||||
|
||||
if (!codeValid)
|
||||
{
|
||||
return Unauthorized(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "requires_tfa",
|
||||
TfaType = request.Type,
|
||||
Title = erSecret.Envelope!.Title,
|
||||
SenderEmail = erSecret.Envelope.User?.Email,
|
||||
HasPhoneNumber = erSecret.HasPhoneNumber,
|
||||
ErrorMessage = "Falscher Code."
|
||||
});
|
||||
}
|
||||
|
||||
await HttpContext.SignInEnvelopeAsync(erSecret, Role.ReceiverFull);
|
||||
|
||||
return Ok(new ReceiverAuthResponse
|
||||
{
|
||||
Status = "show_document",
|
||||
Title = erSecret.Envelope!.Title,
|
||||
Message = erSecret.Envelope.Message,
|
||||
SenderEmail = erSecret.Envelope.User?.Email,
|
||||
ReadOnly = erSecret.Envelope.ReadOnly
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error submitting TFA code for key {Key}", key);
|
||||
return StatusCode(500, new ReceiverAuthResponse
|
||||
{
|
||||
Status = "error",
|
||||
ErrorMessage = "Ein unerwarteter Fehler ist aufgetreten."
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
78
EnvelopeGenerator.API/Models/ReceiverAuthResponse.cs
Normal file
78
EnvelopeGenerator.API/Models/ReceiverAuthResponse.cs
Normal file
@@ -0,0 +1,78 @@
|
||||
namespace EnvelopeGenerator.API.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Einheitliche Antwort des ReceiverAuthControllers.
|
||||
///
|
||||
/// WARUM ein einziges Response-Objekt für alle Endpunkte?
|
||||
/// - Der Client braucht nur ein Format zu verstehen
|
||||
/// - Der Status-String bestimmt, welche Felder relevant sind
|
||||
/// - Entspricht dem, was der Web-Controller bisher über ViewData verteilt hat
|
||||
///
|
||||
/// Status-Werte und was sie bedeuten:
|
||||
/// - "requires_access_code" → AccessCode-Eingabe zeigen
|
||||
/// - "requires_tfa" → TFA-Code-Eingabe zeigen (nach AccessCode)
|
||||
/// - "show_document" → Dokument laden und anzeigen
|
||||
/// - "already_signed" → Info-Seite "Bereits unterschrieben"
|
||||
/// - "rejected" → Info-Seite "Abgelehnt"
|
||||
/// - "not_found" → Fehler-Seite "Nicht gefunden"
|
||||
/// - "expired" → Fehler-Seite "Link abgelaufen"
|
||||
/// </summary>
|
||||
public class ReceiverAuthResponse
|
||||
{
|
||||
/// <summary>Aktueller Status des Empfänger-Flows</summary>
|
||||
public required string Status { get; init; }
|
||||
|
||||
/// <summary>Titel des Umschlags (z.B. "Vertragsdokument")</summary>
|
||||
public string? Title { get; init; }
|
||||
|
||||
/// <summary>Nachricht des Absenders</summary>
|
||||
public string? Message { get; init; }
|
||||
|
||||
/// <summary>E-Mail des Absenders (für Rückfragen-Hinweis)</summary>
|
||||
public string? SenderEmail { get; init; }
|
||||
|
||||
/// <summary>Name des Empfängers</summary>
|
||||
public string? ReceiverName { get; init; }
|
||||
|
||||
/// <summary>Ob TFA für diesen Umschlag aktiviert ist</summary>
|
||||
public bool TfaEnabled { get; init; }
|
||||
|
||||
/// <summary>Ob der Empfänger eine Telefonnummer hat (für SMS-TFA)</summary>
|
||||
public bool HasPhoneNumber { get; init; }
|
||||
|
||||
/// <summary>Ob das Dokument nur gelesen werden soll (ReadAndConfirm)</summary>
|
||||
public bool ReadOnly { get; init; }
|
||||
|
||||
/// <summary>TFA-Typ: "sms" oder "authenticator" (wenn Status = "requires_tfa")</summary>
|
||||
public string? TfaType { get; init; }
|
||||
|
||||
/// <summary>Ablaufzeit des SMS-Codes (für Countdown-Timer)</summary>
|
||||
public DateTime? TfaExpiration { get; init; }
|
||||
|
||||
/// <summary>Fehlermeldung (z.B. "Falscher Zugangscode")</summary>
|
||||
public string? ErrorMessage { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request-Body für POST /api/receiverauth/{key}/access-code
|
||||
/// </summary>
|
||||
public class AccessCodeRequest
|
||||
{
|
||||
/// <summary>Der vom Empfänger eingegebene Zugangscode</summary>
|
||||
public required string AccessCode { get; init; }
|
||||
|
||||
/// <summary>Ob SMS statt Authenticator bevorzugt wird</summary>
|
||||
public bool PreferSms { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Request-Body für POST /api/receiverauth/{key}/tfa
|
||||
/// </summary>
|
||||
public class TfaCodeRequest
|
||||
{
|
||||
/// <summary>Der eingegebene TFA-Code (6-stellig)</summary>
|
||||
public required string Code { get; init; }
|
||||
|
||||
/// <summary>"sms" oder "authenticator"</summary>
|
||||
public required string Type { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using AutoMapper;
|
||||
using DigitalData.Core.Abstraction.Application.Repository;
|
||||
using EnvelopeGenerator.Application.Common.Dto.EnvelopeReceiver;
|
||||
using EnvelopeGenerator.Application.Common.Extensions;
|
||||
using EnvelopeGenerator.Application.Common.Query;
|
||||
using EnvelopeGenerator.Application.Envelopes.Queries;
|
||||
using EnvelopeGenerator.Application.Receivers.Queries;
|
||||
using EnvelopeGenerator.Domain.Entities;
|
||||
using MediatR;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// Leichte Variante von <see cref="ReadEnvelopeReceiverQuery"/>:
|
||||
/// Lädt NUR Envelope (mit Histories + User) und Receiver.
|
||||
/// Lädt NICHT Documents/Elements.
|
||||
///
|
||||
/// Verwendung: Status-Prüfungen wo keine Dokument-Daten benötigt werden
|
||||
/// (z.B. ReceiverAuthController.GetStatus).
|
||||
/// </summary>
|
||||
public record ReadEnvelopeReceiverLightQuery
|
||||
: EnvelopeReceiverQueryBase<ReadEnvelopeQuery, ReadReceiverQuery>, IRequest<EnvelopeReceiverDto?>;
|
||||
|
||||
/// <summary>
|
||||
/// Handler für <see cref="ReadEnvelopeReceiverLightQuery"/>.
|
||||
/// </summary>
|
||||
public class ReadEnvelopeReceiverLightQueryHandler
|
||||
: IRequestHandler<ReadEnvelopeReceiverLightQuery, EnvelopeReceiverDto?>
|
||||
{
|
||||
private readonly IRepository<EnvelopeReceiver> _repo;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
/// <summary>
|
||||
/// Konstruktor.
|
||||
/// </summary>
|
||||
/// <param name="repo">EnvelopeReceiver-Repository</param>
|
||||
/// <param name="mapper">AutoMapper</param>
|
||||
public ReadEnvelopeReceiverLightQueryHandler(IRepository<EnvelopeReceiver> repo, IMapper mapper)
|
||||
{
|
||||
_repo = repo;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lädt einen EnvelopeReceiver ohne Documents/Elements.
|
||||
/// </summary>
|
||||
public async Task<EnvelopeReceiverDto?> Handle(ReadEnvelopeReceiverLightQuery request, CancellationToken cancel)
|
||||
{
|
||||
var envRcv = await _repo.Query
|
||||
.Where(request, notnull: false)
|
||||
.AsNoTracking()
|
||||
.Include(er => er.Envelope).ThenInclude(e => e!.Histories)
|
||||
.Include(er => er.Envelope).ThenInclude(e => e!.User)
|
||||
.Include(er => er.Receiver)
|
||||
.FirstOrDefaultAsync(cancel);
|
||||
|
||||
return envRcv is null ? null : _mapper.Map<EnvelopeReceiverDto>(envRcv);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user