from database.db import get_connection def _table_sql(cur, table_name: str) -> str: cur.execute( """ SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ? """, (table_name,), ) row = cur.fetchone() return row[0] if row and row[0] else "" def _rebuild_table(cur, table_name: str, create_sql: str, copy_sql: str) -> None: temp_name = f"{table_name}_new" cur.execute(f"DROP TABLE IF EXISTS {temp_name}") cur.execute(create_sql.format(table=temp_name)) cur.execute(copy_sql.format(table=temp_name)) cur.execute(f"DROP TABLE {table_name}") cur.execute(f"ALTER TABLE {temp_name} RENAME TO {table_name}") def init_db(): with get_connection() as conn: cur = conn.cursor() # ─── PINPAD ERRORS ─────────────────────── cur.execute(""" CREATE TABLE IF NOT EXISTS pinpad_errors ( code INTEGER PRIMARY KEY, reason TEXT NOT NULL, action TEXT NOT NULL ); """) # ─── TERMINAL INSTRUCTIONS ─────────────── cur.execute(""" CREATE TABLE IF NOT EXISTS terminal_instructions ( id INTEGER PRIMARY KEY, title TEXT NOT NULL ); """) cur.execute(""" CREATE TABLE IF NOT EXISTS terminal_instruction_keys ( id INTEGER PRIMARY KEY AUTOINCREMENT, instruction_id INTEGER NOT NULL, key TEXT NOT NULL, key_type TEXT CHECK(key_type IN ('code','text')) NOT NULL, FOREIGN KEY (instruction_id) REFERENCES terminal_instructions(id) ON DELETE CASCADE ); """) cur.execute(""" CREATE TABLE IF NOT EXISTS terminal_instruction_steps ( id INTEGER PRIMARY KEY AUTOINCREMENT, instruction_id INTEGER NOT NULL, step_order INTEGER NOT NULL, type TEXT CHECK(type IN ('text','image','pause','goto')) NOT NULL, content TEXT NOT NULL, FOREIGN KEY (instruction_id) REFERENCES terminal_instructions(id) ON DELETE CASCADE ); """) # Tech problems cur.execute(""" CREATE TABLE IF NOT EXISTS tech_problems ( id TEXT PRIMARY KEY, task_type TEXT CHECK(task_type IN ('ADMIN','TECH')) NOT NULL, keywords TEXT NOT NULL ); """) cur.execute(""" CREATE TABLE IF NOT EXISTS tech_problem_solutions ( problem_id TEXT PRIMARY KEY, problem_name TEXT NOT NULL, task_type TEXT CHECK(task_type IN ('ADMIN','TECH')) NOT NULL, can_fix_self TEXT CHECK(can_fix_self IN ('YES','NO')) NOT NULL, need_result_feedback TEXT CHECK(need_result_feedback IN ('YES','NO')) NOT NULL, solution_steps TEXT NOT NULL, tools_needed TEXT NOT NULL, when_stop_and_report TEXT NOT NULL, FOREIGN KEY (problem_id) REFERENCES tech_problems(id) ON DELETE CASCADE ); """) cur.execute(""" CREATE TABLE IF NOT EXISTS tech_problem_progress ( user_id INTEGER PRIMARY KEY, problem_id TEXT NOT NULL, task_type TEXT CHECK(task_type IN ('ADMIN','TECH')) NOT NULL, need_result_feedback TEXT CHECK(need_result_feedback IN ('YES','NO')) NOT NULL, FOREIGN KEY (problem_id) REFERENCES tech_problems(id) ON DELETE CASCADE ); """) # Stores per-user pause progress for terminal instructions cur.execute(""" CREATE TABLE IF NOT EXISTS instruction_progress ( user_id INTEGER PRIMARY KEY, instruction_id INTEGER NOT NULL, next_step INTEGER NOT NULL, pause_at_end INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (instruction_id) REFERENCES terminal_instructions(id) ON DELETE CASCADE ); """) # Chat logs cur.execute(""" CREATE TABLE IF NOT EXISTS message_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, created_at TEXT NOT NULL, message TEXT NOT NULL ); """) cur.execute(""" CREATE TABLE IF NOT EXISTS event_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, created_at TEXT NOT NULL, event_type TEXT NOT NULL, event_value TEXT NOT NULL ); """) # Migrate terminal_instruction_steps if it doesn't allow 'pause' or 'goto' terminal_steps_sql = _table_sql(cur, "terminal_instruction_steps") if terminal_steps_sql and ("pause" not in terminal_steps_sql or "goto" not in terminal_steps_sql): cur.execute(""" CREATE TABLE terminal_instruction_steps_new ( id INTEGER PRIMARY KEY AUTOINCREMENT, instruction_id INTEGER NOT NULL, step_order INTEGER NOT NULL, type TEXT CHECK(type IN ('text','image','pause','goto')) NOT NULL, content TEXT NOT NULL, FOREIGN KEY (instruction_id) REFERENCES terminal_instructions(id) ON DELETE CASCADE ); """) cur.execute(""" INSERT INTO terminal_instruction_steps_new (id, instruction_id, step_order, type, content) SELECT id, instruction_id, step_order, type, content FROM terminal_instruction_steps """) cur.execute("DROP TABLE terminal_instruction_steps") cur.execute("ALTER TABLE terminal_instruction_steps_new RENAME TO terminal_instruction_steps") if "FOREIGN KEY" not in _table_sql(cur, "terminal_instruction_keys").upper(): _rebuild_table( cur, "terminal_instruction_keys", """ CREATE TABLE {table} ( id INTEGER PRIMARY KEY AUTOINCREMENT, instruction_id INTEGER NOT NULL, key TEXT NOT NULL, key_type TEXT CHECK(key_type IN ('code','text')) NOT NULL, FOREIGN KEY (instruction_id) REFERENCES terminal_instructions(id) ON DELETE CASCADE ); """, """ INSERT INTO {table} (id, instruction_id, key, key_type) SELECT k.id, k.instruction_id, k.key, k.key_type FROM terminal_instruction_keys k JOIN terminal_instructions i ON i.id = k.instruction_id """, ) if "FOREIGN KEY" not in _table_sql(cur, "terminal_instruction_steps").upper(): _rebuild_table( cur, "terminal_instruction_steps", """ CREATE TABLE {table} ( id INTEGER PRIMARY KEY AUTOINCREMENT, instruction_id INTEGER NOT NULL, step_order INTEGER NOT NULL, type TEXT CHECK(type IN ('text','image','pause','goto')) NOT NULL, content TEXT NOT NULL, FOREIGN KEY (instruction_id) REFERENCES terminal_instructions(id) ON DELETE CASCADE ); """, """ INSERT INTO {table} (id, instruction_id, step_order, type, content) SELECT s.id, s.instruction_id, s.step_order, s.type, s.content FROM terminal_instruction_steps s JOIN terminal_instructions i ON i.id = s.instruction_id """, ) if "FOREIGN KEY" not in _table_sql(cur, "tech_problem_solutions").upper(): _rebuild_table( cur, "tech_problem_solutions", """ CREATE TABLE {table} ( problem_id TEXT PRIMARY KEY, problem_name TEXT NOT NULL, task_type TEXT CHECK(task_type IN ('ADMIN','TECH')) NOT NULL, can_fix_self TEXT CHECK(can_fix_self IN ('YES','NO')) NOT NULL, need_result_feedback TEXT CHECK(need_result_feedback IN ('YES','NO')) NOT NULL, solution_steps TEXT NOT NULL, tools_needed TEXT NOT NULL, when_stop_and_report TEXT NOT NULL, FOREIGN KEY (problem_id) REFERENCES tech_problems(id) ON DELETE CASCADE ); """, """ INSERT INTO {table} (problem_id, problem_name, task_type, can_fix_self, need_result_feedback, solution_steps, tools_needed, when_stop_and_report) SELECT s.problem_id, s.problem_name, s.task_type, s.can_fix_self, s.need_result_feedback, s.solution_steps, s.tools_needed, s.when_stop_and_report FROM tech_problem_solutions s JOIN tech_problems p ON p.id = s.problem_id """, ) if "FOREIGN KEY" not in _table_sql(cur, "tech_problem_progress").upper(): _rebuild_table( cur, "tech_problem_progress", """ CREATE TABLE {table} ( user_id INTEGER PRIMARY KEY, problem_id TEXT NOT NULL, task_type TEXT CHECK(task_type IN ('ADMIN','TECH')) NOT NULL, need_result_feedback TEXT CHECK(need_result_feedback IN ('YES','NO')) NOT NULL, FOREIGN KEY (problem_id) REFERENCES tech_problems(id) ON DELETE CASCADE ); """, """ INSERT INTO {table} (user_id, problem_id, task_type, need_result_feedback) SELECT p.user_id, p.problem_id, p.task_type, p.need_result_feedback FROM tech_problem_progress p JOIN tech_problems t ON t.id = p.problem_id """, ) if "FOREIGN KEY" not in _table_sql(cur, "instruction_progress").upper(): _rebuild_table( cur, "instruction_progress", """ CREATE TABLE {table} ( user_id INTEGER PRIMARY KEY, instruction_id INTEGER NOT NULL, next_step INTEGER NOT NULL, pause_at_end INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (instruction_id) REFERENCES terminal_instructions(id) ON DELETE CASCADE ); """, """ INSERT INTO {table} (user_id, instruction_id, next_step, pause_at_end) SELECT p.user_id, p.instruction_id, p.next_step, p.pause_at_end FROM instruction_progress p JOIN terminal_instructions i ON i.id = p.instruction_id """, ) conn.commit() print("✅ Database initialized")