Ingredients as many-to-many relations + VS Code/Antigravity launch.json

- new ingredients collection (uid, name, emoji icon, diet_class) synced
  by update-menu.mjs; menu_items.ingredients is an M2M relation — the
  script maps data-file uids to fresh PB record ids
- diet select removed: the green/red FSSAI badge is derived (non-veg when
  any linked ingredient is non-veg); 164 items tagged (11 ingredients)
- menu cards render ingredient chips (icon + name) under the description
- migration 1788500200_menu_ingredients.js (idempotent create — app.save
  returns nil on success in the JSVM, so don't test its return value)
- .vscode/launch.json: PocketBase + Vite dev, build+preview, menu sync,
  full-stack compound (dev PB on :8091 — 8090 is taken on this host)
This commit is contained in:
2026-09-03 19:08:46 +05:30
parent e3444dbc60
commit d9ba227657
9 changed files with 435 additions and 192 deletions
+27 -8
View File
@@ -2,8 +2,9 @@
/**
* Menu sync — replaces the PocketBase menu with app/src/data/menu-data.mjs.
*
* Deletes every record in menu_categories / menu_items and inserts the
* current food menu from code. Nothing else is touched.
* Deletes every record in menu_categories / ingredients / menu_items and
* inserts the current food menu from code. Item ingredient uids are mapped
* to the freshly created ingredient record ids. Nothing else is touched.
*
* Works against any PocketBase instance (local dev or production):
*
@@ -19,7 +20,7 @@ import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const here = dirname(fileURLToPath(import.meta.url))
const { MENU_CATEGORIES, MENU_ITEMS } = await import(
const { INGREDIENTS, MENU_CATEGORIES, MENU_ITEMS } = await import(
new URL(`file://${join(here, '../app/src/data/menu-data.mjs')}`)
)
@@ -84,26 +85,44 @@ async function createRecord(collection, token, body) {
const text = await res.text()
throw new Error(`create ${collection}/${body.uid} failed (HTTP ${res.status}): ${text}`)
}
return res.json()
}
try {
const token = await auth()
console.log(`[menu] authenticated against ${PB_URL}`)
for (const [collection, entries] of [
['menu_categories', MENU_CATEGORIES],
['menu_items', MENU_ITEMS],
]) {
async function replaceCollection(collection, entries) {
const existing = await listAll(collection, token)
for (const record of existing) {
await deleteRecord(collection, token, record.id)
}
const idsByUid = new Map()
for (const entry of entries) {
await createRecord(collection, token, entry)
const created = await createRecord(collection, token, entry)
idsByUid.set(entry.uid, created.id)
}
console.log(`[menu] ${collection}: -${existing.length} old, +${entries.length} new`)
return idsByUid
}
// Ingredients first — menu items reference their PocketBase record ids.
await replaceCollection('menu_categories', MENU_CATEGORIES)
const ingredientIds = await replaceCollection('ingredients', INGREDIENTS)
const items = MENU_ITEMS.map((entry) => {
const { ingredients, ...rest } = entry
return {
...rest,
ingredients: (ingredients ?? []).map((uid) => {
const id = ingredientIds.get(uid)
if (!id) throw new Error(`menu item ${entry.uid} references unknown ingredient '${uid}'`)
return id
}),
}
})
await replaceCollection('menu_items', items)
console.log('[menu] done — the live menu now matches app/src/data/menu-data.mjs')
} catch (error) {
console.error(`[menu] failed: ${error.message}`)