Skip to content

Database Schema

CutX uses a single PostgreSQL database accessed by all three services through Cloudflare Hyperdrive.

TablePurpose
usersUser accounts, roles, auth credentials
magic_link_tokensPasswordless login tokens

users stores authentication data (email, password hash, GitHub ID), role (user, admin, owner), and status (active, suspended, banned).

TablePurpose
credit_balancesCurrent balance per user
credit_transactionsFull audit trail of all credit movements
subscriptionsStripe subscription state and tier

A database trigger auto-creates a credit_balances row with 25 free credits when a user is inserted.

TablePurpose
generation_jobsJob state, type, input params, output
assetsGenerated outputs (images, video, copy, audio)

generation_jobs tracks the full lifecycle:

  • type — copy, static_ad, ugc_video, tts, etc.
  • status — pending through completed/failed
  • input_params — JSONB with generation parameters
  • output_url — final asset URL
  • replicate_prediction_id — external job reference
  • idempotency_key — unique per user to prevent duplicates
  • credits_cost — credits deducted for this job
TablePurpose
projectsOrganizational containers for products
productsIngested product data (name, description, URL)
product_imagesProduct media files
TablePurpose
campaignsAd campaigns across platforms
ad_platform_connectionsOAuth tokens for Meta/TikTok/Google
campaign_analyticsDaily performance metrics
campaign_assetsCreative-to-campaign associations
ad_spend_feesPlatform fee tracking
TablePurpose
landing_pagesPublished landing pages with subdomains
TablePurpose
inventory_itemsSeller inventory with cost/value tracking
showsLive stream events
show_itemsItems assigned to shows with pricing
payout_importsUploaded payout CSVs
payout_line_itemsIndividual sale records from payouts
competitor_streamsCompetitor tracking data
TablePurpose
admin_audit_logAll privileged admin actions
users
├── credit_balances (1:1)
├── credit_transactions (1:many)
├── subscriptions (1:many)
├── generation_jobs (1:many)
│ └── assets (1:many)
├── projects (1:many)
│ └── products (1:many)
│ └── product_images (1:many)
├── campaigns (1:many)
│ ├── campaign_analytics (1:many)
│ └── campaign_assets (1:many)
├── ad_platform_connections (1:many)
├── inventory_items (1:many)
├── shows (1:many)
│ └── show_items (many:many with inventory)
└── landing_pages (1:many)
IndexTableColumnsPurpose
idx_users_emailusersemailLogin lookup
idx_users_githubusersgithub_idOAuth lookup
idx_jobs_user_statusgeneration_jobsuser_id, statusDashboard queries
idx_jobs_replicategeneration_jobsreplicate_prediction_idWebhook matching
idx_jobs_idempotencygeneration_jobsuser_id, idempotency_keyDuplicate prevention (unique)
idx_analytics_campaign_datecampaign_analyticscampaign_id, dateTime-range queries
idx_inventory_searchinventory_itemsname (trgm)Full-text inventory search

Ten tables have updated_at triggers that automatically set the timestamp on every UPDATE:

users, credit_balances, generation_jobs, products, campaigns, ad_platform_connections, landing_pages, inventory_items, shows, subscriptions

Most foreign keys use ON DELETE CASCADE, meaning:

  • Deleting a user removes all their jobs, products, campaigns, etc.
  • Deleting a project removes all its products and product images
  • Deleting a show removes all show-item assignments