const { addBaseUrlToImages } = require("../utils/imageHelper"); const Contact = require("../models/contact"); const ContactSubmission = require("../models/contactSubmission"); const writeAuditLog = require("../audit/writeAuditLog"); const diffObject = require("../audit/diffObject"); const AUDIT_ACTIONS = require("../constants/auditAction"); // Get contact data from MongoDB const getContactData = async () => { const contact = await Contact.findOne({ name: "default" }); if (!contact) { return null; } return contact.toObject(); }; // API to get contact data exports.api = async (req, res) => { try { const contact = await getContactData(); if (!contact) { return res.status(404).json({ error: "Contact data not found" }); } const baseUrl = process.env.BACKEND_URL || `${req.protocol}://${req.get("host")}`; const processedData = addBaseUrlToImages(contact, baseUrl); res.json(processedData); } catch (err) { console.error("API Error:", err); res.status(500).json({ error: "Error loading contact data" }); } }; // API để lấy toàn bộ contact data exports.getContactData = async (req, res) => { try { const contactData = await getContactData(); if (!contactData) { return res.status(404).json({ error: "Contact data not found" }); } res.json(contactData); } catch (error) { console.error("Error getting contact data:", error); res.status(500).json({ error: "Error loading contact data" }); } }; // Render admin view exports.index = async (req, res) => { try { const data = (await getContactData()) || { hero: { title: "Contact Us", backgroundImage: "", overlayColor: "rgba(0, 0, 0, 0)", sectionClass: "", titleClass: "", enableScrollspy: false, backgroundPosition: "center", }, contactCards: [], map: { coordinates: { lat: 0, lng: 0 }, zoom: 15, location: "", markerTitle: "", embedUrl: "", tileLayer: { url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", attribution: "", maxZoom: 18, minZoom: 0, }, }, form: { sectionLabel: "", heading: "", description: "", fields: [], submitButton: { text: "Send Message", icon: "fa-solid fa-arrow-right", buttonClass: "theme-btn style-2", }, }, }; const { startDate, endDate } = req.query; const query = {}; if (startDate || endDate) { query.createdAt = {}; if (startDate) { query.createdAt.$gte = new Date(startDate); } if (endDate) { // Set end date to end of day const end = new Date(endDate); end.setHours(23, 59, 59, 999); query.createdAt.$lte = end; } } const submissions = await ContactSubmission.find(query) .sort({ createdAt: -1 }) .limit(50); const frontendUrl = process.env.FRONTEND_URL; res.render("admin/contact/index", { title: "Contact Management", layout: "layouts/main", data, submissions, startDate, endDate, frontendUrl, currentPath: req.path, user: req.session.user, }); } catch (error) { console.error("Error in contact index:", error); req.flash("error_msg", "An error occurred while loading the page"); res.redirect("/admin/dashboard"); } }; // Cập nhật dữ liệu contact exports.update = async (req, res) => { try { const { hero, contactCards, map, form } = req.body; // Parse JSON strings nếu cần const parseJson = (data) => { if (!data) return null; if (typeof data === "string") { try { return JSON.parse(data); } catch (e) { return null; } } return data; }; const heroData = parseJson(hero); const contactCardsData = parseJson(contactCards); const mapData = parseJson(map); const formData = parseJson(form); // Tìm hoặc tạo contact let contact = await Contact.findOne({ name: "default" }); // ✅ Capture BEFORE state const beforeData = contact ? JSON.parse(JSON.stringify(contact.toObject())) : {}; if (!contact) { // Tạo mới với default values contact = new Contact({ name: "default", hero: heroData || { title: "Contact Us", backgroundImage: "", overlayColor: "rgba(0, 0, 0, 0)", sectionClass: "", titleClass: "", enableScrollspy: false, backgroundPosition: "center", }, contactCards: (contactCardsData || []).map((card) => ({ ...card, iconType: card.iconType || "", iconSource: card.iconSource || (card.iconType && card.iconType.startsWith("/uploads/") ? "image" : "fontawesome"), })), map: mapData || { coordinates: { lat: 0, lng: 0 }, zoom: 15, location: "", markerTitle: "", embedUrl: "", tileLayer: { url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", attribution: "", maxZoom: 18, minZoom: 0, }, }, form: formData || { sectionLabel: "", heading: "", description: "", fields: [], submitButton: { text: "Send Message", icon: "fa-solid fa-arrow-right", buttonClass: "theme-btn style-2", }, }, }); } else { // Cập nhật dữ liệu if (heroData) contact.hero = heroData; if (contactCardsData && Array.isArray(contactCardsData)) { // Đảm bảo mỗi card có iconType và iconSource contact.contactCards = contactCardsData.map((card) => ({ ...card, iconType: card.iconType || "", iconSource: card.iconSource || (card.iconType && card.iconType.startsWith("/uploads/") ? "image" : "fontawesome"), })); } if (mapData) contact.map = mapData; if (formData) contact.form = formData; } await contact.save(); // ✅ Capture AFTER state const afterData = JSON.parse(JSON.stringify(contact.toObject())); // ✅ AUDIT LOGGING - Contact Updated const changes = diffObject(beforeData, afterData); if (changes.length > 0) { await writeAuditLog({ model: "Contact", documentId: contact._id, action: AUDIT_ACTIONS.UPDATE_CONTACT, before: beforeData, after: afterData, changes, req, }); } req.flash("success_msg", "Contact updated successfully"); res.redirect("/admin/contact"); } catch (err) { console.error("Error updating contact:", err); req.flash("error_msg", err.message || "Error updating contact"); res.redirect("/admin/contact"); } }; // API để submit contact form (từ frontend) exports.submitForm = async (req, res) => { try { const { name, email, phone, address, date, message } = req.body; // Validation if (!name || !email) { return res.status(400).json({ success: false, error: "Name and email are required", }); } // Create new submission const submission = new ContactSubmission({ name: name.trim(), email: email.trim().toLowerCase(), phone: phone?.trim() || "", address: address?.trim() || "", date: date?.trim() || "", message: message?.trim() || "", ipAddress: req.ip || req.connection?.remoteAddress || "", userAgent: req.get("User-Agent") || "", }); await submission.save(); res.status(201).json({ success: true, message: "Thank you for contacting us! We will get back to you soon.", data: { id: submission._id, name: submission.name, email: submission.email, }, }); } catch (err) { console.error("Error submitting contact form:", err); // Handle validation errors if (err.name === "ValidationError") { const errors = Object.values(err.errors).map((e) => e.message); return res.status(400).json({ success: false, error: errors.join(", "), }); } res.status(500).json({ success: false, error: "Error submitting form. Please try again later.", }); } }; // API để lấy danh sách submissions (cho admin) exports.getSubmissions = async (req, res) => { try { const { status, page = 1, limit = 20 } = req.query; const query = {}; if (status && ["pending", "read", "replied", "archived"].includes(status)) { query.status = status; } const skip = (parseInt(page) - 1) * parseInt(limit); const [submissions, total] = await Promise.all([ ContactSubmission.find(query) .sort({ createdAt: -1 }) .skip(skip) .limit(parseInt(limit)), ContactSubmission.countDocuments(query), ]); res.json({ success: true, data: submissions, pagination: { page: parseInt(page), limit: parseInt(limit), total, totalPages: Math.ceil(total / parseInt(limit)), }, }); } catch (err) { console.error("Error getting submissions:", err); res.status(500).json({ success: false, error: "Error loading submissions", }); } }; // API để cập nhật status của submission exports.updateSubmissionStatus = async (req, res) => { try { const { id } = req.params; const { status, notes } = req.body; const validStatuses = ["pending", "read", "replied", "archived"]; if (!validStatuses.includes(status)) { return res.status(400).json({ success: false, error: "Invalid status", }); } const updateData = { status }; if (notes !== undefined) updateData.notes = notes; if (status === "replied") updateData.repliedAt = new Date(); const submission = await ContactSubmission.findByIdAndUpdate( id, updateData, { new: true }, ); if (!submission) { return res.status(404).json({ success: false, error: "Submission not found", }); } res.json({ success: true, data: submission, }); } catch (err) { console.error("Error updating submission:", err); res.status(500).json({ success: false, error: "Error updating submission", }); } };