forked from UKSOURCE/cms.hailearning.edu.vn
250 lines
7.2 KiB
JavaScript
250 lines
7.2 KiB
JavaScript
const { addBaseUrlToImages } = require("../utils/imageHelper");
|
|
const Home = require("../models/home");
|
|
|
|
// -------------------- Helpers --------------------
|
|
|
|
const getHomeDoc = async () => {
|
|
// Keep newest document as the source of truth
|
|
return await Home.findOne().sort({ updatedAt: -1 });
|
|
};
|
|
|
|
const getHomeData = async () => {
|
|
const doc = await Home.findOne().sort({ updatedAt: -1 }).lean();
|
|
return doc || {};
|
|
};
|
|
|
|
/**
|
|
* Default structure used by the current CMS Home admin UI (`views/admin/home/index.ejs`).
|
|
* This is intentionally permissive; the Home model itself also supports the Next.js
|
|
* structure from `hailearning.edu.vn/app/home.json`.
|
|
*/
|
|
const getDefaultHomeData = () => ({
|
|
hero: {
|
|
title: "",
|
|
description: "",
|
|
backgroundImage: "",
|
|
button: { label: "Book Your Adventure", href: "/booking" },
|
|
contactBox: {
|
|
welcomeText: "",
|
|
phone: { label: "Call us", number: "", href: "" },
|
|
email: { label: "Email", address: "", href: "" },
|
|
workingHours: { label: "Working Hours", hours: "" },
|
|
},
|
|
},
|
|
about: {
|
|
title: "",
|
|
subtitle: "",
|
|
description: "",
|
|
images: { mainImage1: "", mainImage2: "", avatars: [] },
|
|
features: [],
|
|
quote: "",
|
|
button: { label: "", href: "" },
|
|
stats: { customerCount: 0, customerLabel: "" },
|
|
},
|
|
missionVision: {
|
|
title: "",
|
|
subtitle: "",
|
|
backgroundImage: "",
|
|
cards: [],
|
|
},
|
|
whyChooseUs: {
|
|
title: "",
|
|
subtitle: "",
|
|
description: "",
|
|
button: { label: "", href: "" },
|
|
features: [],
|
|
tags: [],
|
|
cta: { text: "", linkText: "", linkHref: "" },
|
|
},
|
|
activities: { cards: [] },
|
|
faq: {
|
|
title: "",
|
|
subtitle: "",
|
|
description: "",
|
|
image: "",
|
|
contact: { title: "", info: "" },
|
|
questions: [],
|
|
},
|
|
partners: {
|
|
title: "",
|
|
subtitle: "",
|
|
backgroundImage: "",
|
|
logos: [],
|
|
cta: { badge: "", text: "", linkText: "", linkHref: "" },
|
|
},
|
|
programs: {
|
|
title: "",
|
|
subtitle: "",
|
|
button: { label: "", href: "" },
|
|
card: {
|
|
pricePrefix: "from",
|
|
priceSuffix: "USD",
|
|
buttonLabel: "Camp Detail",
|
|
buttonHref: "/camp-profiles",
|
|
},
|
|
items: [],
|
|
},
|
|
newsletter: {
|
|
title: "",
|
|
subtitle: "",
|
|
description: "",
|
|
image: "",
|
|
decorativeImage: "",
|
|
button: { label: "", placeholder: "", href: "" },
|
|
},
|
|
latestPosts: {
|
|
title: "",
|
|
subtitle: "",
|
|
searchPlaceholder: "",
|
|
sidebarTitle: "",
|
|
blogPosts: [],
|
|
sidebarPosts: [],
|
|
featuredCard: { image: "", title: "", description: "" },
|
|
},
|
|
});
|
|
|
|
// -------------------- Admin --------------------
|
|
|
|
exports.index = async (req, res) => {
|
|
try {
|
|
let data = await getHomeData();
|
|
|
|
if (!data || Object.keys(data).length === 0) {
|
|
data = getDefaultHomeData();
|
|
} else {
|
|
// Merge minimal defaults to keep the view safe
|
|
const defaults = getDefaultHomeData();
|
|
data.hero = data.hero || defaults.hero;
|
|
data.about = data.about || defaults.about;
|
|
data.missionVision = data.missionVision || defaults.missionVision;
|
|
data.whyChooseUs = data.whyChooseUs || defaults.whyChooseUs;
|
|
data.activities = data.activities || defaults.activities;
|
|
data.faq = data.faq || defaults.faq;
|
|
data.partners = data.partners || defaults.partners;
|
|
data.programs = data.programs || defaults.programs;
|
|
data.newsletter = data.newsletter || defaults.newsletter;
|
|
data.latestPosts = data.latestPosts || defaults.latestPosts;
|
|
}
|
|
|
|
const frontendUrl = process.env.FRONTEND_URL || "http://localhost:3000";
|
|
|
|
return res.render("admin/home/index", {
|
|
layout: "layouts/main",
|
|
title: "Home Management",
|
|
data,
|
|
frontendUrl,
|
|
currentPath: req.path,
|
|
user: req.session.user,
|
|
});
|
|
} catch (err) {
|
|
console.error("Home index error:", err);
|
|
req.flash("error_msg", "Error loading home data");
|
|
return req.session.save(() => res.redirect("/admin/dashboard"));
|
|
}
|
|
};
|
|
|
|
exports.update = async (req, res) => {
|
|
try {
|
|
const currentDoc = await getHomeDoc();
|
|
const currentData = currentDoc ? currentDoc.toObject() : {};
|
|
const updatedData = { ...currentData };
|
|
|
|
// Quick fields (Hero) from classic form fields
|
|
if (req.body.heroTitle || req.body.heroDescription || req.body.heroBackgroundImage) {
|
|
updatedData.hero = {
|
|
title: req.body.heroTitle || "",
|
|
description: req.body.heroDescription || "",
|
|
backgroundImage: req.body.heroBackgroundImage || "",
|
|
button: {
|
|
label: req.body.heroButtonLabel || "Book Your Adventure",
|
|
href: req.body.heroButtonHref || "/booking",
|
|
},
|
|
contactBox: {
|
|
welcomeText: req.body.heroContactWelcome || "",
|
|
phone: {
|
|
label: "Call us",
|
|
number: req.body.heroContactPhone || "",
|
|
href: req.body.heroContactPhone ? `tel:${req.body.heroContactPhone}` : "",
|
|
},
|
|
email: {
|
|
label: "Email",
|
|
address: req.body.heroContactEmail || "",
|
|
href: req.body.heroContactEmail ? `mailto:${req.body.heroContactEmail}` : "",
|
|
},
|
|
workingHours: {
|
|
label: "Working Hours",
|
|
hours: req.body.heroContactHours || "",
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
// Handle sections sent as JSON payloads
|
|
const sections = [
|
|
"hero",
|
|
"about",
|
|
"missionVision",
|
|
"whyChooseUs",
|
|
"activities",
|
|
"faq",
|
|
"partners",
|
|
"programs",
|
|
"newsletter",
|
|
"latestPosts",
|
|
];
|
|
|
|
let hasChanges = false;
|
|
|
|
for (const section of sections) {
|
|
if (!req.body[section]) continue;
|
|
|
|
try {
|
|
const newSectionData = JSON.parse(req.body[section]);
|
|
const currentSectionData = currentData?.[section];
|
|
|
|
if (JSON.stringify(newSectionData) !== JSON.stringify(currentSectionData)) {
|
|
updatedData[section] = newSectionData;
|
|
hasChanges = true;
|
|
}
|
|
} catch (e) {
|
|
console.error(`Error processing section "${section}":`, e);
|
|
req.flash("error_msg", `Invalid JSON for section "${section}": ${e.message}`);
|
|
return req.session.save(() => res.redirect("/admin/home"));
|
|
}
|
|
}
|
|
|
|
if (!hasChanges) {
|
|
req.flash("info_msg", "No changes were made");
|
|
return req.session.save(() => res.redirect("/admin/home"));
|
|
}
|
|
|
|
if (currentDoc?._id) {
|
|
await Home.findByIdAndUpdate(currentDoc._id, updatedData, { new: true });
|
|
} else {
|
|
await Home.create(updatedData);
|
|
}
|
|
|
|
req.flash("success_msg", "Home data updated successfully");
|
|
return req.session.save(() => res.redirect("/admin/home"));
|
|
} catch (err) {
|
|
console.error("Home update error:", err);
|
|
req.flash("error_msg", `Update error: ${err.message || "Unknown"}`);
|
|
return req.session.save(() => res.redirect("/admin/home"));
|
|
}
|
|
};
|
|
|
|
// -------------------- Public API --------------------
|
|
|
|
exports.api = async (req, res) => {
|
|
try {
|
|
const homeData = await getHomeData();
|
|
const baseUrl = process.env.BACKEND_URL || `${req.protocol}://${req.get("host")}`;
|
|
const processedData = addBaseUrlToImages(homeData, baseUrl);
|
|
return res.json(processedData);
|
|
} catch (err) {
|
|
console.error("Home API error:", err);
|
|
return res.status(500).json({ error: "Error loading home data" });
|
|
}
|
|
};
|
|
|