SPB Git

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%
5.5 KB · 164 lines typescript
Raw Blame History
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