c94e8f0acb
This pull request completely overhauls the Creator editor flow, resolves the editor duplication race condition, aligns layout/styling themes in light and dark mode, and adds Docker staging setups. ### Key Changes 1. **Creator Flow Polish**: Redesigned the editor canvas to prevent double scrolling by delegating overflow to the editor canvas layer, updated styles to a premium aesthetic. 2. **Race Condition Prevention**: Resolved Crepe editor duplication when loading or switching chapters by tracking state via shared window maps (`window.editorCache`, `window.editorStates`) and checking `_lastInitializedEditorId` synchronously in Blazor. 3. **Theme Synchronization**: Integrated explicit theme initialization (`ThemeService.InitializeAsync()`) and anchored CSS isolation selectors to correctly sync with Light (Soft Sepia) and Deep Dark theme preferences. 4. **Staging Automation**: Created staging docker configurations with `--nexus-only` flag to allow iterative development without resetting PG/Neo4j database containers. --------- Co-authored-by: Marek Jasiński <jasins.marek@gmail.com> Reviewed-on: #83 Co-authored-by: Antigravity <antigravity@google.com> Co-committed-by: Antigravity <antigravity@google.com>
64 lines
2.6 KiB
C#
64 lines
2.6 KiB
C#
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;
|
|
|
|
/// <summary>
|
|
/// Query to load all revisions for a specific Book, checking multi-tenant ownership boundaries.
|
|
/// </summary>
|
|
/// <param name="BookId">The unique identifier of the target Book.</param>
|
|
/// <param name="UserId">The ID of the creator requesting revision data.</param>
|
|
/// <param name="TenantId">The tenant ID for multi-tenant isolation.</param>
|
|
public record GetBookRevisionsQuery(Guid BookId, string UserId, string TenantId) : IQuery<List<CreatorBookRevisionDto>>;
|
|
|
|
/// <summary>
|
|
/// Handler that lists past revisions of a Book, verifying ownership to prevent cross-tenant leakages.
|
|
/// </summary>
|
|
public class GetBookRevisionsQueryHandler : IQueryHandler<GetBookRevisionsQuery, List<CreatorBookRevisionDto>>
|
|
{
|
|
private readonly IDbContextFactory<AppDbContext> _dbContextFactory;
|
|
|
|
public GetBookRevisionsQueryHandler(IDbContextFactory<AppDbContext> dbContextFactory)
|
|
{
|
|
_dbContextFactory = dbContextFactory;
|
|
}
|
|
|
|
public async Task<FluentResults.Result<List<CreatorBookRevisionDto>>> 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<List<CreatorBookRevisionDto>>(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);
|
|
}
|
|
}
|