from __future__ import annotations import sqlite3 from pathlib import Path SCHEMA = """ CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, user_key TEXT NOT NULL, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ); CREATE TABLE IF NOT EXISTS user_resources ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, app_id TEXT NOT NULL DEFAULT 'default', project_id TEXT NOT NULL DEFAULT 'default', session_id TEXT NOT NULL, original_filename TEXT, mime_type TEXT, content_type TEXT NOT NULL, uri TEXT NOT NULL, uri_public BOOLEAN NOT NULL DEFAULT FALSE, sha256 TEXT, size_bytes INTEGER, title TEXT, description TEXT, status TEXT NOT NULL DEFAULT 'pending', error_message TEXT, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL, deleted_at TIMESTAMP ); CREATE INDEX IF NOT EXISTS idx_user_resources_user_scope ON user_resources (user_id, app_id, project_id, status, deleted_at); CREATE INDEX IF NOT EXISTS idx_user_resources_session_id ON user_resources (session_id); CREATE INDEX IF NOT EXISTS idx_user_resources_user_id ON user_resources (user_id); CREATE TABLE IF NOT EXISTS memory_attachments ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, app_id TEXT NOT NULL DEFAULT 'default', project_id TEXT NOT NULL DEFAULT 'default', session_id TEXT NOT NULL, resource_id TEXT, content_type TEXT NOT NULL, name TEXT NOT NULL, internal_uri TEXT NOT NULL, source TEXT NOT NULL, sha256 TEXT, created_at TIMESTAMP NOT NULL, deleted_at TIMESTAMP ); CREATE UNIQUE INDEX IF NOT EXISTS idx_memory_attachments_unique_uri ON memory_attachments (user_id, session_id, internal_uri); CREATE INDEX IF NOT EXISTS idx_memory_attachments_user_session ON memory_attachments (user_id, session_id, deleted_at); CREATE INDEX IF NOT EXISTS idx_memory_attachments_resource ON memory_attachments (resource_id, deleted_at); INSERT OR IGNORE INTO memory_attachments ( id, user_id, app_id, project_id, session_id, resource_id, content_type, name, internal_uri, source, sha256, created_at, deleted_at ) SELECT 'a_resource_' || id, user_id, app_id, project_id, session_id, id, content_type, COALESCE(original_filename, id), uri, 'resource_upload', sha256, created_at, deleted_at FROM user_resources; CREATE TABLE IF NOT EXISTS memory_tombstones ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, memory_id TEXT, session_id TEXT, reason TEXT, created_at TIMESTAMP NOT NULL ); CREATE INDEX IF NOT EXISTS idx_memory_tombstones_user_memory ON memory_tombstones (user_id, memory_id); CREATE INDEX IF NOT EXISTS idx_memory_tombstones_user_session ON memory_tombstones (user_id, session_id); CREATE TABLE IF NOT EXISTS memory_overrides ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, memory_id TEXT, session_id TEXT, override_text TEXT NOT NULL, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP NOT NULL, updated_at TIMESTAMP NOT NULL ); CREATE INDEX IF NOT EXISTS idx_memory_overrides_user_memory_active ON memory_overrides (user_id, memory_id, is_active); """ def connect(db_path: Path) -> sqlite3.Connection: conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row conn.execute("PRAGMA journal_mode=WAL") conn.execute("PRAGMA foreign_keys=ON") conn.execute("PRAGMA busy_timeout=5000") return conn def init_db(db_path: Path) -> None: db_path.parent.mkdir(parents=True, exist_ok=True) with connect(db_path) as conn: conn.executescript(SCHEMA)