Files
jetour-bi/migrate.py
Jetour BI 9ff9ec27e9 Add database migrations + README
- migrations/001_initial.sql: full schema with 12 tables, triggers, indexes
- migrate.py: idempotent migration runner with version tracking
- README.md: deploy instructions, architecture overview
- .gitignore: exclude migration tracking data
2026-06-18 20:43:33 +00:00

86 lines
2.4 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Jetour BI — Database Migration Runner
Usage:
python migrate.py [DB_URL]
Defaults to DATABASE_URL env var, or:
postgresql://postgres:password@host:5432/jetour-bi
Runs all .sql files in migrations/ in order, tracking applied
migrations in a _migrations table. Safe to run repeatedly —
only unapplied migrations are executed.
"""
import os, sys, re, psycopg
from pathlib import Path
MIGRATIONS_DIR = Path(__file__).parent / "migrations"
DB_URL = sys.argv[1] if len(sys.argv) > 1 else os.environ.get(
"DATABASE_URL", "postgresql://postgres:postgres@localhost:5432/jetour-bi"
)
def main():
conn = psycopg.connect(DB_URL)
conn.autocommit = True
cur = conn.cursor()
# Ensure tracking table exists
cur.execute("""
CREATE TABLE IF NOT EXISTS _migrations (
id SERIAL PRIMARY KEY,
filename VARCHAR(200) NOT NULL UNIQUE,
applied_at TIMESTAMPTZ DEFAULT NOW()
)
""")
# Get list of applied migrations
cur.execute("SELECT filename FROM _migrations ORDER BY id")
applied = {r[0] for r in cur.fetchall()}
# Find and run pending .sql files
sql_files = sorted(MIGRATIONS_DIR.glob("*.sql"))
if not sql_files:
print("No migration files found in", MIGRATIONS_DIR)
return
pending = 0
for f in sql_files:
if f.name in applied:
continue
print(f" Applying: {f.name} ...", end=" ", flush=True)
try:
sql = f.read_text()
cur.execute(sql)
cur.execute("INSERT INTO _migrations (filename) VALUES (%s)", (f.name,))
print("")
pending += 1
except Exception as e:
print(f"{e}")
conn.rollback()
sys.exit(1)
if pending:
print(f"\n{pending} migration(s) applied")
else:
print("\n✓ All migrations already applied")
# Create a default admin user if none exists
cur.execute("SELECT count(*) FROM dashboard_users")
if cur.fetchone()[0] == 0:
import bcrypt
pw = bcrypt.hashpw(b"admin", bcrypt.gensalt()).decode()
cur.execute(
"INSERT INTO dashboard_users (username, email, password_hash, role) VALUES (%s, %s, %s, %s)",
("admin", "admin@jetour.mu", pw, "admin"),
)
print("✓ Default admin user created (username: admin, password: admin)")
conn.close()
if __name__ == "__main__":
main()