File size: 2,773 Bytes
4080217
 
 
 
 
 
0e14acb
 
 
 
4080217
0e14acb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43d336e
 
0e14acb
 
 
 
4080217
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0e14acb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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;