This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/scripts/db-validate.sh
2026-02-21 19:03:19 +00:00

228 lines
7.8 KiB
Bash

#!/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