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.
340 lines
13 KiB
C#
340 lines
13 KiB
C#
using AutoMapper;
|
|
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
|
|
using EnvelopeGenerator.Application.Envelopes.Queries;
|
|
using EnvelopeGenerator.Domain.Entities;
|
|
using EnvelopeGenerator.Server.Models;
|
|
using MediatR;
|
|
using Microsoft.AspNetCore.Authorization;
|
|
using Microsoft.AspNetCore.Mvc;
|
|
using Microsoft.Data.SqlClient;
|
|
using Microsoft.Extensions.Options;
|
|
using System.Data;
|
|
using EnvelopeGenerator.Application.Common.SQL;
|
|
using EnvelopeGenerator.Application.Common.Dto.Receiver;
|
|
using EnvelopeGenerator.Application.Common.Interfaces.SQLExecutor;
|
|
using EnvelopeGenerator.Server.Extensions;
|
|
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;
|
|
|
|
/// <summary>
|
|
/// Controller für die Verwaltung von Umschlagempfängern.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Dieser Controller bietet Endpunkte für das Abrufen und Verwalten von Umschlagempfängerdaten.
|
|
/// </remarks>
|
|
/// <remarks>
|
|
/// Konstruktor für den EnvelopeReceiverController.
|
|
/// </remarks>
|
|
[Route("api/[controller]")]
|
|
[Authorize]
|
|
[ApiController]
|
|
public class EnvelopeReceiverController(ILogger<EnvelopeReceiverController> Logger, IMediator Mediator, IMapper Mapper, IEnvelopeExecutor EnvelopeExecutor, IEnvelopeReceiverExecutor ER_Executor, IDocumentExecutor DocExecutor, IOptions<ConnectionString> csOpt, IOptionsMonitor<SenderFeatureAccessOptions> senderFeatAccessOpt) : ControllerBase
|
|
{
|
|
private readonly string _cnnStr = csOpt.Value.Value;
|
|
private readonly IOptionsMonitor<SenderFeatureAccessOptions> SenderFeatAccessOpt = senderFeatAccessOpt;
|
|
|
|
/// <summary>
|
|
/// Ruft eine Liste von Umschlagempfängern basierend auf den angegebenen Abfrageparametern ab.
|
|
/// </summary>
|
|
/// <param name="envelopeReceiver">Die Abfrageparameter für die Filterung von Umschlagempfängern.</param>
|
|
/// <returns>Eine HTTP-Antwort mit der Liste der gefundenen Umschlagempfänger oder einem Fehlerstatus.</returns>
|
|
/// <remarks>
|
|
/// Dieser Endpunkt ermöglicht es, Umschlagempfänger basierend auf dem Benutzernamen und optionalen Statusfiltern abzurufen.
|
|
/// Wenn der Benutzername nicht ermittelt werden kann, wird ein Serverfehler zurückgegeben.
|
|
/// </remarks>
|
|
/// <response code="200">Die Liste der Umschlagempfänger wurde erfolgreich abgerufen.</response>
|
|
/// <response code="401">Wenn kein autorisierter Token vorhanden ist</response>
|
|
/// <response code="500">Ein unerwarteter Fehler ist aufgetreten.</response>
|
|
[Authorize]
|
|
[HttpGet]
|
|
public async Task<IActionResult> GetEnvelopeReceiver([FromQuery] ReadEnvelopeReceiverQuery envelopeReceiver)
|
|
{
|
|
envelopeReceiver = envelopeReceiver with { Username = User.GetUsername() };
|
|
|
|
var result = await Mediator.Send(envelopeReceiver);
|
|
|
|
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.SingleOrDefault());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ruft den Namen des zuletzt verwendeten Empfängers basierend auf der angegebenen E-Mail-Adresse ab.
|
|
/// </summary>
|
|
/// <param name="receiver">Abfrage, bei der nur eine der Angaben ID, Signatur oder E-Mail-Adresse des Empfängers eingegeben werden muss.</param>
|
|
/// <returns>Eine HTTP-Antwort mit dem Namen des Empfängers oder einem Fehlerstatus.</returns>
|
|
/// <remarks>
|
|
/// Dieser Endpunkt ermöglicht es, den Namen des zuletzt verwendeten Empfängers basierend auf der E-Mail-Adresse abzurufen.
|
|
/// </remarks>
|
|
/// <response code="200">Der Name des Empfängers wurde erfolgreich abgerufen.</response>
|
|
/// <response code="401">Wenn kein autorisierter Token vorhanden ist</response>
|
|
/// <response code="404">Kein Empfänger gefunden.</response>
|
|
/// <response code="500">Ein unerwarteter Fehler ist aufgetreten.</response>
|
|
[Authorize]
|
|
[HttpGet("salute")]
|
|
public async Task<IActionResult> GetReceiverName([FromQuery] ReadReceiverNameQuery receiver)
|
|
{
|
|
var name = await Mediator.Send(receiver);
|
|
return name is null ? NotFound() : Ok(name);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="envelopeUuid"></param>
|
|
/// <param name="receiverSignature"></param>
|
|
/// <returns></returns>
|
|
[Authorize(AuthenticationSchemes = AuthScheme.Sender)]
|
|
[HttpGet("receiver-envelope-url")]
|
|
public IActionResult GetReceiverEnvelopeUrl([FromQuery] string envelopeUuid, [FromQuery] string receiverSignature)
|
|
{
|
|
if (!CanUseReceiverEnvelopeUrlFeature())
|
|
return Forbid();
|
|
|
|
if (string.IsNullOrWhiteSpace(envelopeUuid) || string.IsNullOrWhiteSpace(receiverSignature))
|
|
return BadRequest("Envelope uuid and receiver signature are required.");
|
|
|
|
var envelopeKey = (envelopeUuid, receiverSignature).ToEnvelopeKey();
|
|
var absoluteUrl = $"{Request.Scheme}://{Request.Host}/envelope/{Uri.EscapeDataString(envelopeKey)}";
|
|
|
|
return Ok(new { url = absoluteUrl });
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
[Authorize(AuthenticationSchemes = AuthScheme.Sender)]
|
|
[HttpGet("receiver-envelope-url/enabled")]
|
|
public IActionResult GetReceiverEnvelopeUrlFeatureEnabled()
|
|
{
|
|
return Ok(new { enabled = CanUseReceiverEnvelopeUrlFeature() });
|
|
}
|
|
|
|
private bool CanUseReceiverEnvelopeUrlFeature()
|
|
{
|
|
var senderEmail = User.GetEmail();
|
|
if (string.IsNullOrWhiteSpace(senderEmail))
|
|
return false;
|
|
|
|
var allowedEmails = SenderFeatAccessOpt.CurrentValue.AllowedReceiverEnvelopeUrlEmails;
|
|
return allowedEmails.Any(email => string.Equals(email?.Trim(), senderEmail, StringComparison.OrdinalIgnoreCase));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns recent receiver suggestions for the authenticated sender.
|
|
/// </summary>
|
|
/// <param name="query">Suggestion query options (take 3..5 is applied in handler).</param>
|
|
/// <param name="cancel">Cancellation token.</param>
|
|
/// <returns>List of recent distinct receiver suggestions.</returns>
|
|
[Authorize(AuthenticationSchemes = AuthScheme.Sender)]
|
|
[HttpGet("recent-suggestions")]
|
|
public async Task<IActionResult> GetRecentSuggestions([FromQuery] ReadRecentReceiverSuggestionsQuery query, CancellationToken cancel)
|
|
{
|
|
var result = await Mediator.Send(query.ForSender(User.GetUsername()), cancel);
|
|
return Ok(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Datenübertragungsobjekt mit Informationen zu Umschlägen, Empfängern und Unterschriften.
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="cancel"></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"
|
|
/// }
|
|
/// ],
|
|
/// "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(AuthenticationSchemes = AuthScheme.Sender)]
|
|
[HttpPost]
|
|
public async Task<IActionResult> CreateAsync([FromBody] CreateEnvelopeReceiverCommand request, CancellationToken cancel)
|
|
{
|
|
#region Create Envelope
|
|
var envelope = await EnvelopeExecutor.CreateEnvelopeAsync(User.GetId(), request.Title, request.Message, request.TFAEnabled, cancel);
|
|
#endregion
|
|
|
|
#region Add receivers
|
|
List<EnvelopeReceiver> sentReceivers = new();
|
|
List<ReceiverGetOrCreateCommand> unsentReceivers = new();
|
|
|
|
foreach (var receiver in request.Receivers)
|
|
{
|
|
var envelopeReceiver = await ER_Executor.AddEnvelopeReceiverAsync(envelope.Uuid, receiver.EmailAddress, receiver.Salution, receiver.PhoneNumber, cancel);
|
|
|
|
if (envelopeReceiver is null)
|
|
unsentReceivers.Add(receiver);
|
|
else
|
|
sentReceivers.Add(envelopeReceiver);
|
|
}
|
|
|
|
var res = Mapper.Map<CreateEnvelopeReceiverResponse>(envelope);
|
|
res.UnsentReceivers = unsentReceivers;
|
|
res.SentReceiver = Mapper.Map<List<ReceiverDto>>(sentReceivers.Select(er => er.Receiver));
|
|
#endregion
|
|
|
|
#region Add document
|
|
var document = await DocExecutor.CreateDocumentAsync(request.Document.DataAsBase64, envelope.Uuid, cancel);
|
|
|
|
if (document is null)
|
|
return StatusCode(StatusCodes.Status500InternalServerError, "Document creation is failed.");
|
|
#endregion
|
|
|
|
#region Add document element
|
|
// @DOC_ID, @RECEIVER_ID, @POSITION_X, @POSITION_Y, @PAGE
|
|
string sql = @"
|
|
DECLARE @OUT_SUCCESS bit;
|
|
|
|
EXEC [dbo].[PRSIG_API_ADD_DOC_RECEIVER_ELEM]
|
|
{0},
|
|
{1},
|
|
{2},
|
|
{3},
|
|
{4},
|
|
@OUT_SUCCESS OUTPUT;
|
|
|
|
SELECT @OUT_SUCCESS as [@OUT_SUCCESS];";
|
|
|
|
foreach (var rcv in res.SentReceiver)
|
|
foreach (var sign in request.Receivers.Where(r => r.EmailAddress == rcv.EmailAddress).FirstOrDefault()?.DocReceiverElements ?? Enumerable.Empty<DocReceiverElementCreateDto>())
|
|
{
|
|
using SqlConnection conn = new(_cnnStr);
|
|
conn.Open();
|
|
|
|
var formattedSQL = string.Format(sql, document.Id.ToSqlParam(), rcv.Id.ToSqlParam(), sign.X.ToSqlParam(), sign.Y.ToSqlParam(), sign.Page.ToSqlParam());
|
|
|
|
using SqlCommand cmd = new(formattedSQL, conn);
|
|
cmd.CommandType = CommandType.Text;
|
|
|
|
using SqlDataReader reader = cmd.ExecuteReader();
|
|
if (reader.Read())
|
|
{
|
|
bool outSuccess = reader.GetBoolean(0);
|
|
if (!outSuccess)
|
|
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);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Create history
|
|
// ENV_UID, STATUS_ID, USER_ID,
|
|
string sql_hist = @"
|
|
DECLARE @OUT_SUCCESS bit;
|
|
|
|
EXEC [dbo].[PRSIG_API_ADD_HISTORY_STATE]
|
|
{0},
|
|
{1},
|
|
{2},
|
|
@OUT_SUCCESS OUTPUT;
|
|
|
|
SELECT @OUT_SUCCESS as [@OUT_SUCCESS];";
|
|
|
|
using (SqlConnection conn = new(_cnnStr))
|
|
{
|
|
conn.Open();
|
|
var formattedSQL_hist = string.Format(sql_hist, envelope.Uuid.ToSqlParam(), 1003.ToSqlParam(), User.GetId().ToSqlParam());
|
|
using SqlCommand cmd = new(formattedSQL_hist, conn);
|
|
cmd.CommandType = CommandType.Text;
|
|
|
|
using SqlDataReader reader = cmd.ExecuteReader();
|
|
if (reader.Read())
|
|
{
|
|
bool outSuccess = reader.GetBoolean(0);
|
|
if (!outSuccess)
|
|
Logger.LogWarning(
|
|
"PRSIG_API_ADD_HISTORY_STATE returned OUT_SUCCESS=false. EnvelopeUuid={EnvelopeUuid}",
|
|
envelope.Uuid);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
return Ok(res);
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="input"></param>
|
|
/// <returns></returns>
|
|
public static bool IsBase64String(string input)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(input))
|
|
return false;
|
|
|
|
try
|
|
{
|
|
Convert.FromBase64String(input);
|
|
return true;
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="request"></param>
|
|
/// <param name="cancel"></param>
|
|
/// <returns></returns>
|
|
[HttpDelete]
|
|
[Authorize(AuthenticationSchemes = AuthScheme.Sender)]
|
|
public async Task<IActionResult> RemoveAsync([FromQuery] RemoveEnvelopeReceiverCommand request, CancellationToken cancel)
|
|
{
|
|
request = request.Authenticated(User.GetId());
|
|
await Mediator.Send(request, cancel);
|
|
return Ok();
|
|
}
|
|
}
|