2e23a032d3
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>
48 lines
1.4 KiB
C#
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);
|
|
}
|
|
}
|