#!/bin/bash # ============================================ # Chat Switchboard - Schema Validation # ============================================ # Verifies the database schema is correct after # migration. Checks expected tables, key columns, # and constraints. Exits non-zero on any failure. # # Required env vars: # PGHOST, PGPORT, PGUSER, PGPASSWORD, PGDATABASE # # Usage: # scripts/db-validate.sh # ============================================ set -euo pipefail echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Schema validation: ${PGDATABASE}" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" ERRORS=0 # ── Helper: check table exists ─────────────── check_table() { local table="$1" local exists exists=$(psql -tAc "SELECT 1 FROM information_schema.tables WHERE table_schema='public' AND table_name='${table}';") if [[ "${exists}" == "1" ]]; then echo " ✓ table: ${table}" else echo " ✗ MISSING table: ${table}" ERRORS=$((ERRORS + 1)) fi } # ── Helper: check column exists ────────────── check_column() { local table="$1" local column="$2" local exists exists=$(psql -tAc "SELECT 1 FROM information_schema.columns WHERE table_schema='public' AND table_name='${table}' AND column_name='${column}';") if [[ "${exists}" == "1" ]]; then echo " ✓ column: ${table}.${column}" else echo " ✗ MISSING column: ${table}.${column}" ERRORS=$((ERRORS + 1)) fi } # ── Helper: check index exists ─────────────── check_index() { local index="$1" local exists exists=$(psql -tAc "SELECT 1 FROM pg_indexes WHERE schemaname='public' AND indexname='${index}';") if [[ "${exists}" == "1" ]]; then echo " ✓ index: ${index}" else echo " ✗ MISSING index: ${index}" ERRORS=$((ERRORS + 1)) fi } # ── Helper: check extension ────────────────── check_extension() { local ext="$1" local exists exists=$(psql -tAc "SELECT 1 FROM pg_extension WHERE extname='${ext}';") if [[ "${exists}" == "1" ]]; then echo " ✓ extension: ${ext}" else echo " ✗ MISSING extension: ${ext}" ERRORS=$((ERRORS + 1)) fi } # ── 1. Extensions ──────────────────────────── echo "" echo "Extensions:" check_extension "uuid-ossp" check_extension "pgcrypto" # ── 2. Migration tracking ─────────────────── echo "" echo "Migration tracking:" check_table "schema_migrations" MIGRATION_COUNT=$(psql -tAc "SELECT COUNT(*) FROM schema_migrations;" 2>/dev/null || echo "0") echo " ✓ ${MIGRATION_COUNT} migrations applied" LATEST=$(psql -tAc "SELECT version FROM schema_migrations ORDER BY version DESC LIMIT 1;" 2>/dev/null || echo "none") echo " ✓ latest: ${LATEST}" # ── 3. Core tables (001 schema, renamed in 006) ─ echo "" echo "Core tables:" check_table "users" check_table "api_configs" check_table "channels" check_table "messages" # Key columns check_column "users" "id" check_column "users" "email" check_column "users" "role" check_column "api_configs" "user_id" check_column "api_configs" "provider" check_column "api_configs" "api_key_encrypted" check_column "channels" "user_id" check_column "channels" "type" check_column "messages" "channel_id" check_column "messages" "role" check_column "messages" "parent_id" # ── 4. Refresh tokens (002) ───────────────── echo "" echo "Auth tables:" check_table "refresh_tokens" check_column "refresh_tokens" "token_hash" check_column "refresh_tokens" "user_id" # ── 5. Global settings (003) ──────────────── echo "" echo "Settings tables:" check_table "global_settings" check_column "global_settings" "key" check_column "global_settings" "value" # ── 6. Model configs (004) ────────────────── echo "" echo "Model tables:" check_table "model_configs" check_column "model_configs" "api_config_id" check_column "model_configs" "model_id" check_column "model_configs" "capabilities" # ── 7. Provider capabilities (005) ────────── echo "" echo "Provider capabilities:" check_column "api_configs" "custom_headers" check_column "api_configs" "is_global" check_table "user_model_preferences" check_column "user_model_preferences" "user_id" check_column "user_model_preferences" "model_config_id" # ── 8. Channel unification (006-008) ──────── echo "" echo "Channel model (006-008):" check_column "channels" "description" check_column "messages" "participant_type" check_column "messages" "participant_id" check_table "channel_members" check_column "channel_members" "channel_id" check_column "channel_members" "user_id" check_table "channel_models" check_column "channel_models" "channel_id" check_column "channel_models" "model_id" check_table "channel_cursors" check_column "channel_cursors" "active_leaf_id" # ── 9. Folders & Projects (009) ───────────── echo "" echo "Organization (009):" check_table "folders" check_column "folders" "user_id" check_table "projects" check_column "projects" "user_id" check_table "project_channels" # ── 10. Banners (010) ─────────────────────── echo "" echo "Banners (010):" # Banner config is seeded into global_settings; just verify the key exists BANNER_KEY=$(psql -tAc "SELECT 1 FROM global_settings WHERE key = 'banner';" 2>/dev/null || echo "0") if [[ "${BANNER_KEY}" == "1" ]]; then echo " ✓ global_settings: banner" else echo " ✗ MISSING global_settings key: banner" ERRORS=$((ERRORS + 1)) fi # ── 11. Model Presets (012) ───────────────── echo "" echo "Model Presets (012):" check_table "model_presets" check_column "model_presets" "name" check_column "model_presets" "base_model_id" check_column "model_presets" "api_config_id" check_column "model_presets" "system_prompt" check_column "model_presets" "scope" check_column "model_presets" "created_by" check_column "model_presets" "is_active" # ── Migration 013: Message Forking ─────────── echo "" echo "Migration 013: Message Forking" check_column "messages" "deleted_at" check_column "messages" "sibling_index" # ── Migration 014: Notes ───────────────────── echo "" echo "Migration 014: Notes" check_table "notes" check_column "notes" "user_id" check_column "notes" "title" check_column "notes" "content" check_column "notes" "folder_path" check_column "notes" "tags" check_column "notes" "metadata" check_column "notes" "source_channel_id" check_column "notes" "search_vector" # ═══════════════════════════════════════════ # ADD NEW MIGRATION CHECKS ABOVE THIS LINE # ═══════════════════════════════════════════ # ── Summary ────────────────────────────────── echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" if [[ ${ERRORS} -eq 0 ]]; then echo "✅ Schema validation passed (${MIGRATION_COUNT} migrations)" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" exit 0 else echo "❌ Schema validation FAILED: ${ERRORS} errors" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" exit 1 fi