feat: make venues public

This commit is contained in:
2026-09-20 02:35:57 +02:00
parent de0d746d52
commit 7ef06b7a26
2 changed files with 169 additions and 4 deletions
+6 -4
View File
@@ -1,12 +1,10 @@
import { Controller, Get, Param, UseGuards, Req } from '@nestjs/common'; import { Controller, Get, Param, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger'; import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { VenuesService } from './venues.service'; import { VenuesService } from './venues.service';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard'; import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
@ApiTags('Venues') @ApiTags('Venues')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@Controller('venues') @Controller('venues')
export class VenuesController { export class VenuesController {
constructor( constructor(
@@ -15,18 +13,22 @@ export class VenuesController {
) {} ) {}
@Get() @Get()
@ApiOperation({ summary: 'List all venues' }) @ApiOperation({ summary: 'List all venues (public)' })
async findAll() { async findAll() {
return this.venuesService.findAll(); return this.venuesService.findAll();
} }
@Get(':venueId') @Get(':venueId')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@ApiOperation({ summary: 'Get venue by ID' }) @ApiOperation({ summary: 'Get venue by ID' })
async findOne(@Param('venueId') venueId: string) { async findOne(@Param('venueId') venueId: string) {
return this.venuesService.findOne(venueId); return this.venuesService.findOne(venueId);
} }
@Get(':venueId/devices') @Get(':venueId/devices')
@ApiBearerAuth()
@UseGuards(JwtAuthGuard)
@ApiOperation({ summary: 'List devices for a venue' }) @ApiOperation({ summary: 'List devices for a venue' })
async findDevices(@Param('venueId') venueId: string) { async findDevices(@Param('venueId') venueId: string) {
const devices = await this.prisma.device.findMany({ const devices = await this.prisma.device.findMany({
+163
View File
@@ -0,0 +1,163 @@
## KoolTab Flutter App — Remote Backoffice Integration (Improved Pairing Flow)
You are implementing the device-side integration for the KoolTab remote tab control system. The Flutter app (bar POS terminal) needs to register with a remote backend, sync open tabs, and process remote close-tab commands.
### Project Location
`C:\Users\bram\StudioProjects\kooltab2`
### Architecture Overview
- State management: `provider` (ChangeNotifier pattern)
- Routing: `go_router`
- Local DB: `drift` (SQLite ORM)
- HTTP: `http` package
- Auth tokens stored in `flutter_secure_storage`
- MVVM pattern: views → viewmodels → services
- Models in `lib/models/`
- Services in `lib/services/`
- ViewModels in `lib/viewmodels/`
- Views in `lib/views/`
- Config constants in `lib/utils/app_config.dart`
- Main app bootstrap in `lib/app/app_bootstrap.dart`
### Pairing Flow (improved)
Instead of typing a UUID venueId, the flow is:
1. Tablet calls `GET https://api.kooltab.com/api/v1/venues/` (public, no auth)
2. Gets list of `[{id, name}]` — admin picks venue from a dropdown
3. Admin enters device name (e.g. "Bar terminal 1")
4. Admin enters the 6-character registration code (generated from dashboard)
5. Tablet calls `POST /devices/register` with venueId + name + code
6. Stores deviceId + token + venueId + serverUrl in secure storage
7. Sync starts automatically
### Existing Sync Infrastructure (already built)
Do NOT rewrite these files, just modify as needed:
- **`lib/services/remote_sync_service.dart`** (410 lines) — `setCredentials()`, `startSync()`, `stopSync()`, sync loop, command processing, ACKs
- **`lib/viewmodels/remote_sync_view_model.dart`** (90 lines) — ChangeNotifier wrapper
- **`lib/models/remote_command.dart`** (135 lines) — DTOs: `RemoteCommand`, `SyncRequest`, `SyncResponse`, `SnapshotTab`, `CommandAck`
- **`lib/utils/app_config.dart`** — URL constants
- **`lib/models/payment_method.dart`** — payment enums
### What to Build
#### 1. Update Server URL
In `lib/utils/app_config.dart`:
```dart
const kRemoteServerUrl = 'https://api.kooltab.com/api/v1/';
```
#### 2. Add Venue Model
Create `lib/models/venue.dart`:
```dart
class Venue {
final String id;
final String name;
Venue({required this.id, required this.name});
factory Venue.fromJson(Map<String, dynamic> json) => Venue(id: json['id'], name: json['name']);
}
```
#### 3. Update RemoteSyncService — Add venueId Storage
In `lib/services/remote_sync_service.dart`:
- Add `venueId` parameter to `setCredentials()` signature
- Store venueId in FlutterSecureStorage alongside deviceId/token/serverUrl
- Add `String? get venueId` getter
- Update `isConfigured` to also check venueId exists
- Add `clearCredentials()` method (clears all stored credentials)
#### 4. Registration ViewModel
Create `lib/viewmodels/registration_view_model.dart`:
- `Future<List<Venue>> fetchVenues()` — calls `GET /api/v1/venues/`
- `Future<void> registerDevice(String venueId, String name, String code)` — calls `POST /devices/register`, then stores credentials via `RemoteSyncService.setCredentials()`
- States: `initial`, `loading`, `venuesLoaded`, `error`, `registered`
- Expose as ChangeNotifier
#### 5. Registration UI
Create `lib/views/registration_view.dart` — a full-screen registration flow with steps:
**Step 1 — Select venue:**
- Dropdown list of venues fetched from API
- Show loading spinner while fetching
- Show error + retry if fetch fails
- "Next" button when venue selected
**Step 2 — Device details:**
- Device name text field (with default "Bar terminal 1")
- Registration code text field (6 characters)
- "Register" button
- Loading state while registering
- Error display if registration fails
**Step 3 — Success screen:**
- Success animation/message
- "Start using KoolTab" button → navigates to main bar screen and triggers sync
#### 6. Wire Registration into Navigation
In `lib/app/app_bootstrap.dart`:
- On app startup, check `remoteSyncService.isConfigured`
- If NOT configured: show registration screen instead of main bar screen
- If configured: proceed to main bar screen as normal
In `lib/app/router.dart`:
- Add route for registration screen as initial route when not configured
#### 7. Disconnect Option
In the **Settings screen** (`lib/views/settings_view.dart`) or **Admin screen**:
- If configured, show "Connected to [venue name]" with device name
- "Disconnect" button → calls `clearCredentials()`, navigates back to registration screen
### API Endpoints Summary
| Method | Endpoint | Auth | Purpose |
|--------|----------|------|---------|
| GET | `/api/v1/venues/` | None | List venues (returns `[{id, name}]`) |
| POST | `/api/v1/devices/register` | None | Register device (body: venueId, name, registrationCode) |
| POST | `/api/v1/devices/:id/sync` | Bearer token | Sync tabs, receive commands |
| POST | `/api/v1/devices/:id/commands/:cmdId/ack` | Bearer token | Acknowledge command |
### Registration API Details
**Venues list:**
```
GET https://api.kooltab.com/api/v1/venues/
Response: [{"id": "uuid", "name": "KoolTab Demo Bar"}, ...]
```
**Device registration:**
```
POST https://api.kooltab.com/api/v1/devices/register
{
"venueId": "uuid-from-venue-list",
"name": "Bar terminal 1",
"registrationCode": "A8F3C2"
}
Response 201:
{
"deviceId": "uuid",
"token": "dev_abc123...",
"tokenExpiresAt": "2026-..."
}
```
The registration code is generated from the dashboard (Devices → Add Device → generates 6-char code valid for 10 minutes).
### Files to Create
- `lib/models/venue.dart`
- `lib/viewmodels/registration_view_model.dart`
- `lib/views/registration_view.dart`
### Files to Modify
- `lib/utils/app_config.dart` — update URL
- `lib/services/remote_sync_service.dart` — add venueId + clearCredentials
- `lib/app/app_bootstrap.dart` — check isConfigured, route to registration if not
- `lib/app/router.dart` — add registration route
- `lib/views/admin_screen_view.dart` or `lib/views/settings_view.dart` — add disconnect option
### Important Notes
- Do NOT break the existing sync loop — registration only configures credentials
- The existing sync sends `SnapshotTab` and processes `close_tab` commands stays unchanged
- Use `flutter_secure_storage` for persisting credentials
- Handle loading/error states in the UI
- After registration, call `remoteSyncViewModel.start()` to begin syncing
- Tabs appear on the dashboard within ~90 seconds of first sync