feat: suivi de la progression de lecture par livre

- API : nouvelle table book_reading_progress (migration 0016) et module
  reading_progress.rs avec GET/PATCH /books/:id/progress (token read)
- API : GET /books/:id enrichi avec reading_status, reading_current_page,
  reading_last_read_at via LEFT JOIN
- Backoffice : badge de statut (Non lu / En cours · p.N / Lu) sur la page
  de détail et overlay sur les BookCards
- OpenSpec : change reading-progress avec proposal/design/specs/tasks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 21:53:52 +01:00
parent 278f422206
commit 648d86970f
16 changed files with 516 additions and 11 deletions

View File

@@ -0,0 +1,16 @@
## MODIFIED Requirements
### Requirement: Consulter le détail d'un livre
Le système SHALL retourner les détails d'un livre via `GET /books/:id`, incluant désormais les informations de progression de lecture : `reading_status` (valeur par défaut `"unread"`), `reading_current_page` (nullable), et `reading_last_read_at` (nullable).
#### Scenario: Livre sans progression enregistrée
- **WHEN** le client appelle `GET /books/:id` pour un livre sans progression
- **THEN** le système retourne HTTP 200 avec les champs de progression à leurs valeurs par défaut : `reading_status = "unread"`, `reading_current_page = null`, `reading_last_read_at = null`
#### Scenario: Livre avec progression en cours
- **WHEN** le client appelle `GET /books/:id` pour un livre dont la progression est `reading`
- **THEN** le système retourne HTTP 200 avec `reading_status = "reading"`, `reading_current_page = <n>`, `reading_last_read_at = <timestamp>`
#### Scenario: Livre inexistant
- **WHEN** le client appelle `GET /books/:id` avec un UUID inexistant
- **THEN** le système retourne HTTP 404

View File

@@ -0,0 +1,54 @@
## ADDED Requirements
### Requirement: Consulter la progression de lecture d'un livre
Le système SHALL retourner la progression de lecture d'un livre via `GET /books/:id/progress`. Si aucune progression n'a été enregistrée, le système SHALL retourner `{ "status": "unread", "current_page": null, "last_read_at": null }` sans erreur 404.
#### Scenario: Progression inexistante
- **WHEN** le client appelle `GET /books/:id/progress` pour un livre sans progression enregistrée
- **THEN** le système retourne HTTP 200 avec `{ "status": "unread", "current_page": null, "last_read_at": null }`
#### Scenario: Progression existante — livre en cours
- **WHEN** le client appelle `GET /books/:id/progress` pour un livre avec `status = "reading"`
- **THEN** le système retourne HTTP 200 avec `{ "status": "reading", "current_page": <n>, "last_read_at": <timestamp> }`
#### Scenario: Livre inexistant
- **WHEN** le client appelle `GET /books/:id/progress` avec un UUID invalide ou inexistant
- **THEN** le système retourne HTTP 404
### Requirement: Mettre à jour la progression de lecture
Le système SHALL permettre de mettre à jour la progression de lecture via `PATCH /books/:id/progress`. Cette route SHALL être accessible avec un token `read` ou `admin`.
#### Scenario: Marquer un livre comme lu
- **WHEN** le client envoie `PATCH /books/:id/progress` avec `{ "status": "read" }`
- **THEN** le système enregistre `status = "read"`, `current_page = null`, `last_read_at = NOW()` et retourne HTTP 200 avec la progression mise à jour
#### Scenario: Marquer un livre comme en cours avec page courante
- **WHEN** le client envoie `PATCH /books/:id/progress` avec `{ "status": "reading", "current_page": 42 }`
- **THEN** le système enregistre `status = "reading"`, `current_page = 42`, `last_read_at = NOW()` et retourne HTTP 200 avec la progression mise à jour
#### Scenario: Réinitialiser la progression
- **WHEN** le client envoie `PATCH /books/:id/progress` avec `{ "status": "unread" }`
- **THEN** le système enregistre `status = "unread"`, `current_page = null`, `last_read_at = NOW()` et retourne HTTP 200 avec la progression mise à jour
#### Scenario: current_page manquant pour status reading
- **WHEN** le client envoie `PATCH /books/:id/progress` avec `{ "status": "reading" }` sans `current_page`
- **THEN** le système retourne HTTP 422 avec un message d'erreur
#### Scenario: current_page invalide (zéro ou négatif)
- **WHEN** le client envoie `PATCH /books/:id/progress` avec `{ "status": "reading", "current_page": 0 }`
- **THEN** le système retourne HTTP 422 avec un message d'erreur
#### Scenario: current_page ignoré pour status non-reading
- **WHEN** le client envoie `PATCH /books/:id/progress` avec `{ "status": "read", "current_page": 42 }`
- **THEN** le système enregistre `current_page = null` (la valeur fournie est ignorée)
#### Scenario: Livre inexistant
- **WHEN** le client envoie `PATCH /books/:id/progress` avec un UUID de livre inexistant
- **THEN** le système retourne HTTP 404
### Requirement: last_read_at mis à jour à chaque modification
Le système SHALL mettre à jour `last_read_at` à l'horodatage courant à chaque appel `PATCH /books/:id/progress`, quel que soit le statut.
#### Scenario: last_read_at actualisé sur tout changement
- **WHEN** le client envoie `PATCH /books/:id/progress` avec n'importe quel statut valide
- **THEN** `last_read_at` dans la réponse est égal à l'heure de la requête (NOW())