05.1 ปรบปรง backend ทงหมด และ frontend/login

This commit is contained in:
admin
2025-10-01 11:14:11 +07:00
parent 5be0f5407b
commit 905afb56f5
43 changed files with 2285 additions and 2834 deletions

View File

@@ -1,72 +1,6 @@
// FILE: src/index.js (ESM)
// Main entry point for the backend API server
// - Sets up Express app with middleware, routes, error handling
// - Connects to database
// - Starts server and handles graceful shutdown
// ==========================
// Context:
// - Node.js >= 18 (ESM)
// - Express.js 4/5
// - MySQL database (using mysql2/promise)
// ==========================
// Features:
// - CORS with dynamic origin checking
// - Cookie parsing
// - JSON and URL-encoded body parsing
// - Access logging
// - Health, livez, readyz, info endpoints
// - JWT authentication middleware
// - Principal loading middleware
// - Modular route handlers for various resources
// - 404 and error handling middleware
// - Graceful shutdown on SIGTERM/SIGINT
// ==========================
// Assumptions:
// - Environment variables for configuration (e.g., PORT, DB connection, FRONTEND_ORIGIN)
// - Database connection module at ./db/index.js
// - Middleware modules for auth, permissions, principal loading
// - Route modules for different API resources
// - Logs directory exists or can be created
// - Code is written in JavaScript (ESM) and runs in Node.js environment
// - Uses ES6+ features for cleaner and more maintainable code
// ==========================
// Notes:
// - Adjust CORS origins as needed for your frontend applications
// - Ensure proper error handling and logging as per your requirements
// - Customize middleware and routes as per your application's needs
// ==========================
// Best Practices Followed:
// - Assumes existence of necessary database tables and columns
// - Assumes existence of necessary middleware and utility functions
// - Assumes Express.js app is set up to use this router for /api path
// - Assumes existence of necessary environment variables
// - Assumes existence of necessary directories and permissions for file storage
// - Assumes multer is installed and configured
// - Assumes fs and path modules are available for file system operations
// - Assumes sql module is set up for database interactions
// - Assumes middleware modules are correctly implemented and exported
// - Assumes route modules are correctly implemented and exported
// - Uses environment variables for configuration
// - Uses middleware for modular functionality
// - Uses async/await for asynchronous operations
// - Uses try/catch for error handling in async functions (if needed)
// - Uses parameterized queries to prevent SQL injection
// - Uses HTTP status codes for responses (e.g., 404 for not found, 400 for bad request)
// - Uses JSON responses for API endpoints
// - Uses destructuring and default parameters for cleaner function signatures
// - Uses best practices for Express.js route handling
// - Uses modular code structure for maintainability
// - Uses comments for documentation and clarity
// - Uses ES6+ features for cleaner and more maintainable code
// - Uses template literals for SQL query construction
// - Uses array methods for filtering and joining conditions
// - Uses utility functions for common tasks (e.g., building SQL WHERE clauses)
// ==========================
// FILE: backend/src/index.js (ESM) ไฟล์ฉบับ “Bearer-only”
import fs from "node:fs";
import path from "node:path";
import express from "express";
import cookieParser from "cookie-parser";
import cors from "cors";
import sql from "./db/index.js";
@@ -91,93 +25,87 @@ import uploadsRoutes from "./routes/uploads.js";
import usersRoutes from "./routes/users.js";
import permissionsRoutes from "./routes/permissions.js";
/* ==========================
* CONFIG
* ========================== */
const PORT = Number(process.env.PORT || 3001);
const NODE_ENV = process.env.NODE_ENV || "production";
const NODE_ENV = process.env.NODE_ENV || "development";
// Origin ของ Frontend (ตั้งผ่าน ENV ต่อ environment; dev ใช้ localhost)
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);
// ที่เก็บ log ภายใน container ถูก bind ไปที่ /share/Container/dms/logs/backend
const LOG_DIR = process.env.BACKEND_LOG_DIR || "/app/logs";
// สร้างโฟลเดอร์ log ถ้ายังไม่มี
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);
}
/* ==========================
* APP INIT
* ========================== */
const app = express();
// ✅ อยู่หลัง NPM/Reverse proxy → ให้ trust proxy เพื่อให้ cookie secure / proto ทำงานถูก
app.set("trust proxy", 1);
// CORS แบบกำหนด origin ตามรายการที่อนุญาต + อนุญาต credentials (จำเป็นสำหรับ cookie)
// CORS: allow list
app.use(
cors({
origin(origin, cb) {
if (!origin) return cb(null, true); // server-to-server / curl
return cb(null, ALLOW_ORIGINS.includes(origin));
cb(null, ALLOW_ORIGINS.includes(origin));
},
credentials: true,
credentials: false, // Bearer-only
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"],
allowedHeaders: [
"Content-Type",
"Authorization",
"X-Requested-With",
"Accept",
"Origin",
"Referer",
"User-Agent",
"Cache-Control",
"Pragma",
],
exposedHeaders: ["Content-Disposition", "Content-Length"],
})
);
// preflight
app.options(
"*",
cors({
origin(origin, cb) {
if (!origin) return cb(null, true);
return cb(null, ALLOW_ORIGINS.includes(origin));
cb(null, ALLOW_ORIGINS.includes(origin));
},
credentials: true,
credentials: false,
})
);
app.use(cookieParser());
// Payload limits
app.use(express.json({ limit: "10mb" }));
app.use(express.urlencoded({ extended: true, limit: "10mb" }));
// Access log (ขั้นต่ำ)
// minimal access log
app.use((req, _res, next) => {
console.log(`[REQ] ${req.method} ${req.originalUrl}`);
next();
});
/* ==========================
* HEALTH / READY / INFO
* ========================== */
app.get("/health", async (req, res) => {
// health/info (เปิดทั้ง /health, /livez, /readyz, /info)
app.get("/health", async (_req, res) => {
try {
const [[{ now }]] = await sql.query("SELECT NOW() AS now");
return res.json({ status: "ok", db: "ok", now });
res.json({ status: "ok", db: "ok", now });
} catch (e) {
return res
.status(500)
.json({ status: "degraded", db: "fail", error: e?.message });
res.status(500).json({ status: "degraded", db: "fail", error: e?.message });
}
});
// Kubernetes-style endpoints (ถ้าใช้)
app.get("/livez", (req, res) => res.send("ok"));
app.get("/readyz", async (req, res) => {
app.get("/livez", (_req, res) => res.send("ok"));
app.get("/readyz", async (_req, res) => {
try {
await sql.query("SELECT 1");
res.send("ready");
@@ -185,26 +113,20 @@ app.get("/readyz", async (req, res) => {
res.status(500).send("not-ready");
}
});
app.get("/info", (req, res) => {
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,
});
});
})
);
/* ==========================
* ROUTES
* ========================== */
// /api/health (ถอดจาก healthRouter)
// ---------- Public (no auth) ----------
app.use("/api", healthRouter);
// ✅ auth กลุ่มนี้ "ไม่ต้อง" ผ่าน authJwt
app.use("/api/auth", authRoutes);
// จากนี้ไป ทุก /api/* ต้องผ่าน JWT + principal
// ---------- Protected (Bearer + Principal) ----------
app.use("/api", authJwt(), loadPrincipalMw());
app.use("/api/lookup", lookupRoutes);
@@ -222,33 +144,22 @@ app.use("/api/uploads", uploadsRoutes);
app.use("/api/users", usersRoutes);
app.use("/api/permissions", permissionsRoutes);
/* ==========================
* NOT FOUND & ERROR HANDLERS
* ========================== */
app.use((req, res) => {
res.status(404).json({ error: "NOT_FOUND", path: req.originalUrl });
});
// 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) => {
app.use((err, _req, res, _next) => {
console.error("[UNHANDLED ERROR]", err);
const status = err?.status || 500;
res.status(status).json({
error: "SERVER_ERROR",
message: NODE_ENV === "production" ? undefined : err?.message,
});
res.status(err?.status || 500).json({ error: "SERVER_ERROR" });
});
/* ==========================
* START SERVER
* ========================== */
// START
const server = app.listen(PORT, () => {
console.log(`Backend API listening on ${PORT} (env=${NODE_ENV})`);
});
/* ==========================
* GRACEFUL SHUTDOWN
* ========================== */
// Shutdown
async function shutdown(signal) {
try {
console.log(`[SHUTDOWN] ${signal} received`);