# 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 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 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 ```text 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 ```bash 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 ```bash JWT_SECRET="replace-me" docker compose up --build ``` Services : - `api` : API interne sur `api:3000`, non publiée directement sur l’hôte. - `web` : Nginx + frontend, publié sur `http://localhost:3000`. Ports : - Hôte `3000` -> conteneur `web: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.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 l’enrichissement distant. - `DATABASE_PATH=/data/readabook.sqlite` - `STORAGE_DIR=/data/storage` ## Bootstrap admin Après démarrage compose : ```bash curl -c cookies.txt \ -H "content-type: application/json" \ -d '{"email":"admin@example.com","password":"password123","name":"Admin"}' \ http://localhost:3000/auth/bootstrap ``` Connexion : ```bash 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 : ```bash 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 ```bash 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 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.