Spaces:
Sleeping
Sleeping
| """ | |
| SQLite Database Module | |
| βββββββββββββββββββββ | |
| Handles connection, initialization, and health checks. | |
| Sources: | |
| - Python sqlite3 docs: https://docs.python.org/3/library/sqlite3.html | |
| - Litestream tips: https://litestream.io/tips/ | |
| - SQLite WAL mode: https://www.sqlite.org/wal.html | |
| - SQLite PRAGMA: https://www.sqlite.org/pragma.html | |
| """ | |
| import sqlite3 | |
| import os | |
| import threading | |
| import time | |
| DB_PATH = "/tmp/data/app.db" | |
| # Thread-local storage for connections | |
| _local = threading.local() | |
| def get_db() -> sqlite3.Connection: | |
| """ | |
| Get a thread-safe database connection. | |
| Each thread gets its own connection (SQLite requirement for WAL mode). | |
| Connections are cached per-thread to avoid overhead. | |
| PRAGMA explanations: | |
| - journal_mode=WAL β Required for Litestream. Allows concurrent reads. | |
| - busy_timeout=5000 β Wait 5s if another thread is writing. | |
| - synchronous=NORMAL β Safe with WAL, faster than FULL. | |
| - cache_size=-20000 β 20MB page cache in memory. | |
| - foreign_keys=ON β Enforce relational integrity. | |
| - wal_autocheckpoint=1000 β Checkpoint every 1000 pages (~4MB). | |
| """ | |
| if not hasattr(_local, 'conn') or _local.conn is None: | |
| os.makedirs(os.path.dirname(DB_PATH), exist_ok=True) | |
| conn = sqlite3.connect(DB_PATH, check_same_thread=False) | |
| conn.row_factory = sqlite3.Row | |
| # ββ Critical PRAGMAs ββ | |
| conn.execute("PRAGMA journal_mode=WAL") | |
| conn.execute("PRAGMA busy_timeout=5000") | |
| conn.execute("PRAGMA synchronous=NORMAL") | |
| conn.execute("PRAGMA cache_size=-20000") | |
| conn.execute("PRAGMA foreign_keys=ON") | |
| conn.execute("PRAGMA wal_autocheckpoint=1000") | |
| _local.conn = conn | |
| return _local.conn | |
| def close_db(): | |
| """Close the thread-local connection.""" | |
| if hasattr(_local, 'conn') and _local.conn: | |
| _local.conn.close() | |
| _local.conn = None | |
| def init_db(): | |
| """ | |
| Create tables on first run. | |
| Safe to call multiple times (IF NOT EXISTS). | |
| """ | |
| conn = get_db() | |
| conn.executescript(""" | |
| -- βββββββββββββββββββββββββββββββββββββββββββ | |
| -- USERS | |
| -- βββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE TABLE IF NOT EXISTS users ( | |
| id TEXT PRIMARY KEY, | |
| email TEXT UNIQUE NOT NULL, | |
| name TEXT, | |
| avatar_url TEXT, | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ); | |
| -- βββββββββββββββββββββββββββββββββββββββββββ | |
| -- PROJECTS | |
| -- βββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE TABLE IF NOT EXISTS projects ( | |
| id TEXT PRIMARY KEY, | |
| user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, | |
| name TEXT NOT NULL, | |
| description TEXT, | |
| design_tokens JSON, | |
| status TEXT DEFAULT 'draft' CHECK(status IN ('draft','active','archived')), | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, | |
| updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ); | |
| -- βββββββββββββββββββββββββββββββββββββββββββ | |
| -- COLOR PALETTES | |
| -- βββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE TABLE IF NOT EXISTS color_palettes ( | |
| id TEXT PRIMARY KEY, | |
| project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, | |
| name TEXT NOT NULL, | |
| colors JSON NOT NULL, | |
| mood TEXT, | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ); | |
| -- βββββββββββββββββββββββββββββββββββββββββββ | |
| -- DESIGN ASSETS (references to HF Bucket files) | |
| -- βββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE TABLE IF NOT EXISTS design_assets ( | |
| id TEXT PRIMARY KEY, | |
| project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, | |
| asset_type TEXT NOT NULL, | |
| bucket_path TEXT NOT NULL, | |
| file_size INTEGER, | |
| metadata JSON, | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ); | |
| -- βββββββββββββββββββββββββββββββββββββββββββ | |
| -- BACKUP LOG (tracks backup history) | |
| -- βββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE TABLE IF NOT EXISTS backup_log ( | |
| id INTEGER PRIMARY KEY AUTOINCREMENT, | |
| backup_type TEXT NOT NULL, | |
| file_path TEXT, | |
| file_size INTEGER, | |
| status TEXT DEFAULT 'success', | |
| error_message TEXT, | |
| created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP | |
| ); | |
| -- βββββββββββββββββββββββββββββββββββββββββββ | |
| -- INDEXES | |
| -- βββββββββββββββββββββββββββββββββββββββββββ | |
| CREATE INDEX IF NOT EXISTS idx_projects_user | |
| ON projects(user_id); | |
| CREATE INDEX IF NOT EXISTS idx_palettes_project | |
| ON color_palettes(project_id); | |
| CREATE INDEX IF NOT EXISTS idx_assets_project | |
| ON design_assets(project_id); | |
| CREATE INDEX IF NOT EXISTS idx_backup_log_date | |
| ON backup_log(created_at); | |
| """) | |
| conn.commit() | |
| def integrity_check() -> dict: | |
| """ | |
| Run SQLite integrity check. | |
| Returns health status of the database. | |
| Source: https://www.sqlite.org/pragma.html#pragma_integrity_check | |
| """ | |
| conn = get_db() | |
| result = conn.execute("PRAGMA integrity_check").fetchone() | |
| wal_size = conn.execute("PRAGMA wal_checkpoint(PASSIVE)").fetchone() | |
| page_count = conn.execute("PRAGMA page_count").fetchone()[0] | |
| page_size = conn.execute("PRAGMA page_size").fetchone()[0] | |
| db_size = page_count * page_size | |
| return { | |
| "integrity": result[0], # "ok" if healthy | |
| "db_size_bytes": db_size, | |
| "db_size_mb": round(db_size / (1024 * 1024), 2), | |
| "page_count": page_count, | |
| "page_size": page_size, | |
| "wal_pages": wal_size[1] if wal_size else 0, | |
| "journal_mode": conn.execute("PRAGMA journal_mode").fetchone()[0], | |
| } | |