- 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)
126 lines
4.4 KiB
JavaScript
126 lines
4.4 KiB
JavaScript
/// <reference path="../pb_data/types.d.ts" />
|
|
migrate(
|
|
(app) => {
|
|
// v4: ingredients become a many-to-many relation instead of the diet
|
|
// select. Each ingredient carries its own icon (emoji) and diet class;
|
|
// an item's veg/non-veg badge is derived from its ingredients. The menu
|
|
// records are re-synced by scripts/update-menu.mjs.
|
|
const ingredientsCollectionDef = () =>
|
|
new Collection({
|
|
name: 'ingredients',
|
|
type: 'base',
|
|
listRule: '',
|
|
viewRule: '',
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
{ name: 'uid', type: 'text', required: true },
|
|
{ name: 'name', type: 'text', required: true },
|
|
{ name: 'icon', type: 'text' },
|
|
{ name: 'diet_class', type: 'select', maxSelect: 1, values: ['veg', 'nonveg'], required: true },
|
|
{ name: 'sort', type: 'number' },
|
|
],
|
|
indexes: ['CREATE UNIQUE INDEX idx_ingredients_uid ON ingredients (uid)'],
|
|
})
|
|
|
|
// app.save() returns nil (falsy) on success, so don't test its return
|
|
// value — resolve the saved collection by name instead. The lookup-first
|
|
// pattern keeps the migration idempotent if a previous run created the
|
|
// collection before failing.
|
|
let ingredientsCollection
|
|
try {
|
|
ingredientsCollection = app.findCollectionByNameOrId('ingredients')
|
|
} catch (e) {
|
|
app.save(ingredientsCollectionDef())
|
|
ingredientsCollection = app.findCollectionByNameOrId('ingredients')
|
|
}
|
|
|
|
try {
|
|
const oldCollection = app.findCollectionByNameOrId('menu_items')
|
|
app.delete(oldCollection)
|
|
} catch (e) {
|
|
// collection may not exist on a fresh database — nothing to migrate
|
|
}
|
|
|
|
const menuItems = new Collection({
|
|
name: 'menu_items',
|
|
type: 'base',
|
|
listRule: '',
|
|
viewRule: '',
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
{ name: 'uid', type: 'text', required: true },
|
|
{ name: 'category', type: 'text', required: true },
|
|
{ name: 'name', type: 'text', required: true },
|
|
{ name: 'variant', type: 'text' },
|
|
{ name: 'description', type: 'text' },
|
|
{ name: 'price', type: 'number', required: true },
|
|
{ name: 'ingredients', type: 'relation', collectionId: ingredientsCollection.id, cascadeDelete: false, maxSelect: 99 },
|
|
{ name: 'pairing', type: 'text' },
|
|
{ name: 'tag', type: 'text' },
|
|
{ name: 'image', type: 'text' },
|
|
{ name: 'sku', type: 'text' },
|
|
{ name: 'available', type: 'bool' },
|
|
{ name: 'tax_category', type: 'text' },
|
|
{ name: 'sort', type: 'number' },
|
|
],
|
|
indexes: [
|
|
'CREATE UNIQUE INDEX idx_menu_items_uid ON menu_items (uid)',
|
|
'CREATE UNIQUE INDEX idx_menu_items_sku ON menu_items (sku) WHERE sku != \'\'',
|
|
],
|
|
})
|
|
return app.save(menuItems)
|
|
},
|
|
(app) => {
|
|
// Revert to the v3 menu_items (diet select) and drop ingredients.
|
|
try {
|
|
const newCollection = app.findCollectionByNameOrId('menu_items')
|
|
app.delete(newCollection)
|
|
} catch (e) {
|
|
// nothing to revert
|
|
}
|
|
|
|
const menuItems = new Collection({
|
|
name: 'menu_items',
|
|
type: 'base',
|
|
listRule: '',
|
|
viewRule: '',
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null,
|
|
fields: [
|
|
{ name: 'uid', type: 'text', required: true },
|
|
{ name: 'category', type: 'text', required: true },
|
|
{ name: 'name', type: 'text', required: true },
|
|
{ name: 'variant', type: 'text' },
|
|
{ name: 'description', type: 'text' },
|
|
{ name: 'price', type: 'number', required: true },
|
|
{ name: 'diet', type: 'select', maxSelect: 1, values: ['veg', 'nonveg', 'mixed'] },
|
|
{ name: 'pairing', type: 'text' },
|
|
{ name: 'tag', type: 'text' },
|
|
{ name: 'image', type: 'text' },
|
|
{ name: 'sku', type: 'text' },
|
|
{ name: 'available', type: 'bool' },
|
|
{ name: 'tax_category', type: 'text' },
|
|
{ name: 'sort', type: 'number' },
|
|
],
|
|
indexes: [
|
|
'CREATE UNIQUE INDEX idx_menu_items_uid ON menu_items (uid)',
|
|
'CREATE UNIQUE INDEX idx_menu_items_sku ON menu_items (sku) WHERE sku != \'\'',
|
|
],
|
|
})
|
|
app.save(menuItems)
|
|
|
|
try {
|
|
const ingredients = app.findCollectionByNameOrId('ingredients')
|
|
app.delete(ingredients)
|
|
} catch (e) {
|
|
// nothing to revert
|
|
}
|
|
return true
|
|
}
|
|
)
|