From ff5582c9b563a8799b37d2eb89281338489f809b Mon Sep 17 00:00:00 2001 From: TekH Date: Tue, 27 Oct 2026 18:32:47 +0100 Subject: [PATCH] Add RemoveEnvelopeReceiverCommand and refactor controller Introduced `RemoveEnvelopeReceiverCommand` and its handler to facilitate the removal of envelope receivers. Updated the `EnvelopeReceiverController` to use C# 9's record-like syntax for dependency injection, eliminating explicit private fields. Added a `RemoveAsync` method to handle DELETE requests for removing envelope receivers. Enhanced code documentation with additional XML comments. --- .../Commands/RemoveEnvelopeReceiverCommand.cs | 84 +++++++++++++++++++ .../Controllers/EnvelopeReceiverController.cs | 79 +++++++++-------- 2 files changed, 127 insertions(+), 36 deletions(-) create mode 100644 EnvelopeGenerator.Application/EnvelopeReceivers/Commands/RemoveEnvelopeReceiverCommand.cs diff --git a/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/RemoveEnvelopeReceiverCommand.cs b/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/RemoveEnvelopeReceiverCommand.cs new file mode 100644 index 00000000..b64e4e43 --- /dev/null +++ b/EnvelopeGenerator.Application/EnvelopeReceivers/Commands/RemoveEnvelopeReceiverCommand.cs @@ -0,0 +1,84 @@ +using DigitalData.Core.Abstraction.Application.Repository; +using DigitalData.Core.Exceptions; +using EnvelopeGenerator.Domain.Constants; +using EnvelopeGenerator.Domain.Entities; +using EnvelopeGenerator.Domain.Exceptions; +using MediatR; +using Microsoft.EntityFrameworkCore; + +namespace EnvelopeGenerator.Application.EnvelopeReceivers.Commands; + +/// +/// +/// +public record RemoveEnvelopeReceiverCommand : IRequest +{ + /// + /// + /// + public required int EnvelopeId { get; init; } + + /// + /// + /// + public required int ReceiverId { get; init; } + + internal int? UserId { get; private set; } + + /// + /// + /// + /// + /// + public RemoveEnvelopeReceiverCommand Authenticated(int userId) + { + UserId = userId; + return this; + } +} + +/// +/// +/// +/// +public class RemoveEnvelopeReceiverCommandHandler( + IRepository Repo, + IRepository ElementRepo, + IRepository AnnotRepo) : IRequestHandler +{ + /// + /// + /// + /// + /// + /// + /// + public async Task Handle(RemoveEnvelopeReceiverCommand request, CancellationToken cancel) + { + var er = await Repo.Query + .Where(er => er.EnvelopeId == request.EnvelopeId && er.ReceiverId == request.ReceiverId) + .Include(er => er.Envelope) + .SingleOrDefaultAsync(cancel) + ?? throw new NotFoundException(); + + if (er.Envelope is null) + throw new InvalidOperationException($"In {nameof(RemoveEnvelopeReceiverCommandHandler)} EnvelopeReceiver.Envelope is null." + + $"This should not happen." + + $"EnvelopeId: {request.EnvelopeId}, ReceiverId: {request.ReceiverId}"); + + if (request.UserId is int userId && er.Envelope.UserId != userId) + throw new AuthorizationException("Cannot remove receiver from envelope that you do not own."); + + if (er.Envelope.Status > EnvelopeStatus.EnvelopeSaved) + throw new ForbiddenException("Cannot remove receiver from envelope that is already processed."); + + // 1. Delete all ElementAnnotations for this EnvelopeReceiver + await AnnotRepo.DeleteAsync(ann => ann.Element.Document.EnvelopeId == request.EnvelopeId && ann.Element.ReceiverId == request.ReceiverId, cancel); + + // 2. Delete all DocReceiverElements for this EnvelopeReceiver + await ElementRepo.DeleteAsync(el => el.Document.EnvelopeId == request.EnvelopeId && el.ReceiverId == request.ReceiverId, cancel); + + // 3. Delete the EnvelopeReceiver + await Repo.DeleteAsync(er => er.EnvelopeId == request.EnvelopeId && er.ReceiverId == request.ReceiverId, cancel); + } +} \ No newline at end of file diff --git a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/EnvelopeReceiverController.cs b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/EnvelopeReceiverController.cs index fe85f9ed..7e1c313f 100644 --- a/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/EnvelopeReceiverController.cs +++ b/EnvelopeGenerator.Server/EnvelopeGenerator.Server/Controllers/EnvelopeReceiverController.cs @@ -17,6 +17,7 @@ using EnvelopeGenerator.Domain.Constants; using EnvelopeGenerator.Application.EnvelopeReceivers.Commands.CreateEnvelopeReceiver; using EnvelopeGenerator.Application.Common.Extensions; using EnvelopeGenerator.Server.Options; +using EnvelopeGenerator.Application.EnvelopeReceivers.Commands; namespace EnvelopeGenerator.Server.Controllers; @@ -26,34 +27,16 @@ namespace EnvelopeGenerator.Server.Controllers; /// /// Dieser Controller bietet Endpunkte für das Abrufen und Verwalten von Umschlagempfängerdaten. /// +/// +/// Konstruktor für den EnvelopeReceiverController. +/// [Route("api/[controller]")] [Authorize] [ApiController] -public class EnvelopeReceiverController : ControllerBase +public class EnvelopeReceiverController(ILogger Logger, IMediator Mediator, IMapper Mapper, IEnvelopeExecutor EnvelopeExecutor, IEnvelopeReceiverExecutor ER_Executor, IDocumentExecutor DocExecutor, IOptions csOpt, IOptionsMonitor senderFeatAccessOpt) : ControllerBase { - private readonly ILogger _logger; - private readonly IMediator _mediator; - private readonly IMapper _mapper; - private readonly IEnvelopeExecutor _envelopeExecutor; - private readonly IEnvelopeReceiverExecutor _erExecutor; - private readonly IDocumentExecutor _documentExecutor; - private readonly string _cnnStr; - private readonly IOptionsMonitor _senderFeatureAccessOptions; - - /// - /// Konstruktor für den EnvelopeReceiverController. - /// - public EnvelopeReceiverController(ILogger logger, IMediator mediator, IMapper mapper, IEnvelopeExecutor envelopeExecutor, IEnvelopeReceiverExecutor erExecutor, IDocumentExecutor documentExecutor, IOptions csOpt, IOptionsMonitor senderFeatureAccessOptions) - { - _logger = logger; - _mediator = mediator; - _mapper = mapper; - _envelopeExecutor = envelopeExecutor; - _erExecutor = erExecutor; - _documentExecutor = documentExecutor; - _cnnStr = csOpt.Value.Value; - _senderFeatureAccessOptions = senderFeatureAccessOptions; - } + private readonly string _cnnStr = csOpt.Value.Value; + private readonly IOptionsMonitor SenderFeatAccessOpt = senderFeatAccessOpt; /// /// Ruft eine Liste von Umschlagempfängern basierend auf den angegebenen Abfrageparametern ab. @@ -73,7 +56,7 @@ public class EnvelopeReceiverController : ControllerBase { envelopeReceiver = envelopeReceiver with { Username = User.GetUsername() }; - var result = await _mediator.Send(envelopeReceiver); + var result = await Mediator.Send(envelopeReceiver); return Ok(result); } @@ -88,7 +71,7 @@ public class EnvelopeReceiverController : ControllerBase [HttpGet("{envelopeKey}")] public async Task GetEnvelopeReceiverOfReceiver([FromRoute] string envelopeKey, CancellationToken cancel) { - var er = await _mediator.Send(new ReadEnvelopeReceiverQuery() + var er = await Mediator.Send(new ReadEnvelopeReceiverQuery() { Key = envelopeKey }, cancel); @@ -112,10 +95,16 @@ public class EnvelopeReceiverController : ControllerBase [HttpGet("salute")] public async Task GetReceiverName([FromQuery] ReadReceiverNameQuery receiver) { - var name = await _mediator.Send(receiver); + var name = await Mediator.Send(receiver); return name is null ? NotFound() : Ok(name); } + /// + /// + /// + /// + /// + /// [Authorize(AuthenticationSchemes = AuthScheme.Sender)] [HttpGet("receiver-envelope-url")] public IActionResult GetReceiverEnvelopeUrl([FromQuery] string envelopeUuid, [FromQuery] string receiverSignature) @@ -132,6 +121,10 @@ public class EnvelopeReceiverController : ControllerBase return Ok(new { url = absoluteUrl }); } + /// + /// + /// + /// [Authorize(AuthenticationSchemes = AuthScheme.Sender)] [HttpGet("receiver-envelope-url/enabled")] public IActionResult GetReceiverEnvelopeUrlFeatureEnabled() @@ -145,7 +138,7 @@ public class EnvelopeReceiverController : ControllerBase if (string.IsNullOrWhiteSpace(senderEmail)) return false; - var allowedEmails = _senderFeatureAccessOptions.CurrentValue.AllowedReceiverEnvelopeUrlEmails; + var allowedEmails = SenderFeatAccessOpt.CurrentValue.AllowedReceiverEnvelopeUrlEmails; return allowedEmails.Any(email => string.Equals(email?.Trim(), senderEmail, StringComparison.OrdinalIgnoreCase)); } @@ -159,7 +152,7 @@ public class EnvelopeReceiverController : ControllerBase [HttpGet("recent-suggestions")] public async Task GetRecentSuggestions([FromQuery] ReadRecentReceiverSuggestionsQuery query, CancellationToken cancel) { - var result = await _mediator.Send(query.ForSender(User.GetUsername()), cancel); + var result = await Mediator.Send(query.ForSender(User.GetUsername()), cancel); return Ok(result); } @@ -206,7 +199,7 @@ public class EnvelopeReceiverController : ControllerBase public async Task CreateAsync([FromBody] CreateEnvelopeReceiverCommand request, CancellationToken cancel) { #region Create Envelope - var envelope = await _envelopeExecutor.CreateEnvelopeAsync(User.GetId(), request.Title, request.Message, request.TFAEnabled, cancel); + var envelope = await EnvelopeExecutor.CreateEnvelopeAsync(User.GetId(), request.Title, request.Message, request.TFAEnabled, cancel); #endregion #region Add receivers @@ -215,7 +208,7 @@ public class EnvelopeReceiverController : ControllerBase foreach (var receiver in request.Receivers) { - var envelopeReceiver = await _erExecutor.AddEnvelopeReceiverAsync(envelope.Uuid, receiver.EmailAddress, receiver.Salution, receiver.PhoneNumber, cancel); + var envelopeReceiver = await ER_Executor.AddEnvelopeReceiverAsync(envelope.Uuid, receiver.EmailAddress, receiver.Salution, receiver.PhoneNumber, cancel); if (envelopeReceiver is null) unsentReceivers.Add(receiver); @@ -223,13 +216,13 @@ public class EnvelopeReceiverController : ControllerBase sentReceivers.Add(envelopeReceiver); } - var res = _mapper.Map(envelope); + var res = Mapper.Map(envelope); res.UnsentReceivers = unsentReceivers; - res.SentReceiver = _mapper.Map>(sentReceivers.Select(er => er.Receiver)); + res.SentReceiver = Mapper.Map>(sentReceivers.Select(er => er.Receiver)); #endregion #region Add document - var document = await _documentExecutor.CreateDocumentAsync(request.Document.DataAsBase64, envelope.Uuid, cancel); + var document = await DocExecutor.CreateDocumentAsync(request.Document.DataAsBase64, envelope.Uuid, cancel); if (document is null) return StatusCode(StatusCodes.Status500InternalServerError, "Document creation is failed."); @@ -266,7 +259,7 @@ public class EnvelopeReceiverController : ControllerBase { bool outSuccess = reader.GetBoolean(0); if (!outSuccess) - _logger.LogWarning( + Logger.LogWarning( "PRSIG_API_ADD_DOC_RECEIVER_ELEM returned OUT_SUCCESS=false. DOC_ID={DocId}, RECEIVER_ID={ReceiverId}, Page={Page}", document.Id, rcv.Id, sign.Page); } @@ -298,7 +291,7 @@ public class EnvelopeReceiverController : ControllerBase { bool outSuccess = reader.GetBoolean(0); if (!outSuccess) - _logger.LogWarning( + Logger.LogWarning( "PRSIG_API_ADD_HISTORY_STATE returned OUT_SUCCESS=false. EnvelopeUuid={EnvelopeUuid}", envelope.Uuid); } @@ -329,4 +322,18 @@ public class EnvelopeReceiverController : ControllerBase } } + /// + /// + /// + /// + /// + /// + [HttpDelete] + [Authorize(AuthenticationSchemes = AuthScheme.Sender)] + public async Task RemoveAsync([FromQuery] RemoveEnvelopeReceiverCommand request, CancellationToken cancel) + { + request = request.Authenticated(User.GetId()); + await Mediator.Send(request, cancel); + return Ok(); + } }