chore: initial commit — monorepo ReadaBook (API NestJS, web PWA, Docker)

This commit is contained in:
Git Agent
2026-08-23 09:56:53 +02:00
commit 8f1140127f
79 changed files with 6456 additions and 0 deletions

171
README.md Normal file
View File

@ -0,0 +1,171 @@
# 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
```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 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 :
```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 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.