Load the real food menu (11 categories, 137 items)

- app/src/data/menu-data.mjs: single source for PB sync + offline fallback
- pb/migrations/1788500000_menu_items_v2.js: price number -> text (supports
  ranges like 449/549), adds group field for veg/non-veg badges
- scripts/update-menu.mjs: replaces menu_categories/menu_items in any
  PocketBase (local dev or https://admin.greatbear.in)
- menu cards: category icons when no image, FSSAI-style dietary badge,
  text prices, optional pairing/tag footer
- content.json drops placeholder menu arrays (menu now lives in code)
This commit is contained in:
2026-09-03 18:04:46 +05:30
parent 0ff90cde27
commit bcb41353ce
8 changed files with 784 additions and 140 deletions
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/env node
/**
* 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.
*
* Works against any PocketBase instance (local dev or production):
*
* # local dev
* PB_URL=http://127.0.0.1:8090 PB_SUPERUSER_EMAIL=dev@greatbear.in \
* PB_SUPERUSER_PASSWORD=devpass12345 node scripts/update-menu.mjs
*
* # production (admin domain serves the PocketBase API)
* PB_URL=https://admin.greatbear.in PB_SUPERUSER_EMAIL=admin@greatbear.in \
* PB_SUPERUSER_PASSWORD=... node scripts/update-menu.mjs
*/
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(
new URL(`file://${join(here, '../app/src/data/menu-data.mjs')}`)
)
const PB_URL = (process.env.PB_URL ?? 'http://127.0.0.1:8090').replace(/\/$/, '')
const EMAIL = process.env.PB_SUPERUSER_EMAIL
const PASSWORD = process.env.PB_SUPERUSER_PASSWORD
if (!EMAIL || !PASSWORD) {
console.error(
'[menu] PB_SUPERUSER_EMAIL and PB_SUPERUSER_PASSWORD are required\n' +
' e.g. PB_URL=https://admin.greatbear.in PB_SUPERUSER_EMAIL=admin@greatbear.in \\\n' +
' PB_SUPERUSER_PASSWORD=... node scripts/update-menu.mjs',
)
process.exit(1)
}
async function auth() {
const res = await fetch(`${PB_URL}/api/collections/_superusers/auth-with-password`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ identity: EMAIL, password: PASSWORD }),
})
if (!res.ok) throw new Error(`superuser auth failed against ${PB_URL} (HTTP ${res.status})`)
const json = await res.json()
return json.token
}
async function listAll(collection, token) {
const records = []
let page = 1
for (;;) {
const res = await fetch(
`${PB_URL}/api/collections/${collection}/records?sort=sort&perPage=200&page=${page}`,
{ headers: { Authorization: token } },
)
if (!res.ok) throw new Error(`list ${collection} failed (HTTP ${res.status})`)
const json = await res.json()
records.push(...json.items)
if (json.items.length < json.perPage || page >= json.totalPages) break
page += 1
}
return records
}
async function deleteRecord(collection, token, id) {
const res = await fetch(`${PB_URL}/api/collections/${collection}/records/${id}`, {
method: 'DELETE',
headers: { Authorization: token },
})
if (!res.ok && res.status !== 404) {
throw new Error(`delete ${collection}/${id} failed (HTTP ${res.status})`)
}
}
async function createRecord(collection, token, body) {
const res = await fetch(`${PB_URL}/api/collections/${collection}/records`, {
method: 'POST',
headers: { Authorization: token, 'Content-Type': 'application/json' },
body: JSON.stringify(body),
})
if (!res.ok) {
const text = await res.text()
throw new Error(`create ${collection}/${body.uid} failed (HTTP ${res.status}): ${text}`)
}
}
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],
]) {
const existing = await listAll(collection, token)
for (const record of existing) {
await deleteRecord(collection, token, record.id)
}
for (const entry of entries) {
await createRecord(collection, token, entry)
}
console.log(`[menu] ${collection}: -${existing.length} old, +${entries.length} new`)
}
console.log('[menu] done — the live menu now matches app/src/data/menu-data.mjs')
} catch (error) {
console.error(`[menu] failed: ${error.message}`)
process.exit(1)
}