"""Database connection management and schema initialisation.""" from __future__ import annotations import os import sqlite3 from contextlib import asynccontextmanager from pathlib import Path from typing import AsyncGenerator import aiosqlite DB_PATH: str = os.getenv("DB_PATH", "data/tracker.db") _DDL = """ CREATE TABLE IF NOT EXISTS runs ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), scenario TEXT NOT NULL, identifier TEXT NOT NULL, success INTEGER NOT NULL, failure_reason TEXT, steps_json TEXT NOT NULL DEFAULT '[]', total_ms INTEGER NOT NULL DEFAULT 0 ); CREATE INDEX IF NOT EXISTS idx_runs_scenario ON runs(scenario); CREATE INDEX IF NOT EXISTS idx_runs_created_at ON runs(created_at); CREATE INDEX IF NOT EXISTS idx_runs_identifier ON runs(identifier); CREATE TABLE IF NOT EXISTS flow_configs ( id INTEGER PRIMARY KEY AUTOINCREMENT, created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), name TEXT NOT NULL, is_active INTEGER NOT NULL DEFAULT 0, search_texts_json TEXT NOT NULL DEFAULT '[]', search_box_selector TEXT NOT NULL DEFAULT '', search_box_selector_type TEXT NOT NULL DEFAULT 'css', search_submit_selector TEXT NOT NULL DEFAULT '', search_submit_selector_type TEXT NOT NULL DEFAULT 'css', search_results_selector TEXT NOT NULL DEFAULT '', scroll_container_selector TEXT NOT NULL DEFAULT '', max_scrolls INTEGER NOT NULL DEFAULT 30, scroll_pause_ms INTEGER NOT NULL DEFAULT 1200, no_new_content_timeout_ms INTEGER NOT NULL DEFAULT 3000, item_selector_template TEXT NOT NULL DEFAULT '', item_selector_type TEXT NOT NULL DEFAULT 'css', target_item_ids_json TEXT NOT NULL DEFAULT '[]' ); """ def init_db() -> None: """Create the DB file and apply the schema (sync, called once at startup).""" path = Path(DB_PATH) path.parent.mkdir(parents=True, exist_ok=True) conn = sqlite3.connect(str(path)) conn.executescript(_DDL) conn.commit() conn.close() @asynccontextmanager async def get_db() -> AsyncGenerator[aiosqlite.Connection, None]: """Async context manager — yields an open, Row-factory-enabled connection.""" async with aiosqlite.connect(DB_PATH) as db: db.row_factory = aiosqlite.Row yield db