-- schema version tracked in app_meta CREATE TABLE IF NOT EXISTS app_meta ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, username TEXT NOT NULL UNIQUE COLLATE NOCASE, password_hash TEXT NOT NULL, is_admin INTEGER NOT NULL DEFAULT 0, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS sessions ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, token_hash TEXT NOT NULL UNIQUE, expires_at TEXT NOT NULL, created_at TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token_hash); CREATE INDEX IF NOT EXISTS idx_sessions_user ON sessions(user_id); CREATE TABLE IF NOT EXISTS projects ( id TEXT PRIMARY KEY, name TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS project_members ( project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, role TEXT NOT NULL, PRIMARY KEY (project_id, user_id) ); CREATE INDEX IF NOT EXISTS idx_members_user ON project_members(user_id); CREATE TABLE IF NOT EXISTS tickets ( id TEXT PRIMARY KEY, title TEXT NOT NULL, description TEXT NOT NULL DEFAULT '', status TEXT NOT NULL, project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, created_at TEXT NOT NULL, updated_at TEXT NOT NULL, source TEXT NOT NULL DEFAULT '', external_id TEXT NOT NULL DEFAULT '', external_url TEXT NOT NULL DEFAULT '', synced_at TEXT NOT NULL DEFAULT '' ); CREATE INDEX IF NOT EXISTS idx_tickets_project ON tickets(project_id); CREATE INDEX IF NOT EXISTS idx_tickets_external ON tickets(external_id); CREATE TABLE IF NOT EXISTS time_entries ( id TEXT PRIMARY KEY, ticket_id TEXT NOT NULL, project_id TEXT NOT NULL, start TEXT NOT NULL, end TEXT NOT NULL, duration_ns INTEGER NOT NULL, source TEXT NOT NULL DEFAULT '', note TEXT NOT NULL DEFAULT '', external_id TEXT NOT NULL DEFAULT '' ); CREATE INDEX IF NOT EXISTS idx_time_ticket ON time_entries(ticket_id); CREATE INDEX IF NOT EXISTS idx_time_project ON time_entries(project_id); CREATE TABLE IF NOT EXISTS active_timer ( id INTEGER PRIMARY KEY CHECK (id = 1), ticket_id TEXT NOT NULL, project_id TEXT NOT NULL, started_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS connections ( id TEXT PRIMARY KEY, name TEXT NOT NULL, kind TEXT NOT NULL, base_url TEXT NOT NULL, scope TEXT NOT NULL, encrypted_token BLOB NOT NULL, created_by TEXT REFERENCES users(id) ON DELETE SET NULL, created_at TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS sync_backends ( project_id TEXT PRIMARY KEY REFERENCES projects(id) ON DELETE CASCADE, kind TEXT NOT NULL DEFAULT 'none', base_url TEXT NOT NULL DEFAULT '', project_key TEXT NOT NULL DEFAULT '', query TEXT NOT NULL DEFAULT '', connection_id TEXT REFERENCES connections(id) ON DELETE SET NULL ); CREATE TABLE IF NOT EXISTS revisions ( id INTEGER PRIMARY KEY AUTOINCREMENT, entity_type TEXT NOT NULL, entity_id TEXT NOT NULL, project_id TEXT NOT NULL DEFAULT '', rev INTEGER NOT NULL, at TEXT NOT NULL, actor_user_id TEXT, op TEXT NOT NULL, snapshot_json TEXT NOT NULL ); CREATE INDEX IF NOT EXISTS idx_revisions_entity ON revisions(entity_type, entity_id); CREATE INDEX IF NOT EXISTS idx_revisions_project ON revisions(project_id);