Files
kooltab-backoffice/docs/architecture.md
T
brammie15 3caa6d6f0d
CI / build-and-test (push) Canceled after 0s
fix: auth + double sending etc
2026-10-30 00:41:58 +01:00

52 lines
3.2 KiB
Markdown

# Architecture
KoolTap is a modular monolith: one NestJS API, one Next.js dashboard, one PostgreSQL database, and a shared package
that holds the contract between them and the bar terminals.
## The server holds the venue
The venue's open tabs, stock and catalog live on the server. A terminal records what happens on it as **events**
(`tab_opened`, `line_added`, `settled`, `stock_set`, `product_updated`, …) in an outbox, and every sync:
1. sends the outbox; the server stores each event once (by its id) and applies it to the venue,
2. gets back the venue's complete state: the open tabs, the stock, the catalog (only when it changed) and the
settlements since its cursor,
3. replaces its own state with that and replays the events it still has to send on top.
Terminals apply the same rules the server does, so a terminal that is offline keeps working and lands in the same
state once it syncs. The dashboard changes the same state directly (settling or deleting a tab, stock movements,
products); terminals see that on their next sync. See [terminal-protocol.md](terminal-protocol.md).
## API (`apps/api`)
| Module | What it does |
|---|---|
| `ledger/` | The rules, pure and unit tested: `tab-state` (tabs, lines, drinks, settling), `stock-rules`, `product-fields` (per-field last-write-wins), and `venue-ledger`, which loads a venue under a row lock, applies changes and writes back only what changed |
| `terminal/` | Pairing, sync and picture uploads for terminals; device token auth; the minimum app version |
| `auth/` | Dashboard sign-in with httpOnly cookies, refresh token rotation, `GET /auth/me` |
| `tabs/`, `customers/`, `stock/`, `reminders/`, `venues/` | The dashboard API, one module per page |
| `admin/` | The superadmin page: venues, terminals, users, resets, consistency checks, purges; daily housekeeping |
| `realtime/` | Socket.IO: tells dashboards which data changed (topics only, never data) |
| `email/` | Templates in `templates/emails` and Resend |
| `images/` | Product pictures, processed with sharp and stored on disk by content hash |
Every request body and query is validated with a zod schema from `@kooltap/shared`; responses are typed with the
shared interfaces, so the dashboard and the API can't drift apart.
## Data
- **TabUnit** is one drink ever rung up. It belongs to an open **Tab** until a **Settlement** takes it; a drink taken
off again keeps its `removedAt` and moves with the settlement. Lines are the units sharing a `lineId`. Sales reports
are plain SQL over this table.
- **Product** holds the catalog and the current stock; **StockMovement** is the dashboard's deliveries, breakage and
counts.
- **TabEvent** is what terminals reported, as they sent it: the record of which events were applied.
- **Customer** is a regular: the next sync opens a tab for a customer without one, and their email is used for
reminders. Tabs match customers by name.
- Everything belongs to a **Venue** and is deleted with it.
## Dashboard (`apps/dashboard`)
Next.js App Router, rendered on the client. TanStack Query for data, refetched when the realtime socket says a topic
changed (polling only while the socket is down). Translations in `src/locales/{en,nl}.json`.