import { drizzle } from "drizzle-orm/node-postgres"; import { eq } from "drizzle-orm"; import { users, customSymbols, User, NewUser, CustomSymbol, NewCustomSymbol } from "../shared/schema"; import { Pool } from "pg"; // PostgreSQL connection (local on production droplet) const pool = new Pool({ host: process.env.DB_HOST || "127.0.0.1", port: parseInt(process.env.DB_PORT || "5432"), user: process.env.DB_USER || "gn_admin", password: process.env.DB_PASSWORD || "", database: process.env.DB_NAME || "gammanexus", ssl: (process.env.DB_SSL === "true" && process.env.NODE_ENV === "production") ? { rejectUnauthorized: false } : false, max: 20, idleTimeoutMillis: 30000, connectionTimeoutMillis: 5000, }); export const db = drizzle(pool); // Use raw pool client for DDL — Drizzle wraps queries in prepared statements // and PostgreSQL rejects CREATE/ALTER IF NOT EXISTS in prepared queries. async function executeDDL(query: string): Promise { const client = await pool.connect(); try { await client.query(query); } catch { // Table/column already exists — ignore silently } finally { client.release(); } } // Initialize users table if it doesn't exist (called at startup) export async function initDb(): Promise { await executeDDL(` CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, password_hash TEXT NOT NULL, email_verified BOOLEAN NOT NULL DEFAULT FALSE, verification_token TEXT, verification_token_expiry TIMESTAMP WITH TIME ZONE, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ) `); // Add columns if they don't exist (for existing tables) await executeDDL(`ALTER TABLE users ADD COLUMN email_verified BOOLEAN NOT NULL DEFAULT FALSE`); await executeDDL(`ALTER TABLE users ADD COLUMN verification_token TEXT`); await executeDDL(`ALTER TABLE users ADD COLUMN verification_token_expiry TIMESTAMP WITH TIME ZONE`); // Create custom_symbols table await executeDDL(` CREATE TABLE IF NOT EXISTS custom_symbols ( id SERIAL PRIMARY KEY, ticker TEXT NOT NULL UNIQUE, name TEXT NOT NULL, type TEXT NOT NULL DEFAULT 'etf', sector TEXT, user_id TEXT, created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() ) `); } export async function createUser(data: NewUser): Promise { const [user] = await db.insert(users).values(data).returning(); return user; } export async function getUserByEmail(email: string): Promise { const result = await db.select().from(users).where(eq(users.email, email.toLowerCase())); return result[0]; } export async function getUserById(id: number): Promise { const result = await db.select().from(users).where(eq(users.id, id)); return result[0]; } export async function updateUser(id: number, data: Partial>): Promise { const result = await db.update(users).set(data).where(eq(users.id, id)).returning(); return result[0]; } export async function updateUserPassword(id: number, passwordHash: string): Promise { const result = await db.update(users).set({ passwordHash }).where(eq(users.id, id)).returning(); return result[0]; } export async function updateUserVerification(id: number, token: string, expiry: Date): Promise { await db.update(users).set({ verificationToken: token, verificationTokenExpiry: expiry }).where(eq(users.id, id)); } export async function getUserByVerificationToken(token: string): Promise { const { and, eq, gt } = await import("drizzle-orm"); const result = await db.select().from(users).where( and( eq(users.verificationToken, token), gt(users.verificationTokenExpiry, new Date()) ) ); return result[0]; } export async function markEmailVerified(id: number): Promise { await db.update(users).set({ emailVerified: true, verificationToken: null, verificationTokenExpiry: null }).where(eq(users.id, id)); } // --------------------------------------------------------------------------- // Custom Symbols CRUD // --------------------------------------------------------------------------- export async function getAllCustomSymbols(): Promise { return await db.select().from(customSymbols); } export async function getCustomSymbolByTicker(ticker: string): Promise { const result = await db.select().from(customSymbols).where(eq(customSymbols.ticker, ticker.toUpperCase())); return result[0]; } export async function createCustomSymbol(data: NewCustomSymbol): Promise { const [symbol] = await db.insert(customSymbols).values({ ...data, ticker: data.ticker?.toUpperCase() || data.ticker, }).returning(); return symbol; } export async function deleteCustomSymbol(ticker: string): Promise { const result = await db.delete(customSymbols).where(eq(customSymbols.ticker, ticker.toUpperCase())).returning(); return result.length > 0; }