52 lines
3.2 KiB
Markdown
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`.
|