#!/bin/bash # ============================================ # Chat Switchboard - Schema Validation (v0.9) # ============================================ # 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 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 "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 ────────────────────────── echo "" echo "Core tables:" check_table "users" check_table "provider_configs" check_table "channels" check_table "messages" check_table "teams" check_table "team_members" check_table "refresh_tokens" check_table "global_settings" # ── 4. Users ──────────────────────────────── echo "" echo "Users:" check_column "users" "id" check_column "users" "username" check_column "users" "email" check_column "users" "role" check_column "users" "is_active" check_column "users" "avatar_url" check_column "users" "display_name" check_column "users" "settings" # ── 5. Provider Configs (replaces api_configs) ─ echo "" echo "Provider Configs:" check_column "provider_configs" "scope" check_column "provider_configs" "owner_id" check_column "provider_configs" "provider" check_column "provider_configs" "endpoint" check_column "provider_configs" "api_key_enc" check_column "provider_configs" "headers" check_column "provider_configs" "settings" check_column "provider_configs" "is_active" # ── 6. Model Catalog (replaces model_configs) ─ echo "" echo "Model Catalog:" check_table "model_catalog" check_column "model_catalog" "provider_config_id" check_column "model_catalog" "model_id" check_column "model_catalog" "display_name" check_column "model_catalog" "capabilities" check_column "model_catalog" "pricing" check_column "model_catalog" "visibility" # ── 7. Personas (replaces model_presets) ──── echo "" echo "Personas:" check_table "personas" check_column "personas" "scope" check_column "personas" "owner_id" check_column "personas" "name" check_column "personas" "base_model_id" check_column "personas" "provider_config_id" check_column "personas" "system_prompt" check_column "personas" "created_by" # ── 8. Channels ───────────────────────────── echo "" echo "Channels:" check_column "channels" "user_id" check_column "channels" "type" check_column "channels" "provider_config_id" check_column "channels" "team_id" check_column "channels" "settings" check_table "channel_members" check_table "channel_models" check_table "channel_cursors" # ── 9. Messages ───────────────────────────── echo "" echo "Messages:" check_column "messages" "channel_id" check_column "messages" "role" check_column "messages" "parent_id" check_column "messages" "sibling_index" check_column "messages" "deleted_at" check_column "messages" "participant_type" # ── 10. Organization ──────────────────────── echo "" echo "Organization:" check_table "folders" check_table "projects" check_table "notes" check_column "notes" "search_vector" # ── 11. User Model Settings ──────────────── echo "" echo "User Model Settings:" check_table "user_model_settings" check_column "user_model_settings" "user_id" check_column "user_model_settings" "model_id" check_column "user_model_settings" "hidden" check_column "user_model_settings" "sort_order" # ── 12. Audit Log ─────────────────────────── echo "" echo "Audit Log:" check_table "audit_log" check_column "audit_log" "actor_id" check_column "audit_log" "action" check_column "audit_log" "resource_type" # ═══════════════════════════════════════════ # 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