+41
-34
@@ -1,44 +1,51 @@
|
||||
# KoolTap Architecture
|
||||
# Architecture
|
||||
|
||||
## Overview
|
||||
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.
|
||||
|
||||
KoolTap is a modular monolith designed for remote tab control in venues.
|
||||
## The server holds the venue
|
||||
|
||||
## Components
|
||||
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:
|
||||
|
||||
### Backend (NestJS)
|
||||
- REST API with versioned endpoints (`/api/v1`)
|
||||
- JWT authentication for dashboard users
|
||||
- Bearer token authentication for devices
|
||||
- PostgreSQL database via Prisma ORM
|
||||
- Scheduled command expiry job
|
||||
- In-memory rate limiting
|
||||
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.
|
||||
|
||||
### Frontend (Next.js)
|
||||
- App Router with client-side rendering
|
||||
- TanStack Query for API data fetching
|
||||
- shadcn/ui component library
|
||||
- Tailwind CSS styling
|
||||
- Tokens kept in cookies (see security.md)
|
||||
- Socket.IO connection for live updates, polling as fallback
|
||||
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).
|
||||
|
||||
### Shared Package
|
||||
- TypeScript types and constants
|
||||
- Shared across API and dashboard
|
||||
## API (`apps/api`)
|
||||
|
||||
## Data Flow
|
||||
| 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 |
|
||||
| `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 |
|
||||
|
||||
1. Device syncs open tabs → Server stores snapshots + ownership
|
||||
2. Dashboard creates close command → Server resolves device owner
|
||||
3. Device syncs → Receives pending commands
|
||||
4. Device processes → Acknowledges command
|
||||
5. Dashboard polls → Sees updated status
|
||||
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.
|
||||
|
||||
## Security Model
|
||||
## Data
|
||||
|
||||
- Every user/device scoped to one venue
|
||||
- Venue isolation enforced at service level
|
||||
- Device tokens hashed with SHA-256
|
||||
- Raw token returned only once during registration
|
||||
- JWT tokens with refresh token rotation
|
||||
- Rate limiting on sensitive endpoints
|
||||
- **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`.
|
||||
|
||||
Reference in New Issue
Block a user