using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using NexusReader.Application.Abstractions.Messaging; using NexusReader.Application.DTOs.Creator; using NexusReader.Data.Persistence; using NexusReader.Domain.Exceptions; namespace NexusReader.Application.Queries.Creator; /// /// Query to load all revisions for a specific Book, checking multi-tenant ownership boundaries. /// /// The unique identifier of the target Book. /// The ID of the creator requesting revision data. /// The tenant ID for multi-tenant isolation. public record GetBookRevisionsQuery(Guid BookId, string UserId, string TenantId) : IQuery>; /// /// Handler that lists past revisions of a Book, verifying ownership to prevent cross-tenant leakages. /// public class GetBookRevisionsQueryHandler : IQueryHandler> { private readonly IDbContextFactory _dbContextFactory; public GetBookRevisionsQueryHandler(IDbContextFactory dbContextFactory) { _dbContextFactory = dbContextFactory; } public async Task>> Handle(GetBookRevisionsQuery request, CancellationToken cancellationToken) { using var dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); // Verify the book exists and belongs to this tenant/user to prevent cross-tenant data leaks var bookExists = await dbContext.Books .AnyAsync(b => b.Id == request.BookId && b.UserId == request.UserId && b.TenantId == request.TenantId, cancellationToken); if (!bookExists) { return FluentResults.Result.Fail>(new FluentResults.Error($"Book with ID '{request.BookId}' was not found.")); } // Fetch all revisions sorted chronologically var revisions = await dbContext.BookRevisions .AsNoTracking() .Where(r => r.BookId == request.BookId) .OrderByDescending(r => r.CreatedAt) .Select(r => new CreatorBookRevisionDto( r.Id, r.VersionString, r.IsPublished, r.CreatedAt, r.PublishedAt )) .ToListAsync(cancellationToken); return FluentResults.Result.Ok(revisions); } }