diff --git a/src/NexusReader.Application/Abstractions/Services/IConceptsMapService.cs b/src/NexusReader.Application/Abstractions/Services/IConceptsMapService.cs new file mode 100644 index 0000000..77f21c0 --- /dev/null +++ b/src/NexusReader.Application/Abstractions/Services/IConceptsMapService.cs @@ -0,0 +1,9 @@ +using FluentResults; +using NexusReader.Application.Queries.Concepts; + +namespace NexusReader.Application.Abstractions.Services; + +public interface IConceptsMapService +{ + Task> GetConceptsMapAsync(Guid bookId); +} diff --git a/src/NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor b/src/NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor index 1646a98..066e336 100644 --- a/src/NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor +++ b/src/NexusReader.UI.Shared/Components/Organisms/ReaderCanvas.razor @@ -103,7 +103,7 @@ _isInteractive = true; if (ViewModel != null) { - await Coordinator.ProcessFullPageAsync(GetFullPageContent()); + await Coordinator.ProcessFullPageAsync(GetFullPageContent(), ebookId: ViewModel.EbookId); } } @@ -246,7 +246,7 @@ if (_isInteractive) { - await Coordinator.ProcessFullPageAsync(GetFullPageContent()); + await Coordinator.ProcessFullPageAsync(GetFullPageContent(), ebookId: ViewModel.EbookId); } } else diff --git a/src/NexusReader.UI.Shared/Pages/ConceptsDashboard.razor b/src/NexusReader.UI.Shared/Pages/ConceptsDashboard.razor index bdd75e6..e8df5f5 100644 --- a/src/NexusReader.UI.Shared/Pages/ConceptsDashboard.razor +++ b/src/NexusReader.UI.Shared/Pages/ConceptsDashboard.razor @@ -7,7 +7,8 @@ @using NexusReader.Application.Queries.Graph @using NexusReader.Application.Queries.Concepts @using System.Net.Http.Json -@inject HttpClient Http +@using NexusReader.Application.Abstractions.Services +@inject IConceptsMapService ConceptsMapService @inject NavigationManager NavigationManager @inject IIdentityService IdentityService @inject ISyncService SyncService @@ -203,11 +204,11 @@ if (BookId.HasValue && BookId.Value != Guid.Empty) { - var result = await Http.GetFromJsonAsync($"api/book/{BookId}/concepts-map"); - if (result != null) + var result = await ConceptsMapService.GetConceptsMapAsync(BookId.Value); + if (result.IsSuccess) { - Nodes = result.Nodes; - LastReadBlockId = result.LastReadBlockId; + Nodes = result.Value.Nodes; + LastReadBlockId = result.Value.LastReadBlockId; if (Nodes.Any()) { @@ -216,7 +217,7 @@ } else { - _errorMessage = "Brak odpowiedzi od serwera."; + _errorMessage = result.Errors.FirstOrDefault()?.Message ?? "Brak odpowiedzi od serwera."; } } } diff --git a/src/NexusReader.UI.Shared/Services/KnowledgeCoordinator.cs b/src/NexusReader.UI.Shared/Services/KnowledgeCoordinator.cs index 9489616..1986436 100644 --- a/src/NexusReader.UI.Shared/Services/KnowledgeCoordinator.cs +++ b/src/NexusReader.UI.Shared/Services/KnowledgeCoordinator.cs @@ -75,7 +75,7 @@ public sealed partial class KnowledgeCoordinator : IDisposable } } - public async Task ProcessFullPageAsync(string fullContent, string tenantId = "global") + public async Task ProcessFullPageAsync(string fullContent, string tenantId = "global", Guid? ebookId = null) { if (string.IsNullOrWhiteSpace(fullContent)) return; @@ -87,7 +87,7 @@ public sealed partial class KnowledgeCoordinator : IDisposable try { - var result = await _knowledgeService.GetGraphDataAsync(fullContent, tenantId); + var result = await _knowledgeService.GetGraphDataAsync(fullContent, tenantId, ebookId); if (result.IsSuccess) { var packet = result.Value; diff --git a/src/NexusReader.Web.Client/Program.cs b/src/NexusReader.Web.Client/Program.cs index 9eed57e..a011bc3 100644 --- a/src/NexusReader.Web.Client/Program.cs +++ b/src/NexusReader.Web.Client/Program.cs @@ -36,6 +36,7 @@ builder.Services.AddCascadingAuthenticationState(); // AI & Content Services builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddTransient(); builder.Services.AddHttpClient("NexusAPI", client => diff --git a/src/NexusReader.Web.Client/Services/WasmConceptsMapService.cs b/src/NexusReader.Web.Client/Services/WasmConceptsMapService.cs new file mode 100644 index 0000000..41d10fa --- /dev/null +++ b/src/NexusReader.Web.Client/Services/WasmConceptsMapService.cs @@ -0,0 +1,36 @@ +using System.Net.Http.Json; +using FluentResults; +using NexusReader.Application.Abstractions.Services; +using NexusReader.Application.Queries.Concepts; + +namespace NexusReader.Web.Client.Services; + +public class WasmConceptsMapService : IConceptsMapService +{ + private readonly HttpClient _httpClient; + + public WasmConceptsMapService(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task> GetConceptsMapAsync(Guid bookId) + { + try + { + var response = await _httpClient.GetAsync($"api/book/{bookId}/concepts-map"); + if (response.IsSuccessStatusCode) + { + var result = await response.Content.ReadFromJsonAsync(); + return result != null ? Result.Ok(result) : Result.Fail("Błąd deserializacji mapy pojęć."); + } + + var errorContent = await response.Content.ReadAsStringAsync(); + return Result.Fail($"Błąd serwera ({response.StatusCode}): {errorContent}"); + } + catch (Exception ex) + { + return Result.Fail(new Error("Błąd sieci przy pobieraniu mapy pojęć.").CausedBy(ex)); + } + } +} diff --git a/src/NexusReader.Web/Program.cs b/src/NexusReader.Web/Program.cs index 6f71195..70cdb68 100644 --- a/src/NexusReader.Web/Program.cs +++ b/src/NexusReader.Web/Program.cs @@ -74,6 +74,7 @@ builder.Services.AddHttpClient("NexusAPI", (sp, client) => builder.Services.AddScoped(sp => sp.GetRequiredService().CreateClient("NexusAPI")); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddApplication(); @@ -87,6 +88,11 @@ builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblies( // Authorization Policies builder.Services.AddScoped(); builder.Services.AddAuthorizationBuilder() + .SetDefaultPolicy(new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder( + IdentityConstants.ApplicationScheme, + IdentityConstants.BearerScheme) + .RequireAuthenticatedUser() + .Build()) .AddPolicy("ProUser", policy => policy.RequireClaim("Plan", SubscriptionPlan.ProName, SubscriptionPlan.EnterpriseName)) .AddPolicy("HasAvailableTokens", policy => policy.AddRequirements(new TokenLimitRequirement())); diff --git a/src/NexusReader.Web/Services/ServerConceptsMapService.cs b/src/NexusReader.Web/Services/ServerConceptsMapService.cs new file mode 100644 index 0000000..5dd01e7 --- /dev/null +++ b/src/NexusReader.Web/Services/ServerConceptsMapService.cs @@ -0,0 +1,59 @@ +using System.Security.Claims; +using FluentResults; +using MediatR; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Http; +using NexusReader.Application.Abstractions.Services; +using NexusReader.Application.Queries.Concepts; + +namespace NexusReader.Web.Services; + +public class ServerConceptsMapService : IConceptsMapService +{ + private readonly IMediator _mediator; + private readonly AuthenticationStateProvider _authStateProvider; + private readonly IHttpContextAccessor _httpContextAccessor; + + public ServerConceptsMapService( + IMediator mediator, + AuthenticationStateProvider authStateProvider, + IHttpContextAccessor httpContextAccessor) + { + _mediator = mediator; + _authStateProvider = authStateProvider; + _httpContextAccessor = httpContextAccessor; + } + + public async Task> GetConceptsMapAsync(Guid bookId) + { + try + { + var authState = await _authStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + + if (user == null || !user.Identity?.IsAuthenticated == true) + { + user = _httpContextAccessor.HttpContext?.User; + } + + if (user == null || !user.Identity?.IsAuthenticated == true) + { + return Result.Fail("Użytkownik nie jest uwierzytelniony."); + } + + var userId = user.FindFirstValue(ClaimTypes.NameIdentifier); + var tenantId = user.FindFirstValue("TenantId") ?? "global"; + + if (string.IsNullOrEmpty(userId)) + { + return Result.Fail("Nie znaleziono identyfikatora użytkownika."); + } + + return await _mediator.Send(new GetBookConceptsMapQuery(bookId, userId, tenantId)); + } + catch (Exception ex) + { + return Result.Fail(new Error("Błąd pobierania mapy pojęć na serwerze.").CausedBy(ex)); + } + } +} diff --git a/src/NexusReader.Web/Services/ServerIdentityService.cs b/src/NexusReader.Web/Services/ServerIdentityService.cs index 2a1aaff..6396f19 100644 --- a/src/NexusReader.Web/Services/ServerIdentityService.cs +++ b/src/NexusReader.Web/Services/ServerIdentityService.cs @@ -9,6 +9,7 @@ using NexusReader.Application.Queries.User; using MediatR; using NexusReader.Application.Constants; using NexusReader.Application.Abstractions.Services; +using Microsoft.AspNetCore.Components.Authorization; namespace NexusReader.Web.Services; @@ -19,6 +20,7 @@ public class ServerIdentityService : IIdentityService private readonly IHttpContextAccessor _httpContextAccessor; private readonly IMediator _mediator; private readonly INativeStorageService _storageService; + private readonly AuthenticationStateProvider _authStateProvider; public event Func? OnStateInvalidated; @@ -27,13 +29,15 @@ public class ServerIdentityService : IIdentityService SignInManager signInManager, IHttpContextAccessor httpContextAccessor, IMediator mediator, - INativeStorageService storageService) + INativeStorageService storageService, + AuthenticationStateProvider authStateProvider) { _userManager = userManager; _signInManager = signInManager; _httpContextAccessor = httpContextAccessor; _mediator = mediator; _storageService = storageService; + _authStateProvider = authStateProvider; } public async Task LoginAsync(string email, string password, bool rememberMe = false) @@ -107,7 +111,14 @@ public class ServerIdentityService : IIdentityService public async Task> GetProfileAsync() { - var user = _httpContextAccessor.HttpContext?.User; + var authState = await _authStateProvider.GetAuthenticationStateAsync(); + var user = authState.User; + + if (user == null || !user.Identity?.IsAuthenticated == true) + { + user = _httpContextAccessor.HttpContext?.User; + } + if (user == null || !user.Identity?.IsAuthenticated == true) return Result.Fail("Not authenticated."); var userId = user.FindFirstValue(ClaimTypes.NameIdentifier);