diff --git a/apps/api/src/db/recipe-source-sync.ts b/apps/api/src/db/recipe-source-sync.ts index 8ad5a55..1ffe1be 100644 --- a/apps/api/src/db/recipe-source-sync.ts +++ b/apps/api/src/db/recipe-source-sync.ts @@ -1,5 +1,5 @@ import type { PrismaClient } from "@prisma/client"; -import { listRecipeSources } from "../lib/recipe-source-registry.js"; +import { listRecipeSources } from "../lib/recipe-sources/recipe-source-registry.js"; /** * Upserts one `Source` row (schema.prisma) per adapter currently in diff --git a/apps/api/src/lib/ingredient-matcher.ts b/apps/api/src/lib/recipe-matching/ingredient-matcher.ts similarity index 99% rename from apps/api/src/lib/ingredient-matcher.ts rename to apps/api/src/lib/recipe-matching/ingredient-matcher.ts index 2c3c71f..a483879 100644 --- a/apps/api/src/lib/ingredient-matcher.ts +++ b/apps/api/src/lib/recipe-matching/ingredient-matcher.ts @@ -3,7 +3,7 @@ import { INGREDIENT_LABELS_EN, UNIT_LABELS_EN, } from "@batch-cooking/shared"; -import { prisma } from "../db/prisma.js"; +import { prisma } from "../../db/prisma.js"; import { normalizeText } from "./tech-step-matcher.js"; /** diff --git a/apps/api/src/lib/recipe-translation.ts b/apps/api/src/lib/recipe-matching/recipe-translation.ts similarity index 99% rename from apps/api/src/lib/recipe-translation.ts rename to apps/api/src/lib/recipe-matching/recipe-translation.ts index a3de19e..7211c6d 100644 --- a/apps/api/src/lib/recipe-translation.ts +++ b/apps/api/src/lib/recipe-matching/recipe-translation.ts @@ -1,4 +1,9 @@ import type { UnitType } from "@batch-cooking/shared"; +import type { + ParsedRecipe, + ParsedRecipeIngredient, + ParsedRecipeStep, +} from "../recipe-sources/recipe-source-adapter.js"; import { extractQuantity, type IngredientMatchEntry, @@ -8,11 +13,6 @@ import { matchUnit, type UnitMatchEntry, } from "./ingredient-matcher.js"; -import type { - ParsedRecipe, - ParsedRecipeIngredient, - ParsedRecipeStep, -} from "./recipe-source-adapter.js"; import { loadTechStepMappingRules, matchTechSteps, diff --git a/apps/api/src/lib/tech-step-matcher.ts b/apps/api/src/lib/recipe-matching/tech-step-matcher.ts similarity index 99% rename from apps/api/src/lib/tech-step-matcher.ts rename to apps/api/src/lib/recipe-matching/tech-step-matcher.ts index 6803b2e..421e559 100644 --- a/apps/api/src/lib/tech-step-matcher.ts +++ b/apps/api/src/lib/recipe-matching/tech-step-matcher.ts @@ -1,4 +1,4 @@ -import { prisma } from "../db/prisma.js"; +import { prisma } from "../../db/prisma.js"; /** * Auto-detects which cooking techniques (`TechStep`) a free-text recipe diff --git a/apps/api/src/lib/recipe-source-adapter.ts b/apps/api/src/lib/recipe-sources/recipe-source-adapter.ts similarity index 100% rename from apps/api/src/lib/recipe-source-adapter.ts rename to apps/api/src/lib/recipe-sources/recipe-source-adapter.ts diff --git a/apps/api/src/lib/recipe-source-errors.ts b/apps/api/src/lib/recipe-sources/recipe-source-errors.ts similarity index 100% rename from apps/api/src/lib/recipe-source-errors.ts rename to apps/api/src/lib/recipe-sources/recipe-source-errors.ts diff --git a/apps/api/src/lib/recipe-source-registry.ts b/apps/api/src/lib/recipe-sources/recipe-source-registry.ts similarity index 100% rename from apps/api/src/lib/recipe-source-registry.ts rename to apps/api/src/lib/recipe-sources/recipe-source-registry.ts diff --git a/apps/api/src/modules/recipe/recipe.service.ts b/apps/api/src/modules/recipe/recipe.service.ts index e29b1fb..10801e7 100644 --- a/apps/api/src/modules/recipe/recipe.service.ts +++ b/apps/api/src/modules/recipe/recipe.service.ts @@ -14,7 +14,10 @@ import { } from "@batch-cooking/shared"; import type { Prisma } from "@prisma/client"; import { prisma } from "../../db/prisma.js"; -import { loadTechStepMappingRules, matchTechStepSpans } from "../../lib/tech-step-matcher.js"; +import { + loadTechStepMappingRules, + matchTechStepSpans, +} from "../../lib/recipe-matching/tech-step-matcher.js"; // No user-language preference exists anywhere in the app yet (a single // "fr" translation file, no locale field on User/UserProfile) — steps are diff --git a/apps/api/src/modules/sources/sources.service.ts b/apps/api/src/modules/sources/sources.service.ts index cb14c01..161b14a 100644 --- a/apps/api/src/modules/sources/sources.service.ts +++ b/apps/api/src/modules/sources/sources.service.ts @@ -15,15 +15,21 @@ import { loadIngredientCatalog, loadUnitCatalog, type UnitMatchEntry, -} from "../../lib/ingredient-matcher.js"; -import { markAlreadyImported, type RecipeSourceAdapter } from "../../lib/recipe-source-adapter.js"; -import { RecipeSourceError } from "../../lib/recipe-source-errors.js"; -import { getRecipeSource } from "../../lib/recipe-source-registry.js"; +} from "../../lib/recipe-matching/ingredient-matcher.js"; import { mergeDuplicateIngredients, translateRecipeIngredients, -} from "../../lib/recipe-translation.js"; -import { loadTechStepMappingRules, matchTechStepSpans } from "../../lib/tech-step-matcher.js"; +} from "../../lib/recipe-matching/recipe-translation.js"; +import { + loadTechStepMappingRules, + matchTechStepSpans, +} from "../../lib/recipe-matching/tech-step-matcher.js"; +import { + markAlreadyImported, + type RecipeSourceAdapter, +} from "../../lib/recipe-sources/recipe-source-adapter.js"; +import { RecipeSourceError } from "../../lib/recipe-sources/recipe-source-errors.js"; +import { getRecipeSource } from "../../lib/recipe-sources/recipe-source-registry.js"; import { getHouseSourceIds } from "../house/house.service.js"; import { createImportedRecipe } from "../recipe/recipe.service.js"; import { getIngredients, getUnits } from "../reference/reference.service.js"; diff --git a/apps/api/src/sources/index.ts b/apps/api/src/sources/index.ts index 3ff3159..2348cfd 100644 --- a/apps/api/src/sources/index.ts +++ b/apps/api/src/sources/index.ts @@ -1,4 +1,4 @@ -import { registerRecipeSource } from "../lib/recipe-source-registry.js"; +import { registerRecipeSource } from "../lib/recipe-sources/recipe-source-registry.js"; import { theMealDbAdapter } from "./the-meal-db.js"; /** diff --git a/apps/api/src/sources/json-ld-recipe.ts b/apps/api/src/sources/json-ld-recipe.ts index 10971a2..de38a9a 100644 --- a/apps/api/src/sources/json-ld-recipe.ts +++ b/apps/api/src/sources/json-ld-recipe.ts @@ -2,8 +2,11 @@ import type { ParsedRecipe, ParsedRecipeIngredient, RecipeSourceAdapter, -} from "../lib/recipe-source-adapter.js"; -import { RecipeSourceFetchError, RecipeSourceParseError } from "../lib/recipe-source-errors.js"; +} from "../lib/recipe-sources/recipe-source-adapter.js"; +import { + RecipeSourceFetchError, + RecipeSourceParseError, +} from "../lib/recipe-sources/recipe-source-errors.js"; const SOURCE_KEY = "jsonLdRecipe"; diff --git a/apps/api/src/sources/the-meal-db.ts b/apps/api/src/sources/the-meal-db.ts index d4841d2..d57f29c 100644 --- a/apps/api/src/sources/the-meal-db.ts +++ b/apps/api/src/sources/the-meal-db.ts @@ -3,8 +3,11 @@ import type { RecipeSourceAdapter, RecipeSourceListParams, RecipeSourceListResult, -} from "../lib/recipe-source-adapter.js"; -import { RecipeSourceFetchError, RecipeSourceParseError } from "../lib/recipe-source-errors.js"; +} from "../lib/recipe-sources/recipe-source-adapter.js"; +import { + RecipeSourceFetchError, + RecipeSourceParseError, +} from "../lib/recipe-sources/recipe-source-errors.js"; const SOURCE_KEY = "theMealDb"; diff --git a/apps/api/test/house.test.ts b/apps/api/test/house.test.ts index f621b56..2774ec6 100644 --- a/apps/api/test/house.test.ts +++ b/apps/api/test/house.test.ts @@ -6,8 +6,11 @@ import request from "supertest"; import { createApp } from "../src/app.js"; import { prisma } from "../src/db/prisma.js"; import { syncRecipeSources } from "../src/db/recipe-source-sync.js"; -import type { RecipeSourceAdapter } from "../src/lib/recipe-source-adapter.js"; -import { clearRecipeSources, registerRecipeSource } from "../src/lib/recipe-source-registry.js"; +import type { RecipeSourceAdapter } from "../src/lib/recipe-sources/recipe-source-adapter.js"; +import { + clearRecipeSources, + registerRecipeSource, +} from "../src/lib/recipe-sources/recipe-source-registry.js"; import { resetDatabase } from "../test-support/reset-db.js"; function buildSignupPayload(): SignupInput { diff --git a/apps/api/test/ingredient-matcher.test.ts b/apps/api/test/ingredient-matcher.test.ts index 1114001..6eb3e0a 100644 --- a/apps/api/test/ingredient-matcher.test.ts +++ b/apps/api/test/ingredient-matcher.test.ts @@ -9,7 +9,7 @@ import { matchIngredientName, matchUnit, type UnitMatchEntry, -} from "../src/lib/ingredient-matcher.js"; +} from "../src/lib/recipe-matching/ingredient-matcher.js"; import { resetDatabase } from "../test-support/reset-db.js"; describe("ingredient-matcher", () => { diff --git a/apps/api/test/json-ld-recipe.test.ts b/apps/api/test/json-ld-recipe.test.ts index 36ea5a6..a45e43f 100644 --- a/apps/api/test/json-ld-recipe.test.ts +++ b/apps/api/test/json-ld-recipe.test.ts @@ -1,5 +1,8 @@ import { expect } from "chai"; -import { RecipeSourceFetchError, RecipeSourceParseError } from "../src/lib/recipe-source-errors.js"; +import { + RecipeSourceFetchError, + RecipeSourceParseError, +} from "../src/lib/recipe-sources/recipe-source-errors.js"; import { jsonLdRecipeAdapter } from "../src/sources/json-ld-recipe.js"; /** Stubs `globalThis.fetch` to return `html` as the response body — same reasoning/pattern as `the-meal-db.test.ts`'s `stubFetch`, just returning text instead of JSON. */ diff --git a/apps/api/test/recipe-source-sync.test.ts b/apps/api/test/recipe-source-sync.test.ts index e366af4..3153668 100644 --- a/apps/api/test/recipe-source-sync.test.ts +++ b/apps/api/test/recipe-source-sync.test.ts @@ -5,8 +5,11 @@ import request from "supertest"; import { createApp } from "../src/app.js"; import { prisma } from "../src/db/prisma.js"; import { findImportedRecipeIds, syncRecipeSources } from "../src/db/recipe-source-sync.js"; -import type { RecipeSourceAdapter } from "../src/lib/recipe-source-adapter.js"; -import { clearRecipeSources, registerRecipeSource } from "../src/lib/recipe-source-registry.js"; +import type { RecipeSourceAdapter } from "../src/lib/recipe-sources/recipe-source-adapter.js"; +import { + clearRecipeSources, + registerRecipeSource, +} from "../src/lib/recipe-sources/recipe-source-registry.js"; import { resetDatabase } from "../test-support/reset-db.js"; /** See `recipe.test.ts` — generated rather than hardcoded, no test fixture looks like a real person's data. */ diff --git a/apps/api/test/recipe-source.test.ts b/apps/api/test/recipe-source.test.ts index e10b3c1..b8a2198 100644 --- a/apps/api/test/recipe-source.test.ts +++ b/apps/api/test/recipe-source.test.ts @@ -5,19 +5,19 @@ import type { RecipeSourceListItem, RecipeSourceListParams, RecipeSourceListResult, -} from "../src/lib/recipe-source-adapter.js"; -import { markAlreadyImported } from "../src/lib/recipe-source-adapter.js"; +} from "../src/lib/recipe-sources/recipe-source-adapter.js"; +import { markAlreadyImported } from "../src/lib/recipe-sources/recipe-source-adapter.js"; import { RecipeSourceError, RecipeSourceFetchError, RecipeSourceParseError, -} from "../src/lib/recipe-source-errors.js"; +} from "../src/lib/recipe-sources/recipe-source-errors.js"; import { clearRecipeSources, getRecipeSource, listRecipeSources, registerRecipeSource, -} from "../src/lib/recipe-source-registry.js"; +} from "../src/lib/recipe-sources/recipe-source-registry.js"; interface FakeRawRecipe { externalId: string; diff --git a/apps/api/test/recipe-translation.test.ts b/apps/api/test/recipe-translation.test.ts index 6dbebc2..6bf46ca 100644 --- a/apps/api/test/recipe-translation.test.ts +++ b/apps/api/test/recipe-translation.test.ts @@ -1,7 +1,9 @@ import { expect } from "chai"; import { prisma } from "../src/db/prisma.js"; -import type { IngredientMatchEntry, UnitMatchEntry } from "../src/lib/ingredient-matcher.js"; -import type { ParsedRecipe, ParsedRecipeIngredient } from "../src/lib/recipe-source-adapter.js"; +import type { + IngredientMatchEntry, + UnitMatchEntry, +} from "../src/lib/recipe-matching/ingredient-matcher.js"; import { mergeDuplicateIngredients, type TranslatedRecipeIngredient, @@ -9,8 +11,12 @@ import { translateRecipeIngredients, translateRecipeSteps, type UnitConversionEntry, -} from "../src/lib/recipe-translation.js"; -import type { TechStepMappingRule } from "../src/lib/tech-step-matcher.js"; +} from "../src/lib/recipe-matching/recipe-translation.js"; +import type { TechStepMappingRule } from "../src/lib/recipe-matching/tech-step-matcher.js"; +import type { + ParsedRecipe, + ParsedRecipeIngredient, +} from "../src/lib/recipe-sources/recipe-source-adapter.js"; import { resetDatabase } from "../test-support/reset-db.js"; /** A minimal fixture `ParsedRecipe` — only `steps` (built from `descriptions`) matters for most tests here, the rest is filler to prove it survives translation untouched. */ diff --git a/apps/api/test/recipe.test.ts b/apps/api/test/recipe.test.ts index 90ffd67..5b35d94 100644 --- a/apps/api/test/recipe.test.ts +++ b/apps/api/test/recipe.test.ts @@ -6,8 +6,11 @@ import request from "supertest"; import { createApp } from "../src/app.js"; import { prisma } from "../src/db/prisma.js"; import { syncRecipeSources } from "../src/db/recipe-source-sync.js"; -import type { RecipeSourceAdapter } from "../src/lib/recipe-source-adapter.js"; -import { clearRecipeSources, registerRecipeSource } from "../src/lib/recipe-source-registry.js"; +import type { RecipeSourceAdapter } from "../src/lib/recipe-sources/recipe-source-adapter.js"; +import { + clearRecipeSources, + registerRecipeSource, +} from "../src/lib/recipe-sources/recipe-source-registry.js"; import { resetDatabase } from "../test-support/reset-db.js"; /** A minimal `RecipeSourceAdapter` — only `key`/`name`/`official`/`iconUrl` matter for `syncRecipeSources` fixtures here. */ @@ -60,7 +63,7 @@ async function techStepId(key: string): Promise { return techStep.id; } -/** A step's detected technique sequence, in order — mirrors `matchTechSteps`' return shape (`../src/lib/tech-step-matcher.js`) so tests can assert on it directly. */ +/** A step's detected technique sequence, in order — mirrors `matchTechSteps`' return shape (`../src/lib/recipe-matching/tech-step-matcher.js`) so tests can assert on it directly. */ async function stepTechStepIds(stepId: number): Promise { const links = await prisma.stepTechStep.findMany({ where: { stepId }, diff --git a/apps/api/test/reference.test.ts b/apps/api/test/reference.test.ts index 77ed7ba..05de918 100644 --- a/apps/api/test/reference.test.ts +++ b/apps/api/test/reference.test.ts @@ -4,8 +4,11 @@ import { createApp } from "../src/app.js"; import { prisma } from "../src/db/prisma.js"; import { syncRecipeSources } from "../src/db/recipe-source-sync.js"; import { seedReferenceData } from "../src/db/reference-seed-data.js"; -import type { RecipeSourceAdapter } from "../src/lib/recipe-source-adapter.js"; -import { clearRecipeSources, registerRecipeSource } from "../src/lib/recipe-source-registry.js"; +import type { RecipeSourceAdapter } from "../src/lib/recipe-sources/recipe-source-adapter.js"; +import { + clearRecipeSources, + registerRecipeSource, +} from "../src/lib/recipe-sources/recipe-source-registry.js"; import { resetDatabase } from "../test-support/reset-db.js"; /** A minimal `RecipeSourceAdapter` — only `key`/`name`/`official`/`iconUrl` matter for `syncRecipeSources` fixtures here. */ diff --git a/apps/api/test/sources-index.test.ts b/apps/api/test/sources-index.test.ts index f13bcb3..8ebbafd 100644 --- a/apps/api/test/sources-index.test.ts +++ b/apps/api/test/sources-index.test.ts @@ -3,7 +3,7 @@ import { clearRecipeSources, getRecipeSource, listRecipeSources, -} from "../src/lib/recipe-source-registry.js"; +} from "../src/lib/recipe-sources/recipe-source-registry.js"; import { registerAllRecipeSources } from "../src/sources/index.js"; // Not exercised by any other test file — `registerAllRecipeSources` is diff --git a/apps/api/test/sources.test.ts b/apps/api/test/sources.test.ts index e869cab..16fd7b1 100644 --- a/apps/api/test/sources.test.ts +++ b/apps/api/test/sources.test.ts @@ -11,9 +11,12 @@ import type { RecipeSourceAdapter, RecipeSourceListParams, RecipeSourceListResult, -} from "../src/lib/recipe-source-adapter.js"; -import { RecipeSourceFetchError } from "../src/lib/recipe-source-errors.js"; -import { clearRecipeSources, registerRecipeSource } from "../src/lib/recipe-source-registry.js"; +} from "../src/lib/recipe-sources/recipe-source-adapter.js"; +import { RecipeSourceFetchError } from "../src/lib/recipe-sources/recipe-source-errors.js"; +import { + clearRecipeSources, + registerRecipeSource, +} from "../src/lib/recipe-sources/recipe-source-registry.js"; import { resetDatabase } from "../test-support/reset-db.js"; /** See `recipe.test.ts` — generated rather than hardcoded, no test fixture looks like a real person's data. */ diff --git a/apps/api/test/tech-step-matcher.test.ts b/apps/api/test/tech-step-matcher.test.ts index 8646b2a..e3faa9d 100644 --- a/apps/api/test/tech-step-matcher.test.ts +++ b/apps/api/test/tech-step-matcher.test.ts @@ -6,7 +6,7 @@ import { matchTechSteps, normalizeText, type TechStepMappingRule, -} from "../src/lib/tech-step-matcher.js"; +} from "../src/lib/recipe-matching/tech-step-matcher.js"; import { resetDatabase } from "../test-support/reset-db.js"; describe("tech-step-matcher", () => { diff --git a/apps/api/test/the-meal-db.test.ts b/apps/api/test/the-meal-db.test.ts index e0e0d99..0517fe2 100644 --- a/apps/api/test/the-meal-db.test.ts +++ b/apps/api/test/the-meal-db.test.ts @@ -1,5 +1,8 @@ import { expect } from "chai"; -import { RecipeSourceFetchError, RecipeSourceParseError } from "../src/lib/recipe-source-errors.js"; +import { + RecipeSourceFetchError, + RecipeSourceParseError, +} from "../src/lib/recipe-sources/recipe-source-errors.js"; import { type TheMealDbMeal, theMealDbAdapter } from "../src/sources/the-meal-db.js"; /** diff --git a/specs/backend-architecture.md b/specs/backend-architecture.md index 9f94b1e..95e60b6 100644 --- a/specs/backend-architecture.md +++ b/specs/backend-architecture.md @@ -286,7 +286,7 @@ Le module « Import d'une recette » du plan initial (voir [batch-cooking-architecture.md](./batch-cooking-architecture.md)) est implémenté. Pièces principales : -### `RecipeSourceAdapter` (`apps/api/src/lib/recipe-source-adapter.ts`) +### `RecipeSourceAdapter` (`apps/api/src/lib/recipe-sources/recipe-source-adapter.ts`) Contrat générique que chaque source concrète implémente : `list(params)` (parcours paginé, `query`/`cursor` optionnels), `fetchDetail(externalId)` diff --git a/specs/batch-cooking-architecture.md b/specs/batch-cooking-architecture.md index 08ee94c..adc4add 100644 --- a/specs/batch-cooking-architecture.md +++ b/specs/batch-cooking-architecture.md @@ -56,7 +56,7 @@ Logique de calcul du batch-cooking (optimisation du planning/des recettes selon 1. **Import depuis source** — chaque source concrète (aujourd'hui : TheMealDB, API officielle) implémente le contrat `RecipeSourceAdapter` - (`apps/api/src/lib/recipe-source-adapter.ts` — `list`/`fetchDetail`/`parse`), + (`apps/api/src/lib/recipe-sources/recipe-source-adapter.ts` — `list`/`fetchDetail`/`parse`), enregistré dans un registre en mémoire et synchronisé vers la table `sources`. Un scraper générique JSON-LD/schema.org (`json-ld-recipe.ts`) existe aussi, en briques réutilisables par un futur adaptateur dédié à un