Merge pull request 'fea/thanh-02022026-news' (#16) from fea/thanh-02022026-news into main

Reviewed-on: UKSOURCE/cms.hailearning.edu.vn#16
This commit is contained in:
2026-02-04 09:23:00 +00:00
9 changed files with 48 additions and 10 deletions

View File

@@ -91,7 +91,6 @@ const blogSchema = new mongoose.Schema({
}); });
// Indexes // Indexes
blogSchema.index({ slug: 1 });
blogSchema.index({ status: 1, createdAt: -1 }); blogSchema.index({ status: 1, createdAt: -1 });
blogSchema.index({ category: 1, status: 1 }); blogSchema.index({ category: 1, status: 1 });
blogSchema.index({ isFeatured: 1, status: 1 }); blogSchema.index({ isFeatured: 1, status: 1 });

View File

@@ -30,7 +30,6 @@ const blogCategorySchema = new mongoose.Schema({
}); });
// Indexes // Indexes
blogCategorySchema.index({ slug: 1 });
blogCategorySchema.index({ isActive: 1, name: 1 }); blogCategorySchema.index({ isActive: 1, name: 1 });
// Remove __v from JSON output // Remove __v from JSON output

View File

@@ -26,7 +26,6 @@ const blogTagSchema = new mongoose.Schema({
}); });
// Indexes // Indexes
blogTagSchema.index({ slug: 1 });
blogTagSchema.index({ isActive: 1, name: 1 }); blogTagSchema.index({ isActive: 1, name: 1 });
// Remove __v from JSON output // Remove __v from JSON output

View File

@@ -179,9 +179,10 @@ const DetailedViewSchema = new mongoose.Schema(
// Main VisaCountry Schema (Individual country object) // Main VisaCountry Schema (Individual country object)
const VisaCountrySchema = new mongoose.Schema( const VisaCountrySchema = new mongoose.Schema(
{ {
id: { type: Number, required: true, index: true }, // Không dùng `index: true` ở đây vì đã tạo index riêng cho hero.summaryList.* bên dưới
name: { type: String, required: true, index: true }, id: { type: Number, required: true },
slug: { type: String, required: true, unique: true, index: true }, name: { type: String, required: true },
slug: { type: String, required: true, unique: true },
icon: { type: String, default: "" }, icon: { type: String, default: "" },
services: { services: {
type: [String], type: [String],

View File

@@ -37,6 +37,7 @@
"express": "^4.18.2", "express": "^4.18.2",
"express-ejs-layouts": "^2.5.1", "express-ejs-layouts": "^2.5.1",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"connect-mongo": "^5.1.0",
"i18n": "^0.15.1", "i18n": "^0.15.1",
"mongoose": "^8.16.1", "mongoose": "^8.16.1",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",

View File

@@ -3,6 +3,7 @@ const dotenv = require("dotenv");
const path = require("path"); const path = require("path");
const cookieParser = require("cookie-parser"); const cookieParser = require("cookie-parser");
const session = require("express-session"); const session = require("express-session");
const MongoStore = require("connect-mongo");
const flash = require("connect-flash"); const flash = require("connect-flash");
const expressLayouts = require("express-ejs-layouts"); const expressLayouts = require("express-ejs-layouts");
const fs = require("fs"); const fs = require("fs");
@@ -56,13 +57,22 @@ app.use(
// Serve other public files // Serve other public files
app.use(express.static(path.join(__dirname, "public"))); app.use(express.static(path.join(__dirname, "public")));
// Session configuration // Session configuration (using MongoDB store to avoid logout khi server restart)
app.use( app.use(
session({ session({
secret: process.env.SESSION_SECRET || "secret", secret: process.env.SESSION_SECRET || "secret",
resave: true, resave: false,
saveUninitialized: false, saveUninitialized: false,
cookie: { maxAge: 1000 * 60 * 60 * 24 }, // 24 hours 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",
},
}), }),
); );

View File

@@ -230,7 +230,7 @@
</div> </div>
</div> </div>
<div class="col-md-4 border-end border-top"> <div class="col-md-4 border-top">
<div class="p-4"> <div class="p-4">
<div class="d-flex align-items-center mb-3"> <div class="d-flex align-items-center mb-3">
<div class="rounded-circle d-flex align-items-center justify-content-center me-3" <div class="rounded-circle d-flex align-items-center justify-content-center me-3"
@@ -273,6 +273,23 @@
</a> </a>
</div> </div>
</div> </div>
<div class="col-md-4 border-top">
<div class="p-4">
<div class="d-flex align-items-center mb-3">
<div class="rounded-circle d-flex align-items-center justify-content-center me-3"
style="width: 50px; height: 50px; background-color: rgba(184, 183, 106, 0.1);">
<i class="fas fa-blog fa-lg" style="color: var(--primary-color);"></i>
</div>
<div>
<h5 class="mb-0">Blog</h5>
<p class="text-muted mb-0 small">Manage blog posts</p>
</div>
</div>
<a href="/admin/blog" class="btn btn-sm btn-primary w-100 mt-2">
<i class="fas fa-edit me-2"></i>Edit
</a>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -85,6 +85,14 @@
<a class="nav-link" href="/admin/camp-location">Camp Location</a> <a class="nav-link" href="/admin/camp-location">Camp Location</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/admin/blog">Blog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/blog-category">Blog Category</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/admin/blog-tag">Blog Tag</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/admin/form">Form</a> <a class="nav-link" href="/admin/form">Form</a>
</li> </li>

View File

@@ -769,6 +769,10 @@
</li> </li>
</ul> </ul>
</li> </li>
<li class="nav-item">
<a class="nav-link <%= currentPath === '/admin/blog' ? 'active' : '' %>"
href="/admin/blog">Blog</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link <%= currentPath === '/admin/contact' ? 'active' : '' %>" <a class="nav-link <%= currentPath === '/admin/contact' ? 'active' : '' %>"
href="/admin/contact">Contact Us</a> href="/admin/contact">Contact Us</a>