ReadaBook
ReadaBook est une application locale-first pour cataloguer, rechercher et lire une bibliothèque personnelle de livres EPUB/PDF stockés sur disque. Le MVP livré vise un usage domestique : un administrateur déclare un dossier local, lance un scan, puis les livres deviennent accessibles via un catalogue web et une API locale.
Dépôt distant
Le dépôt est hébergé sur un Gitea self-hosted et peut être cloné via :
git clone https://gitea.anthonybouteiller.ovh/blomios/ReadaBook.git
Le déploiement git suit un git-flow simplifié : main (releases), develop (intégration), feature/* / fix/* (travail en cours).
Fonctionnalités MVP présentes
- Backend NestJS/Fastify exécutable avec healthcheck
GET /healthz. - Base SQLite locale avec Drizzle, WAL, migrations idempotentes au démarrage et index FTS5.
- Auth locale : bootstrap du premier admin, login/logout, cookie JWT httpOnly, rôles
admin/user. - Administration : CRUD utilisateurs, CRUD bibliothèques, déclenchement de scan, consultation des jobs.
- Scan récursif de bibliothèques EPUB/PDF.
- Extraction locale pragmatique : métadonnées EPUB, métadonnées PDF simples, jaquettes EPUB quand présentes.
- Enrichissement opportuniste Open Library, désactivable.
- Catalogue : liste, recherche FTS, fiche livre, stream fichier, stream couverture.
- Progression utilisateur : sauvegarde d’un locator/percent et étagère “continuer”.
- Frontend PWA Vite/React dans
apps/web, installable en mode standalone et servi par Nginx en compose. - Expérience web “cabinet de curiosités numérique” : login, bootstrap admin, accueil, bibliothèques, recherche, fiches livre, lecteur, profil et administration.
- Lecteur web : rendu PDF via
pdfjs-dist, adaptateur EPUBfoliate-js, reprise de progression via/progress.
Stack actuelle
- Monorepo
pnpm. apps/api: NestJS 11, Fastify, SQLitebetter-sqlite3, Drizzle ORM, FTS5, Argon2, JOSE JWT.apps/web: Vite, React, TypeScript,lucide-react,pdfjs-dist,foliate-js, service worker et manifeste PWA, Nginx runtime.packages/shared: contrats Zod partagés.- Docker multi-stage Node 22 pour build/runtime API, Nginx pour web.
Structure repo
apps/
api/ Backend NestJS/Fastify
web/ Frontend Vite/React
packages/
shared/ Types et schémas Zod partagés
data/
library/ Dossier local à scanner, monté en lecture seule dans compose
storage/ Cache backend, notamment couvertures extraites
Dockerfile Image API
docker-compose.yaml Services API + web
pnpm-workspace.yaml Workspace monorepo
Développement local
corepack enable
pnpm install
pnpm dev
Scripts utiles :
pnpm dev: lance API + web en parallèle.pnpm dev:api: backend seul.pnpm dev:web: frontend seul.pnpm build: build récursif.pnpm test: tests récursifs.
Note sandbox constatée : sur Node 24 local, better-sqlite3 peut nécessiter un build natif. Le chemin Docker utilise Node 22 et a été validé.
Docker Compose
JWT_SECRET="replace-me" docker compose up --build
Services :
api: API interne surapi:3000, non publiée directement sur l’hôte.web: Nginx + frontend, publié surhttp://localhost:3000.
Ports :
- Hôte
3000-> conteneurweb:80. - L’API est accessible depuis le navigateur via les routes proxifiées par Nginx :
/auth,/admin,/books,/progress,/healthz.
Frontend PWA
Le frontend est accessible sur http://localhost:3000 en compose. Il expose les routes MVP :
/login: connexion locale./setup/*: bootstrap du premier administrateur./home: accueil, reprise de lecture, bibliothèques et catalogue./library/:libraryId: ouvrages d’une bibliothèque./book/:bookId: fiche livre./reader/:bookId: lecture EPUB/PDF avec sauvegarde de progression./search: recherche catalogue./me: profil/session./admin/*: bibliothèques, scans, jobs et comptes.
La PWA fournit manifest.webmanifest, sw.js, une icône maskable SVG et display: standalone. Les appels API passent par le même origin en compose via Nginx, ce qui conserve les cookies httpOnly de session.
Volumes :
./data:/data: base SQLite/data/readabook.sqliteet cache/data/storage.${READABOOK_LIBRARY_HOST_PATH:-./data/library}:/library:ro: bibliothèque locale scannée en lecture seule.
Variables principales :
JWT_SECRET: secret JWT, à changer hors développement.OPEN_LIBRARY_ENABLED=true|false: active/désactive l’enrichissement distant.READABOOK_LIBRARY_HOST_PATH: dossier hôte monté en lecture seule sur/library.READABOOK_LIBRARY_ALIAS_FROM: chemin alternatif accepté par l’API et traduit vers/library.DATABASE_PATH=/data/readabook.sqliteSTORAGE_DIR=/data/storage
Bootstrap admin
Après démarrage compose :
curl -c cookies.txt \
-H "content-type: application/json" \
-d '{"email":"admin@example.com","password":"password123","name":"Admin"}' \
http://localhost:3000/auth/bootstrap
Connexion :
curl -c cookies.txt -b cookies.txt \
-H "content-type: application/json" \
-d '{"email":"admin@example.com","password":"password123"}' \
http://localhost:3000/auth/login
Ajouter une bibliothèque /library
Place les fichiers EPUB/PDF dans data/library côté hôte, puis déclare le volume monté dans le conteneur :
curl -b cookies.txt \
-H "content-type: application/json" \
-d '{"name":"Bibliothèque locale","path":"/library","enabled":true}' \
http://localhost:3000/admin/libraries
Pour tester le corpus réel local Books/ sans le versionner, crée un .env local :
READABOOK_LIBRARY_HOST_PATH=./Books
READABOOK_LIBRARY_ALIAS_FROM=/chemin/absolu/vers/ReadaBook/Books
QA peut ensuite créer la bibliothèque avec le chemin absolu saisi dans READABOOK_LIBRARY_ALIAS_FROM;
l’API le traduit vers /library, puis le scan manuel teste l’extraction ISBN/métadonnées/jaquettes sur ce corpus.
Lancer un scan
curl -b cookies.txt -X POST http://localhost:3000/admin/libraries/1/scan
curl -b cookies.txt http://localhost:3000/admin/jobs
curl -b cookies.txt http://localhost:3000/books
Endpoints principaux
GET /healthzPOST /auth/bootstrapPOST /auth/loginPOST /auth/logoutGET /auth/meGET|POST /admin/usersGET|POST|PATCH|DELETE /admin/librariesPOST /admin/libraries/:id/scanGET /admin/jobsGET /booksGET /books/search?q=termGET /books/:idGET /books/:id/fileGET /books/:id/coverGET|PUT /progress/:bookIdGET /progress/continue
Limites connues
- Extraction PDF limitée aux champs metadata simples, sans OCR ni parsing complet.
- Extraction EPUB correcte pour les cas standards, pas exhaustive sur tous les EPUB malformés.
- Open Library est opportuniste : échec silencieux en cas d’erreur réseau ou de rate limit.
- Jobs in-process : pas de worker externe, pas de reprise fine d’un scan interrompu.
- Pas encore de migrations versionnées Drizzle Kit ; migrations SQL idempotentes exécutées au démarrage.
- L’API n’est pas exposée directement en compose ; elle passe par le reverse proxy Nginx du service web.