Skip to main content
Royal Glow runs on Neon PostgreSQL 16 with Drizzle ORM (pure TypeScript, edge-native). The schema lives in packages/db/src/schema/ as 15 files defining 38 tables that cover auth, profiles, services, scheduling, bookings, billing, memberships, offers, CRM, leads, loyalty, notifications, branches, and system operations.
Every monetary value is an integer in paise (₹1 = 100 paise) — never a float. Every timestamp is timestamptz (stored UTC, displayed IST). Prices are GST-inclusive at 18%.

Conventions

These rules are mandatory across every table.

Schema files & table groupings

The 15 schema files in packages/db/src/schema/ map to 38 tables. The tree below lists the actual pgTable names defined in each file.
packages/db/src/schema
enums.ts — all PostgreSQL enums (not a table)
auth.ts — user, session, account, verification (4)
profile.ts — customer_profile, staff_profile (2)
service.ts — service_category, service, staff_service (3)
schedule.ts — staff_schedule, staff_time_off, business_hour, holiday (4)
booking.ts — booking, booking_service, booking_status_log, waitlist (4)
invoice.ts — invoice, invoice_item (2)
membership.ts — spa_membership_tier, spa_membership (2)
offer.ts — offer, offer_service, offer_redemption (3)
lead.ts — lead, lead_note (2)
crm.ts — customer_tag, customer_tag_assignment, customer_note (3)
loyalty.ts — loyalty_account, loyalty_transaction (2)
notification.ts — notification, push_subscription (2)
branch.ts — branch (1)
system.ts — daily_sales_summary, monthly_gst_summary, audit_log, system_setting (4)
The auth.ts tables (user, session, account, verification) are owned by Better Auth’s Drizzle adapter. Don’t add custom columns to them — extend via customer_profile / staff_profile instead. Enums are defined separately in enums.ts (not counted as a table). Totals: 15 files, 38 tables.

Key enums

Defined natively in PostgreSQL via enums.ts.

ID & number formats

H = salon (hair/beauty), S = spa. The -M suffix marks a membership session booking.

GST (18% inclusive)

Customer-facing prices already include GST. The invoice back-calculates the base and tax from the inclusive amount.

Loyalty (gems) rules

  • Earn: 1 gem per ₹100 invoiced (floor), on invoice_type = 'service' only.
  • No gems on membership_purchase or membership_session invoices.
  • Expiry: 365 days from earn date, auto-deducted by the QStash gems-auto-expire job.
  • Redemption: against specific catalogue services — not a ₹ discount.
  • Cannot combine with an offer on the same booking.

Booking lifecycle

Walk-ins skip pending and go straight to confirmed. Status transitions are recorded in booking_status_log.

Business rules enforced at the DB level

Unique constraint on offer_redemption (customer + date).
Partial unique index on spa_membership where status = 'active'.
A booking is salon OR spa, never mixed.
booking_service.price_at_booking_paise and invoice_item.unit_price_paise are frozen copies.

Conventions

Money, dates, layering, and the API response envelope

Background Jobs

The cron + QStash jobs that maintain this data

API Reference

Endpoints that read and write these tables
Last modified on August 29, 2026