Files
ReadaBook/README.md

6.3 KiB
Raw Blame History

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.
  • ./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.
  • 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

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.