import express from "express"; import helmet from "helmet"; import { env } from "./config/env"; import { requestContext } from "./shared/middleware/requestContext"; import { requestLogging } from "./shared/middleware/requestLogging"; import { rateLimit } from "./shared/middleware/rateLimit"; import { handleErrors, successResponse } from "./shared/middleware/errorMiddleware"; import { NextFunction, Request, Response } from "express"; import adminRoutes from "./routes/admin"; import integrationRoutes from "./routes/integrations"; import deviceRoutes from "./routes/device"; import merchantRoutes from "./routes/merchant"; import speakerRoutes from "./routes/speaker"; import { startNotificationOrchestrator } from "./shared/orchestrators/notificationOrchestrator"; import { startDynamicQrExpiryScheduler } from "./shared/services/dynamicQrExpiryScheduler"; import { startExportJobWorker } from "./shared/services/exportJobWorker"; import { startMqttSubscriber } from "./shared/services/mqttSubscriber"; import { getDatabaseHealth } from "./shared/services/health"; import path from "node:path"; import fs from "node:fs"; const app = express(); if (env.TRUST_PROXY === "true") { app.set("trust proxy", 1); } const allowedPreviewOrigins = new Set([ "http://127.0.0.1:4173", "http://localhost:4173" ]); app.use((req, res, next) => { const origin = req.header("origin"); if (origin && allowedPreviewOrigins.has(origin)) { res.header("Access-Control-Allow-Origin", origin); res.header("Vary", "Origin"); res.header("Access-Control-Allow-Headers", "authorization, content-type, x-request-id, idempotency-key"); res.header("Access-Control-Allow-Methods", "GET,POST,PATCH,PUT,DELETE,OPTIONS"); } if (req.method === "OPTIONS") { return res.sendStatus(204); } return next(); }); startNotificationOrchestrator(); startDynamicQrExpiryScheduler(); startExportJobWorker(); startMqttSubscriber(); app.use( helmet({ hidePoweredBy: true, referrerPolicy: { policy: "no-referrer" }, hsts: process.env.NODE_ENV === "production" ? { maxAge: 15552000, includeSubDomains: true } : false, crossOriginResourcePolicy: { policy: "cross-origin" }, contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], scriptSrc: ["'self'", "'unsafe-inline'", "'unsafe-eval'", "https://cdn.tailwindcss.com"], scriptSrcAttr: ["'unsafe-inline'"], styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"], fontSrc: ["'self'", "https://fonts.gstatic.com", "data:"], imgSrc: ["'self'", "data:", "https://lh3.googleusercontent.com", "https://*.googleusercontent.com"], connectSrc: ["'self'"], objectSrc: ["'none'"], baseUri: ["'self'"] } } }) ); app.use(express.json({ limit: env.JSON_BODY_LIMIT })); app.use(requestContext); app.use(requestLogging); const loginLimiter = rateLimit({ name: "login", windowMs: env.RATE_LIMIT_LOGIN_WINDOW_MS, max: env.RATE_LIMIT_LOGIN_MAX }); const deviceLimiter = rateLimit({ name: "device", windowMs: env.RATE_LIMIT_DEVICE_WINDOW_MS, max: env.RATE_LIMIT_DEVICE_MAX, key: (req) => `${req.ip}:${req.header("x-device-id") || "legacy"}` }); const adminWriteLimiter = rateLimit({ name: "admin_write", windowMs: env.RATE_LIMIT_ADMIN_WRITE_WINDOW_MS, max: env.RATE_LIMIT_ADMIN_WRITE_MAX, key: (req) => `${req.ip}:${req.header("authorization") || "anonymous"}` }); app.get("/", (_req, res) => { res.redirect(302, "/ui/soundbox-ops"); }); app.get("/favicon.ico", (_req, res) => { res .type("image/svg+xml") .send( `` ); }); function resolveUiPageFile(slug: string) { const workspaceRoot = process.cwd(); const candidates = [ path.resolve(workspaceRoot, "ui", slug, "index.html"), path.resolve(workspaceRoot, "ui", slug.replace(/_/g, "-"), "index.html"), path.resolve(workspaceRoot, "ui", slug.replace(/-/g, "_"), "index.html") ]; return candidates.find((candidate) => fs.existsSync(candidate)) || null; } app.get("/ui", (_req, res) => { res.redirect(302, "/ui/soundbox-ops"); }); app.get("/ui/hub", (_req, res) => { const filePath = path.resolve(process.cwd(), "ui/hub.html"); res.sendFile(filePath); }); app.use("/ui/shared", express.static(path.resolve(process.cwd(), "ui", "shared"))); app.get("/ui/:page", (req, res, next) => { const filePath = resolveUiPageFile(req.params.page); if (!filePath) { return next(); } res.sendFile(filePath); }); app.use("/admin/login", loginLimiter); app.use("/merchant/login", loginLimiter); app.use("/admin", (req, res, next) => { if (req.path === "/login") { return next(); } if (["POST", "PATCH", "PUT", "DELETE"].includes(req.method)) { return adminWriteLimiter(req, res, next); } return next(); }); app.use("/device", deviceLimiter); app.use("/speaker", deviceLimiter); app.use("/integrations", rateLimit({ name: "integrations", windowMs: env.RATE_LIMIT_DEVICE_WINDOW_MS, max: env.RATE_LIMIT_DEVICE_MAX })); app.use("/admin", adminRoutes); app.use("/merchant", merchantRoutes); app.use("/integrations", integrationRoutes); app.use("/device", deviceRoutes); app.use("/speaker", speakerRoutes); app.use((err: Error, _req: Request, res: Response, next: NextFunction) => { handleErrors(err, _req, res, next); }); app.get("/health", (req, res) => { res.status(200).json( successResponse(req, { status: "healthy", time: new Date().toISOString() }) ); }); app.get("/health/deep", async (req, res) => { const database = await getDatabaseHealth(); const healthy = database.status === "ok"; res.status(healthy ? 200 : 503).json( successResponse(req, { status: healthy ? "healthy" : "degraded", time: new Date().toISOString(), checks: { database } }) ); }); app.use((req, res) => { res.status(404).json({ code: "NOT_FOUND", message: `Route ${req.path} not found`, request_id: req.requestId, timestamp: new Date().toISOString() }); }); export default app;