samu's picture
v3 backend
c151c44
raw
history blame
10.6 kB
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'.")