import "dotenv/config"; import express from "express"; import cors from "cors"; import helmet from "helmet"; import rateLimit from "express-rate-limit"; import { chatRouter } from "./routes/chat"; import { projectsRouter } from "./routes/projects"; import { projectChatRouter } from "./routes/projectChat"; import { documentsRouter } from "./routes/documents"; import { tabularRouter } from "./routes/tabular"; import { workflowsRouter } from "./routes/workflows"; import { userRouter } from "./routes/user"; import { downloadsRouter } from "./routes/downloads"; const app = express(); const PORT = process.env.PORT ?? 3001; const isProduction = process.env.NODE_ENV === "production"; function envInt(name: string, fallback: number): number { const raw = process.env[name]; if (!raw) return fallback; const parsed = Number.parseInt(raw, 10); return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback; } function minutes(value: number): number { return value * 60 * 1000; } function hours(value: number): number { return minutes(value * 60); } function makeLimiter(options: { windowMs: number; max: number; message?: string; }) { return rateLimit({ windowMs: options.windowMs, max: options.max, standardHeaders: true, legacyHeaders: false, skip: (req) => req.method === "OPTIONS", message: { detail: options.message ?? "Too many requests. Please try again later.", }, }); } const generalLimiter = makeLimiter({ windowMs: minutes(envInt("RATE_LIMIT_GENERAL_WINDOW_MINUTES", 15)), max: envInt("RATE_LIMIT_GENERAL_MAX", 300), }); const chatLimiter = makeLimiter({ windowMs: minutes(envInt("RATE_LIMIT_CHAT_WINDOW_MINUTES", 15)), max: envInt("RATE_LIMIT_CHAT_MAX", 30), message: "Too many chat requests. Please try again later.", }); const chatCreateLimiter = makeLimiter({ windowMs: minutes(envInt("RATE_LIMIT_CHAT_CREATE_WINDOW_MINUTES", 15)), max: envInt("RATE_LIMIT_CHAT_CREATE_MAX", 60), }); const uploadLimiter = makeLimiter({ windowMs: hours(envInt("RATE_LIMIT_UPLOAD_WINDOW_HOURS", 1)), max: envInt("RATE_LIMIT_UPLOAD_MAX", 50), message: "Too many upload requests. Please try again later.", }); app.disable("x-powered-by"); app.set("trust proxy", envInt("TRUST_PROXY_HOPS", 1)); app.use( helmet({ contentSecurityPolicy: false, crossOriginEmbedderPolicy: false, hsts: isProduction ? { maxAge: 15552000, includeSubDomains: true, } : false, referrerPolicy: { policy: "no-referrer" }, }), ); app.use( cors({ origin: process.env.FRONTEND_URL ?? "http://localhost:3000", credentials: true, }), ); app.use(generalLimiter); app.use(express.json({ limit: "50mb" })); app.post("/chat", chatLimiter); app.post("/projects/:projectId/chat", chatLimiter); app.post("/tabular-review/:reviewId/chat", chatLimiter); app.post("/tabular-review/:reviewId/generate", chatLimiter); app.post("/chat/create", chatCreateLimiter); app.post("/chat/:chatId/generate-title", chatCreateLimiter); app.post("/single-documents", uploadLimiter); app.post("/single-documents/:documentId/versions", uploadLimiter); app.post("/projects/:projectId/documents", uploadLimiter); app.use("/chat", chatRouter); app.use("/projects", projectsRouter); app.use("/projects/:projectId/chat", projectChatRouter); app.use("/single-documents", documentsRouter); app.use("/tabular-review", tabularRouter); app.use("/workflows", workflowsRouter); app.use("/user", userRouter); app.use("/users", userRouter); app.use("/download", downloadsRouter); app.get("/health", (_req, res) => res.json({ ok: true })); app.listen(PORT, () => { console.log(`Mike backend running on port ${PORT}`); });