diff --git a/README.md b/README.md index 9e40008..6a2261d 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,63 @@ premier endpoint à combiner `requireAuth`/`AuthLocals` avec un handler async, c a mis au jour une contrainte générique trop stricte, corrigée à la source : [specs/backend-architecture.md](specs/backend-architecture.md). +## Données de référence — régimes & allergènes (apps/api) + +- `GET /reference/diets` — liste des régimes alimentaires (`Diet`, 5 valeurs seedées). +- `GET /reference/allergies` — liste des allergènes sélectionnables, `{ id, name }` + (le nom vient de `Category.name` — la table `allergy` elle-même ne porte pas de + nom, voir `schema.prisma` — chaque allergène = une `Category` + une unique + `Allergy` sous cette catégorie). + +Les deux sont **publics** (pas de `requireAuth`) : ce sont des données de référence, +pas des données de foyer, et le wizard d'inscription doit pouvoir les lire avant +qu'un compte (donc une session) n'existe. + +Données seedées via `apps/api/prisma/seed.ts` (`pnpm --filter api prisma:seed`, ou +automatiquement après `prisma migrate reset` — config `prisma.seed` dans +`package.json`). La logique réelle (listes + upsert idempotent) vit dans +`src/db/reference-seed-data.ts`, partagée avec `test-support/reset-db.ts` : chaque +test repart d'une base **avec** ces données de référence, pas de tables vides — +nécessaire pour tester `dietId`/`allergyIds` sur de vraies lignes. + +`Diet.name` et `Category.name` sont `@unique` — ajouté à ce schéma (pas dans le doc +spec d'origine) précisément pour permettre cet upsert idempotent par nom. + +Liste des 14 allergènes : ceux du règlement UE 1169/2011 (annexe II) — liste +standard, pas inventée. + +**Allergies vs intolérances** (retour fonctionnel, pas dans le doc spec d'origine) : +`Category.kind` (`AllergenKind` — `ALLERGY` | `INTOLERANCE`) classe chaque allergène. +Seuls `Gluten` et `Sulfites` sont en `INTOLERANCE` (réaction non-immunitaire +documentée) ; les 12 autres en `ALLERGY` (réaction immunitaire classique). Classifié +par substance, pas par utilisateur — un même foyer ne peut pas déclarer "allergie au +lait" pour un membre et "intolérance au lait" pour un autre ; a suffi pour le besoin +exprimé, à revoir si ça devient un problème réel. `GET /reference/allergies` renvoie +`kind` dans chaque `AllergyView` ; `PATCH /profile/allergies` ne change pas (une +seule liste d'IDs, `kind` ne sert qu'à grouper l'affichage côté client). + +## Foyer & profil — nom, régime, allergènes (apps/api) + +Nécessitent tous une session (`requireAuth`) — contrairement aux endpoints de +référence ci-dessus, ce sont des données propres à l'utilisateur/au foyer. + +- `GET`/`PATCH /house/current` — foyer de l'utilisateur connecté. `GET` renvoie + `null` si le profil n'a pas encore de foyer (cas théorique : le signup en crée + toujours un) ; `PATCH { name }` le renomme (`404 HOUSE_NOT_FOUND` si le profil + n'a pas de foyer). +- `PATCH /profile/diet { dietId: number | null }` — régime du profil connecté ; + `null` efface le régime (étape "skippable" du parcours). `404 DIET_NOT_FOUND` si + `dietId` ne correspond à aucun régime de référence. +- `GET`/`PATCH /profile/allergies` — allergènes/intolérances du profil connecté, + sous forme de liste d'IDs (`number[]`). `PATCH { allergyIds }` **remplace** + l'ensemble (pas une fusion — le client renvoie toujours la sélection complète, + cohérent avec un composant de multi-sélection). `404 ALLERGY_NOT_FOUND` si un ID + ne correspond à aucun allergène de référence. + +`apps/api/src/lib/safe-profile.ts` centralise le retrait du `passwordHash` +(`toSafeProfile`), auparavant dupliqué dans `auth.service.ts` et +`require-auth.ts` — `profile.service.ts` le réutilise aussi. + ## Page de connexion / inscription (apps/web) - `src/api/client.ts` — `ApiClient` (classe, instance unique exportée `apiClient`) : @@ -211,17 +268,67 @@ Une fois connecté, l'utilisateur atterrit sur `src/layouts/AppLayout.tsx` — s `` pour la route active — montée une seule fois comme route parente de tout l'espace authentifié (`App.tsx`), pas dupliquée par page. `src/pages/HomePage.tsx` (routée sur `/`) affiche le planning de la semaine du foyer (`GET /planning/current`, -voir plus haut) avec ses états chargement/erreur/vide/rempli ; `Recettes`, `Liste de -courses` et `Foyer & profil` n'ont pas encore de backend dédié et rendent pour -l'instant le même composant `ComingSoonPage`. Détail complet (pourquoi une seule -route parente, pourquoi un composant stub partagé) : +voir plus haut) avec ses états chargement/erreur/vide/rempli ; `Recettes` et `Liste de +courses` n'ont pas encore de backend dédié et rendent pour l'instant le même +composant `ComingSoonPage` — `Foyer & profil` (`src/pages/HouseholdPage.tsx`), lui, +est une vraie page (voir section suivante). Détail complet (pourquoi une seule route +parente, pourquoi un composant stub partagé) : [specs/frontend-architecture.md](specs/frontend-architecture.md#applayout--sidebar-commune-à-lespace-connecté). +## Parcours profil — foyer, régime, allergènes (apps/web) + +- `src/features/profile/` — `HouseNameField`, `DietSelect`, `AllergySelect` : champs + contrôlés et "dumb" (reçoivent leurs données en props, ne fetchent rien + eux-mêmes), partagés par les deux surfaces ci-dessous. `AllergySelect` utilise une + grille de cases à cocher dans un `
`/`` plutôt qu'un + `` — far more + * discoverable/tappable, especially on the mobile viewport this app is + * eventually embedded into via Capacitor) for a group of allergens. Used + * both by the signup wizard's allergens step and the `/foyer` settings + * page, and rendered *twice* by each — once for allergies, once for + * intolerances (`AllergyView.kind` groups them; callers filter and pass + * two separate lists rather than this component knowing about the split). + * An empty `value` is a normal, valid state (no declared allergies, or + * this skippable step was skipped), not an incomplete one. + * + * `legend` (not a fixed internal label) — the same component serves both + * groups, only the heading differs. A `
`/`` (not a bare + * `