import sqlite3 import contextlib from typing import Generator from app import config def get_db_connection() -> sqlite3.Connection: conn = sqlite3.connect( config.DB_PATH, timeout=15.0 ) conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode=WAL;") conn.execute("PRAGMA synchronous=NORMAL;") conn.execute("PRAGMA foreign_keys=ON;") return conn @contextlib.contextmanager def get_db() -> Generator[sqlite3.Connection, None, None]: conn = get_db_connection() try: yield conn conn.commit() except Exception: conn.rollback() raise finally: conn.close() def init_db(): config.DB_PATH.parent.mkdir(parents=True, exist_ok=True) with get_db() as conn: conn.executescript(""" CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL COLLATE NOCASE, password_hash TEXT NOT NULL, salt TEXT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS sessions ( token TEXT PRIMARY KEY, user_id INTEGER NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP NOT NULL, user_agent TEXT, ip TEXT, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS vocabulary_cards ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, term TEXT NOT NULL, translation TEXT NOT NULL, context_sentence TEXT, phonetic TEXT, audio_url TEXT, part_of_speech TEXT, definition_en TEXT, tags TEXT, notes TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_vocab_user ON vocabulary_cards(user_id); CREATE INDEX IF NOT EXISTS idx_vocab_term ON vocabulary_cards(term); CREATE INDEX IF NOT EXISTS idx_vocab_created ON vocabulary_cards(created_at DESC); CREATE TABLE IF NOT EXISTS dictionary_cache ( query TEXT PRIMARY KEY COLLATE NOCASE, response_json TEXT NOT NULL, cached_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); """)