mirror of
https://codeberg.org/PostERG/xamxam.git
synced 2026-05-06 19:19:19 +02:00
145 lines
5.6 KiB
Bash
Executable File
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
|