const Insurance = require("../models/insurance"); const { addBaseUrlToImages } = require("../utils/imageHelper"); const writeAuditLog = require("../audit/writeAuditLog"); const diffObject = require("../audit/diffObject"); const AUDIT_ACTIONS = require("../constants/auditAction"); // API để lấy insurance data (cho frontend) exports.api = async (req, res) => { try { const language = req.query.lang || "en"; // Sử dụng getDefault để đảm bảo luôn có data const insurance = await Insurance.getDefault(language); // Trả về data với cấu trúc mới const insuranceData = insurance.toObject(); // Sử dụng helper để thêm base URL vào đường dẫn ảnh const baseUrl = process.env.BACKEND_URL ?? `${req.protocol}://${req.get("host")}`; const processedData = addBaseUrlToImages(insuranceData, baseUrl); // Trả về trực tiếp hero, page, content (không wrap trong object) res.json({ hero: processedData.hero, page: processedData.page, content: processedData.content, }); } catch (error) { console.error("API Error:", error); res.status(500).json({ success: false, error: "Error loading insurance data", message: error.message, }); } }; // API để lấy toàn bộ insurance data (cho admin) exports.getInsuranceData = async (req, res) => { try { const language = req.query.lang || "en"; const insurance = await Insurance.findOne({ name: "default", language: language, }); if (!insurance) { return res.status(404).json({ success: false, error: "Insurance data not found", }); } const insuranceData = insurance.toObject(); // Thêm base URL vào đường dẫn ảnh const baseUrl = process.env.BACKEND_URL ?? `${req.protocol}://${req.get("host")}`; const processedData = addBaseUrlToImages(insuranceData, baseUrl); res.json({ success: true, data: processedData, }); } catch (error) { console.error("Error getting insurance data:", error); res.status(500).json({ success: false, error: "Error loading insurance data", }); } }; // API để lấy data theo ngôn ngữ exports.getByLanguage = async (req, res) => { try { const language = req.params.lang || "en"; const insurance = await Insurance.findOne({ name: "default", language: language, }); if (!insurance) { return res.status(404).json({ success: false, error: "Insurance data not found", }); } const insuranceData = insurance.toObject(); // Thêm base URL vào đường dẫn ảnh const baseUrl = process.env.BACKEND_URL ?? `${req.protocol}://${req.get("host")}`; const processedData = addBaseUrlToImages(insuranceData, baseUrl); res.json({ success: true, data: { hero: processedData.hero, page: processedData.page, content: processedData.content, }, }); } catch (error) { console.error("Error getting insurance by language:", error); res.status(500).json({ success: false, error: "Error loading insurance data", }); } }; // Render admin view exports.index = async (req, res) => { try { // Luôn đảm bảo có default data const insurance = await Insurance.getDefault("en"); const data = insurance.toObject(); const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000"; res.render("admin/insurance/index", { title: "Insurance Management", layout: "layouts/main", data, frontendUrl, currentPath: req.path, user: req.session.user, }); } catch (error) { console.error("Error in insurance index:", error); req.flash("error_msg", "An error occurred while loading the page"); res.redirect("/admin/dashboard"); } }; // Seed data từ JSON file (cấu trúc mới) exports.seed = async (req, res) => { try { const fs = require("fs").promises; const path = require("path"); // Đọc file JSON const jsonPath = path.join(__dirname, "../data/insurance.json"); const jsonData = JSON.parse(await fs.readFile(jsonPath, "utf8")); console.log("Seeding insurance from JSON..."); // Migrate từ cấu trúc cũ sang mới const insurance = await Insurance.migrateFromJson(jsonData, "en"); res.json({ success: true, message: "Insurance data seeded successfully", data: { id: insurance._id, hero: insurance.hero, page: insurance.page, content: insurance.content, }, }); } catch (error) { console.error("Error seeding insurance:", error); res.status(500).json({ success: false, error: error.message || "Error seeding insurance data", }); } }; // API preview cho admin (tạo HTML preview) exports.preview = async (req, res) => { try { const { hero, page, content } = req.body; // Parse JSON strings const parseJson = (data) => { if (!data) return null; if (typeof data === "string") { try { return JSON.parse(data); } catch (e) { console.error("JSON parse error:", e); return null; } } return data; }; const heroData = parseJson(hero) || {}; const pageData = parseJson(page) || {}; const contentData = parseJson(content) || {}; // Thêm base URL vào đường dẫn ảnh cho preview const baseUrl = process.env.BACKEND_URL ?? `${req.protocol}://${req.get("host")}`; const processedHeroData = addBaseUrlToImages(heroData, baseUrl); // Render preview HTML const html = ` ${pageData.title || "Insurance Preview"}

${heroData.title || "Insurance"}

${heroData.subtitle || ""}

${renderContentItems(contentData.content || [])}
`; res.send(html); } catch (error) { console.error("Error generating preview:", error); res.status(500).send("Error generating preview"); } }; // Helper function để render content items function renderContentItems(contentItems) { if (!Array.isArray(contentItems) || contentItems.length === 0) { return "

No content available.

"; } return contentItems .map((item) => { switch (item.type) { case "header": return `${item.text}`; case "paragraph": return `

${item.text}

`; case "section": return `

${item.title}

${item.content}

`; case "list": const listItems = (item.items || []) .map((li) => `
  • ${li}
  • `) .join(""); return ``; case "note": return `
    ${item.text}
    `; case "embed": if (item.source === "youtube") { return `
    ${item.caption ? `

    ${item.caption}

    ` : ""}
    `; } return ""; default: return ""; } }) .join(""); } // API để tạo insurance mới (cho các ngôn ngữ khác) exports.create = async (req, res) => { try { const { hero, page, content, language } = req.body; if (!language) { return res.status(400).json({ success: false, error: "Language is required", }); } // Kiểm tra đã tồn tại chưa const existing = await Insurance.findOne({ name: "default", language: language, }); if (existing) { return res.status(400).json({ success: false, error: "Insurance already exists for this language", }); } // Parse JSON nếu cần const parseJson = (data) => { if (!data) return null; if (typeof data === "string") { try { return JSON.parse(data); } catch (e) { console.error("JSON parse error:", e); return null; } } return data; }; const insurance = new Insurance({ name: "default", language: language, hero: parseJson(hero) || {}, page: parseJson(page) || {}, content: parseJson(content) || {}, version: "2.0.0", isActive: true, migratedFromOldStructure: false, }); await insurance.save(); res.json({ success: true, message: "Insurance created successfully for language: " + language, data: insurance, }); } catch (error) { console.error("Error creating insurance:", error); res.status(500).json({ success: false, error: error.message || "Error creating insurance", }); } }; // Cập nhật dữ liệu insurance (CẬP NHẬT CẤU TRÚC MỚI) exports.update = async (req, res) => { try { const { hero, page, content } = req.body; // Parse JSON strings const parseJson = (data) => { if (!data) return null; if (typeof data === "string") { try { return JSON.parse(data); } catch (e) { console.error("JSON parse error:", e); return null; } } return data; }; // Parse all data với cấu trúc mới const heroData = parseJson(hero) || {}; const pageData = parseJson(page) || {}; const contentData = parseJson(content) || {}; // Normalize embed blocks (convert YouTube watch URLs to /embed/ URLs) function extractYouTubeId(url) { const regex = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/; const match = url.match(regex); return match ? match[1] : null; } if (contentData && Array.isArray(contentData.content)) { contentData.content.forEach((item) => { if (item.type === "embed" && item.source === "youtube") { if (item.url && item.url.includes("watch?v=")) { const videoId = extractYouTubeId(item.url); if (videoId) { item.url = `https://www.youtube.com/embed/${videoId}`; item.videoId = videoId; } } if (item.embed && item.embed.includes("watch?v=")) { const videoId = extractYouTubeId(item.embed); if (videoId) { item.embed = `https://www.youtube.com/embed/${videoId}`; item.videoId = videoId; } } } }); } // Tìm hoặc tạo insurance let insurance = await Insurance.findOne({ name: "default", language: "en", }); // ✅ Capture BEFORE state const beforeData = insurance ? JSON.parse( JSON.stringify(insurance.toObject ? insurance.toObject() : insurance), ) : {}; if (!insurance) { insurance = new Insurance({ name: "default", language: "en", hero: heroData, page: pageData, content: contentData, version: "2.0.0", isActive: true, }); } else { insurance.hero = heroData; insurance.page = pageData; insurance.content = contentData; insurance.version = "2.0.0"; } await insurance.save(); // ✅ Capture AFTER state const afterData = JSON.parse( JSON.stringify(insurance.toObject ? insurance.toObject() : insurance), ); // ✅ AUDIT LOGGING - Insurance Updated const changes = diffObject(beforeData, afterData); if (changes.length > 0) { await writeAuditLog({ model: "Insurance", documentId: insurance._id, action: AUDIT_ACTIONS.UPDATE_INSURANCE, before: beforeData, after: afterData, changes, req, }); } req.flash("success_msg", "Insurance updated successfully"); res.redirect("/admin/insurance"); } catch (err) { console.error("Error updating insurance:", err); req.flash("error_msg", err.message || "Error updating insurance"); res.redirect("/admin/insurance"); } }; // API để xóa insurance (theo ngôn ngữ) exports.delete = async (req, res) => { try { const language = req.params.lang; if (!language) { return res.status(400).json({ success: false, error: "Language parameter is required", }); } // Không cho phép xóa tiếng Anh mặc định if (language === "en") { return res.status(400).json({ success: false, error: "Cannot delete default English insurance data", }); } const result = await Insurance.deleteOne({ name: "default", language: language, }); if (result.deletedCount === 0) { return res.status(404).json({ success: false, error: "Insurance not found for this language", }); } res.json({ success: true, message: "Insurance deleted successfully for language: " + language, }); } catch (error) { console.error("Error deleting insurance:", error); res.status(500).json({ success: false, error: error.message || "Error deleting insurance", }); } };