Spaces:
Sleeping
Sleeping
| 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; | |