From 85f6295824bd98e49dd86ee7b37282508ae8a8d6 Mon Sep 17 00:00:00 2001 From: r2xrzh9q2z-lab Date: Wed, 4 Feb 2026 13:12:20 +0700 Subject: [PATCH] API Country --- controllers/visaController.js | 185 ++++++++++++++++++++++------------ routes/admin.js | 72 ++++++++++--- 2 files changed, 176 insertions(+), 81 deletions(-) diff --git a/controllers/visaController.js b/controllers/visaController.js index 77ef292..d68af90 100644 --- a/controllers/visaController.js +++ b/controllers/visaController.js @@ -44,7 +44,15 @@ const addBaseUrlToImages = (data, baseUrl) => { return data; }; 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 -------------------- // Get visa data from MongoDB @@ -108,24 +116,65 @@ exports.index = async (req, res) => { // Get single country for edit exports.getCountry = async (req, res) => { + console.log("--------------------------------------------------"); + console.log("🚀 [GET] Request nhận được tại /visa/edit/:id"); + 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(); - if (!visaData.hero || !visaData.hero.summaryList) { - return res.status(404).json({ error: "Visa data not found" }); + // Kiểm tra cấu trúc dữ liệu tổng + 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) { - 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) { - console.error("Get country error:", err); - res.status(500).json({ error: "Error loading country" }); + console.error("🔴 Lỗi nghiêm trọng tại getCountry Controller:", err); + 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 - if (!req.body.name || !req.body.slug) { - return res.status(400).json({ error: "Name and slug are 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` }); + if (!req.body.name) { + return res.status(400).json({ error: "Name is required" }); } + const finalSlug = req.body.slug + ? createSlug(req.body.slug) + : createSlug(req.body.name); // Parse services array let services = []; @@ -240,7 +282,7 @@ exports.addCountry = async (req, res) => { const newCountry = { id: req.body.id || getNextCountryId(visaData.hero.summaryList), name: req.body.name, - slug: req.body.slug, + slug: finalSlug, icon: req.body.icon || "", services: services, ...(detailedView && { detailedView }), @@ -278,77 +320,88 @@ exports.addCountry = async (req, res) => { // Update single country exports.updateCountry = async (req, res) => { try { - const { slug } = req.params; + // 1. Lấy ID từ params (URL) + const { id } = req.params; let visaData = await getVisaData(); - if (!visaData.hero || !visaData.hero.summaryList) { - return res.status(400).json({ error: "Invalid visa data structure" }); - } + // if (!visaData || !visaData.hero || !visaData.hero.summaryList) { + // 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( - (c) => c.slug === slug, + (c) => c.id === parseInt(id), ); 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]; - - // Parse services array - let services = currentCountry.services || []; - if (req.body.services) { - if (typeof req.body.services === "string") { - try { - services = JSON.parse(req.body.services); - } catch (e) { - services = [req.body.services]; - } - } else if (Array.isArray(req.body.services)) { - services = req.body.services; - } + let finalSlug = currentCountry.slug; + if (req.body.name) { + // Nếu name thay đổi, ta có thể cập nhật lại slug (tùy nhu cầu SEO) + // Ở đâ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 + finalSlug = req.body.slug + ? createSlug(req.body.slug) + : createSlug(req.body.name); } - - // Parse detailedView if provided (optional) - let detailedView = currentCountry.detailedView; - if (req.body.detailedView) { + // 3. Xử lý dữ liệu từ req.body + // Vì Client đã gửi JSON stringify, ta lấy trực tiếp hoặc parse nếu cần + let services = req.body.services; + if (typeof services === "string") { try { - detailedView = - typeof req.body.detailedView === "string" - ? JSON.parse(req.body.detailedView) - : req.body.detailedView; + services = JSON.parse(services); } 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 = { - ...currentCountry, + ...currentCountry, // Giữ các trường cũ + id: parseInt(id), // Đảm bảo ID không đổi name: req.body.name || currentCountry.name, - slug: req.body.slug || currentCountry.slug, // Allow slug update + slug: finalSlug, icon: req.body.icon || currentCountry.icon, - services: services, - ...(detailedView && { detailedView }), + services: Array.isArray(services) ? services : currentCountry.services, + detailedView: detailedView || currentCountry.detailedView, }; + // 5. Cập nhật vào mảng chính visaData.hero.summaryList[countryIndex] = updatedCountry; - // Update database - const updatedData = { - ...(visaData.toObject ? visaData.toObject() : visaData), - }; - - if (visaData._id) { - await Visa.findByIdAndUpdate(visaData._id, updatedData, { new: true }); - } else { - await Visa.create(updatedData); + // 6. Lưu vào Database + if (visaData.markModified) { + // Bắt buộc với Mongoose khi thay đổi nội dung bên trong Array/Object + visaData.markModified("hero.summaryList"); } - 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({ 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, }); } catch (err) { diff --git a/routes/admin.js b/routes/admin.js index 9c2ecb8..2901ad5 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -385,8 +385,6 @@ router.post( ensureAuthenticated, insuranceController.update, ); -<<<<<<< HEAD -======= // Service routes router.get("/service", ensureAuthenticated, serviceController.index); @@ -413,7 +411,6 @@ router.post( serviceController.updateDetails, ); ->>>>>>> a255d09ef0a6eb0c487595aac19cefbf729d78a2 // Test Image Paths route router.get("/test-images", ensureAuthenticated, (req, res) => { const fs = require("fs"); @@ -478,7 +475,12 @@ router.get("/test-images", ensureAuthenticated, (req, res) => { }); // 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 router.post("/visa/update", ensureAuthenticated, visaController.update); @@ -488,7 +490,7 @@ router.post("/visa/add", ensureAuthenticated, visaController.addCountry); // Update single country router.put( - "/visa/update/:slug", + "/visa/update/:id", ensureAuthenticated, visaController.updateCountry, ); @@ -509,21 +511,61 @@ router.post("/blog/:id/edit", ensureAuthenticated, blogController.update); router.post("/blog/:id/delete", ensureAuthenticated, blogController.destroy); // Blog Categories Management -router.get("/blog/categories", ensureAuthenticated, blogCategoryController.index); -router.get("/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); +router.get( + "/blog/categories", + ensureAuthenticated, + blogCategoryController.index, +); +router.get( + "/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 router.get("/blog/tags", ensureAuthenticated, blogTagController.index); router.get("/blog/tags/create", ensureAuthenticated, blogTagController.create); router.post("/blog/tags/create", ensureAuthenticated, blogTagController.store); router.get("/blog/tags/:id/edit", ensureAuthenticated, blogTagController.edit); -router.post("/blog/tags/:id/edit", ensureAuthenticated, blogTagController.update); -router.post("/blog/tags/:id/delete", ensureAuthenticated, blogTagController.destroy); -router.post("/blog/tags/quick-create", ensureAuthenticated, blogTagController.quickCreate); +router.post( + "/blog/tags/:id/edit", + ensureAuthenticated, + blogTagController.update, +); +router.post( + "/blog/tags/:id/delete", + ensureAuthenticated, + blogTagController.destroy, +); +router.post( + "/blog/tags/quick-create", + ensureAuthenticated, + blogTagController.quickCreate, +); module.exports = router;