// FILE: backend/src/index.js (ESM) ไฟล์ฉบับ “Bearer-only” // FILE: src/index.js (ESM) import fs from "node:fs"; import express from "express"; import cors from "cors"; import sql from "./db/index.js"; import healthRouter from "./routes/health.js"; import { authJwt } from "./middleware/authJwt.js"; import { loadPrincipalMw } from "./middleware/loadPrincipal.js"; // ROUTES import authRoutes from "./routes/auth.js"; import lookupRoutes from "./routes/lookup.js"; import organizationsRoutes from "./routes/organizations.js"; import projectsRoutes from "./routes/projects.js"; import correspondencesRoutes from "./routes/correspondences.js"; import rfasRoutes from "./routes/rfas.js"; import drawingsRoutes from "./routes/drawings.js"; import transmittalsRoutes from "./routes/transmittals.js"; import contractsRoutes from "./routes/contracts.js"; import contractDwgRoutes from "./routes/contract_dwg.js"; import categoriesRoutes from "./routes/categories.js"; import volumesRoutes from "./routes/volumes.js"; import uploadsRoutes from "./routes/uploads.js"; import usersRoutes from "./routes/users.js"; import permissionsRoutes from "./routes/permissions.js"; const PORT = Number(process.env.PORT || 3001); const NODE_ENV = process.env.NODE_ENV || "development"; const FRONTEND_ORIGIN = process.env.FRONTEND_ORIGIN || "https://lcbp3.np-dms.work"; const ALLOW_ORIGINS = [ "http://localhost:3000", "http://127.0.0.1:3000", FRONTEND_ORIGIN, ...(process.env.CORS_ALLOWLIST ? process.env.CORS_ALLOWLIST.split(",").map((x) => x.trim()).filter(Boolean) : []), ].filter(Boolean); const LOG_DIR = process.env.BACKEND_LOG_DIR || "/app/logs"; try { if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true }); } catch (e) { console.warn("[WARN] Cannot ensure LOG_DIR:", LOG_DIR, e?.message); } const app = express(); app.set("trust proxy", 1); // CORS: allow list app.use( cors({ origin(origin, cb) { if (!origin) return cb(null, true); // server-to-server / curl cb(null, ALLOW_ORIGINS.includes(origin)); }, credentials: false, // Bearer-only methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], allowedHeaders: [ "Content-Type", "Authorization", "X-Requested-With", "Accept", "Origin", "Referer", "User-Agent", "Cache-Control", "Pragma", ], exposedHeaders: ["Content-Disposition", "Content-Length"], }) ); app.options( "*", cors({ origin(origin, cb) { if (!origin) return cb(null, true); cb(null, ALLOW_ORIGINS.includes(origin)); }, credentials: false, }) ); app.use(express.json({ limit: "10mb" })); app.use(express.urlencoded({ extended: true, limit: "10mb" })); // minimal access log app.use((req, _res, next) => { console.log(`[REQ] ${req.method} ${req.originalUrl}`); next(); }); // health/info (เปิดทั้ง /health, /livez, /readyz, /info) app.get("/health", async (_req, res) => { try { const [[{ now }]] = await sql.query("SELECT NOW() AS now"); res.json({ status: "ok", db: "ok", now }); } catch (e) { res.status(500).json({ status: "degraded", db: "fail", error: e?.message }); } }); app.get("/livez", (_req, res) => res.send("ok")); app.get("/readyz", async (_req, res) => { try { await sql.query("SELECT 1"); res.send("ready"); } catch { res.status(500).send("not-ready"); } }); app.get("/info", (_req, res) => res.json({ name: "dms-backend", env: NODE_ENV, version: process.env.APP_VERSION || "0.5.0", commit: process.env.GIT_COMMIT || undefined, }) ); // ---------- Public (no auth) ---------- app.use("/api", healthRouter); app.use("/api/auth", authRoutes); // ---------- Protected (Bearer + Principal) ---------- app.use("/api", authJwt(), loadPrincipalMw()); app.use("/api/lookup", lookupRoutes); app.use("/api/organizations", organizationsRoutes); app.use("/api/projects", projectsRoutes); app.use("/api/correspondences", correspondencesRoutes); app.use("/api/rfas", rfasRoutes); app.use("/api/drawings", drawingsRoutes); app.use("/api/transmittals", transmittalsRoutes); app.use("/api/contracts", contractsRoutes); app.use("/api/contract-dwg", contractDwgRoutes); app.use("/api/categories", categoriesRoutes); app.use("/api/volumes", volumesRoutes); app.use("/api/uploads", uploadsRoutes); app.use("/api/users", usersRoutes); app.use("/api/permissions", permissionsRoutes); // 404 / error app.use((req, res) => res.status(404).json({ error: "NOT_FOUND", path: req.originalUrl }) ); // eslint-disable-next-line no-unused-vars app.use((err, _req, res, _next) => { console.error("[UNHANDLED ERROR]", err); res.status(err?.status || 500).json({ error: "SERVER_ERROR" }); }); // START const server = app.listen(PORT, () => { console.log(`Backend API listening on ${PORT} (env=${NODE_ENV})`); }); // Shutdown async function shutdown(signal) { try { console.log(`[SHUTDOWN] ${signal} received`); await new Promise((resolve) => server.close(resolve)); try { await sql.end(); } catch {} console.log("[SHUTDOWN] complete"); process.exit(0); } catch (e) { console.error("[SHUTDOWN] error", e); process.exit(1); } } process.on("SIGTERM", () => shutdown("SIGTERM")); process.on("SIGINT", () => shutdown("SIGINT")); export default app;