174 lines
5.2 KiB
JavaScript
174 lines
5.2 KiB
JavaScript
// 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;
|