from __future__ import annotations import json import os import sqlite3 import threading from contextlib import contextmanager from datetime import datetime, timezone from pathlib import Path from typing import Sequence DEFAULT_STORAGE_DIR = Path("/data/hackathon-space-recommender") def _default_storage_dir() -> Path: custom = os.getenv("BSQF_STORAGE_DIR", "").strip() if custom: candidate = Path(custom).expanduser() if candidate.is_absolute() and str(candidate).startswith("/data/"): return candidate raise RuntimeError("BSQF_STORAGE_DIR must point inside the HF bucket mount under /data/") return DEFAULT_STORAGE_DIR STORAGE_DIR = _default_storage_dir() DB_PATH = STORAGE_DIR / "build_small_quest_finder.sqlite3" DB_LOCK = threading.RLock() def _ensure_parent(path: Path) -> None: if not Path("/data").exists(): raise RuntimeError( "HF Storage Bucket mount not found at /data. " "This app is bucket-only and must run inside a Hugging Face Space with the bucket mounted." ) path.parent.mkdir(parents=True, exist_ok=True) @contextmanager def get_connection(): with DB_LOCK: _ensure_parent(DB_PATH) conn = sqlite3.connect( DB_PATH, timeout=30, check_same_thread=False, isolation_level=None, ) conn.row_factory = sqlite3.Row conn.execute("PRAGMA busy_timeout = 30000") conn.execute("PRAGMA foreign_keys = ON") conn.execute("PRAGMA journal_mode = DELETE") conn.execute("PRAGMA synchronous = NORMAL") try: yield conn finally: conn.close() @contextmanager def _transaction(conn: sqlite3.Connection): conn.execute("BEGIN IMMEDIATE") try: yield conn.commit() except Exception: conn.rollback() raise def init_db() -> None: with get_connection() as conn: conn.execute( """ CREATE TABLE IF NOT EXISTS llm_cache ( cache_key TEXT PRIMARY KEY, cache_value TEXT NOT NULL, updated_at TEXT NOT NULL ) """ ) conn.execute( """ CREATE TABLE IF NOT EXISTS quest_feedback ( id INTEGER PRIMARY KEY AUTOINCREMENT, repo_id TEXT NOT NULL, space_title TEXT NOT NULL, track TEXT NOT NULL, question TEXT NOT NULL, answer TEXT NOT NULL, source_query TEXT, owner_session_id TEXT NOT NULL DEFAULT '', updated_at TEXT NOT NULL DEFAULT '', deleted_at TEXT, created_at TEXT NOT NULL ) """ ) existing_columns = {row["name"] for row in conn.execute("PRAGMA table_info(quest_feedback)").fetchall()} if "owner_session_id" not in existing_columns: conn.execute("ALTER TABLE quest_feedback ADD COLUMN owner_session_id TEXT NOT NULL DEFAULT ''") if "updated_at" not in existing_columns: conn.execute("ALTER TABLE quest_feedback ADD COLUMN updated_at TEXT NOT NULL DEFAULT ''") if "deleted_at" not in existing_columns: conn.execute("ALTER TABLE quest_feedback ADD COLUMN deleted_at TEXT") conn.execute( """ CREATE TABLE IF NOT EXISTS app_state ( state_key TEXT PRIMARY KEY, state_value TEXT NOT NULL, updated_at TEXT NOT NULL ) """ ) conn.execute( """ CREATE TABLE IF NOT EXISTS space_metrics ( repo_id TEXT PRIMARY KEY, likes INTEGER NOT NULL DEFAULT 0, synced_at TEXT NOT NULL DEFAULT '', updated_at TEXT NOT NULL ) """ ) conn.commit() def now_iso() -> str: return datetime.now(timezone.utc).isoformat() def cache_get(cache_key: str) -> str | None: with get_connection() as conn: row = conn.execute( "SELECT cache_value FROM llm_cache WHERE cache_key = ?", (cache_key,), ).fetchone() return row["cache_value"] if row else None def cache_set(cache_key: str, cache_value: str) -> None: with get_connection() as conn: with _transaction(conn): conn.execute( """ INSERT INTO llm_cache (cache_key, cache_value, updated_at) VALUES (?, ?, ?) ON CONFLICT(cache_key) DO UPDATE SET cache_value = excluded.cache_value, updated_at = excluded.updated_at """, (cache_key, cache_value, now_iso()), ) def state_get(state_key: str) -> str | None: with get_connection() as conn: row = conn.execute( "SELECT state_value FROM app_state WHERE state_key = ?", (state_key,), ).fetchone() return row["state_value"] if row else None def state_set(state_key: str, state_value: str) -> None: with get_connection() as conn: with _transaction(conn): conn.execute( """ INSERT INTO app_state (state_key, state_value, updated_at) VALUES (?, ?, ?) ON CONFLICT(state_key) DO UPDATE SET state_value = excluded.state_value, updated_at = excluded.updated_at """, (state_key, state_value, now_iso()), ) def save_feedback( repo_id: str, space_title: str, track: str, question: str, answer: str, source_query: str = "", owner_session_id: str = "", ) -> dict: created_at = now_iso() record = { "repo_id": repo_id, "space_title": space_title, "track": track, "question": question, "answer": answer, "source_query": source_query, "owner_session_id": owner_session_id, "created_at": created_at, "updated_at": created_at, "deleted_at": None, } with get_connection() as conn: with _transaction(conn): cursor = conn.execute( """ INSERT INTO quest_feedback (repo_id, space_title, track, question, answer, source_query, owner_session_id, updated_at, deleted_at, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( repo_id, space_title, track, question, answer, source_query, owner_session_id, record["updated_at"], None, record["created_at"], ), ) record["id"] = int(cursor.lastrowid or 0) return record def get_recent_feedback(limit: int = 10, repo_id: str | None = None) -> list[dict]: query = """ SELECT id, repo_id, space_title, track, question, answer, source_query, owner_session_id, updated_at, deleted_at, created_at FROM quest_feedback WHERE COALESCE(deleted_at, '') = '' """ params: list[object] = [] if repo_id: query += " AND repo_id = ?" params.append(repo_id) query += " ORDER BY id DESC LIMIT ?" params.append(limit) try: with get_connection() as conn: db_rows = conn.execute(query, tuple(params)).fetchall() except Exception: return [] return list(reversed([dict(row) for row in db_rows])) def get_space_metrics(repo_ids: Sequence[str] | None = None) -> dict[str, dict]: query = "SELECT repo_id, likes, synced_at, updated_at FROM space_metrics" params: tuple[object, ...] = () if repo_ids is not None: cleaned = [str(repo_id).strip() for repo_id in repo_ids if str(repo_id).strip()] if not cleaned: return {} placeholders = ",".join("?" for _ in cleaned) query += f" WHERE repo_id IN ({placeholders})" params = tuple(cleaned) try: with get_connection() as conn: rows = conn.execute(query, params).fetchall() except Exception: return {} return {str(row["repo_id"]): dict(row) for row in rows} def upsert_space_metrics(rows: Sequence[dict]) -> None: cleaned_rows: list[tuple[str, int, str, str]] = [] for row in rows: repo_id = str(row.get("repo_id", "")).strip() if not repo_id: continue likes = int(row.get("likes", 0) or 0) synced_at = str(row.get("synced_at", "") or now_iso()) updated_at = str(row.get("updated_at", "") or synced_at) cleaned_rows.append((repo_id, likes, synced_at, updated_at)) if not cleaned_rows: return with get_connection() as conn: with _transaction(conn): conn.executemany( """ INSERT INTO space_metrics (repo_id, likes, synced_at, updated_at) VALUES (?, ?, ?, ?) ON CONFLICT(repo_id) DO UPDATE SET likes = excluded.likes, synced_at = excluded.synced_at, updated_at = excluded.updated_at """, cleaned_rows, ) def update_feedback_comment(comment_id: int, owner_session_id: str, answer: str) -> bool: answer = (answer or "").strip() owner_session_id = (owner_session_id or "").strip() if not comment_id or not answer or not owner_session_id: return False updated_at = now_iso() with get_connection() as conn: with _transaction(conn): row = conn.execute( """ SELECT id FROM quest_feedback WHERE id = ? AND owner_session_id = ? AND COALESCE(deleted_at, '') = '' """, (comment_id, owner_session_id), ).fetchone() if not row: return False conn.execute( """ UPDATE quest_feedback SET answer = ?, updated_at = ? WHERE id = ? AND owner_session_id = ? AND COALESCE(deleted_at, '') = '' """, (answer, updated_at, comment_id, owner_session_id), ) return True def delete_feedback_comment(comment_id: int, owner_session_id: str) -> bool: owner_session_id = (owner_session_id or "").strip() if not comment_id or not owner_session_id: return False deleted_at = now_iso() with get_connection() as conn: with _transaction(conn): row = conn.execute( """ SELECT id FROM quest_feedback WHERE id = ? AND owner_session_id = ? AND COALESCE(deleted_at, '') = '' """, (comment_id, owner_session_id), ).fetchone() if not row: return False conn.execute( """ UPDATE quest_feedback SET deleted_at = ?, updated_at = ? WHERE id = ? AND owner_session_id = ? AND COALESCE(deleted_at, '') = '' """, (deleted_at, deleted_at, comment_id, owner_session_id), ) return True def dump_json(table_name: str) -> str: if table_name == "quest_feedback": rows = get_recent_feedback(limit=100000) else: rows = [] return json.dumps(rows, ensure_ascii=False, indent=2)