feat: move to V2
CI / build-and-test (push) Canceled after 0s

This commit is contained in:
2026-10-29 11:21:52 +01:00
parent 6342b8451e
commit 314717ba3a
244 changed files with 11615 additions and 15292 deletions
+41 -34
View File
@@ -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`.