Files
Nexus.Reader/src/NexusReader.Infrastructure/RealTime/SyncHub.cs
T
mjasin 2e23a032d3 feat(ui): Hub Navigation, Profile Dashboard and Auth Stability Fixes (#31)
This PR implements the Hub Navigation system and the Profile Dashboard, while resolving critical session synchronization issues.

### Key Changes
- **Hub Navigation**: Introduced `MainHubLayout` with a premium glassmorphism sidebar, providing access to Dashboard, Library, Concepts Map, and Profile.
- **Profile Dashboard**: Implemented a high-fidelity Profile page (#27) with learning metrics, AI token usage tracking, and system rank visualization.
- **Stability Fixes**:
    - Resolved an infinite network loop on the `/profile` page by implementing request deduplication and in-memory caching in `IdentityService`.
    - Added environment-aware guards to prevent illegal JavaScript interop calls during server-side prerendering.
    - Implemented automatic session invalidation on `401 Unauthorized` responses to handle stale authentication states gracefully.
- **Reader Integration**: Added a "Return to Dashboard" option in the reader toolbar (#26).

Closes #26
Closes #27

Reviewed-on: #31
Co-authored-by: Marek Jasiński <jasins.marek@gmail.com>
Co-committed-by: Marek Jasiński <jasins.marek@gmail.com>
2026-05-10 17:36:35 +00:00

48 lines
1.4 KiB
C#

using MediatR;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.Authorization;
using NexusReader.Application.Commands.Sync;
using System.Security.Claims;
namespace NexusReader.Infrastructure.RealTime;
[Authorize]
public class SyncHub : Hub
{
private readonly IMediator _mediator;
public SyncHub(IMediator mediator)
{
_mediator = mediator;
}
public async Task UpdateProgress(string pageId, Guid ebookId, double progress, string? chapterTitle, int chapterIndex)
{
var userId = Context.User?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
if (userId != null)
{
await _mediator.Send(new UpdateReadingProgressCommand(pageId, userId, ebookId, progress, chapterTitle, chapterIndex, Context.ConnectionId));
}
}
public override async Task OnConnectedAsync()
{
var userId = Context.UserIdentifier;
if (!string.IsNullOrEmpty(userId))
{
await Groups.AddToGroupAsync(Context.ConnectionId, $"User_{userId}");
}
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
var userId = Context.UserIdentifier;
if (!string.IsNullOrEmpty(userId))
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, $"User_{userId}");
}
await base.OnDisconnectedAsync(exception);
}
}