Chameleon

Database

Database schema and management guide

Database Documentation

Chameleon uses Drizzle ORM and supports both PostgreSQL and MySQL databases.

Database Overview

The system automatically detects your database type from the DATABASE_URL and uses the appropriate driver:

  • PostgreSQL: postgresql:// or postgres://
  • MySQL: mysql://

Supported Databases

PostgreSQL (Recommended)

  • Vercel Postgres - Serverless, auto-scaling (recommended for Vercel)
  • Supabase - Open source, generous free tier
  • Railway - Simple deployment
  • Neon - Serverless Postgres
  • AWS RDS - Enterprise grade

MySQL

  • PlanetScale - Serverless MySQL, generous free tier
  • Railway - Simple deployment
  • AWS RDS - Enterprise grade
  • Azure MySQL - Cloud database

Database Schema

Core Tables

users

Stores user account information.

ColumnTypeDescription
idintegerPrimary key
uuidvarchar(255)Unique user identifier
emailvarchar(255)User email (unique)
nicknamevarchar(255)Display name
avatar_urlvarchar(255)Profile picture URL
invite_codevarchar(255)Auto-generated unique invite code
invited_byvarchar(255)UUID of referrer
is_affiliatebooleanAffiliate program status
created_attimestampAccount creation time
updated_attimestampLast update time

Key Features:

  • Auto-generated UUID for each user
  • Auto-generated unique invite code (6-8 characters)
  • OAuth integration (Google, GitHub)
  • Referral tracking

orders

Stores payment orders and subscriptions.

ColumnTypeDescription
idintegerPrimary key
order_novarchar(255)Unique order number
user_uuidvarchar(255)User who placed order
product_idvarchar(255)Product identifier
product_namevarchar(255)Product display name
amountintegerAmount in cents
currencyvarchar(10)Currency code (USD, EUR, etc.)
statusvarchar(50)Order status
pay_typevarchar(50)Payment method (stripe, creem)
sub_idvarchar(255)Stripe subscription ID
intervalvarchar(50)Billing interval (month, year)
created_attimestampOrder creation time
paid_attimestampPayment completion time

Statuses:

  • pending - Order created, awaiting payment
  • paid - Payment successful
  • failed - Payment failed
  • cancelled - Order cancelled
  • refunded - Order refunded

credits

Stores user credit balances.

ColumnTypeDescription
idintegerPrimary key
user_uuidvarchar(255)User identifier
left_creditsintegerCurrent balance
total_creditsintegerLifetime earned credits
updated_attimestampLast update time

credits_trans

Credit transaction history.

ColumnTypeDescription
idintegerPrimary key
user_uuidvarchar(255)User identifier
trans_typevarchar(50)Transaction type
creditsintegerCredit amount (+ or -)
expired_atvarchar(255)Expiration time
created_attimestampTransaction time

Transaction Types:

  • new_user - New user bonus (100 credits)
  • order_pay - Purchased credits
  • system_add - Admin added credits
  • ping - Daily check-in reward (1 credit)
  • ai_generation - AI generation cost

generations

AI generation history and results.

ColumnTypeDescription
idintegerPrimary key
gen_idvarchar(255)Unique generation ID
user_uuidvarchar(255)User who created
ai_typevarchar(50)Type: text, image, video
providervarchar(50)AI provider name
modelvarchar(255)Model name
prompttextUser prompt
result_urlstextJSON array of result URLs
statusvarchar(50)Generation status
credits_costintegerCredits consumed
created_attimestampTask creation time
completed_attimestampCompletion time
error_messagetextError details if failed

Statuses:

  • pending - Task created
  • processing - AI is generating
  • completed - Success, results available
  • failed - Generation failed

ai_provider_keys

AI provider API key management (Admin only).

ColumnTypeDescription
idintegerPrimary key
provider_namevarchar(50)Provider identifier (unique)
api_keytextEncrypted API key
model_configtextJSON configuration
is_enabledbooleanEnable/disable provider
updated_attimestampLast update time

Providers:

  • openai, deepseek, kling, seedance, replicate, openrouter, siliconflow

Content Tables

posts

Blog posts and content.

ColumnTypeDescription
idintegerPrimary key
uuidvarchar(255)Unique post ID
titlevarchar(255)Post title
descriptiontextPost description
contenttextMarkdown content
slugvarchar(255)URL slug
localevarchar(10)Language (en, zh, etc.)
statusvarchar(50)Draft or published
created_attimestampCreation time
updated_attimestampLast edit time

feedbacks

User feedback submissions.

ColumnTypeDescription
idintegerPrimary key
user_uuidvarchar(255)Submitter
contenttextFeedback text
ratinginteger1-5 star rating
created_attimestampSubmission time

affiliates

Referral program tracking.

ColumnTypeDescription
idintegerPrimary key
user_uuidvarchar(255)Affiliate (referrer)
order_uuidvarchar(255)Referred order
customer_emailvarchar(255)Referred customer
order_amountintegerOrder value
commission_rateinteger% rate (0-100)
commission_amountintegerCommission earned
statusvarchar(50)Pending, approved, paid
created_attimestampRecord creation

apikeys

User API keys for programmatic access.

ColumnTypeDescription
idintegerPrimary key
namevarchar(255)Key name/label
keyvarchar(255)API key (unique)
user_uuidvarchar(255)Key owner
statusvarchar(50)Active or revoked
created_attimestampCreation time

Database Migrations

Running Migrations

# Push schema changes to database
pnpm db:push

This command:

  1. Reads the schema from src/db/schema.ts
  2. Compares with current database state
  3. Generates and applies migration SQL
  4. Creates new tables and columns

Migration Files

Migrations are stored in src/db/migrations/ for reference but are auto-generated.

Rollback

Drizzle doesn't support automatic rollback. To undo changes:

  1. Manually drop tables or columns
  2. Or restore from database backup

Switching Databases

From MySQL to PostgreSQL

  1. Export data from MySQL:

    mysqldump -u user -p database > backup.sql
    
  2. Update DATABASE_URL to PostgreSQL

    DATABASE_URL="postgresql://user:pass@host:5432/database"
    
  3. Run migration:

    pnpm db:push
    
  4. Import data (manual process, adjust SQL syntax)

From PostgreSQL to MySQL

  1. Export data:

    pg_dump database > backup.sql
    
  2. Update DATABASE_URL to MySQL

  3. Run migration

  4. Import data (adjust SQL syntax)

Backup and Restore

Automated Backups

Vercel Postgres:

  • Automatic daily backups (Pro plan)
  • Point-in-time recovery

PlanetScale:

  • Automatic backups on paid plans
  • Branching for testing

Manual Backup

PostgreSQL:

pg_dump $DATABASE_URL > backup_$(date +%Y%m%d).sql

MySQL:

mysqldump -u user -p database > backup_$(date +%Y%m%d).sql

Restore

PostgreSQL:

psql $DATABASE_URL < backup.sql

MySQL:

mysql -u user -p database < backup.sql

Database Performance

Indexes

Key indexes for performance:

  • users.email - Unique index for fast login
  • users.uuid - Unique index for user lookups
  • users.invite_code - Unique index for referrals
  • orders.user_uuid - Index for user orders
  • credits_trans.user_uuid - Index for credit history
  • generations.gen_id - Unique index for generation lookup
  • generations.user_uuid - Index for user generation history

Optimization Tips

  1. Use connection pooling (built-in with Drizzle)
  2. Limit query results - Always use .limit() for lists
  3. Index frequently queried fields
  4. Archive old data - Move old records to archive tables
  5. Monitor slow queries - Use database analytics

Common Queries

Get user with credits

import { db } from "@/db";
import { users, credits } from "@/db/schema";
import { eq } from "drizzle-orm";

const userWithCredits = await db()
  .select({
    user: users,
    credits: credits,
  })
  .from(users)
  .leftJoin(credits, eq(users.uuid, credits.user_uuid))
  .where(eq(users.email, "user@example.com"))
  .limit(1);

Get user's orders

import { orders } from "@/db/schema";
import { eq, desc } from "drizzle-orm";

const userOrders = await db()
  .select()
  .from(orders)
  .where(eq(orders.user_uuid, user_uuid))
  .orderBy(desc(orders.created_at))
  .limit(10);

Get AI generation history

import { generations } from "@/db/schema";

const history = await db()
  .select()
  .from(generations)
  .where(eq(generations.user_uuid, user_uuid))
  .orderBy(desc(generations.created_at))
  .limit(20);

Database Security

  1. Use environment variables for database credentials
  2. Enable SSL for remote connections
  3. Restrict IP access to database server
  4. Regular backups - Daily minimum
  5. Encrypt sensitive data - Consider encrypting API keys in database
  6. Audit access logs - Monitor database access

Troubleshooting

Connection refused

Check:

  • Database server is running
  • Firewall allows connection
  • DATABASE_URL is correct
  • SSL settings match database requirements

Permission denied

  • Verify database user has CREATE, SELECT, INSERT, UPDATE, DELETE permissions
  • Check connection string credentials

Table doesn't exist

Run migration:

pnpm db:push

Slow queries

  • Add indexes to frequently queried columns
  • Use .limit() on all queries
  • Consider database caching (Redis)

Next Steps