PRAGMA foreign_keys = ON; CREATE TABLE IF NOT EXISTS config ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT NOT NULL UNIQUE, data TEXT NOT NULL, created_at TEXT NOT NULL, updated_at TEXT NOT NULL ); -- Per-user sync revision date CREATE TABLE IF NOT EXISTS user_revisions ( user_id TEXT PRIMARY KEY, revision_date TEXT NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS ciphers ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, deleted_at TEXT, updated_at TEXT NOT NULL, data TEXT NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_ciphers_user_updated ON ciphers(user_id, updated_at); CREATE INDEX IF NOT EXISTS idx_ciphers_user_deleted ON ciphers(user_id, deleted_at); CREATE TABLE IF NOT EXISTS folders ( id TEXT PRIMARY KEY, user_id TEXT NOT NULL, updated_at TEXT NOT NULL, data TEXT NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_folders_user_updated ON folders(user_id, updated_at); CREATE TABLE IF NOT EXISTS attachments ( id TEXT PRIMARY KEY, cipher_id TEXT, data TEXT NOT NULL, FOREIGN KEY (cipher_id) REFERENCES ciphers(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_attachments_cipher ON attachments(cipher_id); CREATE TABLE IF NOT EXISTS refresh_tokens ( token TEXT PRIMARY KEY, user_id TEXT NOT NULL, expires_at INTEGER NOT NULL, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ); CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user ON refresh_tokens(user_id); -- Rate limiting CREATE TABLE IF NOT EXISTS login_attempts ( email TEXT PRIMARY KEY, attempts INTEGER NOT NULL, locked_until INTEGER, updated_at INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS api_rate_limits ( identifier TEXT NOT NULL, window_start INTEGER NOT NULL, count INTEGER NOT NULL, PRIMARY KEY (identifier, window_start) ); CREATE INDEX IF NOT EXISTS idx_api_rate_window ON api_rate_limits(window_start);