import express from "express"; import cors from "cors"; import dotenv from "dotenv"; import fs from "node:fs"; import path from "node:path"; import multer from "multer"; import rubricRoutes from "./routes/rubricRoutes.js"; import essayRoutes from "./routes/essayRoutes.js"; import authRoutes from "./routes/authRoutes.js"; import evaluationRoutes from "./routes/evaluationRoutes.js"; dotenv.config({ quiet: true }); const app = express(); // Behind a host's proxy (Hugging Face, Render) the client address arrives in // X-Forwarded-For; without this every visitor would share the proxy's IP for rate limits. app.set("trust proxy", true); // Only the website itself may call the API from a browser. CORS_ORIGINS lists the allowed // sites, comma-separated; locally it defaults to the Vite dev server. When the website is // served by this same server (the hosted setup), requests are same-origin and CORS does // not apply at all. const allowedOrigins = (process.env.CORS_ORIGINS || "http://localhost:5173,http://127.0.0.1:5173") .split(",") .map((o) => o.trim().replace(/\/+$/, "")) .filter(Boolean); app.use( cors({ origin: (origin, cb) => cb(null, !origin || allowedOrigins.includes(origin)), }) ); app.use(express.json({ limit: "200kb" })); app.use("/api/auth", authRoutes); app.use("/api", rubricRoutes); app.use("/api", essayRoutes); app.use("/api", evaluationRoutes); // Hosted, the built website is served from here too: one address, no CORS, one process. const frontendDist = process.env.FRONTEND_DIST ? path.resolve(process.env.FRONTEND_DIST) : null; if (frontendDist && fs.existsSync(path.join(frontendDist, "index.html"))) { app.use(express.static(frontendDist)); // Any non-API path is the single-page app. app.get(/^\/(?!api\/).*/, (req, res) => res.sendFile(path.join(frontendDist, "index.html"))); } else { app.get("/", (req, res) => { res.send("Essay AI Backend Running"); }); } // Oversized uploads and oversized JSON bodies become readable messages, not stack traces. app.use((err, req, res, next) => { if (err instanceof multer.MulterError && err.code === "LIMIT_FILE_SIZE") { return res.status(413).json({ error: "File too large", message: "The photo is too large. Take it again at a lower resolution." }); } if (err?.type === "entity.too.large") { return res.status(413).json({ error: "Too large", message: "The answer is too long." }); } if (err?.message === "Only image files can be uploaded") { return res.status(400).json({ error: "Wrong file type", message: err.message }); } next(err); }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); });