import express, { type Express, type Request, type Response, type NextFunction, } from "express"; import cors from "cors"; import cookieParser from "cookie-parser"; import pinoHttp from "pino-http"; import path from "path"; import multer from "multer"; import { logger } from "./lib/logger"; import router from "./routes"; const app: Express = express(); app.set("trust proxy", 1); app.use( pinoHttp({ logger, serializers: { req(req) { return { id: req.id, method: req.method, url: req.url?.split("?")[0], }; }, res(res) { return { statusCode: res.statusCode, }; }, }, }), ); app.use(cors({ origin: true, credentials: true })); app.use(cookieParser()); app.use(express.json({ limit: "50mb" })); app.use(express.urlencoded({ extended: true, limit: "50mb" })); // API routes app.use("/api", router); // ── Static frontend serving (production / Docker / HF Spaces) ───────────── if (process.env.NODE_ENV === "production") { const frontendDist = process.env.FRONTEND_DIST || path.join(__dirname, "../../raqim/dist/public"); app.use(express.static(frontendDist)); // SPA fallback — serve index.html for every non-API path // Express 5 / path-to-regexp v8 requires named wildcard syntax app.get("/{*path}", (_req: Request, res: Response, _next: NextFunction) => { res.sendFile(path.join(frontendDist, "index.html")); }); } // ── Global error handler — must have 4 params for Express to treat as error handler ── // Catches multer errors, route errors, and anything else that calls next(err) app.use((err: unknown, req: Request, res: Response, _next: NextFunction) => { // Log to pino and stderr so it always appears in HF Space container logs const error = err instanceof Error ? err : new Error(String(err)); logger.error({ err: error, url: req.url, method: req.method }, "unhandled error"); console.error("[RAQIM ERROR]", req.method, req.url, error.message, error.stack); // Multer-specific errors (file too large, wrong field name, etc.) if (err instanceof multer.MulterError) { const msg = err.code === "LIMIT_FILE_SIZE" ? "حجم الملف يتجاوز الحد المسموح (500 ميغابايت)" : `خطأ في رفع الملف: ${err.message}`; res.status(400).json({ error: "upload_error", message: msg }); return; } // Generic error — avoid leaking stack traces to clients const status = (err as any)?.status ?? (err as any)?.statusCode ?? 500; res.status(status).json({ error: "server_error", message: error.message || "حدث خطأ غير متوقع في الخادم", }); }); export default app;