Refactor document retrieval endpoints and authorization

- Updated DocumentController to use class-level [Authorize] and method-level role-based authorization for sender and receiver endpoints.
- Replaced ReadEnvelopeReceiverQuery with ReadDocumentQuery for sender document retrieval; simplified response logic.
- Added a new endpoint for fully authenticated receivers to fetch documents by envelope ID from user claims.
- Refactored ReadDocumentQuery and handler to always return DocumentDto, throw NotFoundException when needed, and use _repo.Query.
- Cleaned up using directives and removed legacy error handling and logging.
This commit is contained in:
2026-02-03 09:48:33 +01:00
parent 1b840f4ae3
commit 5465996563
2 changed files with 33 additions and 25 deletions

View File

@@ -1,6 +1,5 @@
using DigitalData.Core.Exceptions; using EnvelopeGenerator.API.Extensions;
using EnvelopeGenerator.Application.Common.Extensions; using EnvelopeGenerator.Application.Documents.Queries;
using EnvelopeGenerator.Application.EnvelopeReceivers.Queries;
using EnvelopeGenerator.Domain.Constants; using EnvelopeGenerator.Domain.Constants;
using MediatR; using MediatR;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
@@ -14,7 +13,7 @@ namespace EnvelopeGenerator.API.Controllers;
/// <remarks> /// <remarks>
/// Initializes a new instance of the <see cref="DocumentController"/> class. /// Initializes a new instance of the <see cref="DocumentController"/> class.
/// </remarks> /// </remarks>
[Authorize(Roles = Role.FullyAuth)] [Authorize]
[ApiController] [ApiController]
[Route("api/[controller]")] [Route("api/[controller]")]
public class DocumentController(IMediator mediator, ILogger<DocumentController> logger) : ControllerBase public class DocumentController(IMediator mediator, ILogger<DocumentController> logger) : ControllerBase
@@ -25,19 +24,28 @@ public class DocumentController(IMediator mediator, ILogger<DocumentController>
/// <param name="query">Encoded envelope key.</param> /// <param name="query">Encoded envelope key.</param>
/// <param name="cancel">Cancellation token.</param> /// <param name="cancel">Cancellation token.</param>
[HttpGet] [HttpGet]
public async Task<IActionResult> GetDocument(ReadEnvelopeReceiverQuery query, CancellationToken cancel) [Authorize(Roles = Role.Sender)]
public async Task<IActionResult> GetDocument(ReadDocumentQuery query, CancellationToken cancel)
{ {
var envRcv = await mediator.Send(query, cancel).FirstAsync(Exceptions.NotFound); var doc = await mediator.Send(query, cancel);
return doc.ByteData is byte[] docByte
var byteData = envRcv.Envelope?.Documents?.FirstOrDefault()?.ByteData; ? File(docByte, "application/octet-stream")
: NotFound("Document is empty.");
if (byteData is null || byteData.Length == 0)
{
logger.LogError("Document byte data is null or empty for envelope-receiver entity:\n{envelopeKey}.",
envRcv.ToJson(Format.Json.ForDiagnostics));
throw new NotFoundException("Document is empty.");
}
return File(byteData, "application/octet-stream");
} }
}
/// <summary>
/// Returns the document bytes for the receiver.
/// </summary>
/// <param name="cancel">Cancellation token.</param>
[HttpGet]
[Authorize(Roles = Role.Receiver.FullyAuth)]
public async Task<IActionResult> GetDocument(CancellationToken cancel)
{
var envelopeId = User.GetEnvelopeIdOfReceiver();
var doc = await mediator.Send(new ReadDocumentQuery() { EnvelopeId = envelopeId }, cancel);
return doc.ByteData is byte[] docByte
? File(docByte, "application/octet-stream")
: NotFound("Document is empty.");
}
}

View File

@@ -4,6 +4,7 @@ using EnvelopeGenerator.Domain.Entities;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using AutoMapper; using AutoMapper;
using EnvelopeGenerator.Application.Common.Dto; using EnvelopeGenerator.Application.Common.Dto;
using DigitalData.Core.Exceptions;
namespace EnvelopeGenerator.Application.Documents.Queries; namespace EnvelopeGenerator.Application.Documents.Queries;
@@ -12,14 +13,14 @@ namespace EnvelopeGenerator.Application.Documents.Queries;
/// </summary> /// </summary>
/// <param name="Id">The unique identifier of the document. Optional.</param> /// <param name="Id">The unique identifier of the document. Optional.</param>
/// <param name="EnvelopeId">The identifier of the envelope associated with the document. Optional.</param> /// <param name="EnvelopeId">The identifier of the envelope associated with the document. Optional.</param>
public record ReadDocumentQuery(int? Id = null, int? EnvelopeId = null) : IRequest<DocumentDto?> public record ReadDocumentQuery(int? Id = null, int? EnvelopeId = null) : IRequest<DocumentDto>
{ {
} }
/// <summary> /// <summary>
/// Handles queries for reading <see cref="Document"/> data based on either the document ID or the envelope ID. /// Handles queries for reading <see cref="Document"/> data based on either the document ID or the envelope ID.
/// </summary> /// </summary>
public class ReadDocumentQueryHandler : IRequestHandler<ReadDocumentQuery, DocumentDto?> public class ReadDocumentQueryHandler : IRequestHandler<ReadDocumentQuery, DocumentDto>
{ {
/// <summary> /// <summary>
/// TempRepo for accessing <see cref="Document"/> entities. /// TempRepo for accessing <see cref="Document"/> entities.
@@ -50,20 +51,19 @@ public class ReadDocumentQueryHandler : IRequestHandler<ReadDocumentQuery, Docum
/// <exception cref="InvalidOperationException"> /// <exception cref="InvalidOperationException">
/// Thrown when neither <see cref="ReadDocumentQuery.Id"/> nor <see cref="ReadDocumentQuery.EnvelopeId"/> is provided. /// Thrown when neither <see cref="ReadDocumentQuery.Id"/> nor <see cref="ReadDocumentQuery.EnvelopeId"/> is provided.
/// </exception> /// </exception>
public async Task<DocumentDto?> Handle(ReadDocumentQuery query, CancellationToken cancel) public async Task<DocumentDto> Handle(ReadDocumentQuery query, CancellationToken cancel)
{ {
if (query.Id is not null) if (query.Id is not null)
{ {
var doc = await _repo.ReadOnly().Where(d => d.Id == query.Id).FirstOrDefaultAsync(cancel); var doc = await _repo.Query.Where(d => d.Id == query.Id).FirstOrDefaultAsync(cancel);
return _mapper.Map<DocumentDto>(doc); return _mapper.Map<DocumentDto>(doc);
} }
else if (query.EnvelopeId is not null) else if (query.EnvelopeId is not null)
{ {
var doc = await _repo.ReadOnly().Where(d => d.EnvelopeId == query.EnvelopeId).FirstOrDefaultAsync(cancel); var doc = await _repo.Query.Where(d => d.EnvelopeId == query.EnvelopeId).FirstOrDefaultAsync(cancel);
return _mapper.Map<DocumentDto>(doc); return _mapper.Map<DocumentDto>(doc);
} }
throw new InvalidOperationException( throw new NotFoundException();
$"Invalid {nameof(ReadDocumentQuery)}: either {nameof(query.Id)} or {nameof(query.EnvelopeId)} must be provided.");
} }
} }