File size: 10,595 Bytes
c151c44
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
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'.")