restaurant/docs/data-model.md
Padreug 42a8b08a5b docs: Obsidian-style vault under docs/
Add a navigable Obsidian vault as the project's first-class
technical documentation. Notes cross-reference with [[wikilinks]];
docs/index.md is the Map of Content.

New notes:
  index.md             MOC, entry point
  architecture.md      what the extension owns vs what lives outside
  data-model.md        entity-by-entity schema reference
  menu-tree.md         the arbitrary-depth tree concept
  order-flow.md        state machine + invoice listener + print
  nostr-layer.md       kinds 0/30402/5/1059, signing, t-tags
  api-reference.md     endpoint catalog by audience
  cms.md               Vue 3 + Quasar 2 UMD conventions, q-tree
  webapp-integration.md  multi-restaurant cart pattern + atomicity
  glossary.md          domain terms

Existing notes (kept as-is):
  adr-0001-menu-tree.md  the storage choice rationale
  design-conversation.md trimmed transcript

README.md adds a Documentation section pointing at docs/index.md
with the headline note list. Each note links to ~3-5 others; the
vault forms a connected graph.

A project-level memory rule (saved outside the repo) commits us to
keeping these docs in sync as the code evolves: any commit that
materially changes schema, API, order flow, Nostr surface, CMS
conventions, or webapp integration must update the relevant note(s)
in the same commit.
2026-05-09 07:11:06 +02:00

4.7 KiB

Data model

Schema-by-schema reference. The migration that creates each table is in migrations.py; the pydantic shapes are in models.py; CRUD is in crud.py.

All tables live under the Postgres schema restaurant. (or the SQLite equivalent), keyed off the LNbits Database("ext_restaurant") binding.


restaurants

One row per restaurant. One LNbits wallet can own many.

Column Notes
id urlsafe_short_hash()
wallet LNbits wallet id — payment receiver, signing-key fallback
name, slug slug is the URL segment in /restaurant/<slug>
description, currency, timezone, location, geohash metadata
logo_url, banner_url, social_links (JSON) profile dressing
open_hours (JSON) weekly schedule, see order-flow
is_open, accepts_cash, accepts_lightning runtime toggles
tip_presets (JSON int[]), tax_rate money / UX hints
printer_endpoint URL or nostr:<pubkey> for [[order-flow
nostr_pubkey, nostr_relays (JSON str[]) per-restaurant Nostr identity (optional override; defaults to the LNbits Account keypair)
nostr_event_id, nostr_event_created_at last published kind-0 metadata event
extra (JSON) free-form

Published as a nostr-layer on create / update.


menu_nodes

The menu-tree — adjacency list with materialized path. Capped at 4 levels (MAX_MENU_DEPTH = 3, zero-indexed).

Column Notes
id urlsafe_short_hash()
restaurant_id FK
parent_id self-FK, NULL = root
name, description, image_url, sort_order display
depth denormalized 0..3 — O(1) max-depth checks
path denormalized 'rootid' or 'rootid/childid' — cheap subtree queries
time created_at

Indexes: (restaurant_id), (parent_id), (path). See adr-0001-menu-tree for why this shape and not a closure table.

Not published as a Nostr event itself — internal organizational structure only. Renames trigger re-publish of every item in the subtree (so the [[nostr-layer|ancestor t tag]] stays current).


menu_items

Column Notes
id, restaurant_id
node_id nullable — orphans allowed when a node is deleted with cascade=False
name, description, price, currency, sku core
images, dietary, allergens, ingredients (JSON arrays) structured tags
calories, sort_order, is_available, is_featured display
stock, low_stock_threshold inventory; nullable = unlimited
nostr_event_id, nostr_event_created_at last published kind-30402
extra (JSON) free-form

Published as nostr-layer on create / update; deleted via NIP-09 kind 5.


modifier_groups + modifiers

A menu item can have multiple modifier groups. Each group has a kind (required | optional) and selection (one | many). Each modifier carries a price_delta.

This unifies "required choices" (e.g. Choose your protein: Chicken / Tofu) with "optional addons" (e.g. Extra cheese +5) under one schema.


availability_windows

Per-item time-of-day availability.

Column Notes
menu_item_id FK
weekday 0=Mon..6=Sun, NULL = every day
start_time, end_time 'HH:MM' 24h, restaurant timezone

Used by clients to gray out items outside their window. The extension itself doesn't auto-disable items — it surfaces the windows in the api-reference so the consumer decides.


orders + order_items

See order-flow for the state machine and amounts (msat).

orders.id is set to payment_hash for Lightning orders, giving the invoice listener a zero-metadata lookup path. order_items snapshot price + selected modifiers at order time, so subsequent menu edits don't rewrite history.


print_jobs

Created when an order transitions to paid. printer-pi polls or subscribes, prints, and acknowledges via PUT /print_jobs/{id}/ack.

Column Notes
restaurant_id, order_id FKs
status queuedsentacknowledged, or failed
attempts, last_error retry bookkeeping

settings

Single-row table (id=1) for per-instance toggles.

Toggle Effect
nostr_publish_enabled gate for kind-0 / kind-30402 publishing
nostr_orders_enabled enable subscription to kind-1059 DMs (NIP-17 unwrap is currently stubbed)
invoice_expiry_seconds LNbits invoice lifetime
auto_accept_orders paidaccepted automatically (see order-flow)

See also