fix: ensure user-specific ebook tracking and auto-provisioning for progress sync

This commit is contained in:
2026-05-10 18:37:32 +02:00
parent 652e74c2f5
commit 2950e15633
7 changed files with 35 additions and 13 deletions
@@ -5,5 +5,5 @@ namespace NexusReader.Application.Abstractions.Services;
public interface IEpubService public interface IEpubService
{ {
Task<Result<ReaderPageViewModel>> GetEpubContentAsync(int chapterIndex); Task<Result<ReaderPageViewModel>> GetEpubContentAsync(int chapterIndex, string? userId = null);
} }
@@ -2,4 +2,4 @@ using NexusReader.Application.Abstractions.Messaging;
namespace NexusReader.Application.Queries.Reader; namespace NexusReader.Application.Queries.Reader;
public record GetReaderPageQuery(int ChapterIndex = 0) : IQuery<ReaderPageViewModel>; public record GetReaderPageQuery(int ChapterIndex = 0, string? UserId = null) : IQuery<ReaderPageViewModel>;
@@ -15,6 +15,6 @@ internal sealed class GetReaderPageQueryHandler : IQueryHandler<GetReaderPageQue
public async Task<Result<ReaderPageViewModel>> Handle(GetReaderPageQuery request, CancellationToken cancellationToken) public async Task<Result<ReaderPageViewModel>> Handle(GetReaderPageQuery request, CancellationToken cancellationToken)
{ {
return await _epubService.GetEpubContentAsync(request.ChapterIndex); return await _epubService.GetEpubContentAsync(request.ChapterIndex, request.UserId);
} }
} }
@@ -21,7 +21,7 @@ public class EpubService : IEpubService
_dbContextFactory = dbContextFactory; _dbContextFactory = dbContextFactory;
} }
public async Task<Result<ReaderPageViewModel>> GetEpubContentAsync(int chapterIndex) public async Task<Result<ReaderPageViewModel>> GetEpubContentAsync(int chapterIndex, string? userId = null)
{ {
try try
{ {
@@ -109,14 +109,29 @@ public class EpubService : IEpubService
blocks.Add(CreateAiTrigger($"trigger-{blockCounter++}")); blocks.Add(CreateAiTrigger($"trigger-{blockCounter++}"));
} }
// Find the EbookId from DB for this file // Find the EbookId from DB for this file AND this user
using var context = await _dbContextFactory.CreateDbContextAsync(); using var context = await _dbContextFactory.CreateDbContextAsync();
var ebookId = await context.Ebooks var ebook = await context.Ebooks
.Where(e => e.FilePath.Contains("book.epub")) .Where(e => e.FilePath.Contains("book.epub") && (userId == null || e.UserId == userId))
.Select(e => e.Id)
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
return Result.Ok(new ReaderPageViewModel(blocks, chapterIndex, readingOrder.Count, chapterTitle, ebookId)); // Auto-provision if not found for this user (convenience for dev)
if (ebook == null && !string.IsNullOrEmpty(userId))
{
var author = await context.Authors.FirstOrDefaultAsync() ?? new Author { Name = "Unknown Author" };
ebook = new Ebook
{
Title = "Lives of the Most Excellent Painters, Sculptors, and Architects",
FilePath = "wwwroot/assets/book.epub",
UserId = userId,
Author = author,
TenantId = "global"
};
context.Ebooks.Add(ebook);
await context.SaveChangesAsync();
}
return Result.Ok(new ReaderPageViewModel(blocks, chapterIndex, readingOrder.Count, chapterTitle, ebook?.Id ?? Guid.Empty));
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -11,6 +11,8 @@
@inject KnowledgeCoordinator Coordinator @inject KnowledgeCoordinator Coordinator
@inject IReaderInteractionService InteractionService @inject IReaderInteractionService InteractionService
@inject ISyncService SyncService @inject ISyncService SyncService
@using Microsoft.AspNetCore.Components.Authorization
@inject AuthenticationStateProvider AuthStateProvider
<div class="reader-canvas @(ThemeService.IsLightMode ? "theme-light" : "theme-dark")"> <div class="reader-canvas @(ThemeService.IsLightMode ? "theme-light" : "theme-dark")">
@if (ViewModel == null) @if (ViewModel == null)
@@ -190,7 +192,10 @@
ViewModel = null; ViewModel = null;
StatusMessage = "Fetching content..."; StatusMessage = "Fetching content...";
var result = await Mediator.Send(new GetReaderPageQuery(index)); var authState = await AuthStateProvider.GetAuthenticationStateAsync();
var userId = authState.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
var result = await Mediator.Send(new GetReaderPageQuery(index, userId));
if (result.IsSuccess) if (result.IsSuccess)
{ {
ViewModel = result.Value; ViewModel = result.Value;
@@ -14,7 +14,7 @@ public class WasmEpubService : IEpubService
_httpClient = httpClient; _httpClient = httpClient;
} }
public async Task<Result<ReaderPageViewModel>> GetEpubContentAsync(int chapterIndex) public async Task<Result<ReaderPageViewModel>> GetEpubContentAsync(int chapterIndex, string? userId = null)
{ {
try try
{ {
+4 -2
View File
@@ -226,9 +226,11 @@ app.MapStaticAssets();
app.MapHub<NexusReader.Infrastructure.RealTime.SyncHub>("/synchub"); app.MapHub<NexusReader.Infrastructure.RealTime.SyncHub>("/synchub");
// API endpoint for WASM client to fetch EPUB content // API endpoint for WASM client to fetch EPUB content
app.MapGet("/api/epub/{index}", async (int index, IEpubService epubService) => app.MapGet("/api/epub/{index}", async (int index, IEpubService epubService, ClaimsPrincipal user) =>
{ {
var result = await epubService.GetEpubContentAsync(index); var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);
var result = await epubService.GetEpubContentAsync(index, userId);
if (result.IsSuccess) return Results.Ok(result.Value); if (result.IsSuccess) return Results.Ok(result.Value);
var errorMsg = result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error"; var errorMsg = result.Errors.Count > 0 ? result.Errors[0].Message : "Unknown server error";