feat(api): modèle de données + API des scénarios de didacticiel (#42/#44 — socle backend) #58

Merged
kyuno merged 1 commit from feat/tour-scenarios-backend into main 2026-09-06 05:39:23 +02:00
Owner

Premier volet du tutoriel guidé — tout le backend, entièrement testable en CI. Le moteur front (driver.js), l'instrumentation data-tour-* et le CRUD admin viennent après (voir « Suite » plus bas).

#44 révise directement la conception de #42 : le registre de tours devient un modèle en base éditable depuis l'admin plutôt qu'un tableau TourDefinition[] figé en code, et title/content d'une étape deviennent du texte libre (pas des clés i18n — même distinction que le nom d'une recette : le chrome de l'UI vient d'i18next, le contenu administré dynamiquement est du texte). Cette PR pose ce modèle.

Schéma (schema.prisma + migration 20260906120000_tour_scenarios)

Les 4 modèles de #44 — 4 tables neuves, rien à backfiller :

  • TourTarget (key PK + description) — catalogue des cibles de surlignage valides, seedé, comme TechStep/Unit. Chaque key correspond à un data-tour-<key> à poser côté front ; un admin choisit parmi ces cibles, il n'en invente pas.
  • TourScenario (key stable, name, enabled) — un scénario rejouable.
  • TourScenarioStep (order, route?, targetKey → FK TourTarget, title, content) — @@unique([scenarioId, order]).
  • TourProgress (userProfileId + scenarioId, PK composite, cascade des deux côtés) — présence d'une ligne = scénario vu.

Seed (reference-seed-data.ts)

  • Les 9 TourTarget correspondant aux data-tour-* prévus (nav sidebar, grille planning, bouton +, bouton Cuisiner, liste de courses, description d'étape, sélecteur de technique).
  • Les 2 scénarios de #42 (general, techStepMetadata) — créés seulement s'ils sont absents (un admin en devient propriétaire ensuite ; un reseed en prod ne doit jamais écraser ses modifs). En test, resetDatabase() truncate d'abord, donc ils sont toujours recréés.
  • techStepMetadata reste volontairement court : #36 (le popover de correction se ferme en pleine sélection) est ouvert — v1 ne couvre que les gestes qui marchent déjà (sélectionner du texte, choisir une technique).

API

Route Rôle
GET /reference/tours (public) Scénarios activés + étapes ordonnées — forme consommée directement par le futur useTour()
GET /profile/tours (session) { completedTourKeys: string[] }
POST /profile/tours/:tourKey/complete (session) Upsert idempotent. Corps vide = terminé ; { "skipped": true } = passé (même effet « ne plus proposer », analytics.recordEvent différent). 404 TOUR_NOT_FOUND (nouveau code 4053) sur une clé inconnue/désactivée.

Analytics « quasi gratuites » de #44 : tour.completed / tour.skipped sont émis ici — le dashboard admin agrège déjà tout type d'AnalyticsEvent de façon générique, aucun changement front nécessaire.

reset-db.ts truncate les 4 nouvelles tables (reseedées juste après, comme diet/tech_step).

Tests (Mocha)

  • reference.test.ts : GET /reference/tours renvoie les scénarios seedés avec leurs étapes ordonnées ; un scénario désactivé est exclu.
  • profile.test.ts : /profile/tours — 401 sans session ; vide pour un profil neuf ; marque vu + relit ; upsert idempotent (2× → une seule ligne) ; { skipped: true } marque quand même vu ; 404 clé inconnue ; 404 tour désactivé ; isolation par profil.

pnpm build + pnpm lint verts. Pas de Postgres local sur ce poste → les tests DB tournent en CI (prisma migrate deploy applique la migration écrite à la main, même pratique que les migrations existantes du repo).

Suite (hors de cette PR)

  • PR admin CRUD (#44) : /admin/api/tours* (list / create / patch-remplace-les-étapes-en-bloc / delete + /targets) + page /admin/didacticiels — la partie API est prête et attend juste d'être poussée.
  • PR moteur front (#42) : dépendance driver.js, apps/web/src/features/tour/ useTour() consommant GET /reference/tours, attributs data-tour-* sur AppLayout/PlanningPage/ShoppingListPage/StepDescription, déclenchement auto (tour général sur /, tour métadonnées sur activeCorrection), section « Revoir un tutoriel » dans les paramètres. À faire dans une session où le rendu des overlays driver.js peut être vérifié visuellement (Cypress couvre la cible/le texte/l'appel de complétion, pas le placement pixel du halo). Tour 2 également conditionné par #36.

Refs #42, #44

🤖 Generated with Claude Code

Premier volet du tutoriel guidé — **tout le backend, entièrement testable en CI**. Le moteur front (`driver.js`), l'instrumentation `data-tour-*` et le CRUD admin viennent après (voir « Suite » plus bas). `#44` révise directement la conception de `#42` : le registre de tours devient un modèle en base éditable depuis l'admin plutôt qu'un tableau `TourDefinition[]` figé en code, et `title`/`content` d'une étape deviennent du **texte libre** (pas des clés i18n — même distinction que le nom d'une recette : le chrome de l'UI vient d'i18next, le contenu administré dynamiquement est du texte). Cette PR pose ce modèle. ## Schéma (`schema.prisma` + migration `20260906120000_tour_scenarios`) Les 4 modèles de `#44` — 4 tables neuves, rien à backfiller : - **`TourTarget`** (`key` PK + `description`) — catalogue des cibles de surlignage valides, seedé, comme `TechStep`/`Unit`. Chaque `key` correspond à un `data-tour-<key>` à poser côté front ; un admin choisit parmi ces cibles, il n'en invente pas. - **`TourScenario`** (`key` stable, `name`, `enabled`) — un scénario rejouable. - **`TourScenarioStep`** (`order`, `route?`, `targetKey` → FK `TourTarget`, `title`, `content`) — `@@unique([scenarioId, order])`. - **`TourProgress`** (`userProfileId` + `scenarioId`, PK composite, cascade des deux côtés) — présence d'une ligne = scénario vu. ## Seed (`reference-seed-data.ts`) - Les 9 `TourTarget` correspondant aux `data-tour-*` prévus (nav sidebar, grille planning, bouton `+`, bouton Cuisiner, liste de courses, description d'étape, sélecteur de technique). - Les 2 scénarios de `#42` (`general`, `techStepMetadata`) — **créés seulement s'ils sont absents** (un admin en devient propriétaire ensuite ; un reseed en prod ne doit jamais écraser ses modifs). En test, `resetDatabase()` truncate d'abord, donc ils sont toujours recréés. - `techStepMetadata` reste volontairement court : `#36` (le popover de correction se ferme en pleine sélection) est ouvert — v1 ne couvre que les gestes qui marchent déjà (sélectionner du texte, choisir une technique). ## API | Route | Rôle | |---|---| | `GET /reference/tours` (public) | Scénarios **activés** + étapes ordonnées — forme consommée directement par le futur `useTour()` | | `GET /profile/tours` (session) | `{ completedTourKeys: string[] }` | | `POST /profile/tours/:tourKey/complete` (session) | Upsert idempotent. Corps vide = terminé ; `{ "skipped": true }` = passé (même effet « ne plus proposer », `analytics.recordEvent` différent). 404 `TOUR_NOT_FOUND` (nouveau code `4053`) sur une clé inconnue/désactivée. | Analytics « quasi gratuites » de `#44` : `tour.completed` / `tour.skipped` sont émis ici — le dashboard admin agrège déjà **tout** type d'`AnalyticsEvent` de façon générique, aucun changement front nécessaire. `reset-db.ts` truncate les 4 nouvelles tables (reseedées juste après, comme `diet`/`tech_step`). ## Tests (Mocha) - `reference.test.ts` : `GET /reference/tours` renvoie les scénarios seedés avec leurs étapes ordonnées ; un scénario désactivé est exclu. - `profile.test.ts` : `/profile/tours` — 401 sans session ; vide pour un profil neuf ; marque vu + relit ; **upsert idempotent** (2× → une seule ligne) ; `{ skipped: true }` marque quand même vu ; 404 clé inconnue ; 404 tour désactivé ; isolation par profil. `pnpm build` + `pnpm lint` verts. Pas de Postgres local sur ce poste → les tests DB tournent en CI (`prisma migrate deploy` applique la migration écrite à la main, même pratique que les migrations existantes du repo). ## Suite (hors de cette PR) - **PR admin CRUD** (`#44`) : `/admin/api/tours*` (list / create / patch-remplace-les-étapes-en-bloc / delete + `/targets`) + page `/admin/didacticiels` — la partie API est prête et attend juste d'être poussée. - **PR moteur front** (`#42`) : dépendance `driver.js`, `apps/web/src/features/tour/` `useTour()` consommant `GET /reference/tours`, attributs `data-tour-*` sur `AppLayout`/`PlanningPage`/`ShoppingListPage`/`StepDescription`, déclenchement auto (tour général sur `/`, tour métadonnées sur `activeCorrection`), section « Revoir un tutoriel » dans les paramètres. À faire dans une session où le rendu des overlays `driver.js` peut être vérifié visuellement (Cypress couvre la cible/le texte/l'appel de complétion, pas le placement pixel du halo). Tour 2 également conditionné par `#36`. Refs #42, #44 🤖 Generated with [Claude Code](https://claude.com/claude-code)
kyuno added 1 commit 2026-09-06 05:23:46 +02:00
feat(api): modèle de données + API des scénarios de didacticiel (#42/#44)
All checks were successful
CI / lint (push) Successful in 1m0s
CI / build (push) Successful in 1m16s
CI / intent-service-test (push) Successful in 3m34s
CI / e2e (push) Successful in 6m38s
CI / test (push) Successful in 15m49s
cd8ce42df7
Socle backend, entièrement testable en CI, pour le tutoriel guidé. Le
moteur front (driver.js), l'instrumentation `data-tour-*` et le CRUD admin
viendront ensuite.

Schéma (`schema.prisma` + migration `20260906120000_tour_scenarios`) — les
4 modèles de #44, qui révisent le registre statique prévu par #42 :
- `TourTarget` (`key` PK + `description`) : catalogue des cibles de
  surlignage valides, seedé, comme `TechStep`/`Unit`.
- `TourScenario` (`key` stable, `name`, `enabled`) : un scénario rejouable.
- `TourScenarioStep` (`order`, `route?`, `targetKey`, `title`, `content`) :
  `title`/`content` sont du **texte libre**, pas des clés i18n (#44 :
  contenu administrable → texte, comme le nom d'une recette).
- `TourProgress` (`userProfileId` + `scenarioId`, PK composite) : présence
  d'une ligne = scénario vu (terminé *ou* passé).

Seed (`reference-seed-data.ts`) : les 9 `TourTarget` correspondant aux
`data-tour-*` à poser côté front, + les 2 scénarios de #42 (`general`,
`techStepMetadata`) **créés seulement s'ils sont absents** (un admin en
devient propriétaire ensuite). `techStepMetadata` reste court pour
l'instant — #36 (popover qui se ferme en pleine sélection) est ouvert, v1
ne couvre que les gestes qui marchent déjà.

API :
- `GET /reference/tours` (public) → scénarios **activés** + étapes
  ordonnées, forme consommée directement par le futur `useTour()`.
- `GET /profile/tours` → `{ completedTourKeys: string[] }`.
- `POST /profile/tours/:tourKey/complete` → upsert idempotent ; corps vide
  = terminé, `{ skipped: true }` = passé. Émet `analytics.recordEvent`
  `tour.completed` / `tour.skipped` (le dashboard admin agrège déjà tout
  type d'`AnalyticsEvent` génériquement — analytics de #44 quasi gratuites).
  404 `TOUR_NOT_FOUND` (nouveau code 4053) sur une clé inconnue/désactivée.

`reset-db.ts` truncate les 4 nouvelles tables (reseedées juste après).

Tests Mocha : `GET /reference/tours` (scénarios + étapes ordonnées,
exclusion d'un scénario désactivé) ; `/profile/tours` GET+POST (upsert
idempotent, `skipped`, 404 clé inconnue, 404 tour désactivé, isolation
par profil). Pas de base Postgres locale → exécutés en CI.

Refs #42, #44
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
kyuno merged commit d1948954a6 into main 2026-09-06 05:39:23 +02:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: kyuno/batchCooking#58
No description provided.