172 lines
6.3 KiB
Markdown
172 lines
6.3 KiB
Markdown
# 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.
|