Database Documentation
This document catalogs the Tours PostgreSQL schema. Every table is created and
evolved in TypeScript by idempotent ensure*Schema() functions — there is no ORM
and no migration framework. Each function runs CREATE TABLE IF NOT EXISTS plus
ALTER TABLE … ADD COLUMN IF NOT EXISTS for later additions, guarded so it executes once
per server process and once at request time (so a fresh database never throws
relation does not exist).
Conventions
- No migrations: schema lives in
src/product/booking/schema.ts(tours, departures, bookings, coupons, extras, reviews),src/lib/operators.ts(operators=hosts, earnings, payouts),src/lib/influencers.ts(affiliate),src/product/master/schema.ts(master data),src/lib/vendor-schema.ts(customers, commerce, settings), andsrc/lib/auth-schema.ts(adminusers). CMS tables are created byscripts/init-db.ts. - First-run init:
npm run db:initrunsscripts/init-db.ts(CMS + admin) thenscripts/init-core.ts(core infrastructure). Product-domain tables are provisioned by their ownensure*Schema()at request time / through the install seam. - Keys:
SERIALinteger primary keys; foreign keys viaREFERENCES. Some domain tables (listings,bookings) reference others by id without a formalREFERENCESconstraint (a "soft" link) so provisioning order never blocks. - Naming:
snake_casecolumns. Table and column names were deliberately kept from the original template for stability — a tour is alistingsrow, a departure is aunitsrow, and an operator is ahostsrow (host_id,guest_name, …). - Public ids: guest-facing entities also get a non-sequential
public_id(e.g.bookings.public_id=b…,listings.public_id=p…) alongside the serialid. - Money: integer cents throughout (
total,subtotal,deposit,price_adult,*_cents, …), paired with acurrencycolumn (default'EUR'). - Flexible data: arrays/objects stored as
JSONB; enums stored asVARCHAR. - Common columns:
is_active,sort_order,status,created_at,updated_at.
Tables are grouped below by domain.
Tours & Departures
Defined in src/product/booking/schema.ts. A tour is a listings row; a departure
(a dated, bookable option of a tour) is a units row.
| Table | Purpose | Key columns |
|---|---|---|
listings |
A tour | id, public_id (UQ), slug (UQ), name, summary, description, category_id (→ master_items), owner_id (→ hosts, NULL = admin-owned), hero_image, gallery/videos (JSONB), address/city/region/country, latitude/longitude, duration_days/duration_nights, daily_capacity, meeting_point, difficulty, min_age, group_size_min/group_size_max, itinerary/included/excluded (JSONB), price_adult/price_child/price_infant, private_available/private_price, deposit_pct, cancellation_tier, instant_booking, status, sort_order |
units |
A departure (dated option of a tour) | id, listing_id (→ listings), name, start_date, start_time, capacity (seats; 0 = fall back to listing.group_size_max), price_adult/price_child/price_infant (NULL = fall back to listing), private_price, currency, status (scheduled/cancelled), is_active, sort_order |
seasons |
A pricing season for a tour | id, listing_id (→ listings), name, color, priority |
season_dates |
Date ranges that make up a season | id, season_id (→ seasons), start_date, end_date |
unit_rates |
Per-departure price for a season | id, unit_id (→ units), season_id (→ seasons), price_per_night, price_per_week/_month, weekend_price, min_nights, arrival_days/departure_days (JSONB), UQ (unit_id, season_id) |
unit_blocks |
Blocked-out date ranges for a departure | id, unit_id (→ units), start_date, end_date, reason, source (manual/ical), feed_id |
ical_feeds |
External calendar imports (Airbnb/etc.) | id, unit_id (→ units), name, url, last_synced_at, last_status, event_count |
resource_days |
Per-day calendar overrides | id, resource_type, resource_id, date, price_override, min_nights, inventory, note, UQ (resource_type, resource_id, date) |
resource_notes |
Per-resource calendar notes | id, resource_type, resource_id, body |
The
seasons/unit_rates/unit_blocks/ical_feedstables form the optional per-departure availability & pricing calendar. Departures without a season rate fall back to the departure's ownprice_adult/price_child, then to the tour's base prices.
Inclusions, Extras & Master Data
Inclusion library and upsells in src/product/booking/schema.ts; classification lookups
in src/product/master/schema.ts.
| Table | Purpose | Key columns |
|---|---|---|
amenity_groups |
Inclusion group (e.g. "What's included") | id, title, subtitle, icon, image, sort_order, is_active |
amenities |
A single inclusion within a group | id, group_id (→ amenity_groups), title, subtitle, icon, image, sort_order, is_active |
extras |
Offerable upsells (transfer, meal, wine) | id, name, description, applies_to (all/class/tour/accommodation), price (cents), price_type (flat/per_person/per_night/per_person_night), max_qty, is_active, sort_order |
master_items |
Lookup for tour types, categories, etc. | id, kind, name, slug, description, icon, image, is_active, sort_order, UQ (kind, slug) and (kind, lower(name)) |
listing_master_items |
Tour ↔ master_item classification link | PK (listing_id, master_item_id) |
listing_nearby_places |
Points of interest near a tour | id, listing_id, category_id (→ master_items kind nearby_place_category), name, distance, sort_order |
listings.category_idpoints at amaster_itemsrow withkind = 'property_category'(the tour category). There is no separate categories table.
Bookings, Payments & Reviews
Defined in src/product/booking/schema.ts.
| Table | Purpose | Key columns |
|---|---|---|
bookings |
A reservation (lightly polymorphic) | id, reference (UQ), public_id (UQ), product_type (class/tour/accommodation), product_id, departure_id (→ units, NULL = private on-request), booking_mode (per_person/private), session_id, tour_date/end_date, start_time, nights, customer_id (→ customers), guest_name/guest_email/guest_phone/guest_country, num_adults/num_children/num_infants, guests_detail/price_breakdown (JSONB), subtotal/discount/fees/tax/deposit/total (cents), currency, coupon_id/coupon_code, status, payment_status, host_status, contact_revealed, influencer_id/referral_discount, provider/provider_ref, special_requests, source |
booking_payments |
Deposit / balance / refund per booking | id, booking_id (→ bookings), kind (deposit/balance/refund), amount (cents), currency, provider, provider_ref, status (pending/paid/failed/refunded), paid_at |
booking_extras |
Snapshot of chosen upsells on a booking | id, booking_id (→ bookings), name, qty, unit_price (cents), total |
booking_notes |
Internal staff notes on a booking | id, booking_id (→ bookings), body, author |
booking_emails |
Log of every message sent about a booking | id, booking_id (→ bookings), email_type, recipient, subject, body, status |
listing_reviews |
Traveler review/testimonial for a tour | id, listing_id (→ listings), author_name, author_location, avatar, rating (1–5), comment, review_date, photos/topics (JSONB), is_published, sort_order |
coupons |
Booking discount codes | id, code (UQ), discount_type (percent/fixed), discount_value, applies_to (all/class/tour/accommodation/specific), target_ids (JSONB), min_amount, max_discount, max_redemptions, per_customer_limit, times_redeemed, combinable, valid_from/valid_until, is_active |
A
bookingsrow is keyed byproduct_type+product_id('tour'+ alistings.id) and, for a specific departure,departure_id(→units). These are soft links (noREFERENCESconstraint).customer_idis the traveler and is a real FK. A secondcouponsvariant is also provisioned insrc/lib/vendor-schema.tsfor the storefront; the booking flow uses the columns above.
Operators (Tour Owners)
Defined in src/lib/operators.ts. An operator is a customer — the hosts row holds
the seller-only fields (payout method, commission, verification, status). The platform
collects all booking payments centrally and pays hosts out manually.
| Table | Purpose | Key columns |
|---|---|---|
hosts |
An operator profile (1:1 with a customer) | id, customer_id (→ customers, UQ), business_name, slug (UQ), bio, avatar, paypal_email, commission_override, status (pending/active/suspended), is_verified, verification_status, payout_method, KYC/legal/banking fields |
host_documents |
Uploaded KYC documents, reviewed by admin | id, host_id (→ hosts), kind (id_front/id_back/selfie/proof_of_address), file_url, status, reviewed_by, reviewed_at |
host_earnings |
Earnings ledger — one row per settled booking | id, booking_id (UQ), host_id (→ hosts), gross_cents, commission_cents, fee_cents, net_cents, currency, rate, status (pending/approved/paid/rejected), payout_request_id |
host_payout_requests |
Operator withdrawal request | id, host_id (→ hosts), amount_cents, currency, paypal_email, status (pending/approved/paid/rejected), admin_note, requested_at, processed_at |
host_payment_history |
Record of a paid-out payout | id, host_id (→ hosts), payout_request_id (→ host_payout_requests), amount_cents, paypal_email, transaction_id, status, paid_at |
platform_settings |
Singleton monetization config (id = 1) |
monetization_mode (commission/subscription/hybrid), default_commission_percent, min_payout_cents, payout_schedule, require_host_approval |
subscription_plans |
Admin-managed operator plans | id, name, slug (UQ), price_cents, interval (month/year), max_listings, featured_slots, commission_override, features (JSONB), is_active, sort_order |
host_subscriptions |
An operator's current plan | id, host_id (→ hosts), plan_id (→ subscription_plans), status (active/past_due/cancelled/expired), current_period_end, provider, provider_ref |
conversations |
Traveler ↔ operator thread (per tour) | id, host_id (→ hosts), guest_customer_id (→ customers), listing_id, booking_id, last_message_at, guest_unread, host_unread, UQ (host_id, guest_customer_id, COALESCE(listing_id,0)) |
messages |
A chat message | id, conversation_id (→ conversations), sender (guest/host), body, read_at |
Influencers (Affiliate)
Defined in src/lib/influencers.ts — an affiliate program that attributes bookings to a
referring influencer and mirrors the operator earnings → payout lifecycle.
| Table | Purpose | Key columns |
|---|---|---|
influencers |
Affiliate account | id, username (UQ), email (UQ), first_name/last_name, paypal_email, commission_type (percentage/fixed), commission_value, referral_discount, status, password_hash, min_payout_cents |
influencer_referral_links |
Trackable referral link | id, influencer_id (→ influencers), code, target_type (site/tour/…), target_id, target_slug, label, url, clicks |
influencer_social_links |
Social profiles | id, influencer_id (→ influencers), platform, url, UQ (influencer_id, platform) |
influencer_visits |
Visit tracking | id, influencer_id (→ influencers), ref_code, visitor_id, ip, path, is_unique |
influencer_clicks |
Referral-link clicks | id, influencer_id (→ influencers), link_id (→ influencer_referral_links), target_type, target_id, visitor_id |
influencer_bookings |
A booking attributed to an influencer | id, influencer_id (→ influencers), booking_id (UQ), product_type, product_id, customer_id, amount_cents, discount_cents, status |
influencer_commissions |
Commission ledger per attributed sale | id, influencer_id (→ influencers), booking_id, influencer_booking_id (→ influencer_bookings), amount_cents, commission_type, rate, status, payout_request_id |
influencer_payout_requests |
Influencer withdrawal request | id, influencer_id (→ influencers), amount_cents, paypal_email, status, requested_at, processed_at |
influencer_payment_history |
Record of a paid influencer payout | id, influencer_id (→ influencers), payout_request_id (→ influencer_payout_requests), amount_cents, transaction_id, status, paid_at |
influencer_notifications |
In-app notifications | id, influencer_id (→ influencers), type, title, body, is_read |
influencer_activity_logs |
Activity audit | id, influencer_id (→ influencers), action, meta (JSONB), ip |
Customers, Accounts & Auth
Defined in src/lib/vendor-schema.ts (customers, gift cards, RBAC) and
src/lib/auth-schema.ts (admin users).
| Table | Purpose | Key columns |
|---|---|---|
customers |
Traveler and operator accounts | id, slug (UQ), name, email, password_hash, account_type (traveler/operator role tag), email_verified/phone_verified, otp_code/otp_expires_at/otp_purpose, travel prefs (preferred_language, interests, dietary_notes, trip_type, …), ID/verification fields, notification_prefs (JSONB), is_blacklisted, is_active |
customer_verification_documents |
Traveler ID verification uploads | id, customer_id (→ customers), kind (id_front/id_back/selfie), file_url, status, reviewed_by, reviewed_at |
customer_notifications |
In-app notification per customer | id, customer_id (→ customers), title, body, kind, is_read |
gift_cards |
Stored-value codes | id, code (UQ), initial_cents, balance_cents, currency, status, issued_to_customer_id (→ customers), recipient_email, expires_at |
users |
Admin/staff login accounts | id, email (UQ), password (bcrypt), name, role_id (→ roles), avatar, is_active |
roles |
RBAC roles | id, slug (UQ), name, permissions (JSONB), is_system, is_active |
permissions |
Permission catalog | id, key (UQ), label, group_name, description |
activity_log |
Admin activity audit | id, user_label, action, summary, entity_type, entity_id, meta (JSONB) |
CMS / Website Content
Defined in scripts/init-db.ts (plus admin enhancements added by ALTER TABLE in
src/lib/vendor-schema.ts).
| Table | Purpose | Key columns |
|---|---|---|
pages |
Static CMS pages (privacy, terms, …) | id, slug (UQ), title, content, meta_title, meta_description, is_active |
blogs |
Blog posts | id, slug (UQ), title, category_id (→ blog_categories), excerpt, content, image, author, tags (JSONB), is_published, published_at |
blog_categories |
Blog taxonomy | id, slug (UQ), name, description, sort_order |
gallery |
Media gallery items | id, title, type, url, thumbnail, category, sort_order, is_active |
hero_slides |
Home hero slider | id, eyebrow, title, highlight, subtitle, image, CTA label/href fields, sort_order, is_active |
testimonials |
Customer testimonials | id, name, location, rating, message, image, tour, sort_order, is_active |
about |
Key/value content for the About page | id, key (UQ), value |
contacts |
Contact-form submissions | id, name, email, phone, message, is_read |
enquiries |
Enquiry/lead submissions | id, name, email, phone, subject, category, preferred_date, message, status, is_read |
newsletter |
Newsletter subscribers | id, email (UQ), name, is_active, subscribed_at |
Integrations, Settings & Licensing
Defined in src/lib/vendor-schema.ts.
| Table | Purpose | Key columns |
|---|---|---|
integration_connections |
Per-channel provider config (encrypted) | id, channel (EMAIL/STORAGE/PAYMENT/…), provider, label, is_active, is_primary, config/meta (JSONB, secrets encrypted), last_tested_ok |
app_settings |
Branding + app config (single row, id=1) |
app_name, app_icon, color fields, download URLs, store badges, versioning (JSONB) |
theme_settings |
Theme options (single JSONB row, id=1) |
id=1, data (JSONB) |
app_license |
Domain license record (single row, id=1) |
id=1, data (JSONB LicenseRecord) |
notification_templates / notification_logs |
Per-event templates + dispatch log | event, channel, subject, body / channel, title, audience, status, recipient_count |
message_templates |
Saved broadcast templates per channel | id, name, channel, subject, body, is_active |
api_tokens / webhooks |
API access + outbound webhooks | token_prefix, token_hash, scopes / url, secret, events (JSONB) |
media_assets |
Storage media library | id, object_key, url, mime_type, kind, size_bytes, folder, width/height |
translations |
Multi-language field values | entity_type, entity_id, field, language_code, value, UQ (entity_type, entity_id, field, language_code) |
currencies, languages, countries, locations |
Reference/localization data | code (UQ), name, flags, hierarchy (parent_id / country_code) |
Store (Physical Goods — Optional Commerce)
src/lib/vendor-schema.ts also ships a full storefront schema reused from the ecommerce
base. These coexist with the tours tables and power optional product sales, inventory,
shipping, and order fulfilment.
| Table group | Tables |
|---|---|
| Products | products, product_categories, brands, attributes, product_reviews, stock_movements |
| Orders | orders, order_items, order_returns, order_refunds, order_invoices, shipments, shipping_labels |
| Shipping | shipping_methods, shipping_zones, shipping_rates, delivery_slots, pickup_locations, warehouses, stock_transfers |
| Carts | customer_carts, stock_subscriptions |
| Reviews | vendor_reviews |
Documents (Optional Domain)
src/product/document/schema.ts provisions a document/attachment domain (documents,
document_versions, document_downloads, customer_documents, document_settings,
ai_usage, customer_oauth_accounts). It builds on the shared booking foundation
(ensureBookingSchema()) and hangs off customers; it is provisioned only where its
ensureDocumentSchema() runs.
© CreativeCape Solutions · creative-cape.com · support@creative-cape.com