9 fichiers à plat -> recipe-sources/ (recipe-source-adapter, recipe-source-
errors, recipe-source-registry) et recipe-matching/ (recipe-translation,
ingredient-matcher, tech-step-matcher). jwt.ts, safe-profile.ts et
logger.service.ts restent à la racine de lib/ (pas de sous-domaine
partagé avec les autres).
Chemins relatifs corrigés dans les fichiers déplacés (profondeur +1 vers
db/) et chez tous leurs importeurs (modules/sources, modules/recipe,
sources/*, db/recipe-source-sync.ts, 12 fichiers de test), doc mise à
jour (specs/backend-architecture.md, specs/batch-cooking-architecture.md).
Vérifié : tsc --noEmit, biome check, build complet, 303 tests API.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Première étape du chantier "onglet Sources" (parcourir toutes les
recettes externes des sources activées par le foyer, importées ou non,
et déclencher leur import à l'ajout au planning) — celle-ci pose les
endpoints backend de lecture seule, rien n'est encore sauvegardé.
- RecipeSourceAdapter gagne `locale` (theMealDbAdapter: "en") — nécessaire
pour que translateRecipe/matchTechStepSpans sachent contre quel jeu de
TechStepMapping/labels d'ingrédients traduire une source donnée.
- findImportedExternalIds (recipe-source-sync.ts) devient
findImportedRecipeIds : renvoie une Map<externalId, recipeId> au lieu
d'un simple Set — son premier vrai appelant (le parcours) a besoin de
l'id réel pour naviguer directement vers la recette déjà importée, pas
seulement savoir qu'elle l'est.
- Nouveau module apps/api/src/modules/sources/ :
- GET /sources/:sourceKey/browse — appelle list() de l'adaptateur,
flague chaque item alreadyImported/recipeId. Restreint aux sources
activées par le foyer courant (HouseSource) ; 404 SOURCE_NOT_FOUND
sinon, même si la source existe (même posture que la visibilité des
recettes : "pas trouvée" plutôt que "pas autorisée").
- GET /sources/:sourceKey/preview/:externalId — fetchDetail + parse +
résolution complète (translateRecipeIngredients, matchTechStepSpans
avec spans réels) contre la locale de la source, sans rien
sauvegarder. Ingrédients non résolus → null plutôt qu'une erreur.
- Nouveaux types partagés (packages/shared/src/types/sources.ts) :
BrowsableSourceItemView, RecipeImportDraftView (+ Draft*View).
Vérifié en conditions réelles contre TheMealDB (recette "Chicken Handi") :
ingrédients résolus avec la bonne quantité/unité (1.2 kg de poulet, 8
gousses d'ail...), non-résolus corrects (huile végétale, piment vert),
et chaque étape avec ses techniques détectées et leurs spans exacts
(cook/fry/plate/setAside sur la même phrase, etc.).
Tests : 276 passing (+8 nouveaux, sources.test.ts). Étape suivante (2/4) :
l'UI de parcours (onglet Sources) — voir le plan de session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Marmiton s'est avéré inaccessible pour du scraping (bloqué même via
WebFetch, signe de protection anti-bot) — TheMealDB (themealdb.com)
est une vraie API JSON publique et gratuite, sans scraping, testée
en conditions réelles (list → fetchDetail → parse fonctionnent
bout en bout contre l'API live).
- Source.iconUrl (nullable) + RecipeSourceAdapter.iconUrl (requis,
même convention que `official`) synchronisé par syncRecipeSources.
- apps/api/src/sources/the-meal-db.ts : premier RecipeSourceAdapter
réel — official: true (API officielle, pas de scraping), utilise
fetch natif (aucune dépendance ajoutée). list() fait une recherche
(pas de vrai "browse" côté TheMealDB, mais une requête vide renvoie
un échantillon de secours) ; parse() éclate les instructions en
étapes par ligne et ignore les emplacements d'ingrédients vides.
- apps/api/src/sources/index.ts : registerAllRecipeSources(), appelé
par server.ts (process réel) et prisma/seed.ts — délibérément PAS
importé par app.ts, pour ne jamais dépendre de l'ordre des tests.
- SourceSelect (web) affiche désormais le logo de la source à côté
de son nom.
Vérifié en conditions réelles : seed → table sources peuplée avec le
vrai logo TheMealDB ; endpoint /reference/sources sur serveur réel ;
parcours navigateur complet (onboarding → étape sources visible avec
icône chargée → activation → paramètres foyer reflète le choix).
186 tests passent (16 nouveaux, dont le moteur TheMealDB testé avec
un stub de fetch — aucun appel réseau réel dans la suite automatisée).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Répond à deux besoins : permettre à chaque foyer de choisir quelles
sources apparaissent dans ses onglets de recettes, et distinguer les
sources à API officielle des sources scrapées.
- RecipeSourceAdapter.official (booléen, sans défaut — chaque
adaptateur doit le déclarer explicitement) synchronisé sur
Source.official par syncRecipeSources.
- HouseSource : table de jointure opt-in (House <-> Source) — aucune
ligne = source masquée. Un foyer nouvellement créé ne voit aucune
source tant qu'il ne les active pas explicitement.
- GET /reference/sources (catalogue des sources implémentées, avec le
flag officiel).
- GET/PATCH /house/current/sources (lecture/remplacement complet des
sources activées par le foyer courant).
- recipe.service.ts : sourceVisibilityWhere() filtre désormais TOUS
les onglets (perso/foyer/publique/favoris) — une recette sans
source reste toujours visible ; une recette importée ne l'est que
si sa source est activée pour le foyer du viewer. Un viewer sans
foyer ne voit aucune recette sourcée.
Côté web :
- Nouvelle étape /onboarding/sources dans le wizard d'inscription,
atteinte uniquement si un foyer vient d'être créé/rejoint (sinon on
saute direct aux allergènes) ; s'auto-saute aussi si aucune source
n'est encore implémentée (catalogue vide aujourd'hui).
- Nouvelle section « Sources de recettes » dans /parametres/foyer
(masquée dans les mêmes conditions), avec sauvegarde à la volée
(même pattern que les autres préférences hot-saved).
- SourceSelect (features/house/), grille de cases à cocher avec badge
officiel/non-officielle, sur le même principe qu'AllergySelect.
172 tests backend passent (dont 25 nouveaux). Build et lint propres.
Vérifié manuellement en navigateur : le parcours d'onboarding saute
bien l'étape sources (catalogue vide) et affiche « 4 sur 4 » quand un
foyer a été créé ; la section paramètres reste invisible tant
qu'aucune source n'existe.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- house.test.ts réécrit (le foyer n'est plus auto-créé) + POST /house,
POST /house/join, POST /house/leave, DELETE /house/current,
DELETE /house/members/:id
- auth.test.ts: signup renvoie houseId=null, DELETE /auth/me (mauvais
mot de passe, suppression, transfert d'admin)
- planning.test.ts/steps.ts: création explicite du foyer (POST /house)
- household.feature: scénarios créer/rejoindre/quitter/supprimer/
retirer un membre, via un second agent (CustomWorld.secondAgent)
- auth.feature: scénarios de suppression de compte
- GET/PATCH /house/current — renomme le foyer de l'utilisateur connecté.
PATCH avec houseId null -> 404 HOUSE_NOT_FOUND.
- PATCH /profile/diet { dietId: number | null } — régime du profil ;
null l'efface (étape skippable du parcours). dietId invalide ->
404 DIET_NOT_FOUND.
- GET/PATCH /profile/allergies — allergènes/intolérances, liste d'IDs ;
PATCH remplace l'ensemble complet (pas une fusion, cohérent avec un
multi-select). ID invalide -> 404 ALLERGY_NOT_FOUND.
- 3 nouveaux ErrorCode (4041-4043) + libellés fr.
- Extraction de toSafeProfile() dans src/lib/safe-profile.ts —
auparavant dupliqué dans auth.service.ts et require-auth.ts,
profile.service.ts le réutilise aussi.
- Tests Mocha (28 passing) + Cucumber (15 scenarios) — même convention
que le reste, doc README.
Deuxième commit de la feature profil/foyer/régime/allergènes —
composants front partagés dans le commit suivant.