batchCooking/apps/api/src/modules/admin/admin-catalog.routes.ts
Nicolas 16f593d5d0
Some checks failed
CI / lint (push) Successful in 1m46s
CI / build (push) Successful in 1m55s
CI / e2e (push) Successful in 8m37s
CI / intent-service-test (push) Successful in 17m3s
CI / test (push) Failing after 3h14m19s
feat(recipes): permet d'ajouter des ingredients hors-catalogue
Quand le catalogue seede ne couvre pas un ingredient, l'utilisateur pouvait
etre bloque (creation manuelle) ou perdre silencieusement la ligne (import).
Une ligne de recette accepte desormais `placeholderName` (texte libre) au
lieu de `ingredientId` : l'API cree une ligne `Ingredient` `isPlaceholder`
(cle `placeholder:<uuid>`, `displayName`, `createdById`) dans la transaction
de la recette, et emet `ingredient.placeholder_created`. Ces lignes sont
exclues de `GET /reference/ingredients` et de `ingredient-matcher`.

Front : bouton "Ajouter << ... >>" dans l'etat vide de `IngredientPicker`
(formulaire + import), badge "a completer" sur la ligne, helper
`ingredientLabel` applique partout ou un libelle d'ingredient est rendu.

Admin : `/admin/catalog/*` (+ page `apps/admin-web`) liste les placeholders
regroupes par nom normalise, "marquer traite" (`reviewedAt`) et purge des
orphelins. La promotion en vraie entree catalogue reste manuelle.

Migration `ingredient_placeholder` ecrite a la main (Postgres indisponible).
Suites Mocha DB-backed ecrites, non executees en session ; test pur
`normalizePlaceholderName` + Cypress admin-web/web verts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-28 23:36:42 +02:00

44 lines
1.4 KiB
TypeScript

import { wrapAsyncHandler } from "@batch-cooking/express-tools";
import { listPlaceholdersQuerySchema, markPlaceholdersReviewedSchema } from "@batch-cooking/shared";
import { Router } from "express";
import { requireAdmin } from "../../middlewares/require-admin.js";
import {
listPlaceholderGroups,
markPlaceholdersReviewed,
pruneOrphanPlaceholders,
} from "./admin-catalog.service.js";
/**
* Router mounted at `/admin/catalog` (via `admin.routes.ts`) — every route
* behind {@link requireAdmin}. Surfaces the off-catalog ingredient
* "placeholders" users typed when the seeded catalog fell short, so a
* maintainer can see what's missing and mark gaps as handled.
*/
export const adminCatalogRouter = Router();
adminCatalogRouter.get(
"/placeholders",
requireAdmin,
wrapAsyncHandler(async (req, res) => {
res.status(200).json(await listPlaceholderGroups(listPlaceholdersQuerySchema.parse(req.query)));
}),
);
adminCatalogRouter.patch(
"/placeholders/mark-reviewed",
requireAdmin,
wrapAsyncHandler(async (req, res) => {
res
.status(200)
.json(await markPlaceholdersReviewed(markPlaceholdersReviewedSchema.parse(req.body)));
}),
);
/** Deletes placeholder rows no recipe references any more — see {@link pruneOrphanPlaceholders}. */
adminCatalogRouter.post(
"/placeholders/prune-orphans",
requireAdmin,
wrapAsyncHandler(async (_req, res) => {
res.status(200).json(await pruneOrphanPlaceholders());
}),
);