feat: reminders, Excel customer import and security hardening
Features - Payment reminders via Resend (candidates, send, history, test email) - Bulk import of the customer list from Excel/CSV in the dashboard - Dashboard i18n (en/nl) with language selector Security - Enforce venue isolation on all /venues/:venueId routes - Require admin for planned-tab changes and pairing codes - Rate-limit login and device registration; opt-in TRUST_PROXY - Require JWT_SECRET in production; stop leaking internal errors - Escape customer names in reminder emails Sync and commands - Remove tabs the terminal no longer reports; validate nested sync payload - Return all pending commands on every sync (fixes missed-command race) - First command ack wins; atomic registration code claim - Reject commands for revoked devices, stale versions or duplicates - Remind only unpaid balances, with a 7-day cooldown Dashboard - Share a single token refresh across concurrent 401s (random logouts) - Keep the session alive for the refresh token lifetime Deployment - Replace `db push --accept-data-loss` with migrations (baselines legacy DBs) - Set NODE_ENV=production; pass Resend and TRUST_PROXY env in prod compose
This commit is contained in:
+26
-2
@@ -18,7 +18,7 @@ Swagger UI: `http://localhost:3001/api/docs`
|
||||
## Endpoints
|
||||
|
||||
### Health
|
||||
`GET /health` - Server health check
|
||||
`GET /health` - Server health check (503 when the database is unreachable)
|
||||
|
||||
### Auth
|
||||
`POST /auth/login` - Login with email/password
|
||||
@@ -36,13 +36,37 @@ Swagger UI: `http://localhost:3001/api/docs`
|
||||
`POST /devices/:deviceId/commands/:commandId/ack` - Acknowledge command (device auth)
|
||||
`GET /devices/generation-code` - Generate registration code (admin)
|
||||
|
||||
Sync semantics:
|
||||
- `openTabs` must be the device's **complete** list of open tabs. Tabs it no longer reports
|
||||
(e.g. deleted on the terminal) are removed from the dashboard. Settling a tab on the terminal
|
||||
keeps the tab open under the same name with a zero balance, so the name stays listed.
|
||||
- Every sync returns **all** pending commands for the device; `cursor` is still returned but no longer
|
||||
filters. Devices dedupe by command id and re-send the ack for commands they already processed.
|
||||
- The first ack for a command wins; later acks are accepted as no-ops.
|
||||
|
||||
### Tabs
|
||||
`GET /venues/:venueId/tabs` - List open tabs
|
||||
|
||||
### Planned Tabs (customer list)
|
||||
`GET /venues/:venueId/planned-tabs` - List planned customers
|
||||
`POST /venues/:venueId/planned-tabs` - Add a customer (admin)
|
||||
`POST /venues/:venueId/planned-tabs/import` - Bulk import `{ entries: [{ customerName, email? }], mode: 'merge' | 'replace' }` (admin).
|
||||
Names match case-insensitively and keep their existing spelling; `replace` also removes customers missing from the import.
|
||||
`PUT /venues/:venueId/planned-tabs/:id` - Rename a customer / set email (admin)
|
||||
`DELETE /venues/:venueId/planned-tabs/:id` - Remove a customer (admin)
|
||||
|
||||
### Reminders
|
||||
`GET /venues/:venueId/reminders/candidates` - Customers with an unpaid balance and email, not reminded in the last 7 days
|
||||
`POST /venues/:venueId/reminders/send` - Send payment reminders
|
||||
`GET /venues/:venueId/reminders` - Reminder history
|
||||
`POST /venues/:venueId/reminders/test/:plannedTabId` - Send a test email (admin)
|
||||
|
||||
### Commands
|
||||
`POST /venues/:venueId/commands` - Create a command
|
||||
`GET /venues/:venueId/commands` - List command history
|
||||
`POST /venues/:venueId/devices/:deviceId/revoke` - Revoke device (admin)
|
||||
`POST /venues/:venueId/commands/devices/:deviceId/revoke` - Revoke device (admin)
|
||||
|
||||
All `/venues/:venueId/...` routes return `403 venue_forbidden` unless the user belongs to that venue.
|
||||
|
||||
## Error Format
|
||||
|
||||
|
||||
Reference in New Issue
Block a user