Git Agent 4aacac12da fix(web): lecteurs EPUB/PDF — gestion d'erreurs, worker pdf.js local
Composant ReaderError avec diagnostic et actions de repli, worker
pdf.js servi localement (pdfWorker) pour éviter les CDN, typage du
view foliate, navigation retour vers la fiche livre et styles lecteur
associés.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-23 16:24:02 +02:00

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.

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 dun 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 EPUB foliate-js, reprise de progression via /progress.

Stack actuelle

  • Monorepo pnpm.
  • apps/api : NestJS 11, Fastify, SQLite better-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 sur api:3000, non publiée directement sur lhôte.
  • web : Nginx + frontend, publié sur http://localhost:3000.

Ports :

  • Hôte 3000 -> conteneur web:80.
  • LAPI 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 dune 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.sqlite et 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 lenrichissement distant.
  • READABOOK_LIBRARY_HOST_PATH : dossier hôte monté en lecture seule sur /library.
  • READABOOK_LIBRARY_ALIAS_FROM : chemin alternatif accepté par lAPI et traduit vers /library.
  • DATABASE_PATH=/data/readabook.sqlite
  • STORAGE_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; lAPI le traduit vers /library, puis le scan manuel teste lextraction 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 /healthz
  • POST /auth/bootstrap
  • POST /auth/login
  • POST /auth/logout
  • GET /auth/me
  • GET|POST /admin/users
  • GET|POST|PATCH|DELETE /admin/libraries
  • POST /admin/libraries/:id/scan
  • GET /admin/jobs
  • GET /books
  • GET /books/search?q=term
  • GET /books/:id
  • GET /books/:id/file
  • GET /books/:id/cover
  • GET|PUT /progress/:bookId
  • GET /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 derreur réseau ou de rate limit.
  • Jobs in-process : pas de worker externe, pas de reprise fine dun scan interrompu.
  • Pas encore de migrations versionnées Drizzle Kit ; migrations SQL idempotentes exécutées au démarrage.
  • LAPI nest pas exposée directement en compose ; elle passe par le reverse proxy Nginx du service web.
Description
No description provided
Readme 398 KiB
Languages
TypeScript 94.1%
CSS 5%
Dockerfile 0.4%
JavaScript 0.2%
Shell 0.2%