BOOK-LIBRARY: импорт, метаданные и каталог

Самая интересная часть BOOK-LIBRARY находится не в самой карточке книги, а в пути от файла до этой карточки. Проект принимает разные форматы книг, пытается достать из них метаданные, при нехватке данных обращается к LLM и ищет обложку во внешних источниках.

Это хорошо подходит для реальной коллекции: файлы часто называются по-разному, внутри могут быть неполные метаданные, а руками заполнять каждую запись долго.

Одиночная загрузка

Форма добавления книги находится в frontend/src/routes/admin/add-book/+page.svelte. Она отправляет multipart form на /api/admin/books.

Поля устроены практично:

  • файл книги обязателен;
  • категория в форме отмечена как обязательная;
  • название, автор, описание и обложка могут быть заполнены вручную;
  • подсказка в интерфейсе говорит, что часть информации можно оставить пустой для автоматического заполнения.

На backend это обрабатывает createBook в backend/controllers/adminController.js. Если каких-то данных не хватает, запускается processBookFile. После этого backend выбирает итоговые значения: берёт ручной ввод, а пустые поля дополняет результатом автоимпорта.

Извлечение метаданных из файлов

Сервис backend/services/metadataParser.js подтверждает поддержку нескольких форматов:

  • FB2 — XML-разбор через xml2js, с учётом кодировки и извлечением title-info;
  • EPUB — разбор как ZIP-архива через yauzl, поиск OPF-метаданных и текста;
  • PDF — извлечение текста через pdf-parse.

Для FB2 проект отдельно нормализует HTML/XML-сущности и пытается собрать автора из доступных полей: имя, отчество, фамилия или nickname. Для текста берётся фрагмент первых секций, чтобы дальше дать LLM контекст.

Это не «магический» импорт. Это последовательная попытка получить нормальный минимум: название, автор, описание и текстовый фрагмент, который помогает уточнить карточку.

LLM-обогащение

Сервис backend/services/llmService.js использует OpenAI-compatible клиент и Polza AI endpoint. Prompt просит вернуть строгий JSON с полями:

  • title;
  • author;
  • category;
  • description;
  • coverSearchQuery.

В prompt передаются предполагаемые название и автор, имя файла, существующие категории и фрагмент текста. Это важная деталь: проект не просто просит «описать книгу», а даёт модели контекст и список уже заведённых категорий, чтобы уменьшить хаос в классификации.

В статьях про проект лучше формулировать это осторожно. LLM помогает подготовить черновые метаданные, но не гарантирует безошибочную библиографическую точность. Для личной библиотеки это нормально: администратор может проверить карточку после импорта.

Поиск обложек

Обложки ищутся в backend/services/imageSearchService.js. Сервис пробует несколько источников по очереди:

  1. Open Library;
  2. Google Books;
  3. DuckDuckGo Images.

Если URL найден, autoImportService скачивает изображение во временную папку, а затем обложка обрабатывается через Sharp и сохраняется как WebP. Для каталога это удобно: frontend всегда может запросить /uploads/covers/<id>.webp и не думать о формате исходной картинки.

Пакетный ZIP-импорт

Batch upload нужен, когда книг много. Администратор загружает ZIP-архив на /api/admin/books/batch. Контроллер распаковывает архив и работает в одном из двух режимов:

  • если внутри есть books.json, берёт список и возможные ручные поля оттуда;
  • если books.json нет, сам находит файлы с расширениями pdf, epub, mobi, fb2, djvu.

Каждая книга проходит через автоимпорт. Ответ отправляется как NDJSON-поток: frontend получает сообщения о статусе, прогрессе, логах и финальной статистике. Это хороший UX для долгой операции, потому что пользователь видит, что процесс не завис.

Как карточка попадает в каталог

После успешной обработки backend создаёт запись в таблице books, переносит файл в uploads/books под именем id и расширения, а обложку кладёт в uploads/covers/<id>.webp.

Публичный API /api/categories/:id/books возвращает книги категории и добавляет к каждой карточке:

  • coverUrl;
  • порядковый номер внутри выдачи;
  • title, author, description и file_extension.

Frontend уже не занимается тяжёлой логикой. Он просто показывает список карточек, а кнопку «Скачать» связывает с запросом временной ссылки.

Что стоит проверять при развитии

У этого слоя есть несколько мест, которые особенно важно держать под контролем:

  • валидация ZIP-путей при распаковке;
  • проверка реального MIME/magic bytes, а не только расширения;
  • аккуратная обработка больших файлов;
  • защита внешних запросов при скачивании обложек;
  • журналирование ошибок LLM и источников обложек;
  • ручная правка карточки после автоматического импорта.

Часть этих рисков уже описана в docs/SECURITY.md как рекомендации. Поэтому в портфолио лучше показывать проект честно: рабочий импорт и каталог есть, а усиление безопасности и production-процессов — отдельное направление развития.