feat(ingestion): implement hybrid metadata verification form in ingestion modal #34

This commit is contained in:
2026-05-12 14:55:34 +02:00
parent fe5ff81c98
commit 94fd7cf5c1
13 changed files with 431 additions and 22 deletions
@@ -0,0 +1,12 @@
using NexusReader.Application.Abstractions.Messaging;
namespace NexusReader.Application.Commands.Library;
public record IngestEbookCommand(
string Title,
string AuthorName,
byte[]? CoverImage,
byte[] EpubData,
string UserId,
string TenantId = "global"
) : ICommand<Guid>;
@@ -0,0 +1,71 @@
using FluentResults;
using MediatR;
using Microsoft.EntityFrameworkCore;
using NexusReader.Application.Abstractions.Messaging;
using NexusReader.Application.Abstractions.Services;
using NexusReader.Data.Persistence;
using NexusReader.Domain.Entities;
namespace NexusReader.Application.Commands.Library;
public class IngestEbookCommandHandler : IRequestHandler<IngestEbookCommand, Result<Guid>>
{
private readonly IDbContextFactory<AppDbContext> _dbContextFactory;
private readonly IBookStorageService _storageService;
public IngestEbookCommandHandler(
IDbContextFactory<AppDbContext> dbContextFactory,
IBookStorageService storageService)
{
_dbContextFactory = dbContextFactory;
_storageService = storageService;
}
public async Task<Result<Guid>> Handle(IngestEbookCommand request, CancellationToken cancellationToken)
{
try
{
using var context = await _dbContextFactory.CreateDbContextAsync(cancellationToken);
// 1. Save Files
var epubPath = await _storageService.SaveEbookAsync(request.EpubData, $"{request.Title}.epub");
var coverUrl = request.CoverImage != null && request.CoverImage.Length > 0
? await _storageService.SaveCoverAsync(request.CoverImage, $"{request.Title}_cover.jpg")
: null;
// 2. Resolve Author
var authorName = string.IsNullOrWhiteSpace(request.AuthorName) ? "Unknown Author" : request.AuthorName.Trim();
var author = await context.Authors
.FirstOrDefaultAsync(a => a.Name.ToLower() == authorName.ToLower(), cancellationToken);
if (author == null)
{
author = new Author { Name = authorName };
context.Authors.Add(author);
// We need to save to get the Author ID if we want to use it,
// but EF will handle it if we assign the object.
}
// 3. Create Ebook
var ebook = new Ebook
{
Title = request.Title,
Author = author,
FilePath = epubPath,
CoverUrl = coverUrl,
UserId = request.UserId,
TenantId = request.TenantId,
AddedDate = DateTime.UtcNow
};
context.Ebooks.Add(ebook);
await context.SaveChangesAsync(cancellationToken);
return Result.Ok(ebook.Id);
}
catch (Exception ex)
{
return Result.Fail(new Error("Failed to ingest ebook").CausedBy(ex));
}
}
}
@@ -0,0 +1,8 @@
namespace NexusReader.Application.Commands.Library;
public record IngestEbookRequest(
string Title,
string AuthorName,
string? CoverImageBase64,
string EpubDataBase64
);