Files
EnvelopeGenerator/EnvelopeGenerator.Application/Documents/Queries/ReadDocumentQuery.cs
TekH 86758bb127 feat(application): add UserId filter to ReadDocumentQuery
Adds an optional UserId parameter to ReadDocumentQuery so callers
can scope document lookups to envelopes owned by a specific sender.
Also adds AsNoTracking() to the base query for read-only scenarios.
2026-09-23 15:28:55 +02:00

76 lines
3.2 KiB
C#

using DigitalData.Core.Abstraction.Application.Repository;
using MediatR;
using EnvelopeGenerator.Domain.Entities;
using Microsoft.EntityFrameworkCore;
using AutoMapper;
using EnvelopeGenerator.Application.Common.Dto;
using DigitalData.Core.Exceptions;
namespace EnvelopeGenerator.Application.Documents.Queries;
/// <summary>
/// Represents a query to read a document based on its unique identifier or associated envelope identifier.
/// </summary>
/// <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="UserId">The unique identifier of the sending user. Optional.</param>
public record ReadDocumentQuery(int? Id = null, int? EnvelopeId = null, int? UserId = null) : IRequest<DocumentDto>
{
}
/// <summary>
/// Handles queries for reading <see cref="Document"/> data based on either the document ID or the envelope ID.
/// </summary>
public class ReadDocumentQueryHandler : IRequestHandler<ReadDocumentQuery, DocumentDto>
{
/// <summary>
/// TempRepo for accessing <see cref="Document"/> entities.
/// </summary>
private readonly IRepository<Document> _repo;
private readonly IMapper _mapper;
/// <summary>
/// Initializes a new instance of the <see cref="ReadDocumentQueryHandler"/> class.
/// </summary>
/// <param name="envelopeDocumentRepository">The repository used to access <see cref="Document"/> entities.</param>
/// <param name="mapper"></param>
public ReadDocumentQueryHandler(IRepository<Document> envelopeDocumentRepository, IMapper mapper)
{
_repo = envelopeDocumentRepository;
_mapper = mapper;
}
/// <summary>
/// Handles the <see cref="ReadDocumentQuery"/> and returns a <see cref="DocumentDto"/> based on the provided identifiers.
/// </summary>
/// <param name="query">The query containing the document ID or envelope ID to search for.</param>
/// <param name="cancel">A token to monitor for cancellation requests.</param>
/// <returns>
/// A <see cref="DocumentDto"/> if a matching document is found; otherwise, <c>null</c>.
/// </returns>
/// <exception cref="InvalidOperationException">
/// Thrown when neither <see cref="ReadDocumentQuery.Id"/> nor <see cref="ReadDocumentQuery.EnvelopeId"/> is provided.
/// </exception>
public async Task<DocumentDto> Handle(ReadDocumentQuery query, CancellationToken cancel)
{
var docQuery = _repo.Query.Include(doc => doc.Elements).ThenInclude(e => e.Annotations).AsNoTracking();
if (query.UserId is int userId)
docQuery = docQuery.Where(doc => doc.Envelope.UserId == userId);
if (query.Id is not null)
{
var doc = await docQuery.Where(d => d.Id == query.Id).FirstOrDefaultAsync(cancel);
return _mapper.Map<DocumentDto>(doc);
}
else if (query.EnvelopeId is not null)
{
var doc = await docQuery.Where(d => d.EnvelopeId == query.EnvelopeId).FirstOrDefaultAsync(cancel);
return _mapper.Map<DocumentDto>(doc);
}
throw new NotFoundException();
}
}