phase-10.5: market screener ui enhancements
This commit is contained in:
@@ -2,8 +2,7 @@
|
||||
* SQL Query Constants
|
||||
*
|
||||
* All SQL queries used in the application.
|
||||
* Repositories reference these by name (e.g., MARKET_CALLS_QUERIES.SELECT_ALL).
|
||||
* QueryBuilder looks them up and binds parameters.
|
||||
* Repositories reference these by name.
|
||||
*
|
||||
* All queries use parameterized statements (?) for security.
|
||||
* User input NEVER goes into the SQL string.
|
||||
@@ -12,25 +11,33 @@
|
||||
// ── Holdings Table Queries ───────────────────────────────────────────────────
|
||||
|
||||
export const HOLDINGS_QUERIES = {
|
||||
// Check if any holdings exist
|
||||
EXISTS: 'SELECT COUNT(*) AS n FROM holdings',
|
||||
// Check if any holdings exist for a user
|
||||
EXISTS: 'SELECT COUNT(*) AS n FROM holdings WHERE user_id = ?',
|
||||
|
||||
// Get all holdings, sorted by ticker
|
||||
SELECT_ALL: 'SELECT ticker, shares, cost_basis, type, source FROM holdings ORDER BY ticker ASC',
|
||||
// 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 (UPSERT)
|
||||
// Insert or update a holding scoped to a user
|
||||
UPSERT: `
|
||||
INSERT INTO holdings (ticker, shares, cost_basis, type, source)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
ON CONFLICT(ticker) DO UPDATE SET
|
||||
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
|
||||
DELETE_BY_TICKER: 'DELETE FROM holdings WHERE ticker = ?',
|
||||
// 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 ───────────────────────────────────────────────
|
||||
@@ -65,8 +72,8 @@ export const MARKET_CALLS_QUERIES = {
|
||||
export const MIGRATION_QUERIES = {
|
||||
// Insert holdings during migration
|
||||
HOLDINGS_INSERT_OR_IGNORE: `
|
||||
INSERT OR IGNORE INTO holdings (ticker, shares, cost_basis, type, source)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
INSERT OR IGNORE INTO holdings (ticker, shares, cost_basis, type, source, user_id)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`,
|
||||
|
||||
// Insert market calls during migration
|
||||
@@ -76,15 +83,78 @@ export const MIGRATION_QUERIES = {
|
||||
`,
|
||||
};
|
||||
|
||||
// ── 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 PRIMARY KEY,
|
||||
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'
|
||||
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 (
|
||||
@@ -98,3 +168,11 @@ export const DDL = `
|
||||
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)`,
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user