# batchCooking ## Structure Monorepo pnpm workspaces : - `apps/api` — backend Express/TypeScript (squelette générique : healthcheck, config env, Prisma non modélisé, tests Mocha + Cucumber/BDD) - `apps/web` — frontend React/Vite/TypeScript, prêt à être embarqué par Capacitor plus tard. Page de connexion/inscription en place ; le reste est encore un squelette générique. - `packages/shared` — code partagé entre `api` et `web` : schémas zod (`signupSchema`, `loginSchema`), types (`SafeUserProfile`), et le contrat d'erreurs (`ErrorCode` numérique, `ApiErrorResponse`, voir [specs/error-handling.md](specs/error-handling.md)) — même règles des deux côtés, pas de risque de dérive entre front et back. - `packages/error-tools` — gestion des erreurs, **indépendante de tout framework HTTP** (n'importe pas `express`) : `HttpError`, `ErrorHandlerService`. Séparé d'`express-tools` précisément parce que rien ici ne dépend d'Express. Détail : [specs/error-handling.md](specs/error-handling.md). - `packages/express-tools` — outillage Express générique et réutilisable : `ExpressServer` (init serveur, routes, middlewares), `wrapAsyncHandler`, `createErrorMiddleware` (adapte `ErrorHandlerService` de `error-tools` à Express) — séparé d'`apps/api`, pas de logique métier. Détail : [specs/backend-architecture.md](specs/backend-architecture.md). `packages/shared`, `packages/error-tools` et `packages/express-tools` ont un vrai build (`tsc` → `dist/`, voir leur `package.json`) : consommés en JS compilé, pas en TS brut — nécessaire pour un runtime Node pur (Docker, pas de transpilation à la volée), voir la note dans [specs/frontend-architecture.md](specs/frontend-architecture.md#note-sur-les-fichiers-dts). ## Prérequis - Node.js 22 (voir `.nvmrc`) - pnpm 10 (`corepack enable` puis `corepack use pnpm@10.12.4`, ou installation manuelle) - Docker (pour Postgres en local) ## Installation ```bash pnpm install cp .env.example .env cp apps/api/.env.example apps/api/.env cp apps/web/.env.example apps/web/.env ``` Puis **édite ces deux `.env`** pour renseigner de vrais `POSTGRES_USER`/`POSTGRES_PASSWORD` (et la `DATABASE_URL` correspondante dans `apps/api/.env`) : les fichiers `.env.example` ne contiennent volontairement aucun identifiant réel (juste `changeme`), et `docker-compose.yml` refuse de démarrer tant que `POSTGRES_USER`/`PASSWORD`/`DB` ne sont pas définis dans `.env` — pas de valeur par défaut en dur dans les fichiers commités. Même règle pour `apps/api/.env` : `JWT_SECRET` est **requis, sans défaut** (génère le tien, voir le commentaire dans `apps/api/.env.example`). ### Cypress : téléchargement du binaire `pnpm install` installe le package `cypress` mais **pas forcément son binaire** (le téléchargement du `.exe`/binaire natif peut être ignoré selon l'environnement où `pnpm install` a été lancé — ex. un environnement sandboxé/CI dont le cache ne correspond pas à celui de ta machine). Si `pnpm --filter web e2e` échoue avec une erreur du type : ``` No version of Cypress is installed in: ...\AppData\Local\Cypress\Cache\... Please reinstall Cypress by running: cypress install ``` lance simplement, depuis ta machine : ```bash pnpm --filter web exec cypress install ``` (à faire une seule fois par machine ; le binaire est mis en cache localement, hors du repo). ## Développement ```bash # Base de données Postgres locale docker compose up -d # Applique le schéma (première fois / après un changement de prisma/schema.prisma) pnpm --filter api exec prisma migrate dev # Backend (http://localhost:3000) pnpm dev:api # Frontend (http://localhost:5173) pnpm dev:web ``` > **Conflit de port possible sur `5432`** : si tu as déjà un Postgres natif installé > sur ta machine (service Windows, Homebrew, etc.), il peut occuper le port 5432 et > intercepter les connexions à la place du conteneur Docker (symptôme : Prisma > renvoie `P1000: Authentication failed` alors que les identifiants sont corrects). > Dans ce cas, mets `POSTGRES_PORT=5433` (ou autre) dans ton `.env` **et** adapte le > port dans la `DATABASE_URL` de `apps/api/.env`. ## Qualité / Tests ```bash pnpm lint # Biome (lint + format check) pnpm lint:fix # Biome --write pnpm test # tests unitaires/intégration (Mocha, apps/api) pnpm --filter api test:bdd # tests d'intégration BDD (Cucumber/Gherkin, apps/api) pnpm --filter web e2e # tests e2e (Cypress, démarre le serveur dev automatiquement) pnpm build # build de tous les workspaces ``` La CI GitHub Actions (`.github/workflows/ci.yml`) exécute lint + tests + build sur chaque push/PR vers `main`, puis les tests e2e Cypress. ### Cucumber (apps/api) Tests d'intégration lisibles en Gherkin, en complément de Mocha (qui reste pour les tests unitaires purs) : - `apps/api/features/*.feature` — scénarios en Given/When/Then (`health.feature` sert d'exemple) - `apps/api/features/step-definitions/*.steps.ts` — implémentation des steps - `apps/api/features/support/world.ts` — contexte partagé entre les steps d'un scénario (instancie l'app Express in-process via `createApp()`, comme le fait déjà supertest côté Mocha — pas besoin de lancer un vrai serveur) - `apps/api/cucumber.cjs` — config (extension `.cjs` volontaire, voir la remarque TypeScript/ESM ci-dessous) Pour ajouter un scénario : écrire le `.feature`, lancer `pnpm --filter api test:bdd`, implémenter les steps manquants (Cucumber affiche des snippets tout prêts pour ceux qui n'existent pas encore). > **Piège TypeScript/ESM à connaître** (déjà rencontré avec `cypress.config.ts`) : > les fichiers de config d'outils tiers qui font du chargement dynamique de TS > (`cucumber.cjs`, `cypress.config.ts`…) sont sensibles au `"type": "module"` du > `package.json`. `cucumber.cjs` évite le problème *pour sa propre config* en étant > explicitement CommonJS ; les steps/world restent en `.ts` ESM classique et sont > chargés via `tsx` (`NODE_OPTIONS=--import=tsx`, voir le script `test:bdd`). ## Auth (apps/api) Inscription (création de profil + foyer) et connexion, JWT dans un cookie httpOnly. - `POST /auth/signup` — `{ firstName, lastName, email, password }` → crée le foyer (`house`) et le profil (`user_profiles`) en une transaction, pose le cookie de session, renvoie le profil (201) - `POST /auth/login` — `{ email, password }` → pose le cookie de session, renvoie le profil (200) ; message d'erreur volontairement générique (401) que ce soit l'email ou le mot de passe qui soit incorrect - `POST /auth/logout` — efface le cookie (204) - `GET /auth/me` — profil courant, nécessite le cookie de session (401 sinon) Mots de passe hachés avec argon2. Le hash est indépendant du foyer : un profil crée toujours son propre foyer à l'inscription (rejoindre un foyer existant n'est pas encore implémenté). > **argon2 : version pinnée à `0.31.2`, pas de `^`.** La version `0.45.1` (dernière au > moment de l'écriture) segfault au runtime sur au moins une configuration Windows — > reproduit de façon stable (bash sandboxé, bash non-sandboxé, PowerShell), alors que > `0.31.2` fonctionne parfaitement avec la même API. Si tu montes la version, revérifie > concrètement (`argon2.hash(...)` dans un `node -e`) avant de merger, un `pnpm build` > qui passe ne suffit pas à détecter un crash runtime. Les tests (Mocha + Cucumber) tournent avec un coût argon2 réduit (`NODE_ENV=test`, voir `auth.service.ts`) — le coût par défaut est volontairement élevé (sécurité), ce qui rendrait la suite de tests lente/instable sinon. La CI provisionne un vrai Postgres de service (`.github/workflows/ci.yml`) et exécute `prisma migrate deploy` avant les tests. > **Les tests automatisés et `pnpm dev:api` partagent la même base Postgres locale.** > Lancer `pnpm test`/`test:bdd` **vide `user_profiles`/`house`** (`TRUNCATE ... CASCADE`, > voir `test-support/reset-db.ts`) — si tu es en train de tester manuellement à la main > (via le navigateur ou curl) contre le serveur de dev, un run de tests en parallèle > efface tes données de test sans prévenir. Pas un bug, juste à savoir. ## Page de connexion / inscription (apps/web) - `src/api/client.ts` — `ApiClient` (classe, instance unique exportée `apiClient`) : enveloppe `fetch` vers l'API (`credentials: "include"`, requis pour que le cookie de session httpOnly parte/revienne — l'API et le front sont sur des origines différentes). URL configurable via `VITE_API_URL` (voir `.env.example`). - `src/features/auth/AuthContext.tsx` — état d'auth global ; appelle `GET /auth/me` au chargement pour restaurer la session depuis le cookie. - `src/features/auth/RequireAuth.tsx` / `RedirectIfAuthenticated.tsx` — gardes de route (react-router-dom) : `/` exige d'être connecté, `/login` et `/signup` redirigent vers `/` si on l'est déjà. - `src/pages/{Login,Signup,Home}Page.tsx` — validation client instantanée via les schémas zod partagés (`packages/shared`), erreurs API traduites via `ErrorMessageService` (voir ci-dessous). Détail de l'organisation complète (dossiers, routing, SCSS/theming) : [specs/frontend-architecture.md](specs/frontend-architecture.md). Tests Cypress (`apps/web/cypress/e2e/`) : `smoke.cy.ts` + `auth.cy.ts` mockent l'API via `cy.intercept` plutôt que de dépendre d'un vrai backend — le job e2e de la CI ne provisionne pas de Postgres/API, seulement le serveur de dev Vite. Le comportement réel de l'API est couvert par les suites Mocha/Cucumber d'`apps/api` (contre une vraie base). ## Gestion des erreurs (API ↔ web) Contrat d'erreurs partagé via `packages/shared` (`ErrorCode`, énumération **numérique** groupée par famille — `4000` validation, `401x` auth, `404x` not found, `500x` interne — et `ApiErrorResponse`) : l'API renvoie toujours `{ code, message, details? }` (message en anglais, dev-facing — jamais affiché tel quel), et le client traduit `code` en libellé français via **i18next** (`ErrorMessageService`, `apps/web/src/services/error-message.service.ts` → `apps/web/src/locales/fr/translation.json`). Côté API, `ErrorHandlerService` (`packages/error-tools`) et `createErrorMiddleware` (`packages/express-tools`) centralisent la transformation de toute erreur levée en réponse HTTP conforme — aucune valeur `ErrorCode` codée en dur nulle part (toujours `ErrorCode.XXX`, y compris dans les mocks Cypress). Détail complet (schéma, exemples, comment ajouter un nouveau code d'erreur) : [specs/error-handling.md](specs/error-handling.md). Le profil authentifié (`requireAuth`) passe par `res.locals.userProfile` (typé via `AuthLocals`), pas par une augmentation du namespace global Express — voir [specs/backend-architecture.md](specs/backend-architecture.md) pour le détail et le pourquoi. `packages/shared` fournit aussi `assertIsNever` (vérification d'exhaustivité de switch/if-chain sur une union, erreur de **compilation** si un cas est oublié) — voir [specs/backend-architecture.md](specs/backend-architecture.md#packagesshared--assertisnever). ## i18n **i18next** + **react-i18next** — tout le texte affiché (formulaires, boutons, erreurs) vient de fichiers de locale JSON (`apps/web/src/locales//translation.json`), jamais codé en dur dans un composant. Une seule langue existe aujourd'hui (`fr`) ; en ajouter une est une question de fichier de locale, pas de code. Détail : [specs/frontend-architecture.md](specs/frontend-architecture.md#i18n-internationalisation). ## Données de test (faker.js) `apps/api` utilise [`@faker-js/faker`](https://fakerjs.dev/) pour toutes les données de test dans `test/auth.test.ts` (Mocha) et le "bruit" (prénom/nom de remplissage) des steps Cucumber — jamais de nom/email qui ressemble à une vraie personne en dur dans un fixture. Les valeurs *littérales* des scénarios `.feature` eux-mêmes (ex. `alice@example.com`) restent volontairement statiques : c'est le point des scénarios Gherkin lisibles (exemples illustratifs conventionnels en BDD, pas des données réelles) — seules les données de remplissage hors du texte lisible du scénario sont générées.