forked from UKSOURCE/cms.hailearning.edu.vn
389 lines
10 KiB
JavaScript
389 lines
10 KiB
JavaScript
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",
|
|
});
|
|
}
|
|
};
|