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.
This commit is contained in:
parent
7f7915a041
commit
42a8b08a5b
11 changed files with 1015 additions and 0 deletions
149
docs/data-model.md
Normal file
149
docs/data-model.md
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
# 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]]
|
||||
Loading…
Add table
Add a link
Reference in a new issue