Spaces:
Sleeping
Sleeping
| import psycopg2 | |
| import os | |
| from psycopg2 import sql | |
| from dotenv import load_dotenv | |
| load_dotenv() | |
| # Database Configuration from environment variables | |
| DB_NAME = os.getenv("POSTGRES_DB", "linguaai") | |
| DB_USER = os.getenv("POSTGRES_USER", "linguaai_user") | |
| DB_PASSWORD = os.getenv("POSTGRES_PASSWORD", "LinguaAI1008") | |
| DB_HOST = os.getenv("DB_HOST", "localhost") | |
| DB_PORT = os.getenv("DB_PORT", "5432") | |
| # SQL Schema Definition | |
| SCHEMA_SQL = """ | |
| -- Drop existing objects if they exist | |
| -- Note: Some drops below might be for tables not defined in this specific script. | |
| DROP TABLE IF EXISTS user_activity_progress CASCADE; | |
| DROP TABLE IF EXISTS activities CASCADE; | |
| DROP TABLE IF EXISTS weekly_modules CASCADE; | |
| DROP TABLE IF EXISTS curriculums CASCADE; | |
| DROP TABLE IF EXISTS generated_flashcards CASCADE; | |
| DROP TABLE IF EXISTS flashcard_sets CASCADE; -- Corrected name | |
| DROP TABLE IF EXISTS generated_exercises CASCADE; | |
| DROP TABLE IF EXISTS exercise_sets CASCADE; -- Corrected name | |
| DROP TABLE IF EXISTS simulations CASCADE; -- Corrected name | |
| DROP TABLE IF EXISTS users CASCADE; | |
| DROP TYPE IF EXISTS activity_status CASCADE; | |
| -- Table `users` | |
| CREATE TABLE users ( | |
| user_id SERIAL PRIMARY KEY, | |
| username VARCHAR(50) UNIQUE NOT NULL, | |
| email VARCHAR(100) UNIQUE NOT NULL, | |
| password_hash VARCHAR(255) NOT NULL, | |
| created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, | |
| updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP | |
| ); | |
| -- Trigger function (remains the same) | |
| CREATE OR REPLACE FUNCTION update_updated_at_column() | |
| RETURNS TRIGGER AS $$ | |
| BEGIN | |
| NEW.updated_at = now(); | |
| RETURN NEW; | |
| END; | |
| $$ language 'plpgsql'; | |
| -- Trigger for users (remains the same) | |
| CREATE TRIGGER users_update_updated_at | |
| BEFORE UPDATE ON users | |
| FOR EACH ROW | |
| EXECUTE FUNCTION update_updated_at_column(); | |
| -- ============================================ | |
| -- Tables for Generated Content (Flashcards) | |
| -- ============================================ | |
| -- Table `flashcard_sets` (Represents one request/query) | |
| CREATE TABLE flashcard_sets ( | |
| id SERIAL PRIMARY KEY, | |
| user_id INTEGER NOT NULL REFERENCES users(user_id), -- Added FK reference for completeness | |
| query TEXT NOT NULL, | |
| flashcards JSONB NOT NULL, -- Stores an array of 5 flashcards | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -- Added updated_at for consistency | |
| ); | |
| CREATE INDEX idx_flashcard_set_user ON flashcard_sets(user_id); | |
| -- Corrected Trigger definition for flashcard_sets | |
| CREATE TRIGGER flashcard_sets_update_updated_at -- Renamed trigger | |
| BEFORE UPDATE ON flashcard_sets -- Corrected table name | |
| FOR EACH ROW | |
| EXECUTE FUNCTION update_updated_at_column(); -- Assumes you want updated_at here too | |
| -- Table `generated_flashcards` (Individual flashcards within a set) | |
| CREATE TABLE generated_flashcards ( | |
| flashcard_id SERIAL PRIMARY KEY, | |
| set_id INT NOT NULL REFERENCES flashcard_sets(id) ON DELETE CASCADE, -- Corrected FK reference (table and column) | |
| word TEXT NOT NULL, | |
| definition TEXT NOT NULL, | |
| example TEXT, -- Example might be optional | |
| created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, | |
| updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP | |
| ); | |
| CREATE INDEX idx_flashcard_set ON generated_flashcards(set_id); | |
| -- Trigger for generated_flashcards (remains the same) | |
| CREATE TRIGGER generated_flashcards_update_updated_at | |
| BEFORE UPDATE ON generated_flashcards | |
| FOR EACH ROW | |
| EXECUTE FUNCTION update_updated_at_column(); | |
| -- ============================================ | |
| -- Tables for Generated Content (Exercises) | |
| -- ============================================ | |
| -- Table `exercise_sets` (Represents one request/query) -- Corrected comment | |
| CREATE TABLE exercise_sets ( | |
| id SERIAL PRIMARY KEY, | |
| user_id INTEGER NOT NULL REFERENCES users(user_id), -- Added FK reference for completeness | |
| query TEXT NOT NULL, | |
| exercises JSONB NOT NULL, -- Array of 5 exercises | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -- Added updated_at for consistency | |
| ); | |
| CREATE INDEX idx_exercise_set_user ON exercise_sets(user_id); -- Corrected table name (was already correct but double-checked) | |
| -- Corrected Trigger definition for exercise_sets | |
| CREATE TRIGGER exercise_sets_update_updated_at -- Renamed trigger | |
| BEFORE UPDATE ON exercise_sets -- Corrected table name | |
| FOR EACH ROW | |
| EXECUTE FUNCTION update_updated_at_column(); -- Assumes you want updated_at here too | |
| -- Table `generated_exercises` (Individual exercises within a set) | |
| CREATE TABLE generated_exercises ( | |
| exercise_id SERIAL PRIMARY KEY, | |
| set_id INT NOT NULL REFERENCES exercise_sets(id) ON DELETE CASCADE, -- Corrected FK reference (table and column) | |
| sentence TEXT NOT NULL, | |
| answer TEXT NOT NULL, | |
| choices JSONB NOT NULL, -- Storing the array of choices | |
| created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP, | |
| updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP | |
| ); | |
| CREATE INDEX idx_exercise_set ON generated_exercises(set_id); | |
| -- Trigger for generated_exercises (remains the same) | |
| CREATE TRIGGER generated_exercises_update_updated_at | |
| BEFORE UPDATE ON generated_exercises | |
| FOR EACH ROW | |
| EXECUTE FUNCTION update_updated_at_column(); | |
| -- ============================================ | |
| -- Table for Generated Content (Simulations) | |
| -- ============================================ | |
| -- Table `simulations` (Represents one simulation request/result) -- Corrected comment | |
| CREATE TABLE simulations ( | |
| id SERIAL PRIMARY KEY, | |
| user_id INTEGER NOT NULL REFERENCES users(user_id), -- Added FK reference for completeness | |
| query TEXT NOT NULL, | |
| scenario TEXT NOT NULL, | |
| dialog JSONB NOT NULL, -- Array of turns with 'role', 'chinese', 'pinyin', 'english' | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -- Added updated_at for consistency | |
| ); | |
| CREATE INDEX idx_simulation_user ON simulations(user_id); -- Corrected table name | |
| -- Corrected Trigger definition for simulations | |
| CREATE TRIGGER simulations_update_updated_at -- Renamed trigger | |
| BEFORE UPDATE ON simulations -- Corrected table name | |
| FOR EACH ROW | |
| EXECUTE FUNCTION update_updated_at_column(); -- Assumes you want updated_at here too | |
| """ | |
| def get_db_connection(): | |
| """Get a synchronous database connection.""" | |
| try: | |
| conn = psycopg2.connect( | |
| dbname=DB_NAME, | |
| user=DB_USER, | |
| password=DB_PASSWORD, | |
| host=DB_HOST, | |
| port=DB_PORT | |
| ) | |
| return conn | |
| except psycopg2.Error as e: | |
| print(f"Database connection error: {e}") | |
| raise | |
| def reset_sequences(): | |
| """Generate SQL to reset all sequences (auto-incrementing IDs) to 1.""" | |
| sequences_sql = """ | |
| SELECT 'ALTER SEQUENCE ' || sequence_name || ' RESTART WITH 1;' | |
| FROM information_schema.sequences | |
| WHERE sequence_schema = 'public'; | |
| """ | |
| return sequences_sql | |
| def reset_database(confirm=True): | |
| """Reset the database by dropping all tables and recreating them.""" | |
| if confirm: | |
| user_confirm = input("WARNING: This will DELETE ALL DATA. Type 'yes' to proceed: ") | |
| if user_confirm.lower() != 'yes': | |
| print("Database reset cancelled.") | |
| return | |
| conn = None | |
| try: | |
| conn = get_db_connection() | |
| conn.autocommit = False | |
| print("Database connection established.") | |
| with conn.cursor() as cur: | |
| print("Dropping and recreating schema...") | |
| # Execute the main schema SQL (includes drops) | |
| cur.execute(SCHEMA_SQL) | |
| print("Schema recreated successfully.") | |
| # Generate and execute sequence reset SQL | |
| print("Resetting sequences...") | |
| reset_sql_query = reset_sequences() | |
| cur.execute(reset_sql_query) | |
| reset_commands = cur.fetchall() | |
| for command in reset_commands: | |
| cur.execute(command[0]) | |
| print("Sequences reset successfully.") | |
| conn.commit() | |
| print("Database reset complete.") | |
| except psycopg2.Error as e: | |
| print(f"Database error during reset: {e}") | |
| if conn: | |
| conn.rollback() | |
| print("Transaction rolled back.") | |
| except Exception as e: | |
| print(f"An unexpected error occurred during reset: {e}") | |
| if conn: | |
| conn.rollback() | |
| finally: | |
| if conn: | |
| conn.close() | |
| print("Database connection closed.") | |
| def setup_database(confirm=True): | |
| """Set up the database schema if tables do not exist.""" | |
| if confirm: | |
| user_confirm = input("Do you want to set up the database? Type 'yes' to proceed: ") | |
| if user_confirm.lower() != 'yes': | |
| print("Database setup cancelled.") | |
| return | |
| conn = None | |
| try: | |
| conn = get_db_connection() | |
| conn.autocommit = False | |
| print("Database connection established.") | |
| with conn.cursor() as cur: | |
| print("Checking if tables exist...") | |
| cur.execute(""" | |
| SELECT EXISTS ( | |
| SELECT FROM information_schema.tables | |
| WHERE table_schema = 'public' | |
| AND table_name = 'users' | |
| ); | |
| """) | |
| tables_exist = cur.fetchone()[0] | |
| if tables_exist: | |
| print("Tables already exist. Use reset_database() to reset the database or run setup with confirm=False.") | |
| conn.rollback() # Rollback as no changes should be made | |
| return | |
| print("Creating schema...") | |
| cur.execute(SCHEMA_SQL) | |
| print("Schema created successfully.") | |
| conn.commit() | |
| print("Database setup complete.") | |
| except psycopg2.Error as e: | |
| print(f"Database error during setup: {e}") | |
| if conn: | |
| conn.rollback() | |
| print("Transaction rolled back.") | |
| except Exception as e: | |
| print(f"An unexpected error occurred during setup: {e}") | |
| if conn: | |
| conn.rollback() | |
| finally: | |
| if conn: | |
| conn.close() | |
| print("Database connection closed.") | |
| if __name__ == "__main__": | |
| action = input("Enter 'setup' to setup database or 'reset' to reset database: ").lower() | |
| if action == 'reset': | |
| reset_database() | |
| elif action == 'setup': | |
| setup_database() | |
| else: | |
| print("Invalid action. Use 'setup' or 'reset'.") |