Booking Platforms Module
Overview
The booking-platforms module manages third-party booking platform integrations for a business. Each platform represents an external channel (e.g., Viator, Airbnb Experience, GetYourGuide, Direct Booking) through which customers can book services. Platforms track commission percentages and can be linked to a customer record for accounting purposes.
All data is scoped to a businessId — no cross-business data access is possible.
Domain Concepts
| Concept | Description |
|---|---|
BookingPlatform | A named external booking channel with a commission rate and active status |
businessId | Multi-tenancy boundary. Every query filters by this field |
commissionPercentage | The percentage commission charged by the platform |
customerId | Optional reference to a customer record (for AR/invoicing integration) |
createdBy / updatedBy | Audit trail — employee/user UUID who performed the operation |
Architecture
The module follows strict Hexagonal Architecture:
domain/
booking-platforms-repository.domain.ts ← IBookingPlatformsRepository port (interface) + BOOKING_PLATFORMS_REPOSITORY token
application/
booking-platforms.service.ts ← Orchestrates use cases (depends on domain port via @Inject)
infrastructure/
booking-platforms.repository.ts ← Kysely adapter (implements the port)
interfaces/
booking-platforms.controller.ts ← HTTP REST adapter
dtos/
create-booking-platform.dto.ts
update-booking-platform.dto.ts
query/
paginate-booking-platforms.query.ts
Dependency flow
interfaces → application → domain ← infrastructure
Infrastructure depends on domain (implements its port); the application layer depends only on the domain interface. The repository is wired via a Symbol-based injection token (BOOKING_PLATFORMS_REPOSITORY) defined in the domain layer.
Known design debt:
booking-platforms-repository.domain.tsimportssortableBookingPlatformKeysfrom the interfaces layer. This is a consistent project-wide pattern (present in all modules) and is accepted as a pragmatic trade-off until a cross-module refactor is done.
Use Cases
| Use Case | Method | HTTP |
|---|---|---|
| Create booking platform | createBookingPlatform | POST /booking-platforms |
| List booking platforms (paginated) | getAllBookingPlatforms | GET /booking-platforms |
| Get booking platform by ID | getBookingPlatformById | GET /booking-platforms/:id |
| Update booking platform | updateBookingPlatform | PATCH /booking-platforms/:id |
| Delete booking platform | deleteBookingPlatform | DELETE /booking-platforms/:id |
Authorization
- Authentication: Bearer token (Firebase) — validated by global
AuthGuard - Authorization:
RolesGuard+ CASL permissions- Class-level:
@PermissionResource(PolicyResource.BookingPlatform) - Method-level:
@PermissionAction(PolicyAction.Create | Read | Update | Delete)
- Class-level:
- Swagger:
@ApiBearerAuth()marks all endpoints as requiring authentication
API Endpoints
POST /booking-platforms — Create Booking Platform
Auth: Bearer token required
Permission: BookingPlatform:Create
Request body:
{
"name": "Viator",
"commissionPercentage": 5,
"businessId": "<business-uuid>",
"isActive": true,
"createdBy": "<user-uuid>",
"customerId": "<customer-uuid>"
}
customerId is optional — used to link the platform to a customer record for accounting.
Response: 201 Created — full booking platform object.
GET /booking-platforms — List Booking Platforms
Permission: BookingPlatform:Read
Query params:
| Param | Required | Description |
|---|---|---|
businessId | yes | UUID — scopes the query |
page | no | Page number (default: 1) |
size | no | Page size (default: 20, 0 = all) |
search | no | Full-text search across platform name |
orderBy | no | Column to sort by |
order | no | asc or desc |
Sortable columns: name
Response: 200 OK — IOffsetPagination<BookingPlatform>
{
"data": [...],
"total": 4,
"page": 1,
"size": 20,
"totalPages": 1
}
The list response includes customerName (from the joined customer table) when a customerId is set.
GET /booking-platforms/:id — Get Booking Platform by ID
Permission: BookingPlatform:Read
Query params: businessId (required)
Response: 200 OK — booking platform object, or 404 Not Found.
PATCH /booking-platforms/:id — Update Booking Platform
Permission: BookingPlatform:Update
Request body:
{
"businessId": "<business-uuid>",
"updatedBy": "<user-uuid>",
"name": "Viator Updated",
"commissionPercentage": 8
}
All fields except businessId and updatedBy are optional.
Response: 200 OK — updated booking platform object.
DELETE /booking-platforms/:id — Delete Booking Platform
Permission: BookingPlatform:Delete
Query params: businessId (required)
Response: 200 OK (no body).
Design Decisions
Hard delete
DELETE performs a hard delete (deleteFrom). There is no soft-delete or isActive flag toggle on delete. The isActive field is managed manually through create/update operations.
businessId is not updatable
The PATCH endpoint destructures businessId out of the entity before passing it to the repository. This prevents accidentally moving a platform from one business to another through a PATCH call.
findAll uses a transaction
The count + data query pair runs inside a Kysely transaction for snapshot isolation. This is a consistent project pattern.
findAll includes a customer join
The list query left-joins the customer table to include customerName in the response. This avoids N+1 queries when displaying platforms with their associated customer names.
Symbol-based repository injection
The repository is bound via BOOKING_PLATFORMS_REPOSITORY Symbol token in the module, and injected into the service via @Inject(BOOKING_PLATFORMS_REPOSITORY). This ensures the application layer depends only on the domain port (IBookingPlatformsRepository), not the concrete Kysely implementation.
Bruno API Collection
Located at: api-client/flowpos/collections/booking-platforms/
| File | Method | Route |
|---|---|---|
list booking platforms.yml | GET | /booking-platforms |
create booking platform.yml | POST | /booking-platforms |
get booking platform by id.yml | GET | /booking-platforms/:id |
update booking platform.yml | PATCH | /booking-platforms/:id |
delete booking platform.yml | DELETE | /booking-platforms/:id |
Environment variables used: BASE_URL, ID_TOKEN, businessId, userId, customerId, bookingPlatformId.
All requests include after-response test scripts for status code validation.