This document captures the mechanics: the booking state machine, ID/number formats, GST and gems math, all 19 background jobs, request lifecycles, indexes, and security implementation. Money is always integer paise; status transitions are always logged in
booking_status_log.Booking State Machine
ID & Number Formats
- Booking
- Invoice
H= salon (hair/beauty)S= spa-Msuffix = membership session
GST Calculation
All prices are GST-inclusive (18%, SAC 999721). Back-calculate the taxable base:base + gst always reconstructs the original inclusive amount exactly.
Gems (Loyalty) Calculation
No-Show Policy Logic
Recovery: 3 consecutive completed bookings reset
no_show_count. Walk-in no-shows do NOT count.
Background Jobs (19 Total)
- Scheduled (former pg_cron)
- Scheduled (HTTP)
- Triggered
QStash scheduled jobs — including the seven that were formerly pg_cron.
Realtime Channels (Ably)
Token Auth: clients receive a scoped Ably JWT from
POST /api/ably/token. The token is scoped to only the channels the user is allowed to subscribe to (based on their role and user ID).
Request Lifecycles
The four core write paths, step by step.- Booking creation
- Booking completion
- Membership session
- Lead capture
Customer submits the booking form →
POST /api/bookings.1
Validate session (
requireSession).2
Zod-validate the request body.
3
Check slot conflicts against Neon; no Redis availability cache is implemented.
4
Validate all service IDs exist and are active.
5
Get the default staff for each service.
6
Generate the booking number (
BK-RS-YYMM-H/S-XXXXX).7
INSERT the
booking row (status: pending).8
INSERT
booking_service rows (price snapshot).9
Enqueue QStash job 17 (stale booking alert, +2h).
10
Publish an Ably event to
admin:bookings:{branchId}.11
Return
{ success: true, data: { bookingId, bookingNumber } }.Database Indexes
Key indexes for query performance:Security Implementation
Input validation
Input validation
Every API route validates input with Zod
.safeParse() before any business logic runs. Raw client input never reaches the database.Rate limiting
Rate limiting
@upstash/ratelimit with a sliding-window algorithm, applied in Next.js middleware before auth checks.CSP headers
CSP headers
Strict Content-Security-Policy with nonce-based script loading, configured in
next.config.ts.SQL injection prevention
SQL injection prevention
Drizzle ORM uses parameterized queries exclusively. No raw SQL string concatenation anywhere in the codebase.
CORS
CORS
Exact origin matching (
theroyalglow.in only). No wildcard.Webhook verification
Webhook verification
All inbound webhooks (QStash, Meta, AiSensy) verify HMAC signatures before processing.
Related Pages
High-Level Design
Architecture, decisions matrix, and NFRs
Data Model
All 38 tables, enums, and conventions
Background Jobs
Full job inventory and heartbeats
