Data Model — Convex Schema Reference

Status: Canonical Last Updated: 2026-05-11 Source: apps/backend/convex/schema.ts
Doc Status: Excellent | ✓ All 6 checks passed

Overview

This document describes the Convex database schema for House of Legends, covering all tables from experiences (show templates) through reservations, orders, gamification, and integrations. The schema follows a hierarchical structure: experiences → experienceEvents → reservations.

Entity Relationships

Booking Hierarchy

experiences (show templates) → experienceEvents (scheduled instances) → reservations (bookings) Related: checkIns (QR scans), bookingDrafts (in-progress bookings)

Add-ons

addOns (global add-on library) → linked to reservations.addOns[]

Staff Operations

users (staff/admin accounts) → tables (venue layout) → ordersorderItemsmenuItems

Guest Engagement

guestProfiles (created on QR scan) → guestReactions, photoSubmissionsphotoLikes, spinResults

Challenges

challengeConfigchallengeSubmissions

Notifications

notifications (in-app alerts)

Inquiries

formSessions (inquiry form submissions) → inquirySessions (admin overlay) → inquiryFollowUps

Payments

payments (OnePay transaction tracking) → linked to reservations

Tables

experiences (Level 1 — Show Templates)

FieldTypeDescription
codestringUnique code e.g. “CINE-GASTRO”
titlestringShow title
taglinestringShort description
descriptionstringFull rich text
embeddedVideostring?YouTube/Vimeo URL
gallerystring[]Image URLs
thumbnailUrlstring?Admin-configurable
supportedTicketTypes("DINNER_THEATRE" | "SHOW_ONLY")[]Ticket options
defaultDinnerPricenumberVND
defaultShowOnlyPricenumberVND
defaultCapacitynumberMax 32
status"ACTIVE" | "DRAFT" | "ARCHIVED"Lifecycle
slugstringURL-friendly ID
createdAt, updatedAtnumberUnix timestamps
Indexes: by_status, by_slug, by_code

experienceEvents (Level 2 — Scheduled Instances)

FieldTypeDescription
experienceIdId<"experiences">FK to template
codestringUnique e.g. “TMTL260521N”
datestringISO date “2026-05-21”
timestring”19:30”
dinnerPricenumberDenormalized
experienceOnlyPricenumberDenormalized
experienceOnlyEnabledbooleanAdmin toggle
actualCapacitynumberOverride possible
bookedCountnumberCalculated from PAID
thumbnailUrlstring?Per-event override
status"SCHEDULED" | "CANCELLED" | "SOLD_OUT"Event status
assignedTablesId<"tables">[]Linked tables
createdAt, updatedAtnumberUnix timestamps
Indexes: by_experience, by_date, by_experience_date, by_date_status, by_code

reservations (Level 3 — Bookings)

FieldTypeDescription
eventIdId<"experienceEvents">FK to event
customerFirstNamestring
customerLastNamestring
customerEmailstring
customerPhonestring?
customerNotestring?
ticketType"DINNER_THEATRE" | "SHOW_ONLY"
quantitynumberGuest count
bundleIdstring?Bundle pricing
guestsnumberSame as quantity
tableIdId<"tables">?Assigned table
addOns{addOnId, quantity}[]Selected add-ons
subtotalnumberBefore discounts
totalAmountnumberFinal price
paymentStatussee below
statussee below
paymentMethodstring?
onePayOrderIdstring?OnePay reference
qrCodestring?Generated QR
qrCodeUrlstring?QR image URL
tokenstring?Unique for QR scan
bookingExpiresAtnumber?Seat hold expiry
checkedInAtnumber?When scanned
discountAmountnumberBundle discount
discountPercentnumber
vipSurchargenumber
dayOfWeekSurchargenumberFriday/Saturday
smallPartySurchargenumber1-2 guests
bookingStepsee belowFlow tracking
createdAt, updatedAtnumber
paymentStatus values: PENDING, PAID, REFUNDED, FAILED, CANCELLED, REFUND_PENDING status values: PENDING, PAID_CONFIRMED, CHECKED_IN, CANCELLED, REFUNDED bookingStep values: EXPERIENCE, SHOW, TICKETS, ZONE, BUNDLE, ADDONS, CUSTOMER_INFO, PAYMENT, CONFIRMATION Indexes: by_event, by_email, by_payment_status, by_expires, by_table, by_booking_step, by_token, by_vpcMerchTxnRef

addOns

FieldTypeDescription
namestring
descriptionstring
pricenumberVND
imageUrlstring?
type"COCKTAIL" | "FOOD" | "UPGRADE" | "OTHER"
enabledbooleanGlobal toggle
createdAt, updatedAtnumber
Indexes: by_enabled

users (Staff/Admin)

FieldTypeDescription
emailstringUnique
passwordHashstring?
role"ADMIN" | "STAFF"
namestring
createdAt, updatedAtnumber
Indexes: by_email, by_role

tables

FieldTypeDescription
namestring”T01”, etc.
capacitynumber
status"AVAILABLE" | "OCCUPIED" | "RESERVED" | "MAINTENANCE"
position{x, y}?Floor plan
createdAt, updatedAtnumber
Indexes: by_status
FieldTypeDescription
namestring
descriptionstring
pricenumberVND
categorysee below
station"KITCHEN" | "BAR"
imageUrlstring?
isAvailableboolean
isFeaturedboolean?
createdAt, updatedAtnumber
category values: FOOD, BEVERAGE, DESSERT, COCKTAIL, WINE, BEER Indexes: by_category, by_available, by_station

orders

FieldTypeDescription
tableIdId<"tables">
reservationIdId<"reservations">?
eventIdId<"experienceEvents">?
status"OPEN" | "SUBMITTED" | "PREPARING" | "SERVED" | "PAID" | "CANCELLED"
subtotalnumber
totalAmountnumber
notesstring?
createdAt, updatedAtnumber
Indexes: by_table, by_status, by_event

orderItems

FieldTypeDescription
orderIdId<"orders">
menuItemIdId<"menuItems">
quantitynumber
unitPricenumber
totalPricenumber
notesstring?
status"PENDING" | "PREPARING" | "READY" | "SERVED"
station"KITCHEN" | "BAR"
isCompbooleanComplimentary
compSource"SPIN" | "PHOTO_WIN" | "GOOGLE_REVIEW"?
createdAtnumber
Indexes: by_order, by_order_status, by_station_status

guestProfiles

FieldTypeDescription
reservationIdId<"reservations">?
tableIdId<"tables">?
tokenstringQR identifier
avatarUrlstring?
avatarStorageIdstring?
nicknamestring
countrystring
originstring
moodTagsstring[]
biostring?
experienceDatestringISO date
checkedInboolean
createdAt, updatedAtnumber
Indexes: by_reservation, by_experience_date, by_token

guestReactions

FieldTypeDescription
fromProfileIdId<"guestProfiles">
toProfileIdId<"guestProfiles">
reactionType"WAVE" | "CHEERS" | "HEART"
experienceDatestring
createdAtnumber
Indexes: by_to_profile, by_from_profile, by_experience_date

bookingDrafts

FieldTypeDescription
sessionIdstringUser auth subject
eventIdId<"experienceEvents">?
experiencestring?
ticketType"DINNER_THEATRE" | "SHOW_ONLY"?
quantitynumber?
reservationIdId<"reservations">?
bookingExpiresAtnumber?
addOns{addOnId, quantity}[]?
bundlestring?
guestsnumber?
customerInfo{firstName, lastName, email, phone}?
currentStepsee below?
expiresAtnumberAuto-cleanup
createdAt, updatedAtnumber
currentStep values: EXPERIENCE, SHOW, TICKETS, BUNDLE, ADDONS, CUSTOMER_INFO, PAYMENT, CONFIRMATION Indexes: by_session, by_expires

formSessions

FieldTypeDescription
sessionIdstringlocalStorage UUID
formTypesee below
datastringJSON
submittedboolean
expiresAtnumber7 days
createdAt, updatedAtnumber
formType values: CONTACT, VENUE_RENTAL, PRIVATE_EVENTS, WORKSHOPS, ARTIST_PROPOSAL, HOST_AN_EVENT, FRENCH_MENTALIST, DINNER_THEATER, CHECKOUT, RESERVATION, PROFILE, EXPERIENCE, ADDON Indexes: by_session_type, by_expires, by_data_search

inquirySessions

FieldTypeDescription
formSessionIdId<"formSessions">
formTypesame as formSessions
status"NEW" | "READ" | "REPLIED" | "ARCHIVED"
adminNotesstring?
reviewedBystring?Clerk user ID
reviewedAtnumber?
createdAt, updatedAtnumber
Indexes: by_formSession, by_formType, by_status, by_formType_status

inquiryFollowUps

FieldTypeDescription
inquiryIdId<"inquirySessions">
authorIdstringClerk user ID
authorNamestring
contentstring
createdAtnumber
Indexes: by_inquiry

challengeConfig

FieldTypeDescription
challengeType"PHOTO_WALL" | "LUCKY_SPIN" | "GOOGLE_REVIEW"
enabledboolean
maxValuenumber?
prizeDescriptionstring?
steps{order, text, imageUrl?}[]
activeForDatesstring[]
createdAt, updatedAtnumber
Indexes: by_type

photoSubmissions

FieldTypeDescription
profileIdId<"guestProfiles">
orderIdId<"orders">?
tableIdId<"tables">
imageUrlstring
captionstring?
likeCountnumber
status"ACTIVE" | "HIDDEN"
winnerboolean
experienceDatestring
createdAt, updatedAtnumber
Indexes: by_experience_date, by_profile, by_status, by_table_experience, by_likes

photoLikes

FieldTypeDescription
submissionIdId<"photoSubmissions">
profileIdId<"guestProfiles">
createdAtnumber
Indexes: by_submission, by_profile_submission, by_profile

spinPrizes

FieldTypeDescription
labelstring
prizeType"MENU_ITEM" | "DISCOUNT" | "FREE_ITEM"
menuItemIdId<"menuItems">?
discountPercentnumber?
weightnumber
enabledboolean
createdAt, updatedAtnumber
Indexes: by_enabled

spinResults

FieldTypeDescription
profileIdId<"guestProfiles">
orderIdId<"orders">
tableIdId<"tables">
prizeIdId<"spinPrizes">
displayTextstring
experienceDatestring
createdAt, updatedAtnumber
Indexes: by_experience_date, by_table_experience, by_profile

challengeSubmissions

FieldTypeDescription
profileIdId<"guestProfiles">
orderIdId<"orders">
tableIdId<"tables">
challengeType"GOOGLE_REVIEW"
screenshotUrlstring
status"PENDING" | "APPROVED" | "REJECTED"
rewardMenuItemIdId<"menuItems">?
reviewedById<"users">?
reviewedAtnumber?
notesstring?
experienceDatestring
createdAt, updatedAtnumber
Indexes: by_status, by_experience_date, by_table_experience

notifications

FieldTypeDescription
type"ORDER_READY" | "NEW_RESERVATION" | "EXPERIENCE_REMINDER" | "ALERT" | "SYSTEM"
titlestring
messagestring
isReadboolean
priority"LOW" | "MEDIUM" | "HIGH"
metadatastring?JSON
createdAtnumber
Indexes: by_read, by_type, by_created

notificationLogs

FieldTypeDescription
notificationTypesee below
channel"EMAIL" | "WHATSAPP"
recipientstring
subjectstring?
status"SUCCESS" | "FAILED"
errorMessagestring?
reservationIdId<"reservations">?
createdAtnumber
notificationType values: EMAIL_CONFIRMATION, EMAIL_CANCELLATION, WHATSAPP_CONFIRMATION, EMAIL_ADMIN_NEW_BOOKING Indexes: by_reservation, by_status, by_created

zohoSyncLogs

FieldTypeDescription
operationTypesee below
entityTypesee below
entityIdstring?Zoho ID
status"SUCCESS" | "FAILED"
errorMessagestring?
reservationIdId<"reservations">?
createdAtnumber
operationType values: CONTACT_UPSERT, DEAL_CREATE, INVOICE_CREATE, INVOICE_MARK_PAID entityType values: CONTACT, DEAL, INVOICE Indexes: by_reservation, by_status, by_created

payments

FieldTypeDescription
reservationIdId<"reservations">
vpcMerchTxnRefstringOnePay reference
vpcTransactionNostring?
amountnumber
currencystring
status"PENDING" | "SUCCESS" | "FAILED"
responseCodestring?
messagestring?
cardstring?
cardNumstring?
createdAt, updatedAtnumber
Indexes: by_vpcMerchTxnRef

checkIns

FieldTypeDescription
ticketIdstringReservation token
eventIdId<"experienceEvents">
checkedInAtnumberUnix timestamp
checkedInBystring?Staff user ID
Indexes: by_ticket, by_event