Edu-AI / migrate_data.py
Orbit Automations
Auto-sync from Julius-606/Orbit monorepo
be6ee20
# IDENTITY: backend/migrate_data.py
# βš™οΈ GEAR: Data Migration (SQLite -> PostgreSQL)
import sqlite3
import os
from sqlalchemy import create_engine, text
from sqlalchemy.orm import sessionmaker
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
# Configuration
SQLITE_DB_PATH = "edu_ai_vault.db"
POSTGRES_URL = os.getenv("DATABASE_URL")
if not POSTGRES_URL:
print("❌ Error: DATABASE_URL not found in .env file.")
exit(1)
# Fix for Neon/Heroku: SQLAlchemy requires 'postgresql://' instead of 'postgres://'
if POSTGRES_URL.startswith("postgres://"):
POSTGRES_URL = POSTGRES_URL.replace("postgres://", "postgresql://", 1)
def migrate():
print(f"πŸš€ Starting migration from {SQLITE_DB_PATH} to Neon PostgreSQL...")
# Connect to SQLite
if not os.path.exists(SQLITE_DB_PATH):
print(f"❌ Error: SQLite file {SQLITE_DB_PATH} not found.")
return
sqlite_conn = sqlite3.connect(SQLITE_DB_PATH)
sqlite_conn.row_factory = sqlite3.Row
sqlite_cursor = sqlite_conn.cursor()
# Connect to PostgreSQL
pg_engine = create_engine(POSTGRES_URL)
PgSession = sessionmaker(bind=pg_engine)
pg_session = PgSession()
try:
# 1. Migrate Users
print("πŸ‘€ Migrating Users...")
sqlite_cursor.execute("SELECT * FROM users")
users = sqlite_cursor.fetchall()
for row in users:
# We use raw SQL to ensure IDs are preserved
pg_session.execute(
text("INSERT INTO users (id, username, role, sensory_mode, difficulty, ai_persona, semester_status, interests) "
"VALUES (:id, :username, :role, :sensory_mode, :difficulty, :ai_persona, :semester_status, :interests) "
"ON CONFLICT (id) DO NOTHING"),
dict(row)
)
# 2. Migrate Units
print("πŸ“š Migrating Units...")
sqlite_cursor.execute("SELECT * FROM units")
units = sqlite_cursor.fetchall()
for row in units:
pg_session.execute(
text("INSERT INTO units (id, name, is_active, category, owner_id) "
"VALUES (:id, :name, :is_active, :category, :owner_id) "
"ON CONFLICT (id) DO NOTHING"),
dict(row)
)
# 3. Migrate Quiz History
print("πŸ“Š Migrating Quiz History...")
sqlite_cursor.execute("SELECT * FROM quiz_history")
quizzes = sqlite_cursor.fetchall()
for row in quizzes:
pg_session.execute(
text("INSERT INTO quiz_history (id, unit_name, score, total, pnl, timestamp, owner_id) "
"VALUES (:id, :unit_name, :score, :total, :pnl, :timestamp, :owner_id) "
"ON CONFLICT (id) DO NOTHING"),
dict(row)
)
# 4. Migrate Chat Messages
print("πŸ’¬ Migrating Chat Messages...")
sqlite_cursor.execute("SELECT * FROM chat_messages")
chats = sqlite_cursor.fetchall()
for row in chats:
pg_session.execute(
text("INSERT INTO chat_messages (id, role, content, timestamp, owner_id) "
"VALUES (:id, :role, :content, :timestamp, :owner_id) "
"ON CONFLICT (id) DO NOTHING"),
dict(row)
)
pg_session.commit()
print("βœ… Migration successful! All records synced to Neon.")
# Update sequences in Postgres (Important for ID generation)
print("πŸ”„ Updating ID sequences...")
tables = ["users", "units", "quiz_history", "chat_messages"]
for table in tables:
pg_session.execute(text(f"SELECT setval('{table}_id_seq', (SELECT MAX(id) FROM {table}))"))
pg_session.commit()
except Exception as e:
print(f"❌ Migration failed: {e}")
pg_session.rollback()
finally:
sqlite_conn.close()
pg_session.close()
if __name__ == "__main__":
migrate()