forked from UKSOURCE/cms.hailearning.edu.vn
Merge pull request 'API Country' (#10) from fea/hoang-04022026-Visa/VisaDetail into main
Reviewed-on: UKSOURCE/cms.hailearning.edu.vn#10
This commit is contained in:
@@ -44,7 +44,15 @@ const addBaseUrlToImages = (data, baseUrl) => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
const Visa = require("../models/visa");
|
const Visa = require("../models/visa");
|
||||||
|
const slugify = require("slugify");
|
||||||
|
const createSlug = (text) => {
|
||||||
|
return slugify(text, {
|
||||||
|
lower: true, // Chuyển về chữ thường
|
||||||
|
strict: true, // Loại bỏ ký tự đặc biệt
|
||||||
|
locale: "vi", // Xử lý tiếng Việt chuẩn
|
||||||
|
trim: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
// -------------------- Helper Functions --------------------
|
// -------------------- Helper Functions --------------------
|
||||||
|
|
||||||
// Get visa data from MongoDB
|
// Get visa data from MongoDB
|
||||||
@@ -108,24 +116,65 @@ exports.index = async (req, res) => {
|
|||||||
|
|
||||||
// Get single country for edit
|
// Get single country for edit
|
||||||
exports.getCountry = async (req, res) => {
|
exports.getCountry = async (req, res) => {
|
||||||
|
console.log("--------------------------------------------------");
|
||||||
|
console.log("🚀 [GET] Request nhận được tại /visa/edit/:id");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { slug } = req.params;
|
const { id } = req.params;
|
||||||
|
console.log("📍 ID từ Params (URL):", id, "| Kiểu dữ liệu:", typeof id);
|
||||||
|
|
||||||
const visaData = await getVisaData();
|
const visaData = await getVisaData();
|
||||||
|
|
||||||
if (!visaData.hero || !visaData.hero.summaryList) {
|
// Kiểm tra cấu trúc dữ liệu tổng
|
||||||
return res.status(404).json({ error: "Visa data not found" });
|
if (!visaData) {
|
||||||
|
console.error("❌ Lỗi: Hàm getVisaData() trả về null/undefined");
|
||||||
|
return res.status(404).json({ error: "Dữ liệu gốc không tồn tại" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const country = visaData.hero.summaryList.find((c) => c.slug === slug);
|
if (!visaData.hero || !visaData.hero.summaryList) {
|
||||||
|
console.error("❌ Lỗi: Cấu trúc visaData.hero.summaryList không hợp lệ");
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json({ error: "Không tìm thấy danh sách quốc gia" });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"📊 Tổng số quốc gia hiện có trong mảng:",
|
||||||
|
visaData.hero.summaryList.length,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Tìm quốc gia theo ID
|
||||||
|
const targetId = parseInt(id);
|
||||||
|
console.log("🔍 Đang tìm kiếm Quốc gia có ID (sau khi parse):", targetId);
|
||||||
|
|
||||||
|
const country = visaData.hero.summaryList.find((c) => {
|
||||||
|
// Log từng phần tử để kiểm tra kiểu dữ liệu của c.id trong DB
|
||||||
|
// console.log(`Checking country: ${c.name} | ID in DB: ${c.id} (${typeof c.id})`);
|
||||||
|
return c.id === targetId;
|
||||||
|
});
|
||||||
|
|
||||||
if (!country) {
|
if (!country) {
|
||||||
return res.status(404).json({ error: `Country "${slug}" not found` });
|
console.warn(`⚠️ Không tìm thấy quốc gia nào khớp với ID: ${targetId}`);
|
||||||
|
// In ra danh sách ID hiện có để so sánh
|
||||||
|
const existingIds = visaData.hero.summaryList.map((c) => c.id);
|
||||||
|
console.log("🆔 Các ID hiện có trong Database:", existingIds);
|
||||||
|
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: `Không tìm thấy quốc gia có ID: ${id}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res.json(country);
|
console.log("✅ Tìm thấy dữ liệu quốc gia:", country.name);
|
||||||
|
|
||||||
|
// 3. Trả về dữ liệu
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
country: country,
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Get country error:", err);
|
console.error("🔴 Lỗi nghiêm trọng tại getCountry Controller:", err);
|
||||||
res.status(500).json({ error: "Error loading country" });
|
res.status(500).json({ error: "Lỗi hệ thống khi tải thông tin quốc gia" });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -195,19 +244,12 @@ exports.addCountry = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if (!req.body.name || !req.body.slug) {
|
if (!req.body.name) {
|
||||||
return res.status(400).json({ error: "Name and slug are required" });
|
return res.status(400).json({ error: "Name is required" });
|
||||||
}
|
|
||||||
|
|
||||||
// Check if slug already exists
|
|
||||||
const slugExists = visaData.hero.summaryList.some(
|
|
||||||
(c) => c.slug === req.body.slug,
|
|
||||||
);
|
|
||||||
if (slugExists) {
|
|
||||||
return res
|
|
||||||
.status(400)
|
|
||||||
.json({ error: `Slug "${req.body.slug}" already exists` });
|
|
||||||
}
|
}
|
||||||
|
const finalSlug = req.body.slug
|
||||||
|
? createSlug(req.body.slug)
|
||||||
|
: createSlug(req.body.name);
|
||||||
|
|
||||||
// Parse services array
|
// Parse services array
|
||||||
let services = [];
|
let services = [];
|
||||||
@@ -240,7 +282,7 @@ exports.addCountry = async (req, res) => {
|
|||||||
const newCountry = {
|
const newCountry = {
|
||||||
id: req.body.id || getNextCountryId(visaData.hero.summaryList),
|
id: req.body.id || getNextCountryId(visaData.hero.summaryList),
|
||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
slug: req.body.slug,
|
slug: finalSlug,
|
||||||
icon: req.body.icon || "",
|
icon: req.body.icon || "",
|
||||||
services: services,
|
services: services,
|
||||||
...(detailedView && { detailedView }),
|
...(detailedView && { detailedView }),
|
||||||
@@ -278,77 +320,88 @@ exports.addCountry = async (req, res) => {
|
|||||||
// Update single country
|
// Update single country
|
||||||
exports.updateCountry = async (req, res) => {
|
exports.updateCountry = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { slug } = req.params;
|
// 1. Lấy ID từ params (URL)
|
||||||
|
const { id } = req.params;
|
||||||
let visaData = await getVisaData();
|
let visaData = await getVisaData();
|
||||||
|
|
||||||
if (!visaData.hero || !visaData.hero.summaryList) {
|
// if (!visaData || !visaData.hero || !visaData.hero.summaryList) {
|
||||||
return res.status(400).json({ error: "Invalid visa data structure" });
|
// return res
|
||||||
}
|
// .status(400)
|
||||||
|
// .json({ error: "Cấu trúc dữ liệu Visa không hợp lệ" });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 2. Tìm index theo ID (Chuyển về Number để so sánh chính xác)
|
||||||
const countryIndex = visaData.hero.summaryList.findIndex(
|
const countryIndex = visaData.hero.summaryList.findIndex(
|
||||||
(c) => c.slug === slug,
|
(c) => c.id === parseInt(id),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (countryIndex === -1) {
|
if (countryIndex === -1) {
|
||||||
return res.status(404).json({ error: `Country "${slug}" not found` });
|
return res
|
||||||
|
.status(404)
|
||||||
|
.json({ error: `Không tìm thấy quốc gia có ID: ${id}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentCountry = visaData.hero.summaryList[countryIndex];
|
const currentCountry = visaData.hero.summaryList[countryIndex];
|
||||||
|
let finalSlug = currentCountry.slug;
|
||||||
// Parse services array
|
if (req.body.name) {
|
||||||
let services = currentCountry.services || [];
|
// Nếu name thay đổi, ta có thể cập nhật lại slug (tùy nhu cầu SEO)
|
||||||
if (req.body.services) {
|
// Ở đây ưu tiên: Nếu có slug mới truyền lên thì dùng, không thì tạo từ name mới
|
||||||
if (typeof req.body.services === "string") {
|
finalSlug = req.body.slug
|
||||||
try {
|
? createSlug(req.body.slug)
|
||||||
services = JSON.parse(req.body.services);
|
: createSlug(req.body.name);
|
||||||
} catch (e) {
|
|
||||||
services = [req.body.services];
|
|
||||||
}
|
|
||||||
} else if (Array.isArray(req.body.services)) {
|
|
||||||
services = req.body.services;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
// 3. Xử lý dữ liệu từ req.body
|
||||||
// Parse detailedView if provided (optional)
|
// Vì Client đã gửi JSON stringify, ta lấy trực tiếp hoặc parse nếu cần
|
||||||
let detailedView = currentCountry.detailedView;
|
let services = req.body.services;
|
||||||
if (req.body.detailedView) {
|
if (typeof services === "string") {
|
||||||
try {
|
try {
|
||||||
detailedView =
|
services = JSON.parse(services);
|
||||||
typeof req.body.detailedView === "string"
|
|
||||||
? JSON.parse(req.body.detailedView)
|
|
||||||
: req.body.detailedView;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Could not parse detailedView");
|
services = [services];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update country data
|
let detailedView = req.body.detailedView;
|
||||||
|
if (typeof detailedView === "string") {
|
||||||
|
try {
|
||||||
|
detailedView = JSON.parse(detailedView);
|
||||||
|
} catch (e) {
|
||||||
|
detailedView = currentCountry.detailedView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Cập nhật Object quốc gia
|
||||||
const updatedCountry = {
|
const updatedCountry = {
|
||||||
...currentCountry,
|
...currentCountry, // Giữ các trường cũ
|
||||||
|
id: parseInt(id), // Đảm bảo ID không đổi
|
||||||
name: req.body.name || currentCountry.name,
|
name: req.body.name || currentCountry.name,
|
||||||
slug: req.body.slug || currentCountry.slug, // Allow slug update
|
slug: finalSlug,
|
||||||
icon: req.body.icon || currentCountry.icon,
|
icon: req.body.icon || currentCountry.icon,
|
||||||
services: services,
|
services: Array.isArray(services) ? services : currentCountry.services,
|
||||||
...(detailedView && { detailedView }),
|
detailedView: detailedView || currentCountry.detailedView,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 5. Cập nhật vào mảng chính
|
||||||
visaData.hero.summaryList[countryIndex] = updatedCountry;
|
visaData.hero.summaryList[countryIndex] = updatedCountry;
|
||||||
|
|
||||||
// Update database
|
// 6. Lưu vào Database
|
||||||
const updatedData = {
|
if (visaData.markModified) {
|
||||||
...(visaData.toObject ? visaData.toObject() : visaData),
|
// Bắt buộc với Mongoose khi thay đổi nội dung bên trong Array/Object
|
||||||
};
|
visaData.markModified("hero.summaryList");
|
||||||
|
|
||||||
if (visaData._id) {
|
|
||||||
await Visa.findByIdAndUpdate(visaData._id, updatedData, { new: true });
|
|
||||||
} else {
|
|
||||||
await Visa.create(updatedData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`✅ Country "${updatedCountry.name}" updated successfully`);
|
if (visaData._id) {
|
||||||
|
await visaData.save(); // Sử dụng save() trực tiếp nếu visaData là Mongoose Document
|
||||||
|
} else {
|
||||||
|
await Visa.create(visaData);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`✅ Country "${updatedCountry.name}" updated successfully by ID: ${id}`,
|
||||||
|
);
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: `Country "${updatedCountry.name}" updated successfully`,
|
message: `Quốc gia "${updatedCountry.name}" đã được cập nhật thành công`,
|
||||||
country: updatedCountry,
|
country: updatedCountry,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -385,8 +385,6 @@ router.post(
|
|||||||
ensureAuthenticated,
|
ensureAuthenticated,
|
||||||
insuranceController.update,
|
insuranceController.update,
|
||||||
);
|
);
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
|
|
||||||
// Service routes
|
// Service routes
|
||||||
router.get("/service", ensureAuthenticated, serviceController.index);
|
router.get("/service", ensureAuthenticated, serviceController.index);
|
||||||
@@ -413,7 +411,6 @@ router.post(
|
|||||||
serviceController.updateDetails,
|
serviceController.updateDetails,
|
||||||
);
|
);
|
||||||
|
|
||||||
>>>>>>> a255d09ef0a6eb0c487595aac19cefbf729d78a2
|
|
||||||
// Test Image Paths route
|
// Test Image Paths route
|
||||||
router.get("/test-images", ensureAuthenticated, (req, res) => {
|
router.get("/test-images", ensureAuthenticated, (req, res) => {
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
@@ -478,7 +475,12 @@ router.get("/test-images", ensureAuthenticated, (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Display visa management page
|
// Display visa management page
|
||||||
router.get("/visa", visaController.index);
|
router.get("/visa", ensureAuthenticated, visaController.index);
|
||||||
|
|
||||||
|
// Get country data for editing
|
||||||
|
router.get("/visa/edit/:id", ensureAuthenticated, visaController.getCountry);
|
||||||
|
|
||||||
|
router.get("/visa/country", visaController.apiCountries);
|
||||||
|
|
||||||
// Update hero title
|
// Update hero title
|
||||||
router.post("/visa/update", ensureAuthenticated, visaController.update);
|
router.post("/visa/update", ensureAuthenticated, visaController.update);
|
||||||
@@ -488,7 +490,7 @@ router.post("/visa/add", ensureAuthenticated, visaController.addCountry);
|
|||||||
|
|
||||||
// Update single country
|
// Update single country
|
||||||
router.put(
|
router.put(
|
||||||
"/visa/update/:slug",
|
"/visa/update/:id",
|
||||||
ensureAuthenticated,
|
ensureAuthenticated,
|
||||||
visaController.updateCountry,
|
visaController.updateCountry,
|
||||||
);
|
);
|
||||||
@@ -509,21 +511,61 @@ router.post("/blog/:id/edit", ensureAuthenticated, blogController.update);
|
|||||||
router.post("/blog/:id/delete", ensureAuthenticated, blogController.destroy);
|
router.post("/blog/:id/delete", ensureAuthenticated, blogController.destroy);
|
||||||
|
|
||||||
// Blog Categories Management
|
// Blog Categories Management
|
||||||
router.get("/blog/categories", ensureAuthenticated, blogCategoryController.index);
|
router.get(
|
||||||
router.get("/blog/categories/create", ensureAuthenticated, blogCategoryController.create);
|
"/blog/categories",
|
||||||
router.post("/blog/categories/create", ensureAuthenticated, blogCategoryController.store);
|
ensureAuthenticated,
|
||||||
router.get("/blog/categories/:id/edit", ensureAuthenticated, blogCategoryController.edit);
|
blogCategoryController.index,
|
||||||
router.post("/blog/categories/:id/edit", ensureAuthenticated, blogCategoryController.update);
|
);
|
||||||
router.post("/blog/categories/:id/delete", ensureAuthenticated, blogCategoryController.destroy);
|
router.get(
|
||||||
router.post("/blog/categories/quick-create", ensureAuthenticated, blogCategoryController.quickCreate);
|
"/blog/categories/create",
|
||||||
|
ensureAuthenticated,
|
||||||
|
blogCategoryController.create,
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
"/blog/categories/create",
|
||||||
|
ensureAuthenticated,
|
||||||
|
blogCategoryController.store,
|
||||||
|
);
|
||||||
|
router.get(
|
||||||
|
"/blog/categories/:id/edit",
|
||||||
|
ensureAuthenticated,
|
||||||
|
blogCategoryController.edit,
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
"/blog/categories/:id/edit",
|
||||||
|
ensureAuthenticated,
|
||||||
|
blogCategoryController.update,
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
"/blog/categories/:id/delete",
|
||||||
|
ensureAuthenticated,
|
||||||
|
blogCategoryController.destroy,
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
"/blog/categories/quick-create",
|
||||||
|
ensureAuthenticated,
|
||||||
|
blogCategoryController.quickCreate,
|
||||||
|
);
|
||||||
|
|
||||||
// Blog Tags Management
|
// Blog Tags Management
|
||||||
router.get("/blog/tags", ensureAuthenticated, blogTagController.index);
|
router.get("/blog/tags", ensureAuthenticated, blogTagController.index);
|
||||||
router.get("/blog/tags/create", ensureAuthenticated, blogTagController.create);
|
router.get("/blog/tags/create", ensureAuthenticated, blogTagController.create);
|
||||||
router.post("/blog/tags/create", ensureAuthenticated, blogTagController.store);
|
router.post("/blog/tags/create", ensureAuthenticated, blogTagController.store);
|
||||||
router.get("/blog/tags/:id/edit", ensureAuthenticated, blogTagController.edit);
|
router.get("/blog/tags/:id/edit", ensureAuthenticated, blogTagController.edit);
|
||||||
router.post("/blog/tags/:id/edit", ensureAuthenticated, blogTagController.update);
|
router.post(
|
||||||
router.post("/blog/tags/:id/delete", ensureAuthenticated, blogTagController.destroy);
|
"/blog/tags/:id/edit",
|
||||||
router.post("/blog/tags/quick-create", ensureAuthenticated, blogTagController.quickCreate);
|
ensureAuthenticated,
|
||||||
|
blogTagController.update,
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
"/blog/tags/:id/delete",
|
||||||
|
ensureAuthenticated,
|
||||||
|
blogTagController.destroy,
|
||||||
|
);
|
||||||
|
router.post(
|
||||||
|
"/blog/tags/quick-create",
|
||||||
|
ensureAuthenticated,
|
||||||
|
blogTagController.quickCreate,
|
||||||
|
);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
Reference in New Issue
Block a user