* feat(recipes): ajoute un adaptateur RecipeSourceAdapter pour Marmiton Étend jsonLdRecipeAdapter (json-ld-recipe.ts) plutôt que de dupliquer sa logique : marmitonAdapter délègue fetchDetail/parse directement à l'adaptateur générique JSON-LD (une page recette marmiton.org expose un Recipe schema.org standard), et n'ajoute que ce que l'adaptateur générique ne peut pas offrir — un list() qui lit l'ItemList schema.org embarqué sur la page de résultats de recherche de marmiton.org (pagination via &page=N, fin de résultats détectée via la réponse 404 renvoyée au-delà de la dernière page). extractJsonLdBlocks est exporté depuis json-ld-recipe.ts pour être réutilisé par marmiton.ts sans dupliquer le regex d'extraction des blocs <script type="application/ld+json">. Enregistre marmitonAdapter dans registerAllRecipeSources (sources/index.ts) — contrairement à jsonLdRecipeAdapter lui-même, c'est un adaptateur concret par site, donc une Source household-toggleable légitime. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(recipes): ajoute un adaptateur RecipeSourceAdapter pour 750g Suit le même schéma que marmitonAdapter (construit sur jsonLdRecipeAdapter), avec deux différences propres à 750g.com : - list() n'a pas d'ItemList JSON-LD à lire sur ses résultats de recherche (la recherche du site est un widget client-side) — appelle donc directement le endpoint GET que ce widget interroge lui-même en interne (un « moteur de réponse IA » qui renvoie un lot de recettes pour une requête en texte libre), et scrape les cartes de résultat par regex en associant à chaque lien de recette sa dernière image précédente plutôt qu'un zip naïf par index (des images décoratives sans carte associée existent réellement dans ce fragment). Vérifié en direct : demander une « page 2 » revient toujours vide, donc nextCursor vaut toujours null, comme theMealDbAdapter. - parse() ne délègue pas aussi directement à jsonLdRecipeAdapter.parse que marmitonAdapter — le générateur JSON-LD de 750g.com a deux bugs réels : des caractères de contrôle bruts non échappés dans certaines chaînes JSON (~1 recette sur 3 dans un échantillon vérifié en direct, sinon JSON.parse échoue et jsonLdRecipeAdapter rapporte à tort « aucun Recipe trouvé »), et un texte parfois doublement encodé en entités HTML (ex. un vrai « é » devient &eacute; au lieu de é). Les deux sont corrigés en pré/post-traitement autour de la même délégation, pas une réimplémentation. Enregistre sevenFiftyGAdapter dans registerAllRecipeSources (sources/index.ts), au même titre que marmitonAdapter. Complète aussi test/sources/sources-index.test.ts, qui ne couvrait encore que TheMealDB malgré l'ajout de Marmiton dans une PR précédente. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * feat(recipes): ajoute un adaptateur RecipeSourceAdapter pour Manger Bouger Suit le même schéma que marmitonAdapter/sevenFiftyGAdapter (construit sur jsonLdRecipeAdapter), avec des différences propres à mangerbouger.fr (« La Fabrique à Menus », Santé publique France) : - list() n'utilise pas de JSON-LD du tout — la page de résultats (une app Next.js) n'embarque aucun ItemList. Elle est cependant rendue côté serveur et expose le même state Redux que le client hydrate, via un <script id="__NEXT_DATA__">, qui contient déjà tout ce dont list() a besoin (slug/nom/image, pagination). Vérifié en direct : ?query=<texte libre> filtre bien côté serveur, et hasMorePages donne un signal de fin de pagination plus propre que le 404 de Marmiton ou l'absence de vraie pagination de 750g. - parse() délègue à jsonLdRecipeAdapter mais corrige deux lacunes réelles et systématiques de son propre JSON-LD (vérifiées sur 9 recettes, 72 étapes) : recipeInstructions[].text est un document Slate.js sérialisé en JSON (pas du texte) plutôt qu'être aplati ; recipeYield est absent partout alors que le nombre de portions existe bien côté site (__NEXT_DATA__) — les deux sont corrigés par un patch structuré (parse → mutation → réécriture) avant délégation, pas une réimplémentation. Enregistre mangerBougerAdapter dans registerAllRecipeSources (sources/index.ts) et complète sources-index.test.ts. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
42 lines
2.2 KiB
TypeScript
42 lines
2.2 KiB
TypeScript
import { registerRecipeSource } from "../lib/recipe-sources/recipe-source-registry.js";
|
|
import { sevenFiftyGAdapter } from "./750g.js";
|
|
import { mangerBougerAdapter } from "./manger-bouger.js";
|
|
import { marmitonAdapter } from "./marmiton.js";
|
|
import { theMealDbAdapter } from "./the-meal-db.js";
|
|
|
|
/**
|
|
* Registers every concrete, *browsable* `RecipeSourceAdapter` this app
|
|
* ships with into the shared in-memory registry (`recipe-source-registry.ts`)
|
|
* — `theMealDbAdapter`, `marmitonAdapter`, `sevenFiftyGAdapter` and
|
|
* `mangerBougerAdapter`. Called once, explicitly, by the two real entry
|
|
* points that need the registry populated:
|
|
*
|
|
* - `server.ts` — the running API process, before it starts listening.
|
|
* - `prisma/seed.ts` — so `syncRecipeSources` has something to mirror into
|
|
* the `sources` table (`Source`, household-toggleable — see
|
|
* `HouseSource`).
|
|
*
|
|
* Deliberately **not** imported by `app.ts`: `createApp()` is what every
|
|
* test file gets via supertest, and registering a real adapter there would
|
|
* make its presence in the registry depend on test *order* (once
|
|
* registered at module load, nothing re-registers it after a test's
|
|
* `clearRecipeSources()` clears it out) instead of each test's own
|
|
* explicit setup. Tests that need a source in the registry register their
|
|
* own throwaway fake instead (see e.g. `test/recipe-source-sync.test.ts`).
|
|
*
|
|
* `jsonLdRecipeAdapter` (json-ld-recipe.ts) itself is deliberately **not**
|
|
* registered here — it's a generic schema.org-JSON-LD parser meant to be
|
|
* specialized per scraped website, not a household-toggleable `Source` in
|
|
* its own right: nobody can meaningfully "trust" or "enable" a generic
|
|
* parsing mechanism the way they can a named website. `marmitonAdapter`
|
|
* (marmiton.ts), `sevenFiftyGAdapter` (750g.ts) and `mangerBougerAdapter`
|
|
* (manger-bouger.ts) are exactly that specialization, one per site — the
|
|
* concrete adapters its own doc comment anticipated ("a concrete adapter
|
|
* for a specific site would use it internally").
|
|
*/
|
|
export function registerAllRecipeSources(): void {
|
|
registerRecipeSource(theMealDbAdapter);
|
|
registerRecipeSource(marmitonAdapter);
|
|
registerRecipeSource(sevenFiftyGAdapter);
|
|
registerRecipeSource(mangerBougerAdapter);
|
|
}
|