/** * SQL Query Constants * * All SQL queries used in the application. * Repositories reference these by name. * * All queries use parameterized statements (?) for security. * User input NEVER goes into the SQL string. */ // ── Holdings Table Queries ─────────────────────────────────────────────────── export const HOLDINGS_QUERIES = { // Check if any holdings exist for a user EXISTS: 'SELECT COUNT(*) AS n FROM holdings WHERE user_id = ?', // Get all holdings for a user, sorted by ticker SELECT_ALL: ` SELECT ticker, shares, cost_basis, type, source FROM holdings WHERE user_id = ? ORDER BY ticker ASC `, // Insert or update a holding scoped to a user UPSERT: ` INSERT INTO holdings (ticker, shares, cost_basis, type, source, user_id) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT(ticker, user_id) DO UPDATE SET shares = excluded.shares, cost_basis = excluded.cost_basis, type = excluded.type, source = excluded.source `, // Delete a holding by ticker for a specific user DELETE_BY_TICKER: 'DELETE FROM holdings WHERE ticker = ? AND user_id = ?', // Migrate ownerless holdings to admin user (one-time) MIGRATE_TO_ADMIN: "UPDATE holdings SET user_id = ? WHERE user_id IS NULL OR user_id = ''", }; // ── Market Calls Table Queries ─────────────────────────────────────────────── export const MARKET_CALLS_QUERIES = { // Get all market calls, newest first SELECT_ALL: ` SELECT id, title, quarter, date, thesis, tickers, snapshot, created_at FROM market_calls ORDER BY created_at DESC `, // Get a single market call by ID SELECT_BY_ID: ` SELECT id, title, quarter, date, thesis, tickers, snapshot, created_at FROM market_calls WHERE id = ? `, // Insert a new market call INSERT: ` INSERT INTO market_calls (id, title, quarter, date, thesis, tickers, snapshot, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `, // Delete a market call by ID DELETE_BY_ID: 'DELETE FROM market_calls WHERE id = ?', }; // ── Migration Queries (for DatabaseInitializer) ────────────────────────────── export const MIGRATION_QUERIES = { // Insert holdings during migration HOLDINGS_INSERT_OR_IGNORE: ` INSERT OR IGNORE INTO holdings (ticker, shares, cost_basis, type, source, user_id) VALUES (?, ?, ?, ?, ?, ?) `, // Insert market calls during migration MARKET_CALLS_INSERT_OR_IGNORE: ` INSERT OR IGNORE INTO market_calls (id, title, quarter, date, thesis, tickers, snapshot, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `, }; // ── User Table Queries ─────────────────────────────────────────────────────── export const USER_QUERIES = { SELECT_BY_EMAIL: ` SELECT id, email, password_hash, role, created_at, last_login FROM users WHERE email = ? `, SELECT_BY_ID: ` SELECT id, email, role, created_at, last_login FROM users WHERE id = ? `, INSERT: ` INSERT INTO users (id, email, password_hash, role, created_at) VALUES (?, ?, ?, ?, ?) `, UPDATE_LAST_LOGIN: ` UPDATE users SET last_login = ? WHERE id = ? `, }; // ── Password Reset Token Queries ───────────────────────────────────────────── export const RESET_TOKEN_QUERIES = { INSERT: ` INSERT INTO password_reset_tokens (token, user_id, expires_at) VALUES (?, ?, ?) `, FIND: ` SELECT token, user_id, expires_at, used FROM password_reset_tokens WHERE token = ? `, MARK_USED: ` UPDATE password_reset_tokens SET used = 1 WHERE token = ? `, // Clean up expired/used tokens older than 24h PURGE: ` DELETE FROM password_reset_tokens WHERE used = 1 OR expires_at < ? `, }; // ── Schema Definition (DDL) ────────────────────────────────────────────────── export const DDL = ` CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, email TEXT UNIQUE NOT NULL, password_hash TEXT NOT NULL, role TEXT NOT NULL DEFAULT 'viewer' CHECK (role IN ('trader', 'viewer', 'admin')), created_at TEXT NOT NULL, last_login TEXT ); CREATE TABLE IF NOT EXISTS holdings ( ticker TEXT NOT NULL, user_id TEXT NOT NULL REFERENCES users(id), shares REAL NOT NULL, cost_basis REAL NOT NULL DEFAULT 0, type TEXT NOT NULL DEFAULT 'stock', source TEXT NOT NULL DEFAULT 'Manual', PRIMARY KEY (ticker, user_id) ); CREATE TABLE IF NOT EXISTS password_reset_tokens ( token TEXT PRIMARY KEY, user_id TEXT NOT NULL REFERENCES users(id), expires_at TEXT NOT NULL, used INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS market_calls ( id TEXT PRIMARY KEY, title TEXT NOT NULL, quarter TEXT NOT NULL, date TEXT NOT NULL, thesis TEXT NOT NULL, tickers TEXT NOT NULL, -- JSON array snapshot TEXT NOT NULL, -- JSON object created_at TEXT NOT NULL ); `; // ── Runtime migrations (ALTER TABLE for existing DBs) ──────────────────────── // These are safe to run repeatedly — they no-op if the column already exists. export const RUNTIME_MIGRATIONS = [ // Add user_id to holdings if upgrading from pre-auth schema `ALTER TABLE holdings ADD COLUMN user_id TEXT NOT NULL DEFAULT '' REFERENCES users(id)`, ];