#!/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) }