const express = require("express"); const dotenv = require("dotenv"); const path = require("path"); const cookieParser = require("cookie-parser"); const session = require("express-session"); const MongoStore = require("connect-mongo"); const flash = require("connect-flash"); const expressLayouts = require("express-ejs-layouts"); const fs = require("fs"); // Load environment variables dotenv.config(); const connectDB = require("./config/database"); // Connect to MongoDB connectDB(); // Initialize express app const app = express(); // Set view engine app.set("view engine", "ejs"); app.set("views", path.join(__dirname, "views")); // Set up express-ejs-layouts app.use(expressLayouts); app.set("layout", "layouts/main"); app.set("layout extractScripts", true); app.set("layout extractStyles", true); // Middlewares app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(cookieParser()); app.use( "/assets", (req, res, next) => { // Cho phép mọi domain truy cập tài nguyên tĩnh res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Methods", "GET"); next(); }, express.static(path.join(__dirname, "assets")), ); // Map /assets/img to public/img to support frontend paths app.use("/assets/img", express.static(path.join(__dirname, "public", "img"))); // Serve public folder at root (for /js, /img, etc.) app.use(express.static(path.join(__dirname, "public"))); // Serve uploads folder app.use( "/uploads", (req, res, next) => { // Cho phép mọi domain truy cập tài nguyên tĩnh res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Methods", "GET"); next(); }, express.static(path.join(__dirname, "public", "uploads")), ); // Serve other public files app.use(express.static(path.join(__dirname, "public"))); // Session configuration (using MongoDB store to avoid logout khi server restart) app.use( session({ secret: process.env.SESSION_SECRET || "secret", resave: false, saveUninitialized: false, store: MongoStore.create({ mongoUrl: process.env.MONGODB_URI, collectionName: "sessions", ttl: 60 * 60 * 24, // 24 hours (in seconds) }), cookie: { maxAge: 1000 * 60 * 60 * 24, // 24 hours httpOnly: true, sameSite: "lax", }, }), ); // Flash messages app.use(flash()); // Global variables app.use((req, res, next) => { // Lấy flash messages const success_msg = req.flash("success_msg"); const error_msg = req.flash("error_msg"); const error = req.flash("error"); // Lưu vào res.locals để sử dụng trong views res.locals.success_msg = success_msg.length > 0 ? success_msg[0] : null; res.locals.error_msg = error_msg.length > 0 ? error_msg[0] : null; res.locals.error = error.length > 0 ? error[0] : null; // Tạo object flashMessages để sử dụng trong client-side JavaScript res.locals.flashMessagesJSON = JSON.stringify({ success_msg: res.locals.success_msg, error_msg: res.locals.error_msg, error: res.locals.error, }); res.locals.user = req.session.user || null; res.locals.currentPath = req.path; next(); }); // Kiểm tra và tạo thư mục data nếu chưa tồn tại const dataDir = path.join(__dirname, "data"); if (!fs.existsSync(dataDir)) { fs.mkdirSync(dataDir); } // Frontend URL configuration const FRONTEND_URL = process.env.FRONTEND_URL; // Add frontend URL to res.locals for all requests app.use((req, res, next) => { res.locals.frontendUrl = FRONTEND_URL; res.locals.currentPath = req.path; next(); }); // Simple CORS middleware for API endpoints app.use((req, res, next) => { // Allow requests from configured FRONTEND_URL or allow all if not set const origin = req.headers.origin; // Support multiple frontend URLs (dev and production) const allowedOrigins = [ FRONTEND_URL, "http://dev.hailearning.edu.vn", "https://www.hailearning.edu.vn", "http://www.hailearning.edu.vn" ].filter(Boolean); // Remove undefined/empty values const isOriginAllowed = allowedOrigins.includes(origin) || !FRONTEND_URL; if (isOriginAllowed) { res.setHeader("Access-Control-Allow-Origin", origin || "*"); res.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization"); res.setHeader("Access-Control-Allow-Credentials", "true"); } // Handle preflight if (req.method === "OPTIONS") { return res.sendStatus(204); } next(); }); // Routes const authRoutes = require("./routes/auth"); const adminRoutes = require("./routes/admin"); const indexRoutes = require("./routes/index"); app.use("/auth", authRoutes); app.use("/admin", adminRoutes); app.use("/", indexRoutes); // 404 handler app.use((req, res) => { res.status(404); if (req.accepts("html")) return res.render("page/404", { title: "404 - Page Not Found", layout: "layouts/main", }); if (req.accepts("json")) return res.json({ error: "Not found" }); res.type("txt").send("Not found"); }); // Start server const PORT = process.env.PORT || 3001; const HOST = process.env.HOST || "localhost"; app.listen(PORT, HOST, () => { console.log(`🚀 SERVER:[ http://${HOST}:${PORT} ]`); });