Merge pull request #42 from kyuno053/fix/runtime-seed-source-sync

fix(recipes): synchronise les sources en base au démarrage de l'image de prod
This commit is contained in:
kyuno053 2026-08-20 14:00:27 +02:00 committed by GitHub
commit f58ac82a07
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 133 additions and 4 deletions

View file

@ -6,6 +6,12 @@
"runtimeExecutable": "pnpm", "runtimeExecutable": "pnpm",
"runtimeArgs": ["--filter", "web", "dev"], "runtimeArgs": ["--filter", "web", "dev"],
"port": 5173 "port": 5173
},
{
"name": "api",
"runtimeExecutable": "pnpm",
"runtimeArgs": ["--filter", "api", "dev"],
"port": 3000
} }
] ]
} }

View file

@ -1,5 +1,7 @@
import { prisma } from "../db/prisma.js"; import { prisma } from "../db/prisma.js";
import { syncRecipeSources } from "../db/recipe-source-sync.js";
import { seedReferenceData } from "../db/reference-seed-data.js"; import { seedReferenceData } from "../db/reference-seed-data.js";
import { registerAllRecipeSources } from "../sources/index.js";
/** /**
* Runtime seed entry point for the production Docker image run via * Runtime seed entry point for the production Docker image run via
@ -14,11 +16,25 @@ import { seedReferenceData } from "../db/reference-seed-data.js";
* alongside everything else, and it runs under plain `node`, no tsx * alongside everything else, and it runs under plain `node`, no tsx
* needed at runtime. * needed at runtime.
* *
* Also registers and syncs the recipe-source registry
* (`registerAllRecipeSources`/`syncRecipeSources`) mirroring
* `prisma/seed.ts`'s own two calls. Without this, `server.ts`'s own
* `registerAllRecipeSources()` call only populates *that* process' in-memory
* registry (each `node` invocation in the Docker CMD chain is a separate
* process), so the `Source` table itself would stay permanently empty in
* production and `GET /reference/sources` would always return `[]` which
* is exactly what silently hid the whole "sources" section of
* `HouseholdSettingsPage` (`apps/web`) until this was added.
*
* Safe to run on every container start: `seedReferenceData` upserts by * Safe to run on every container start: `seedReferenceData` upserts by
* each row's unique name, so re-running it against a database that * each row's unique name, and `syncRecipeSources` is equally idempotent
* (see its own doc comment) re-running both against a database that
* already has this data is a no-op. * already has this data is a no-op.
*/ */
registerAllRecipeSources();
seedReferenceData(prisma) seedReferenceData(prisma)
.then(() => syncRecipeSources(prisma))
.then(() => prisma.$disconnect()) .then(() => prisma.$disconnect())
.catch(async (err) => { .catch(async (err) => {
console.error(err); console.error(err);

View file

@ -53,6 +53,33 @@ Feature: Household settings
Then the household deletion request should have been made Then the household deletion request should have been made
And I should see "Créer un foyer" And I should see "Créer un foyer"
Scenario: Shows and saves the household's enabled recipe sources
Given I am signed in as "Alice" "Martin"
And my household id is 1
And the household request returns the two-member household
And the sources reference list has options
And the household's enabled sources are empty
And saving the source selection will succeed
When I visit "/parametres/foyer"
And I scroll to the section "Sources disponibles"
Then I should see the section "Sources disponibles"
And I should see "TheMealDB"
And I should see "Officielle"
And I should see "Import générique (JSON-LD)"
And I should see "Non officielle"
When I check the checkbox "TheMealDB"
Then the source selection update request should have been made with source id 1
And I should see "Enregistré "
Scenario: Hides the sources section entirely when no source is implemented yet
Given I am signed in as "Alice" "Martin"
And my household id is 1
And the household request returns the two-member household
And the sources reference list is empty
And the household's enabled sources are empty
When I visit "/parametres/foyer"
Then I should not see "Sources de recettes"
Scenario: Leaves the household Scenario: Leaves the household
Given I am signed in as "Bob" "Dupont" Given I am signed in as "Bob" "Dupont"
And my user id is 2 And my user id is 2

View file

@ -52,6 +52,31 @@ Feature: Onboarding wizard
Then the allergies update request should have been made with no allergy ids Then the allergies update request should have been made with no allergy ids
And the URL should be the home page And the URL should be the home page
Scenario: Shows and saves the sources step instead of skipping it, when sources are available
Given the diets reference list is empty
And selecting the diet will succeed
And the household request returns no household
And creating a household will succeed
And the sources reference list has options
And the household's enabled sources are empty
And saving the source selection will succeed
And the allergies reference list is empty
And updating allergies will succeed
And I have signed up
When I click the button "Continuer"
Then the URL should include "/onboarding/foyer"
When I fill in the "houseName" field with "Chez Alice"
And I click the button "Créer"
Then the household creation request should have been made with name "Chez Alice"
And the URL should include "/onboarding/sources"
And I should see "Étape 3 sur 4"
And I should see the section "Sources disponibles"
And I should see "TheMealDB"
When I check the checkbox "TheMealDB"
And I click the button "Continuer"
Then the source selection update request should have been made with source id 1
And the URL should include "/onboarding/allergenes"
Scenario: Lets the household step be completed by joining an existing household instead of creating one Scenario: Lets the household step be completed by joining an existing household instead of creating one
Given the diets reference list is empty Given the diets reference list is empty
And selecting the diet will succeed And selecting the diet will succeed

View file

@ -125,6 +125,14 @@ Then("I should see the section {string}", (legend: string) => {
cy.contains("legend", legend).should("be.visible"); cy.contains("legend", legend).should("be.visible");
}); });
// Needed for a section that can render below the fold of `.app-content`'s
// own scroll (see layout.cy.ts) — a bare `.should("be.visible")` doesn't
// auto-scroll (only interaction commands like `.click()`/`.check()` do),
// so a section low on a long page needs this before asserting on it.
When("I scroll to the section {string}", (legend: string) => {
cy.contains("legend", legend).scrollIntoView();
});
Then("the checkbox {string} should be checked", (label: string) => { Then("the checkbox {string} should be checked", (label: string) => {
cy.contains("label", label).find("input[type=checkbox]").should("be.checked"); cy.contains("label", label).find("input[type=checkbox]").should("be.checked");
}); });

View file

@ -61,3 +61,25 @@ Then(
cy.wait("@joinHouse").its("request.body").should("deep.equal", { inviteCode: code }); cy.wait("@joinHouse").its("request.body").should("deep.equal", { inviteCode: code });
}, },
); );
// Shared between onboarding.feature's sources step and household-settings.feature's
// sources section — both read/write the same `/house/current/sources` endpoint.
Given("the household's enabled sources are empty", () => {
cy.intercept("GET", "**/house/current/sources", { statusCode: 200, body: [] });
});
Given("saving the source selection will succeed", () => {
cy.intercept("PATCH", "**/house/current/sources", (req) => {
req.reply({ statusCode: 200, body: req.body.sourceIds });
}).as("updateSources");
});
Then(
"the source selection update request should have been made with source id {int}",
(id: number) => {
cy.wait("@updateSources")
.its("request.body")
.should("deep.equal", { sourceIds: [id] });
},
);

View file

@ -35,9 +35,34 @@ Given("the allergies reference list is empty", () => {
// Every onboarding scenario that reaches the household step also reaches // Every onboarding scenario that reaches the household step also reaches
// `/onboarding/sources` right after (when a household got created/joined — // `/onboarding/sources` right after (when a household got created/joined —
// see `OnboardingHouseholdPage`'s `goToNextStep`), which reads this before // see `OnboardingHouseholdPage`'s `goToNextStep`), which reads this before
// self-skipping to `/onboarding/allergenes`. No "has options" counterpart // self-skipping to `/onboarding/allergenes` if it comes back empty.
// yet — no source is implemented in the app itself, so there's nothing
// real to mock a populated catalog with.
Given("the sources reference list is empty", () => { Given("the sources reference list is empty", () => {
cy.intercept("GET", "**/reference/sources", { statusCode: 200, body: [] }); cy.intercept("GET", "**/reference/sources", { statusCode: 200, body: [] });
}); });
// Mirrors what's actually seeded (`reference-seed-data.ts`'s
// `registerAllRecipeSources`/`syncRecipeSources`) — one official API source
// with an icon, one unofficial scraper without one — so the sources step
// (onboarding and `/parametres/foyer` alike) has something real to show
// instead of self-skipping.
Given("the sources reference list has options", () => {
cy.intercept("GET", "**/reference/sources", {
statusCode: 200,
body: [
{
id: 1,
key: "theMealDb",
name: "TheMealDB",
official: true,
iconUrl: "https://www.themealdb.com/images/logo.svg",
},
{
id: 2,
key: "jsonLdRecipe",
name: "Import générique (JSON-LD)",
official: false,
iconUrl: null,
},
],
});
});