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>
72 lines
3.2 KiB
TypeScript
72 lines
3.2 KiB
TypeScript
import { errorHandlerService } from "@batch-cooking/error-tools";
|
|
import { ExpressServer, createErrorMiddleware } from "@batch-cooking/express-tools";
|
|
import { ErrorCode } from "@batch-cooking/shared";
|
|
import type { Express, Request, Response } from "express";
|
|
import { env } from "./config/env.js";
|
|
import { authRouter } from "./modules/auth/auth.routes.js";
|
|
import { houseRouter } from "./modules/house/house.routes.js";
|
|
import { planningRouter } from "./modules/planning/planning.routes.js";
|
|
import { preferencesRouter } from "./modules/preferences/preferences.routes.js";
|
|
import { profileRouter } from "./modules/profile/profile.routes.js";
|
|
import { recipeRouter } from "./modules/recipe/recipe.routes.js";
|
|
import { referenceRouter } from "./modules/reference/reference.routes.js";
|
|
import { sourcesRouter } from "./modules/sources/sources.routes.js";
|
|
|
|
/**
|
|
* Builds the API's `ExpressServer`: standard middleware, routes, and the
|
|
* final error handler, in that order. Returns the `ExpressServer` wrapper
|
|
* (not just the raw Express app) so `server.ts` can call `.listen()` on
|
|
* it — {@link createApp} below is the thinner entry point that exposes
|
|
* just the raw `Express` instance, for test tooling (supertest) that
|
|
* expects one.
|
|
*/
|
|
export function createServer(): ExpressServer {
|
|
const server = new ExpressServer();
|
|
server.setupCore({ corsOrigin: env.CORS_ORIGIN });
|
|
|
|
server.addRoute("get", "/health", (_req: Request, res: Response) => {
|
|
res.status(200).json({ status: "ok" });
|
|
});
|
|
|
|
server.mountRouter("/auth", authRouter);
|
|
server.mountRouter("/house", houseRouter);
|
|
server.mountRouter("/planning", planningRouter);
|
|
server.mountRouter("/preferences", preferencesRouter);
|
|
server.mountRouter("/profile", profileRouter);
|
|
server.mountRouter("/recipes", recipeRouter);
|
|
server.mountRouter("/reference", referenceRouter);
|
|
server.mountRouter("/sources", sourcesRouter);
|
|
|
|
// Serves the built frontend (production Docker image only — see
|
|
// FRONTEND_DIST_DIR's doc comment in config/env.ts). Must come after
|
|
// every API route above (so they always win) and before the catch-all
|
|
// 404 below (so unmatched GETs fall through to the SPA's index.html
|
|
// instead of a JSON 404).
|
|
if (env.FRONTEND_DIST_DIR) {
|
|
server.serveStaticFrontend(env.FRONTEND_DIST_DIR);
|
|
}
|
|
|
|
// No route matched — same shape as every other error response, via the
|
|
// shared ErrorCode contract, so clients never special-case 404s.
|
|
server.addMiddleware((_req: Request, res: Response) => {
|
|
res.status(404).json({ code: ErrorCode.NOT_FOUND, message: "Not found" });
|
|
});
|
|
|
|
// Final error-handling middleware: every thrown/`next(err)`-ed error in
|
|
// the app ends up here. All the "what status/body does this error map
|
|
// to" logic lives in ErrorHandlerService, from @batch-cooking/error-tools
|
|
// — this stays a thin adapter.
|
|
server.setErrorHandler(createErrorMiddleware(errorHandlerService));
|
|
|
|
return server;
|
|
}
|
|
|
|
/**
|
|
* Builds a fresh Express application instance (no shared mutable state
|
|
* between calls — used both indirectly by the real server entrypoint
|
|
* (`server.ts`, via {@link createServer}) and directly by tests, which
|
|
* each get their own app via supertest).
|
|
*/
|
|
export function createApp(): Express {
|
|
return createServer().instance;
|
|
}
|