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.
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 |
queued → sent → acknowledged, 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 |
paid → accepted automatically (see order-flow) |