feat(db): auto-migrate both DBs on serve via scripts/migrate.sh

This commit is contained in:
Pontoporeia
2026-03-31 16:19:56 +02:00
parent af06e09caa
commit 72d48c49c3
5 changed files with 139 additions and 1 deletions

View File

@@ -2,5 +2,6 @@
## Fixes
- [x] Fix CSV import UNIQUE constraint crash: skip rows whose identifier already exists in DB
- [x] Auto-migrate both test.db and posterg.db on `just serve` via scripts/migrate.sh
- [x] Fix wrong `require_once` depth in `public/admin/actions/page.php` (`../../``../../../`)
- [x] Fix same path depth bug in `formulaire.php` and `publish.php`

View File

@@ -12,7 +12,7 @@ setup:
@bash scripts/setup-dev.sh
[group('dev')]
serve:
serve: migrate
@php -S 127.0.0.1:8000 -t public/
[group('dev')]
@@ -110,6 +110,19 @@ syntax:
# Database
# ============================================================================
[group('database')]
migrate:
@echo "Running migrations…"
@bash scripts/migrate.sh both
[group('database')]
migrate-test:
@bash scripts/migrate.sh test
[group('database')]
migrate-prod:
@bash scripts/migrate.sh prod
[group('database')]
init-db:
@sqlite3 storage/test.db < storage/schema.sql

124
scripts/migrate.sh Executable file
View File

@@ -0,0 +1,124 @@
#!/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)"
MIGRATIONS_DIR="$REPO_ROOT/storage/migrations"
TEST_DB="$REPO_ROOT/storage/test.db"
PROD_DB="$REPO_ROOT/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 ]
;;
*)
# Unknown migration — assume not applied
return 1
;;
esac
}
migrate_db() {
local db="$1"
local label="$2"
if [ ! -f "$db" ]; then
echo " [$label] database not found, skipping: $db"
return
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

Binary file not shown.

Binary file not shown.