541e9e1fb5
This Pull Request encapsulates all outstanding AI, Blazor InteractiveAuto lifecycle, pgvector, and Firefox authorization/session compatibility fixes. ### Key Accomplishments: 1. **Concurrent Request Deduplication (Option B):** Implemented a thread-safe active task registry in `KnowledgeService` that groups concurrent graph extraction queries for the same content, preventing duplicate AI calls completely. 2. **Resilience Strategy for Downstream Demands:** Extended the `ai-retry` resilience pipeline to automatically intercept and retry on temporary Google API `503 ServiceUnavailable` / `high demand` spikes. 3. **Interactive Graph Generation Guard (Option A):** Prevented server-side prerender-phase graph requests in the reader canvas component. 4. **Firefox Compatibility & Cookie Handler:** Implemented an authentication endpoint and hybrid hidden-form submission flow to solve login, registration, and logout redirections and cookies securely. 5. **Autoscrolling & Graph Exclusions:** Added concept-to-block smooth scrolling, active block badging, and filtered out markdown code blocks from being extracted as nodes. All unit tests compiled and passed 100% cleanly. --------- Co-authored-by: Marek Jasiński <jasins.marek@gmail.com> Reviewed-on: #44 Co-authored-by: Antigravity <antigravity@google.com> Co-committed-by: Antigravity <antigravity@google.com>
83 lines
2.8 KiB
C#
83 lines
2.8 KiB
C#
using FluentResults;
|
|
using MediatR;
|
|
using NexusReader.Application.Abstractions.Messaging;
|
|
using NexusReader.Application.Abstractions.Persistence;
|
|
using NexusReader.Application.Abstractions.Services;
|
|
using NexusReader.Domain.Entities;
|
|
|
|
namespace NexusReader.Application.Commands.Library;
|
|
|
|
public class IngestEbookCommandHandler : IRequestHandler<IngestEbookCommand, Result<Guid>>
|
|
{
|
|
private readonly IEbookRepository _ebookRepository;
|
|
private readonly IBookStorageService _storageService;
|
|
|
|
public IngestEbookCommandHandler(
|
|
IEbookRepository ebookRepository,
|
|
IBookStorageService storageService)
|
|
{
|
|
_ebookRepository = ebookRepository;
|
|
_storageService = storageService;
|
|
}
|
|
|
|
public async Task<Result<Guid>> Handle(IngestEbookCommand request, CancellationToken cancellationToken)
|
|
{
|
|
string epubPath;
|
|
string? coverUrl;
|
|
|
|
try
|
|
{
|
|
// 1. Save Files
|
|
epubPath = await _storageService.SaveEbookAsync(request.EpubData, $"{request.Title}.epub");
|
|
coverUrl = request.CoverImage != null && request.CoverImage.Length > 0
|
|
? await _storageService.SaveCoverAsync(request.CoverImage, $"{request.Title}_cover.jpg")
|
|
: null;
|
|
}
|
|
catch (IOException ex)
|
|
{
|
|
return Result.Fail(new Error($"Storage I/O failure: {ex.Message}").CausedBy(ex));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error($"Storage failure: {ex.Message}").CausedBy(ex));
|
|
}
|
|
|
|
try
|
|
{
|
|
// 2. Resolve Author (case-insensitive via repository)
|
|
var authorName = string.IsNullOrWhiteSpace(request.AuthorName)
|
|
? "Unknown Author"
|
|
: request.AuthorName.Trim();
|
|
|
|
var author = await _ebookRepository.FindAuthorByNameAsync(authorName, cancellationToken);
|
|
if (author == null)
|
|
{
|
|
author = new Author { Name = authorName };
|
|
_ebookRepository.AddAuthor(author);
|
|
}
|
|
|
|
// 3. Create Ebook
|
|
var ebook = new Ebook
|
|
{
|
|
Title = request.Title,
|
|
Author = author,
|
|
FilePath = epubPath,
|
|
CoverUrl = coverUrl,
|
|
Description = request.Description,
|
|
UserId = request.UserId,
|
|
TenantId = request.TenantId,
|
|
AddedDate = DateTime.UtcNow
|
|
};
|
|
|
|
_ebookRepository.AddEbook(ebook);
|
|
await _ebookRepository.SaveChangesAsync(cancellationToken);
|
|
|
|
return Result.Ok(ebook.Id);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Result.Fail(new Error($"Database error during ingestion: {ex.Message}").CausedBy(ex));
|
|
}
|
|
}
|
|
}
|