BOOK-LIBRARY: импорт, метаданные и каталог
У импорта в BOOK-LIBRARY есть незаметная, но очень важная роль: он превращает разрозненный файл книги в запись, которую потом можно найти, понять и спокойно открыть в каталоге. Между «загрузили файл» и «видим аккуратную карточку» проходит несколько проверок и решений: что это за книга, кто автор, куда её положить, какая обложка подходит и не лежит ли она уже в библиотеке.
В раннем варианте акцент был на самой загрузке файла или ZIP-архива и потоковом NDJSON-прогрессе. Сейчас модель стала устойчивее: долгие операции оформлены как persistent jobs, их состояние хранится на backend, а админка показывает прогресс через polling.
Для реальной коллекции это важное изменение. Импорт может зависеть от распаковки, внешнего поиска, извлечения метаданных, LLM, обложек, дублей и категорий. Такой процесс не должен ломаться только потому, что вкладка обновилась или соединение оборвалось: книга должна либо попасть в каталог понятной карточкой, либо оставить после себя внятную причину ошибки.
Одиночный импорт
Одиночная загрузка остаётся базовым сценарием: администратор выбирает файл, при необходимости вводит название, автора, описание, категорию и обложку. Если часть данных не заполнена, backend пытается подготовить черновые метаданные автоматически, чтобы карточка не начиналась с пустого экрана и имени файла вместо книги.
Теперь это не просто синхронная обработка формы. Долгий импорт можно запускать как durable job: backend сохраняет задачу, обновляет статус, фиксирует ошибки и даёт админке возможность следить за выполнением. Пользователь видит не подвисшую кнопку, а понятное состояние операции: книга ещё в очереди, обрабатывается, завершилась или требует внимания.
Внутри импорта остаются привычные шаги:
- принять и проверить файл;
- извлечь метаданные, если формат это позволяет;
- подготовить черновые title, author и description;
- сопоставить категорию с деревом;
- найти или заменить обложку;
- сохранить запись в SQLite и файл в uploads;
- показать результат или причину ошибки.
Batch ZIP без привязки к одному запросу
Пакетный импорт нужен, когда библиотека растёт не по одной книге, а целыми папками и архивами. Раньше его удобно было описывать через NDJSON stream: backend распаковывает архив и пишет прогресс в ответ. Для первого рабочего варианта это нормально, но у такого подхода есть слабое место — он держится на одном HTTP-соединении.
Актуальная модель лучше подходит для долгой операции:
- администратор загружает ZIP;
- backend создаёт import job;
- задача проходит по файлам внутри архива;
- для каждого файла фиксируется результат;
- админка получает статус и события через polling;
- ошибки можно разобрать после завершения, а не ловить в потерянном потоке.
Такой подход спокойнее для больших архивов. Он позволяет показать queued/running/failed/completed состояние, сохранить отчёт и не смешивать транспортный формат ответа с жизненным циклом импорта. Для администратора это превращает импорт из «надеюсь, вкладка доживёт» в наблюдаемую задачу, к которой можно вернуться.
Дубли и спорные совпадения
При импорте книг быстро появляется проблема дублей. Один и тот же текст может лежать в разных форматах, с другим именем файла, неполным автором или слегка отличающимся названием. Для читателя это выглядит как шум в выдаче, а для администратора — как каталог, который приходится чистить вручную. Поэтому импорт не должен бездумно создавать новую запись на каждый файл.
В проекте нужен и частично уже заложен более осторожный сценарий:
- сравнивать title, author, формат и признаки файла;
- показывать администратору возможный конфликт;
- пропускать очевидные дубли;
- сохранять отчёт о том, почему книга не была добавлена;
- давать возможность ручного решения для спорных случаев.
Это не самая эффектная часть каталога, но именно она отличает рабочую библиотеку от папки с автоматическим скриптом: система не просто складывает файлы, а помогает сохранить порядок.
Дерево категорий и category matcher
Категории стали деревом, и именно они помогают читателю не потеряться в коллекции. Поэтому импорт больше не может относиться к категории как к одной строке из плоского списка. Если LLM или внешний источник предлагает «История», «История России» или «Научпоп / История», backend должен сопоставить это с уже существующей структурой, а не плодить рядом почти одинаковые полки.
Category matcher нужен, чтобы:
- не создавать лишние почти одинаковые ветки;
- выбирать существующую категорию, если совпадение достаточно уверенное;
- оставлять спорные варианты для ручной проверки;
- учитывать parent/child структуру;
- сохранять каталог читаемым после массового импорта.
Автоматическая классификация здесь полезна только как черновик. Последнее слово лучше оставить администратору, особенно если библиотека ведётся как curated-каталог: машина помогает разложить книги, но человек отвечает за смысловую навигацию.
Anna’s Archive и Flibusta
Внешний поиск вынесен в React-админку через общий SearchPage. Он объединяет Anna’s Archive и Flibusta: администратор ищет книгу, смотрит найденные варианты и запускает импорт выбранного результата.
Практический смысл этого слоя простой: меньше ручной перекладки между сайтами, файлами и формами. Но внешние источники нельзя воспринимать как безошибочную базу данных. У найденной книги может быть странное описание, не та обложка, неполные метаданные или несколько похожих вариантов. Поэтому поиск здесь не заменяет проверку, а приносит материал для осознанного выбора.
Поэтому внешний импорт должен вести себя как управляемая операция:
- показать источник и найденные candidates;
- дать сравнить варианты;
- запустить импорт как job;
- сохранить ошибку, если источник недоступен;
- после импорта позволить поправить карточку.
Интеллектуальная замена обложек
Обложка сильно влияет на качество каталога: по ней читатель быстрее узнаёт книгу, а карточка перестаёт быть просто строкой с названием. Но автоматическая замена без просмотра часто портит карточки. Поэтому сценарий с обложками лучше строить не вокруг одной найденной картинки, а вокруг candidates.
Актуальная логика выглядит так:
- система ищет варианты обложек;
- администратор видит candidates;
- можно открыть preview;
- выбранная обложка заменяет текущую;
- итоговый файл приводится к формату, удобному для витрины.
Такой подход медленнее, чем полностью автоматический импорт, зато он снижает количество странных карточек. Для библиотеки это важнее скорости: аккуратная обложка помогает доверять каталогу так же, как корректное название и автор.
LLM-провайдеры как настройки, а не константы
LLM используется в нескольких местах: для черновых метаданных, категорий и большого pipeline переводов. Поэтому один захардкоженный provider плохо подходит проекту: импорт должен переживать разные режимы работы, а не зависеть от одной константы в коде.
Провайдеры стали runtime-сущностью в SQLite. Их можно настраивать из админки: включать, менять priority, задавать fallback и использовать разные endpoint без правки кода. Это особенно важно для долгих задач, где часть запросов может упасть или уйти к другому провайдеру.
При этом LLM не считается источником истины. Она помогает подготовить черновик, но карточки, категории, обложки и переводы должны оставаться проверяемыми. В этой архитектуре интеллект полезен не как автопилот без руля, а как помощник, который ускоряет скучную подготовку данных.
Как карточка попадает в публичный каталог
После успешного импорта backend сохраняет запись в SQLite, кладёт файл книги в uploads и связывает книгу с категорией. В этот момент загруженный файл становится частью библиотеки: у него появляется место в дереве, обложка, описание и технические признаки, по которым его можно показать читателю. Публичная витрина получает уже подготовленную карточку через public API.
Для страницы категории API отдаёт книги постранично. Вместе со списком возвращаются признаки пагинации: limit, offset, hasMore, total. Это важно для больших категорий и для Quartz-интеграции, где не всегда нужно забирать весь каталог как живой API-запрос.
Публично показываются только данные, пригодные для читателя: карточка, обложка, описание, формат, опубликованные переводы. Черновые задачи, ошибки импорта, candidates и непроверенные artifacts остаются в админке, потому что витрина должна быть спокойной и понятной, а не похожей на журнал сборки.
Что стоит держать под контролем
У слоя импорта есть несколько постоянных рисков. Они не видны читателю напрямую, но именно от них зависит, будет ли каталог выглядеть надёжным после десятков и сотен добавлений:
- ZIP-пути и временные директории;
- проверка расширений, MIME и реального содержимого;
- очистка временных файлов после ошибок;
- поведение внешних источников и лимиты запросов;
- ложные дубли и пропущенные совпадения;
- случайное разрастание дерева категорий;
- качество LLM-описаний и переводов;
- безопасность скачивания внешних обложек.
Часть этих вопросов уже закрыта архитектурно через jobs, polling, sessions, CSRF/CORS и разделение public/admin API. Остальное лучше развивать постепенно: отчёты, dry-run, проверка целостности, ручное подтверждение спорных действий и хорошие журналы операций. Тогда импорт остаётся не разовой кнопкой «загрузить», а контролируемым мостом между хаотичной коллекцией файлов и каталогом, которым удобно пользоваться.