| |
| |
|
|
| import sqlite3 |
| import os |
| from sqlalchemy import create_engine, text |
| from sqlalchemy.orm import sessionmaker |
| from dotenv import load_dotenv |
|
|
| |
| load_dotenv() |
|
|
| |
| 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) |
|
|
| |
| 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...") |
|
|
| |
| 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() |
|
|
| |
| pg_engine = create_engine(POSTGRES_URL) |
| PgSession = sessionmaker(bind=pg_engine) |
| pg_session = PgSession() |
|
|
| try: |
| |
| print("π€ Migrating Users...") |
| sqlite_cursor.execute("SELECT * FROM users") |
| users = sqlite_cursor.fetchall() |
| for row in users: |
| |
| 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) |
| ) |
|
|
| |
| 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) |
| ) |
|
|
| |
| 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) |
| ) |
|
|
| |
| 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.") |
|
|
| |
| 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() |
|
|