# -*- coding: utf-8 -*- """SQLite schema for Loogle MCP Hub.""" import os import sqlite3 import threading DB_PATH = os.environ.get("MCP_DB", "/data/loogle_mcp.db") _local = threading.local() SCHEMA = """ CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, is_admin INTEGER NOT NULL DEFAULT 0, must_change_password INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE IF NOT EXISTS oauth_clients ( client_id TEXT PRIMARY KEY, client_name TEXT NOT NULL, redirect_uris TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT (datetime('now')) ); CREATE TABLE IF NOT EXISTS oauth_codes ( code TEXT PRIMARY KEY, client_id TEXT NOT NULL, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, redirect_uri TEXT NOT NULL, scope TEXT NOT NULL, code_challenge TEXT, code_challenge_method TEXT, expires_at TEXT NOT NULL, used INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS refresh_tokens ( token TEXT PRIMARY KEY, user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, client_id TEXT NOT NULL, scope TEXT NOT NULL, expires_at TEXT NOT NULL, revoked INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS revoked_jtis ( jti TEXT PRIMARY KEY, revoked_at TEXT NOT NULL DEFAULT (datetime('now')), expires_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS audit_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL, tool_name TEXT NOT NULL, resource_id TEXT, detail TEXT, created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')) ); CREATE TABLE IF NOT EXISTS indexed_documents ( doc_id INTEGER PRIMARY KEY, title TEXT, owner TEXT, visibility TEXT NOT NULL DEFAULT 'family', indexed_at TEXT NOT NULL DEFAULT (datetime('now')), chunk_count INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS indexed_gitea_files ( repo TEXT NOT NULL, path TEXT NOT NULL, sha TEXT, owner TEXT NOT NULL, visibility TEXT NOT NULL DEFAULT 'family', chunk_count INTEGER NOT NULL DEFAULT 0, indexed_at TEXT NOT NULL DEFAULT (datetime('now')), PRIMARY KEY (repo, path) ); CREATE INDEX IF NOT EXISTS idx_gitea_indexed_repo ON indexed_gitea_files(repo); CREATE INDEX IF NOT EXISTS idx_gitea_indexed_at ON indexed_gitea_files(indexed_at DESC); CREATE TABLE IF NOT EXISTS indexed_apps_records ( source TEXT NOT NULL, record_id TEXT NOT NULL, title TEXT, owner TEXT NOT NULL DEFAULT 'family', chunk_count INTEGER NOT NULL DEFAULT 0, indexed_at TEXT NOT NULL DEFAULT (datetime('now')), PRIMARY KEY (source, record_id) ); CREATE INDEX IF NOT EXISTS idx_apps_indexed_source ON indexed_apps_records(source); CREATE INDEX IF NOT EXISTS idx_apps_indexed_at ON indexed_apps_records(indexed_at DESC); CREATE INDEX IF NOT EXISTS idx_audit_created ON audit_log(created_at DESC); CREATE INDEX IF NOT EXISTS idx_oauth_codes_expires ON oauth_codes(expires_at); """ def get_conn() -> sqlite3.Connection: conn = getattr(_local, "conn", None) if conn is None: os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) conn = sqlite3.connect(DB_PATH, timeout=30) conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode=WAL") conn.execute("PRAGMA foreign_keys=ON") _local.conn = conn return conn def init_db() -> None: get_conn().executescript(SCHEMA)