SEER Booking Service — Requirements Specification
1. Service Overview
Build the SEER Booking Service (seer-booking) as a new SEER microservice that manages resource reservation (rooms/zones and devices). The service handles conflict detection, schedule queries, recurring bookings, and integrates with SEER's authentication, IAM, webhook, and rule engine systems.
Why a Separate Service
The existing task service does not cover:
- Conflict / overlap detection for shared resources
- Schedule status queries with time-range filtering
- Recurring booking management
- Available-slot computation
- Booking-specific policies (max duration, min notice, cancellation rules)
A dedicated booking service addresses these gaps cleanly without overloading the task service.
2. Bookable Resources
| Resource Type | SEER Source Service | Identifier | Notes |
|---|---|---|---|
| Zone / Room | Device Management (/device/v1/) | zoneId | Meeting rooms, labs, shared spaces. Zone has name, hierarchy (parent zone). |
| Device | Device Management (/device/v1/devices) | deviceId | Shared sensors, equipment, cameras. Device has type, name, extName, workspace. |
Rules
- The booking service MUST NOT duplicate zone/device data. It stores a reference (
resourceType+resourceId) and fetches display details from the SEER device service on demand or via cache. - When a SEER zone/device is deleted, the booking resource should be soft-deactivated.
3. Functional Requirements
3.1 Resource Registration
- Admin (LV2/LV3) can register a SEER zone or device as a bookable resource.
- Each resource has:
- Operating hours (per weekday)
- Booking policy (see 3.6)
- Capacity (for rooms, optional)
- Active / inactive toggle
- A resource can only be registered once per workspace.
3.2 Booking Lifecycle
pending ──→ confirmed ──→ completed
│ │
│ └──→ cancelled
│
└──→ cancelled
| Status | Description |
|---|---|
pending | Created, awaiting confirmation (optional — configurable per resource whether auto-confirm) |
confirmed | Slot reserved |
completed | Booking time passed, checked out or auto-completed |
cancelled | Cancelled by booker or admin |
3.3 Conflict Detection
This is the critical requirement for the booking service.
On every booking create or reschedule:
- Check for overlapping bookings where:
- Same
resourceId status IN (pending, confirmed)(existingStart < newEnd) AND (existingEnd > newStart)
- Same
- If overlap found → return
409 Conflictwith conflicting booking IDs. - If no overlap → proceed with creation.
Database-level enforcement is required (see Section 7.3). Application-level checks are a first line of defense; a PostgreSQL exclusion constraint is the safety net.
3.4 Schedule Query
| Endpoint | Description |
|---|---|
GET /resources/:id/schedule?from=&to= | Returns all bookings in a date range |
GET /resources/:id/available-slots?date= | Returns free time slots for a given day, respecting operating hours and existing bookings |
- Time slots MUST be returned in the requesting user's timezone (passed via
tzquery parameter, IANA format, e.g.Asia/Hong_Kong).
3.5 Recurring Bookings
- Support iCal RRULE format for recurrence (e.g.,
FREQ=WEEKLY;BYDAY=MO,WE,FR). - Storage strategy:
- One parent record with the RRULE stored in
recurrenceRulefield. - Individual occurrence records linked by
recurrenceParentId.
- One parent record with the RRULE stored in
- Operations on recurring bookings:
- Edit single occurrence: creates an exception (detached from series).
- Edit series: updates parent and all future occurrences.
- Cancel series: cancels parent and all future occurrences.
- Recurring booking creation MUST validate no conflicts across ALL occurrences before committing.
3.6 Booking Policies
Each bookable resource has a configurable policy:
| Policy | Type | Description |
|---|---|---|
maxDurationMinutes | number | Maximum single booking length |
maxAdvanceDays | number | How far in advance a booking can be made |
minNoticeMinutes | number | Minimum notice before booking start |
allowRecurring | boolean | Whether recurring bookings are allowed |
autoConfirm | boolean | Skip pending state, go directly to confirmed |
maxBookingsPerUser | number | Max active bookings per user per resource |
cancellationPolicy | object | How late a booking can be cancelled (e.g., 30 min before start) |
gracePeriodMinutes | number | Minutes after start before no-show is triggered |
3.7 No-Show Handling
- If a confirmed booking is not checked in within
gracePeriodMinutesafterstartTime, status transitions tono_show. - The system MUST emit a
booking.no_showwebhook event so SEER rules can trigger alerts or release the slot. - Grace period is configurable per resource.
3.8 Check-In / Check-Out
PATCH /bookings/:id/checkin— marks the booking as actively in use.PATCH /bookings/:id/checkout— marks the booking as completed early.- If no checkout is performed, the booking auto-completes at
endTime.
4. Integration Requirements
4.1 Authentication
- All endpoints require JWT Bearer token in
Authorizationheader. - Validate token using SEER's auth service public key or JWKS endpoint.
- Token contains:
sub(profileId),workspace, roles/permissions.
4.2 IAM / Authorization
Use the existing SEER role hierarchy:
| Action | LV2 (WS Admin) | LV3 (Group Admin) | LV4 (User) | LV5 (Guest) |
|---|---|---|---|---|
| Register / manage resources | ✅ | ✅ | ❌ | ❌ |
| Create booking | ✅ | ✅ | ✅ | ❌ |
| View own bookings | ✅ | ✅ | ✅ | ✅ |
| View all bookings (workspace) | ✅ | ✅ | ❌ | ❌ |
| Cancel any booking | ✅ | ✅ | ❌ | ❌ |
| View schedule (resource) | ✅ | ✅ | ✅ | ✅ |
- Group-scoped resources: LV3 can only manage resources in their group(s).
4.3 Webhook Events
The service MUST emit webhook events to SEER's webhook system (HTTP POST to configured endpoint). Event payload must follow SEER's webhook variable format.
| Event | Trigger | Payload Variables |
|---|---|---|
booking.create | New booking created | id, resourceId, resourceType, bookerEmail, bookerProfileId, startTime, endTime, status, title, workspace |
booking.confirm | Booking confirmed | above + confirmedBy, confirmedAt |
booking.cancel | Booking cancelled | above + cancelledBy, cancelledAt, reason |
booking.reminder | X minutes before start | above + reminderType |
booking.no_show | No check-in detected | above + gracePeriodExpiredAt |
booking.checkin | Booker checked in | above + checkedInAt |
booking.checkout | Booker checked out | above + checkedOutAt |
4.4 SEER Device Service Dependency
- On resource registration: validate that
resourceIdexists by callingGET /device/v1/devices/{id}or the zone equivalent. - On booking display: enrich with resource name/type from device service (cache with TTL 5 min).
- Handle device deletion gracefully (webhook listener or periodic reconciliation).
4.5 API Gateway Registration
- Service must expose an OpenAPI spec at
/booking/docs-yaml. - Routes prefixed with
/booking/v1/. - Register in SEER's API Gateway routing configuration.
5. API Contract
5.1 Resource Management
POST /booking/v1/resources
GET /booking/v1/resources ?workspace=&resourceType=&isActive=&page=&limit=
GET /booking/v1/resources/:id
PATCH /booking/v1/resources/:id
DELETE /booking/v1/resources/:id
GET /booking/v1/resources/:id/schedule ?from=ISO8601&to=ISO8601&tz=IANA
GET /booking/v1/resources/:id/available-slots ?date=YYYY-MM-DD&tz=IANA&slotMinutes=30
5.2 Booking Management
POST /booking/v1/bookings
GET /booking/v1/bookings ?workspace=&resourceId=&status=&bookerProfileId=&from=&to=&page=&limit=
GET /booking/v1/bookings/:id
PATCH /booking/v1/bookings/:id (reschedule — subject to conflict check)
PATCH /booking/v1/bookings/:id/confirm
PATCH /booking/v1/bookings/:id/cancel { reason? }
PATCH /booking/v1/bookings/:id/checkin
PATCH /booking/v1/bookings/:id/checkout
POST /booking/v1/bookings/bulk (create recurring series)
PATCH /booking/v1/bookings/series/:parentId/cancel
5.3 Response Format
Follow SEER's existing response pattern:
// Single resource
{
"id": "uuid",
"resourceId": "seer-zone-or-device-id",
"resourceType": "zone",
"workspace": "workspace-uuid",
"resourceName": "Meeting Room A",
"capacity": 10,
"isActive": true,
"bookingPolicy": { ... },
"availability": { ... },
"createdAt": "2026-07-01T00:00:00.000Z",
"updatedAt": "2026-07-01T00:00:00.000Z"
}
// Paginated list
{
"data": [ ... ],
"count": 10,
"total": 150,
"page": 1,
"pageCount": 15
}
5.4 Error Responses
// 409 Conflict
{
"statusCode": 409,
"message": "Booking conflicts with existing reservations",
"conflicts": [
{
"bookingId": "uuid",
"startTime": "2026-07-01T09:00:00.000Z",
"endTime": "2026-07-01T10:00:00.000Z"
}
]
}
// 422 Policy Violation
{
"statusCode": 422,
"message": "Booking exceeds maximum duration of 120 minutes",
"policy": "maxDurationMinutes"
}
// 400 Bad Request
{
"statusCode": 400,
"message": "endTime must be after startTime"
}
5.5 Health Check
GET /booking/v1/health
Response:
{
"status": "ok",
"info": {
"database": { "status": "up" }
},
"error": null,
"details": {
"database": { "status": "up" }
}
}
6. Non-Functional Requirements
| Requirement | Target |
|---|---|
| Response time (CRUD operations) | < 200ms p95 |
| Response time (schedule query) | < 500ms p95 for 30-day range |
| Concurrent booking requests | Handle 100 concurrent writes without data corruption |
| Availability | 99.5% uptime |
| Data retention | Bookings retained for 2 years, then archived |
| Timezone correctness | All storage in UTC; queries accept IANA timezone param |
7. Technical Constraints
7.1 Language / Framework
- NestJS (Node.js) — match existing SEER microservices.
- TypeORM as ORM.
- Follow
@nestjsx/crudpatterns where applicable.
7.2 Database
- PostgreSQL (required for
tstzrangeand exclusion constraints). - Must use migrations (not auto-sync).
- Connection pooling required.
7.3 Conflict Prevention at DB Level
PostgreSQL exclusion constraint is mandatory:
-- Required: prevent overlapping bookings at the database level
ALTER TABLE bookings
ADD CONSTRAINT no_overlapping_bookings
EXCLUDE USING gist (
resource_id WITH =,
tstzrange(start_time, end_time) WITH &&
) WHERE (status IN ('pending', 'confirmed'));
This guarantees no overlapping bookings even under concurrent writes.
7.4 Docker Deployment
- Provide
Dockerfileanddocker-compose.yml. - Environment variables for:
DB_HOST,DB_PORT,DB_USERNAME,DB_PASSWORD,DB_DATABASESEER_AUTH_ENDPOINT(JWT validation)SEER_DEVICE_SERVICE_URL(resource validation)WEBHOOK_ENDPOINT(SEER webhook system)WEBHOOK_SECRET(webhook signing key)
- Health check endpoint at
GET /booking/v1/health.
7.5 Caching
- Cache zone/device metadata from SEER device service.
- TTL: 5 minutes.
- Use Redis or in-memory cache (follow SEER's caching strategy).
7.6 OpenAPI Specification
- Auto-generate from NestJS decorators.
- Expose at
/booking/docs-yaml(YAML format). - Include request/response examples for all endpoints.
8. Development Deliverables
| # | Item | Details |
|---|---|---|
| 1 | Source code | Repo under the SEER monorepo, .gitignore, consistent branch strategy |
| 2 | Database migrations | TypeORM migration files, forward + rollback support |
| 3 | OpenAPI specification | Auto-generated YAML at /booking/docs-yaml |
| 4 | Docker assets | Dockerfile and docker-compose.yml, env-var documented |
| 5 | Unit tests | > 80% coverage on service layer |
| 6 | Integration tests | Conflict detection, IAM validation, webhook emission |
| 7 | API documentation | Generated from OpenAPI + README |
| 8 | Deployment runbook | Env vars, DB setup, API Gateway route registration steps |
9. Out of Scope
- Frontend / UI development.
- Modifications to other SEER services (API Gateway route registration is part of deployment setup, not a code change to the Gateway service itself).
- Mobile push notification delivery (webhook emission only; delivery is handled by the existing webhook system).
10. Open Design Questions
Decisions to finalize before or during development:
- Recurring booking storage strategy — one row per occurrence vs. on-the-fly expansion. Recommendation: generate occurrences for the next 90 days, lazy-generate the rest.
- Timezone edge cases — DST transitions that cause ambiguous or non-existent times. Recommendation: store UTC, convert on display only.
- Calendar sync scope — Google/Outlook sync is deferred to a later phase. Should we add an abstraction layer now to minimize rework?
- Monitoring — Prometheus metrics endpoint or health check only?
- Webhook retry policy — retry count, backoff strategy, dead-letter handling.
Appendix A: SEER Platform Reference
Existing Services
| Service | Path Prefix | Purpose |
|---|---|---|
| Auth Service | /auth/ | JWT authentication, token validation |
| API Gateway | /api/ | Request routing, rate limiting |
| Device Management | /device/ | Devices, zones, realtime data |
| Workspace IAM | /iam/ | Roles, permissions, workspace/group membership |
| Rule Management | /rule/ | Rule engine (time-triggered, data-triggered) |
| Task & Location | /task/ | Task CRUD, state transitions |
| Control & Health | /control/ | Device control, alarmbox |
| Connector Service | /connector/ | External system integration |
| MinIO Storage | /minio/ | File/object storage |
| App Store | /appstore/ | Application marketplace |
Role Hierarchy
| Role | Level | Description |
|---|---|---|
| System Admin | LV1 | Platform internal use, not for customer |
| Workspace Admin | LV2 | Full access within workspace |
| Group Admin | LV3 | Full access within group |
| User | LV4 | Limited access |
| Guest | LV5 | Read-only access |
Webhook Event Types (Existing)
| Event Type | Description |
|---|---|
infer.rule | New infer data from rule service |
infer.link | Online/offline status change |
alert.trigger | Alert triggered |
new_data.http | New data from HTTP source |
new_data.mqtt | New data from MQTT source |
new_data.ws | New data from WebSocket source |
rule.state_change | Device field state changed by rule |
task.create | New task created |
task.ack | Task acknowledged |
task.close | Task closed |
task.reopen | Task reopened |
The booking service should integrate with this existing webhook infrastructure.