spb/vquant Public MIT
VibeQuant — AI-powered institutional-grade financial intelligence platform.
TypeScript 84.3%
Python 11.7%
JavaScript 1.6%
CSS 1.5%
HTML 0.7%
1/*2 * =============================================================================3 * VibeQuant (vquant) — AI-Powered Financial Intelligence Platform4 * -----------------------------------------------------------------------------5 * File: server/index.ts6 *7 * Author: Simon-Pierre Boucher8 * Contact: contact@spboucher.ai9 * Website: https://www.spboucher.ai10 * Demo: https://www.vquant.ai11 * License: MIT (see LICENSE)12 *13 * Copyright © 2026 Simon-Pierre Boucher. All rights reserved.14 * =============================================================================15 */1617import express, { type Request, Response, NextFunction } from "express";18import cors from "cors";19import session from "express-session";20import connectPgSimple from "connect-pg-simple";21import SqliteStore from "better-sqlite3-session-store";22import path from 'path';23import { registerRoutes } from "./routes";24import { setupVite, serveStatic, log } from "./vite";25import { pool, isSqlite } from "./db";26import { logger } from "./utils/logger";2728const app = express();29app.set('trust proxy', 1);3031// ─── CORS ─────────────────────────────────────────────────32const port = parseInt(process.env.PORT || '5000', 10);33const allowedOrigins = [34 `http://localhost:${port}`,35 `http://127.0.0.1:${port}`,36 'http://localhost:5000',37 'http://127.0.0.1:5000',38 'https://www.vquant.ai',39 'https://vquant.ai',40 'https://vquant.s-pb.dev',41 'https://vquant.s-pb.ai',42 'http://vquant.s-pb.ai',43];4445app.use(cors({46 origin(origin, callback) {47 if (!origin || allowedOrigins.includes(origin)) {48 callback(null, true);49 } else {50 logger.warn(`CORS blocked origin: ${origin}`);51 callback(new Error('Not allowed by CORS'));52 }53 },54 credentials: true,55 methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],56 allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],57 exposedHeaders: ['Content-Range', 'X-Content-Range'],58 maxAge: 86400,59}));6061app.use(express.json({ limit: '10mb' }));62app.use(express.urlencoded({ extended: false, limit: '10mb' }));6364// ─── Session ──────────────────────────────────────────────65let sessionStore: session.Store;66if (isSqlite) {67 const SqliteSessionStore = SqliteStore(session);68 sessionStore = new SqliteSessionStore({69 client: pool,70 expired: { clear: true, intervalMs: 900_000 },71 });72} else {73 const PgSession = connectPgSimple(session);74 sessionStore = new PgSession({75 pool: pool as unknown as connectPgSimple.PGStoreOptions["pool"],76 tableName: "session",77 createTableIfMissing: true,78 });79}8081const sessionSecret = process.env.SESSION_SECRET;82if (!sessionSecret && process.env.NODE_ENV === "production") {83 logger.error("SESSION_SECRET is required in production!");84 process.exit(1);85}8687app.use(88 session({89 store: sessionStore,90 secret: sessionSecret || "dev-only-secret-do-not-use-in-prod",91 resave: false,92 saveUninitialized: false,93 cookie: {94 maxAge: 30 * 24 * 60 * 60 * 1000,95 httpOnly: true,96 secure: process.env.NODE_ENV === "production",97 },98 }),99);100101102// ─── Static files for generated assets ────────────────────103const rootDir = path.resolve(import.meta.dirname, '..');104app.use('/plots', express.static(path.join(rootDir, 'public', 'plots')));105app.use('/figures', express.static(path.join(rootDir, 'dist', 'public', 'figures')));106app.use('/plots', (_req, res) => res.status(404).send(''));107app.use('/figures', (_req, res) => res.status(404).send(''));108109// ─── Request logging middleware ───────────────────────────110app.use((req, res, next) => {111 const start = Date.now();112 const reqPath = req.path;113114 if (reqPath.startsWith("/api")) {115 logger.info(`${req.method} ${reqPath}`);116 }117118 res.on("finish", () => {119 const duration = Date.now() - start;120 if (reqPath.startsWith("/api")) {121 log(`${req.method} ${reqPath} ${res.statusCode} ${duration}ms`);122 }123 });124125 next();126});127128// ─── Bootstrap ────────────────────────────────────────────129(async () => {130 logger.banner('SERVER STARTING', 'VQuant Financial Intelligence Platform');131132 logger.section('Configuration');133 logger.kv('Environment', process.env.NODE_ENV || 'development');134 logger.kv('Database type', isSqlite ? 'SQLite' : 'PostgreSQL');135 logger.kv('Port', process.env.PORT || '5000');136137 logger.server('Registering routes...');138 const server = await registerRoutes(app);139 logger.success('Routes registered');140141 // Global error handler142 app.use((err: Error & { status?: number; statusCode?: number }, _req: Request, res: Response, _next: NextFunction) => {143 const status = err.status || err.statusCode || 500;144 const message = err.message || "Internal Server Error";145 logger.error(`${status} - ${message}`);146 if (!res.headersSent) {147 res.status(status).json({ message });148 }149 });150151 if (app.get("env") === "development") {152 logger.server('Setting up Vite development server...');153 await setupVite(app, server);154 logger.success('Vite development server ready');155 } else {156 serveStatic(app);157 logger.success('Static file serving ready');158 }159160 server.listen(port, "0.0.0.0", () => {161 logger.banner('SERVER READY', `Listening on http://0.0.0.0:${port}`);162 });163})();164