Files
xamxam/scripts/migrate.sh

145 lines
5.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# Apply pending SQL migrations to one or both SQLite databases.
# Usage:
# scripts/migrate.sh # migrates both test.db and posterg.db
# scripts/migrate.sh test # migrates storage/test.db only
# scripts/migrate.sh prod # migrates storage/posterg.db only
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
APP_DIR="$REPO_ROOT/app"
MIGRATIONS_DIR="$APP_DIR/storage/migrations"
TEST_DB="$APP_DIR/storage/test.db"
PROD_DB="$APP_DIR/storage/posterg.db"
# ---------------------------------------------------------------------------
# Check whether a migration's effects are already present in the DB so that
# legacy databases (created before the migrations table existed) can be
# bootstrapped correctly without re-running non-idempotent SQL.
# ---------------------------------------------------------------------------
already_applied_structurally() {
local db="$1"
local name="$2"
case "$name" in
001_rename_keywords_to_tags.sql)
# Effect: table 'tags' and 'thesis_tags' exist
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name IN ('tags','thesis_tags');")
[ "$count" -eq 2 ]
;;
002_add_visibility.sql)
# Effect: access_types seed rows (always safe to re-run with OR IGNORE, but mark done if rows exist)
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM access_types WHERE id IN (1,2,3);" 2>/dev/null || echo 0)
[ "$count" -eq 3 ]
;;
003_seed_license_types.sql)
# Effect: at least one row in license_types
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM license_types;" 2>/dev/null || echo 0)
[ "$count" -gt 0 ]
;;
004_jury_roles.sql)
# Effect: 'role' column on thesis_supervisors
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM pragma_table_info('thesis_supervisors') WHERE name='role';")
[ "$count" -eq 1 ]
;;
005_add_banner.sql)
# Effect: 'banner_path' column on theses
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM pragma_table_info('theses') WHERE name='banner_path';")
[ "$count" -eq 1 ]
;;
006_add_composite_index.sql)
# Effect: index idx_theses_pub_year exists (CREATE INDEX IF NOT EXISTS — safe to re-run anyway)
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='index' AND name='idx_theses_pub_year';")
[ "$count" -eq 1 ]
;;
008_formulaire_settings.sql)
# Effect: site_settings table exists + show_contact column on authors
tbl=$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='site_settings';")
col=$(sqlite3 "$db" "SELECT COUNT(*) FROM pragma_table_info('authors') WHERE name='show_contact';")
[ "$tbl" -eq 1 ] && [ "$col" -eq 1 ]
;;
012_smtp_settings.sql)
tbl=$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='smtp_settings';")
[ "$tbl" -eq 1 ]
;;
013_admin_password.sql)
# Effect: admin_password_hash key exists in site_settings (INSERT OR IGNORE — safe to re-run)
count=$(sqlite3 "$db" "SELECT COUNT(*) FROM site_settings WHERE key='admin_password_hash';")
[ "$count" -eq 1 ]
;;
*)
# Unknown migration — assume not applied
return 1
;;
esac
}
migrate_db() {
local db="$1"
local label="$2"
# Auto-create from schema only when the file is absent or truly empty (no tables)
local table_count
table_count=$(sqlite3 "$db" "SELECT COUNT(*) FROM sqlite_master WHERE type='table';" 2>/dev/null || echo 0)
if [ "$table_count" -eq 0 ]; then
echo " [$label] initialising from schema…"
sqlite3 "$db" < "$APP_DIR/storage/schema.sql"
echo " [$label] schema applied."
fi
# Ensure tracking table exists
sqlite3 "$db" "CREATE TABLE IF NOT EXISTS schema_migrations (
name TEXT PRIMARY KEY,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
);"
local applied=0
local seeded=0
local skipped=0
for migration in "$MIGRATIONS_DIR"/*.sql; do
name="$(basename "$migration")"
already=$(sqlite3 "$db" "SELECT COUNT(*) FROM schema_migrations WHERE name='$name';")
if [ "$already" -eq 1 ]; then
skipped=$((skipped + 1))
continue
fi
# Not in tracking table — check if it was already applied before we started tracking
if already_applied_structurally "$db" "$name"; then
sqlite3 "$db" "INSERT OR IGNORE INTO schema_migrations (name) VALUES ('$name');"
seeded=$((seeded + 1))
echo " [$label] seeded $name (already applied)"
continue
fi
echo " [$label] applying $name"
if sqlite3 "$db" < "$migration"; then
sqlite3 "$db" "INSERT OR IGNORE INTO schema_migrations (name) VALUES ('$name');"
applied=$((applied + 1))
else
echo " [$label] ERROR applying $name — aborting" >&2
exit 1
fi
done
echo " [$label] done — $applied applied, $seeded seeded, $skipped already up-to-date"
}
TARGET="${1:-both}"
case "$TARGET" in
test) migrate_db "$TEST_DB" "test.db" ;;
prod) migrate_db "$PROD_DB" "posterg.db" ;;
both)
migrate_db "$TEST_DB" "test.db"
migrate_db "$PROD_DB" "posterg.db"
;;
*)
echo "Usage: $0 [test|prod|both]" >&2
exit 1
;;
esac