""" Database layer for TalkToDoc. Local development and testing use SQLite (no setup needed). When deployed, DATABASE_URL is set by Render and the app uses Postgres instead, since Render's free tier wipes local files like a SQLite database on every restart. Queries are written once using SQLite-style '?' placeholders and adapted automatically for Postgres, so there's a single query per function rather than two versions of everything. """ import os from pathlib import Path import sqlite3 DB_PATH = Path(__file__).parent / "talktodoc.db" SCHEMA_PATH = Path(__file__).parent / "schema.sql" SCHEMA_PATH_POSTGRES = Path(__file__).parent / "schema_postgres.sql" DATABASE_URL = os.environ.get("DATABASE_URL") if DATABASE_URL: import psycopg2 import psycopg2.extras def get_connection(): if DATABASE_URL: return psycopg2.connect(DATABASE_URL, cursor_factory=psycopg2.extras.RealDictCursor) connection = sqlite3.connect(DB_PATH) connection.row_factory = sqlite3.Row connection.execute("PRAGMA foreign_keys = ON") return connection def _run(connection, query, params=()): if DATABASE_URL: query = query.replace("?", "%s") cursor = connection.cursor() cursor.execute(query, params) return cursor def _insert_and_get_id(connection, query, params): if DATABASE_URL: cursor = _run(connection, query + " RETURNING id", params) return cursor.fetchone()["id"] cursor = _run(connection, query, params) return cursor.lastrowid def init_db(): schema_path = SCHEMA_PATH_POSTGRES if DATABASE_URL else SCHEMA_PATH schema_sql = schema_path.read_text() with get_connection() as connection: if DATABASE_URL: connection.cursor().execute(schema_sql) else: connection.executescript(schema_sql) def add_user(name, preferred_language, role): with get_connection() as connection: return _insert_and_get_id( connection, "INSERT INTO app_user (name, preferred_language, role) VALUES (?, ?, ?)", (name, preferred_language, role), ) def add_session(user_id, start_time): with get_connection() as connection: return _insert_and_get_id( connection, "INSERT INTO session (user_id, start_time) VALUES (?, ?)", (user_id, start_time), ) def end_session(session_id, end_time): with get_connection() as connection: _run(connection, "UPDATE session SET end_time = ? WHERE id = ?", (end_time, session_id)) def add_interaction(user_id, input_text, detected_language, translated_text, nlu_summary, timestamp): with get_connection() as connection: return _insert_and_get_id( connection, """INSERT INTO interaction (user_id, input_text, detected_language, translated_text, nlu_summary, timestamp) VALUES (?, ?, ?, ?, ?, ?)""", (user_id, input_text, detected_language, translated_text, nlu_summary, timestamp), ) def get_interactions_for_user(user_id): with get_connection() as connection: cursor = _run( connection, "SELECT * FROM interaction WHERE user_id = ? ORDER BY timestamp", (user_id,), ) return [dict(row) for row in cursor.fetchall()] def get_pending_interactions(): with get_connection() as connection: cursor = _run( connection, "SELECT * FROM interaction WHERE provider_response IS NULL ORDER BY timestamp", ) return [dict(row) for row in cursor.fetchall()] def get_completed_interactions(): with get_connection() as connection: cursor = _run( connection, "SELECT * FROM interaction WHERE provider_response IS NOT NULL ORDER BY timestamp DESC", ) return [dict(row) for row in cursor.fetchall()] def get_interaction(interaction_id): with get_connection() as connection: cursor = _run(connection, "SELECT * FROM interaction WHERE id = ?", (interaction_id,)) row = cursor.fetchone() return dict(row) if row else None def update_interaction_response(interaction_id, provider_response, translated_response): with get_connection() as connection: _run( connection, "UPDATE interaction SET provider_response = ?, translated_response = ? WHERE id = ?", (provider_response, translated_response, interaction_id), ) if __name__ == "__main__": init_db() if DATABASE_URL: print("Database initialized (Postgres)") else: print("Database created at:", DB_PATH)