BOOK-LIBRARY: архитектура
BOOK-LIBRARY — это не одна страница с перечнем файлов, а несколько связанных приложений вокруг одного backend. Публичная SvelteKit-витрина отвечает за спокойный путь читателя к книге, React-админка — за операционную работу, а backend на Express держит API, SQLite, uploads, очереди, сессии, временные download-токены и production-hosted bundle админки.
Архитектура здесь нужна не ради схемы на бумаге. Она отделяет чтение от обслуживания библиотеки: читатель не видит админского шума, а администратор получает место, где можно импортировать книги, проверять внешние источники, запускать переводы, разбираться с провайдерами, следить за безопасностью и понимать статусы задач. Backend остаётся точкой, где принимаются решения о данных, файлах и долгих процессах.
Главная идея этой статьи — показать, почему BOOK-LIBRARY устроен слоями. Каталог, импорт, переводы, публикация и сопровождение требуют разных интерфейсов и разной степени доверия к данным. Если смешать всё в одну поверхность, библиотека быстро станет хрупкой; если развести роли аккуратно, проект проще развивать и обслуживать.
Основные части
В архитектуре есть четыре заметных слоя:
- Express backend — API, SQLite, uploads, sessions, jobs, download, security и legacy routes;
- SvelteKit storefront — публичная витрина каталога для читателя;
- React admin — основная админка и операционный центр;
- legacy EJS UI — простой HTML-интерфейс внутри backend.
Старая Svelte-admin часть удалена. Это важное изменение: SvelteKit остаётся публичной витриной, а админские задачи больше не конкурируют между двумя frontend-слоями.
Express backend
Backend — это рабочая часть системы, куда сходятся и читательские запросы, и админские операции. Он обслуживает несколько типов маршрутов:
- public API для категорий, книг, переводов и download-token запроса;
- admin API для управления каталогом, импортом, переводами, провайдерами и настройками;
- download routes для выдачи файлов по временным токенам;
- legacy routes для EJS-интерфейса;
- production-hosted React admin bundle, если backend развёрнут вместе с админкой.
На backend также лежит работа, которую нельзя честно переложить на клиент: проверка сессий, CSRF для state-changing admin API, CORS-разделение public/admin origins, доступ к SQLite, работа с uploads и запуск durable jobs. Клиенты остаются интерфейсами, а backend держит правила, состояние и последствия операций.
SQLite и группы данных
SQLite остаётся хорошим выбором для частной библиотеки: его проще разворачивать, бэкапить и переносить вместе с uploads. Но по мере роста проекта база перестаёт быть просто «списком книг». В ней появляется память о том, что происходило с каталогом, импортами, переводами и доступом.
Условно данные делятся на несколько групп:
- каталог: книги, категории, связи с деревом категорий, метаданные и обложки;
- доступ:
admin_users, SQLite-backed sessions, смена пароля и emergency env override; - скачивание: временные токены и сроки их жизни;
- импорт: jobs, статусы, результаты, ошибки, отчёты и повторы;
- внешние источники: данные поиска и импорта из Anna’s Archive и Flibusta;
- LLM: runtime-провайдеры, priority, fallback и настройки;
- переводы: translation jobs, segments, QA findings, artifacts, publish/unpublish состояние.
Ценность такой схемы не в количестве таблиц, а в том, что долгие и спорные процессы получают собственное состояние. Импорт или перевод можно показать в админке, повторить, отменить, разобрать по ошибкам — и не превращать каждый сбой в ручное расследование по файлам и логам.
Дерево категорий
Категории стали деревом, а не плоским списком. Для публичной витрины это меняет навигацию: теперь есть breadcrumbs, подкатегории и subtree counts. Читатель может идти от крупной темы к более узкой, а не угадывать нужный раздел в длинном списке без структуры.
Для API это тоже важно. Книги внутри категории отдаются постранично через limit и offset, вместе с hasMore и total. Такой ответ лучше подходит большим разделам. При этом старый формат не сломан: legacy-клиенты могут запросить format=legacy или all=1.
Публичная SvelteKit-витрина
Storefront отвечает за читательский опыт:
- показать дерево категорий;
- вывести breadcrumbs и подкатегории;
- загрузить книги выбранной категории постранично;
- показать доступные опубликованные переводы;
- запросить временную ссылку для скачивания.
Витрина не должна знать внутренности очередей, провайдеров или админских настроек. Её задача проще и важнее: не мешать человеку найти книгу и скачать доступный файл. Поэтому она работает через public API и получает только то, что можно показать читателю.
React-админка
React-админка стала целевой поверхностью для управления проектом. В ней собраны dashboard, операции, поиск, переводы, настройки, пользователи, смена пароля, провайдеры и security-сценарии. Это место не для читателя, а для человека, который поддерживает библиотеку в порядке.
Админка использует более подходящий для операционного интерфейса стек: React, shadcn/ui-подход к компонентам и тёмную нейтральную палитру. Важнее не визуальная эффектность, а то, чтобы задачи, ошибки и настройки были видны без раскапывания логов.
Отдельная роль React-админки — быть UI для долгих процессов. Импорт архива, поиск во внешнем источнике или LLM-перевод не обязаны укладываться в один запрос. Админка показывает состояние durable jobs через polling и превращает длинные операции в наблюдаемый рабочий процесс.
Durable jobs и polling
Долгие операции вынесены в устойчивые задачи:
- одиночный импорт файла;
- batch ZIP import;
- поиск и импорт из Anna’s Archive;
- поиск и импорт из Flibusta;
- LLM translation jobs;
- повтор неудачных этапов и отмена задач.
Раньше пакетный импорт можно было воспринимать как потоковый ответ. Сейчас главный акцент другой: задача живёт в базе, имеет статус, прогресс, события и ошибки. Клиент может закрыться, обновиться или потерять соединение, а backend всё равно сохраняет текущее состояние операции.
Polling здесь выглядит уместнее, чем попытка держать длинный запрос до конца. Для админки важнее предсказуемость: видеть, что задача queued, running, failed, cancelled или completed, и понимать, что делать дальше. Так импорт и переводы становятся не «чёрным ящиком», а процессом, за которым можно следить и который можно чинить.
Временные токены скачивания
Книги не отдаются по постоянному публичному пути. Читатель или Quartz-интеграция сначала запрашивает временный token, а backend выдаёт файл через download route. Токен живёт ограниченное время и может быть очищен после истечения срока.
Это не превращает систему в защищённый DRM, но убирает прямую раздачу стабильных URL на файлы. Для частной библиотеки это важный слой аккуратности: публичная витрина остаётся удобной, а файловое хранилище не раскрывается как обычная папка со статическими ссылками.
Безопасность
Security-слой заметно продвинулся по сравнению с ранним вариантом:
- сессии администратора хранятся в SQLite;
- есть таблица
admin_users; - пароль можно менять из админки;
- emergency env override оставлен для восстановления доступа;
- state-changing admin API защищены CSRF;
- CORS разделён для public и admin origins;
- public CORP настроен для обложек;
- backend может обслуживать production React admin bundle.
Важно не переоценивать это как «абсолютную безопасность». Файловые загрузки, внешние источники, архивы, OCR и генерация PDF всё равно требуют осторожности. Но базовый слой админского доступа, cookies, origins и CSRF уже не выглядит как черновой прототип.
Production pipeline
Production pipeline разворачивает backend вместе с hosted React admin. Публичная SvelteKit-витрина выкатывается отдельно, если она нужна как отдельная поверхность. Такое разделение удобно: backend и админка составляют операционную часть, а storefront можно обновлять или размещать отдельно.
Для Quartz-интеграции это тоже полезно. Quartz не должен становиться backend для книг. Он может брать generated catalog и mirrored covers, а операции с файлами и download-токенами остаются в BOOK-LIBRARY. В итоге публикация каталога не смешивается с обслуживанием файлов, очередей и переводов.
Legacy EJS UI
Legacy EJS UI остаётся как простой HTML-слой. Его смысл не в том, чтобы конкурировать с SvelteKit или React, а в запасном варианте доступа и в более прямом интерфейсе без отдельной frontend-сборки.
Для проекта это нормальный компромисс: основной путь развивается в сторону SvelteKit storefront и React admin, но backend всё ещё может отдать базовую HTML-страницу. Такой слой полезен именно своей простотой: он не делает систему красивее, зато снижает зависимость от полноценной frontend-сборки.
Текущие ограничения
Несмотря на заметное развитие, у архитектуры есть честные ограничения:
- SQLite требует дисциплины бэкапов вместе с uploads;
- OCR и production PDF renderer для переводов остаются deferred scope;
- DOCX/PDF media extraction, translation memory и budget dashboards ещё не закрыты;
- внешние источники зависят от доступности и качества ответа;
- публичная витрина показывает только published translations, поэтому черновики нужно проверять в админке;
- сложные роли пользователей пока не являются главным сценарием.
Эти ограничения не противоречат текущей стадии проекта. BOOK-LIBRARY уже выглядит как рабочая библиотечная система, а не как набор форм вокруг SQLite. Главное, что архитектура задаёт понятные границы: где читатель видит каталог, где администратор разбирает операции, где backend отвечает за файлы и состояние. Следующий рост логично вести через качество данных, наблюдаемость, бэкапы и аккуратное расширение переводческого pipeline.