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.
149 lines
4.7 KiB
Markdown
149 lines
4.7 KiB
Markdown
# 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|print jobs]] |
|
|
| `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|kind-0 metadata event]] 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|NIP-99 kind 30402]] 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|menu response]] 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]]) |
|
|
|
|
---
|
|
|
|
## See also
|
|
|
|
- [[architecture]]
|
|
- [[menu-tree]]
|
|
- [[order-flow]]
|
|
- [[nostr-layer]]
|
|
- [[adr-0001-menu-tree]]
|