import express from 'express'; import path from 'path'; import { db } from '../db/client.js'; import { isMarketOpen } from '../tracker/market-hours.js'; export function createServer(): express.Application { const app = express(); app.use(express.json()); // Serve the static UI from /public const publicDir = path.resolve(__dirname, '../../public'); app.use(express.static(publicDir)); // ── GET /api/positions ──────────────────────────────────────────────────── // Returns all currently tracked positions (open + recently closed) app.get('/api/positions', (_req, res) => { const rows = db.prepare(` SELECT key, exchange, tradingsymbol, instrumenttype, producttype, netqty, ltp, avg_price, unrealised_pnl, realised_pnl, total_pnl, source, is_closed, updated_at FROM positions ORDER BY ABS(total_pnl) DESC `).all(); res.json({ ok: true, data: rows }); }); // ── GET /api/alerts ─────────────────────────────────────────────────────── // Alert history, newest first, optional ?limit=N&symbol=X app.get('/api/alerts', (req, res) => { const limit = Math.min(parseInt(req.query.limit as string) || 50, 200); const symbol = req.query.symbol as string | undefined; let query = `SELECT * FROM alerts`; const params: (string | number)[] = []; if (symbol) { query += ` WHERE tradingsymbol = ?`; params.push(symbol); } query += ` ORDER BY alerted_at DESC LIMIT ?`; params.push(limit); const rows = db.prepare(query).all(...params); res.json({ ok: true, data: rows }); }); // ── GET /api/config ─────────────────────────────────────────────────────── app.get('/api/config', (_req, res) => { const rows = db.prepare(`SELECT * FROM position_config`).all(); res.json({ ok: true, global: { alertThresholdPct: parseFloat(process.env.ALERT_THRESHOLD_PCT || '5'), alertMinAbsInr: parseFloat(process.env.ALERT_MIN_ABS_INR || '100'), pollIntervalSeconds: parseInt(process.env.POLL_INTERVAL_SECONDS || '60'), }, overrides: rows, }); }); // ── PUT /api/config/:key ────────────────────────────────────────────────── // Set per-position threshold or mute app.put('/api/config/:key', (req, res) => { const { key } = req.params; const { alert_threshold_pct, muted_until, notes } = req.body; db.prepare(` INSERT INTO position_config (position_key, alert_threshold_pct, muted_until, notes) VALUES (?, ?, ?, ?) ON CONFLICT(position_key) DO UPDATE SET alert_threshold_pct = excluded.alert_threshold_pct, muted_until = excluded.muted_until, notes = excluded.notes `).run(key, alert_threshold_pct ?? null, muted_until ?? null, notes ?? null); res.json({ ok: true }); }); // ── GET /api/health ─────────────────────────────────────────────────────── app.get('/api/health', (_req, res) => { const lastError = db.prepare( `SELECT error, occurred_at FROM poll_errors ORDER BY occurred_at DESC LIMIT 1` ).get() as { error: string; occurred_at: string } | undefined; const posCount = (db.prepare(`SELECT COUNT(*) as n FROM positions WHERE is_closed = 0`).get() as { n: number }).n; res.json({ ok: true, marketOpen: isMarketOpen(), openPositions: posCount, lastError: lastError ?? null, uptime: Math.floor(process.uptime()), }); }); // Catch-all: serve index.html for SPA routing app.get('*', (_req, res) => { res.sendFile(path.join(publicDir, 'index.html')); }); return app; }