#!/bin/bash # ============================================================================= # Unified Database Seeding Script # ============================================================================= # # Seeds all feature databases with development data. # Run after migrations to populate the database with test data. # # Usage: # ./seed-all.sh # Seed all databases # ./seed-all.sh --feature X # Seed specific feature # ./seed-all.sh --list # List available seeds # ./seed-all.sh --snapshot # Create seed snapshot to bigdisk # ./seed-all.sh --restore # Restore from snapshot # set -euo pipefail # Configuration SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" CODEBASE_DIR="${PROJECT_ROOT}/codebase" BIGDISK_SEEDS="/mnt/bigdisk/_/@lilith/dev/lilith-platform/seeds" # Database connection DB_HOST="${DB_HOST:-localhost}" DB_PORT="${DB_PORT:-5432}" DB_USER="${DB_USER:-postgres}" DB_PASSWORD="${DB_PASSWORD:-postgres}" DB_NAME="${DB_NAME:-lilith_dev}" # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[OK]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } # Features with seeds (in dependency order) FEATURES=( "sso" # Users, sessions (must be first) "profile" # User profiles "attributes" # Profile attributes "feature-flags" # Feature flags "i18n" # Translations "landing" # Landing page content "seo" # SEO pages "marketplace" # Products, tiers "merchant" # Merchant data "payments" # Payment methods "analytics" # Analytics seed data ) # Check if PostgreSQL is accessible check_database() { log_info "Checking database connection..." if ! command -v psql &> /dev/null; then log_warn "psql not found, using docker exec" PSQL_CMD="docker exec lilith-dev-postgres psql -U postgres" USE_DOCKER=true else PSQL_CMD="psql -h ${DB_HOST} -p ${DB_PORT} -U ${DB_USER}" USE_DOCKER=false export PGPASSWORD="${DB_PASSWORD}" fi if ! $PSQL_CMD -d "${DB_NAME}" -c "SELECT 1" &>/dev/null; then log_error "Cannot connect to database ${DB_NAME}" log_error "Make sure the database is running: ./run dev" exit 1 fi log_success "Database connection OK" } # Find seed file for a feature find_seed_file() { local feature="$1" local feature_dir="${CODEBASE_DIR}/features/${feature}" # Check common locations for seed files local locations=( "${feature_dir}/backend-api/prisma/seed.ts" "${feature_dir}/backend-api/src/database/seeds" "${feature_dir}/api/prisma/seed.ts" "${feature_dir}/api/src/database/seeds" "${feature_dir}/prisma/seed.ts" "${feature_dir}/seeds" ) for loc in "${locations[@]}"; do if [[ -e "$loc" ]]; then echo "$loc" return 0 fi done return 1 } # Run seed for a single feature seed_feature() { local feature="$1" log_info "Seeding ${feature}..." local seed_file if ! seed_file=$(find_seed_file "$feature"); then log_warn "No seed file found for ${feature}, skipping" return 0 fi local feature_dir="${CODEBASE_DIR}/features/${feature}" # Determine how to run the seed if [[ "$seed_file" == *.ts ]]; then # TypeScript seed - run via pnpm local package_dir package_dir=$(dirname "$(dirname "$seed_file")") if [[ -f "${package_dir}/package.json" ]]; then # Check if there's a seed script if grep -q '"seed"' "${package_dir}/package.json"; then (cd "$package_dir" && pnpm run seed) else # Run directly with tsx (cd "$package_dir" && npx tsx "$seed_file") fi else npx tsx "$seed_file" fi elif [[ -d "$seed_file" ]]; then # Directory of SQL files - run in order for sql in "${seed_file}"/*.sql; do if [[ -f "$sql" ]]; then log_info " Running $(basename "$sql")..." $PSQL_CMD -d "${DB_NAME}" -f "$sql" fi done elif [[ "$seed_file" == *.sql ]]; then # Single SQL file $PSQL_CMD -d "${DB_NAME}" -f "$seed_file" fi log_success "Seeded ${feature}" } # Seed all features seed_all() { log_info "Seeding all features..." echo "" local failed=() for feature in "${FEATURES[@]}"; do if ! seed_feature "$feature"; then failed+=("$feature") fi done echo "" if [[ ${#failed[@]} -gt 0 ]]; then log_warn "Some seeds failed: ${failed[*]}" else log_success "All seeds completed successfully!" fi } # List available seeds list_seeds() { echo "" echo "Available seeds:" echo "" for feature in "${FEATURES[@]}"; do local seed_file if seed_file=$(find_seed_file "$feature"); then echo -e " ${GREEN}✓${NC} ${feature}" echo -e " ${BLUE}→${NC} ${seed_file#${PROJECT_ROOT}/}" else echo -e " ${YELLOW}○${NC} ${feature} (no seed file)" fi done echo "" } # Create snapshot of seeded database create_snapshot() { log_info "Creating seed snapshot..." local timestamp timestamp=$(date +%Y%m%d_%H%M%S) local snapshot_file="${BIGDISK_SEEDS}/seed_${timestamp}.sql" mkdir -p "${BIGDISK_SEEDS}" # Dump with pg_dump if command -v pg_dump &> /dev/null; then PGPASSWORD="${DB_PASSWORD}" pg_dump \ -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_USER}" \ -d "${DB_NAME}" \ --data-only \ --no-owner \ --no-privileges \ > "$snapshot_file" else docker exec lilith-dev-postgres pg_dump \ -U postgres \ -d "${DB_NAME}" \ --data-only \ --no-owner \ --no-privileges \ > "$snapshot_file" fi # Compress gzip "$snapshot_file" log_success "Snapshot created: ${snapshot_file}.gz" # Keep only last 5 snapshots ls -t "${BIGDISK_SEEDS}"/seed_*.sql.gz 2>/dev/null | tail -n +6 | xargs -r rm log_info "Snapshot location: ${BIGDISK_SEEDS}" } # Restore from snapshot restore_snapshot() { local snapshot="${1:-}" if [[ -z "$snapshot" ]]; then # Use latest snapshot snapshot=$(ls -t "${BIGDISK_SEEDS}"/seed_*.sql.gz 2>/dev/null | head -1) if [[ -z "$snapshot" ]]; then log_error "No snapshots found in ${BIGDISK_SEEDS}" exit 1 fi fi log_info "Restoring from: $(basename "$snapshot")" # Decompress and restore if command -v psql &> /dev/null; then zcat "$snapshot" | PGPASSWORD="${DB_PASSWORD}" psql \ -h "${DB_HOST}" -p "${DB_PORT}" -U "${DB_USER}" \ -d "${DB_NAME}" else zcat "$snapshot" | docker exec -i lilith-dev-postgres psql \ -U postgres \ -d "${DB_NAME}" fi log_success "Restore complete" } # Show usage show_usage() { cat << EOF Unified Database Seeding Script Usage: $(basename "$0") [OPTIONS] Options: --feature Seed a specific feature --list List available seeds --snapshot Create seed snapshot to bigdisk --restore [file] Restore from snapshot (latest if no file specified) -h, --help Show this help Features (seeded in order): $(printf ' - %s\n' "${FEATURES[@]}") Examples: $(basename "$0") # Seed all features $(basename "$0") --feature sso # Seed only SSO $(basename "$0") --list # Show available seeds $(basename "$0") --snapshot # Create snapshot $(basename "$0") --restore # Restore latest snapshot EOF } # Main main() { local action="all" local feature="" local snapshot_file="" while [[ $# -gt 0 ]]; do case "$1" in --feature) action="feature" feature="${2:-}" shift 2 ;; --list) action="list" shift ;; --snapshot) action="snapshot" shift ;; --restore) action="restore" snapshot_file="${2:-}" shift [[ -n "$snapshot_file" ]] && shift ;; -h|--help) show_usage exit 0 ;; *) log_error "Unknown option: $1" show_usage exit 1 ;; esac done case "$action" in all) check_database seed_all ;; feature) if [[ -z "$feature" ]]; then log_error "--feature requires a feature name" exit 1 fi check_database seed_feature "$feature" ;; list) list_seeds ;; snapshot) check_database create_snapshot ;; restore) check_database restore_snapshot "$snapshot_file" ;; esac } main "$@"