forked from UKSOURCE/cms.hailearning.edu.vn
feat(header): add admin UI and APIs for header management
This commit is contained in:
@@ -1,347 +1,335 @@
|
||||
const { addBaseUrlToImages } = require('../utils/imageHelper');
|
||||
const Header = require('../models/header');
|
||||
const Menu = require('../models/menuHeader');
|
||||
const Header = require("../models/header");
|
||||
|
||||
// Helper function để thêm title và url cho programmes
|
||||
const addProgrammeDetails = (programmes, menuUrl) => {
|
||||
return programmes.map(prog => ({
|
||||
...prog,
|
||||
title: prog.name,
|
||||
url: `${menuUrl}${prog.code}/`
|
||||
}));
|
||||
};
|
||||
|
||||
// Helper function để xử lý menu tree cho API (đơn giản hóa, map menuid thành id)
|
||||
const processMenuTreeForAPI = (menuTree) => {
|
||||
return menuTree.map(item => {
|
||||
const processedItem = {
|
||||
id: item.menuid, // Map menuid to id for frontend
|
||||
title: item.title,
|
||||
url: item.url,
|
||||
order: item.order,
|
||||
parent: item.parent || null,
|
||||
type: item.type,
|
||||
children: []
|
||||
};
|
||||
|
||||
// Đệ quy cho children
|
||||
if (item.children && item.children.length > 0) {
|
||||
processedItem.children = processMenuTreeForAPI(item.children);
|
||||
}
|
||||
|
||||
return processedItem;
|
||||
});
|
||||
};
|
||||
|
||||
// Helper function để xử lý menu tree và thêm programme details (cho admin)
|
||||
const processMenuTree = (menuTree) => {
|
||||
return menuTree.map(item => {
|
||||
const processedItem = { ...item };
|
||||
|
||||
// Nếu có programmes, thêm title và url
|
||||
if (item.programmes && item.programmes.length > 0) {
|
||||
processedItem.programmes = addProgrammeDetails(item.programmes, item.url);
|
||||
}
|
||||
|
||||
// Đệ quy cho children
|
||||
if (item.children && item.children.length > 0) {
|
||||
processedItem.children = processMenuTree(item.children);
|
||||
}
|
||||
|
||||
return processedItem;
|
||||
});
|
||||
};
|
||||
|
||||
// Get header data from MongoDB
|
||||
const getHeaderData = async () => {
|
||||
const header = await Header.findOne({ name: 'default' });
|
||||
|
||||
if (!header) {
|
||||
return {
|
||||
topbar: {
|
||||
contactInfo: {
|
||||
phone: '',
|
||||
email: ''
|
||||
},
|
||||
links: []
|
||||
},
|
||||
mainMenu: [],
|
||||
logo: ''
|
||||
};
|
||||
}
|
||||
|
||||
// Convert to plain object to allow modifications
|
||||
const headerData = header.toObject();
|
||||
|
||||
// Lấy menu tree từ collection menuHeader (đơn giản, không có programmes)
|
||||
try {
|
||||
const menuTree = await Menu.getMenuTree();
|
||||
// Xử lý menu tree để map menuid thành id cho frontend
|
||||
headerData.mainMenu = processMenuTreeForAPI(menuTree);
|
||||
} catch (error) {
|
||||
console.error('Error getting menu tree:', error);
|
||||
headerData.mainMenu = [];
|
||||
}
|
||||
|
||||
return headerData;
|
||||
};
|
||||
|
||||
// API to get header data
|
||||
exports.api = async (req, res) => {
|
||||
try {
|
||||
// Lấy header data
|
||||
const header = await getHeaderData();
|
||||
|
||||
// Xử lý URL cho logo và các hình ảnh khác
|
||||
const processedData = addBaseUrlToImages(header);
|
||||
|
||||
res.json(processedData);
|
||||
} catch (err) {
|
||||
console.error('API Error:', err);
|
||||
res.status(500).json({ error: 'Error loading header data' });
|
||||
}
|
||||
};
|
||||
|
||||
// API để lấy menu tree cho frontend (public API)
|
||||
exports.getMenuTreeAPI = async (req, res) => {
|
||||
try {
|
||||
const menuTree = await Menu.getMenuTree();
|
||||
// Xử lý menu tree để map menuid thành id cho frontend
|
||||
const processedMenuTree = processMenuTreeForAPI(menuTree);
|
||||
res.json(processedMenuTree);
|
||||
} catch (error) {
|
||||
console.error('Error getting menu tree:', error);
|
||||
res.status(500).json({ error: 'Error loading menu tree' });
|
||||
}
|
||||
};
|
||||
|
||||
// API để lấy menu tree (cho admin)
|
||||
exports.getMenuTree = async (req, res) => {
|
||||
try {
|
||||
const menuTree = await Menu.getMenuTree();
|
||||
res.json(menuTree);
|
||||
} catch (error) {
|
||||
console.error('Error getting menu tree:', error);
|
||||
res.status(500).json({ error: 'Error loading menu tree' });
|
||||
}
|
||||
};
|
||||
|
||||
// API để lấy programmes theo menu ID
|
||||
exports.getProgrammesByMenuId = async (req, res) => {
|
||||
try {
|
||||
const { menuId } = req.params;
|
||||
const programmes = await Header.getProgrammesByMenuId(menuId);
|
||||
|
||||
// Lấy menu item để có URL
|
||||
const menuItem = await Menu.findOne({ menuid: menuId });
|
||||
if (menuItem) {
|
||||
const programmesWithDetails = addProgrammeDetails(programmes, menuItem.url);
|
||||
res.json(programmesWithDetails);
|
||||
} else {
|
||||
res.json(programmes);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting programmes by menu ID:', error);
|
||||
res.status(500).json({ error: 'Error loading programmes' });
|
||||
}
|
||||
};
|
||||
|
||||
// API để lấy toàn bộ header data
|
||||
exports.getHeaderData = async (req, res) => {
|
||||
try {
|
||||
const headerData = await getHeaderData();
|
||||
res.json(headerData);
|
||||
} catch (error) {
|
||||
console.error('Error getting header data:', error);
|
||||
res.status(500).json({ error: 'Error loading header data' });
|
||||
}
|
||||
};
|
||||
|
||||
// API để lấy menu item theo ID
|
||||
exports.getMenuItem = async (req, res) => {
|
||||
try {
|
||||
const { menuId } = req.params;
|
||||
const Menu = require('../models/menuHeader');
|
||||
const menuItem = await Menu.findOne({ menuid: menuId });
|
||||
|
||||
if (!menuItem) {
|
||||
return res.status(404).json({ error: 'Menu item not found' });
|
||||
}
|
||||
|
||||
// Nếu là level type và có fetch = true, lấy programmes
|
||||
if (menuItem.type === 'level' && menuItem.fetch) {
|
||||
const programmes = await Menu.getProgrammesByMenuId(menuItem.menuid);
|
||||
menuItem.programmes = addProgrammeDetails(programmes, menuItem.url);
|
||||
}
|
||||
|
||||
res.json(menuItem);
|
||||
} catch (error) {
|
||||
console.error('Error getting menu item:', error);
|
||||
res.status(500).json({ error: 'Error loading menu item' });
|
||||
}
|
||||
};
|
||||
|
||||
// Render admin view
|
||||
// Admin: Render header management page
|
||||
exports.index = async (req, res) => {
|
||||
try {
|
||||
const data = await getHeaderData();
|
||||
try {
|
||||
const header = await Header.findOne().sort({ order: 1 });
|
||||
|
||||
res.render('admin/header/index', {
|
||||
title: 'Header Management',
|
||||
data
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error in header index:', error);
|
||||
req.flash('error_msg', 'An error occurred while loading the page');
|
||||
res.redirect('/admin/dashboard');
|
||||
}
|
||||
};
|
||||
|
||||
// Update header (chỉ cập nhật topbar và logo)
|
||||
exports.update = async (req, res) => {
|
||||
try {
|
||||
const headerData = req.body;
|
||||
|
||||
// Tìm và cập nhật header
|
||||
const header = await Header.findOne({ name: 'default' });
|
||||
if (!header) {
|
||||
req.flash('error_msg', 'Header not found');
|
||||
return res.redirect('/admin/header');
|
||||
}
|
||||
|
||||
// Cập nhật từng phần
|
||||
if (headerData.topbarJson) {
|
||||
header.topbar = JSON.parse(headerData.topbarJson);
|
||||
}
|
||||
|
||||
if (headerData.logo) {
|
||||
header.logo = headerData.logo;
|
||||
}
|
||||
header.updatedAt = new Date();
|
||||
await header.save();
|
||||
|
||||
// Process menu updates if any
|
||||
let menuUpdateCount = 0;
|
||||
let menuErrorCount = 0;
|
||||
|
||||
if (headerData.menuUpdates) {
|
||||
try {
|
||||
const updates = JSON.parse(headerData.menuUpdates);
|
||||
|
||||
if (Array.isArray(updates) && updates.length > 0) {
|
||||
const Menu = require('../models/menuHeader');
|
||||
|
||||
for (const update of updates) {
|
||||
try {
|
||||
const { menuid, title, order, type, parent, fetch, isActive } = update;
|
||||
|
||||
const updateData = {
|
||||
title: title,
|
||||
order: order,
|
||||
type: type,
|
||||
parent: parent
|
||||
// Prepare data for view
|
||||
const data = header
|
||||
? {
|
||||
topbar: {
|
||||
contactInfo: {
|
||||
phone: header.top?.phone || "",
|
||||
email: header.top?.email || "",
|
||||
location: header.top?.location || "",
|
||||
},
|
||||
socialLinks: header.top?.socialLinks || [],
|
||||
},
|
||||
logo: header.logo?.light || "",
|
||||
}
|
||||
: {
|
||||
topbar: {
|
||||
contactInfo: {
|
||||
phone: "",
|
||||
email: "",
|
||||
location: "",
|
||||
},
|
||||
socialLinks: [],
|
||||
},
|
||||
logo: "",
|
||||
};
|
||||
|
||||
// Add fetch field if provided
|
||||
if (fetch !== undefined) {
|
||||
updateData.fetch = fetch;
|
||||
}
|
||||
|
||||
// Add isActive field if provided
|
||||
if (isActive !== undefined) {
|
||||
updateData.isActive = isActive;
|
||||
}
|
||||
|
||||
const result = await Menu.findOneAndUpdate(
|
||||
{ menuid: menuid },
|
||||
updateData,
|
||||
{ new: true }
|
||||
);
|
||||
|
||||
if (result) {
|
||||
menuUpdateCount++;
|
||||
} else {
|
||||
console.warn(`Menu item not found for update: ${menuid}`);
|
||||
menuErrorCount++;
|
||||
}
|
||||
} catch (innerErr) {
|
||||
console.error(`Error updating menu item ${update.menuid}:`, innerErr);
|
||||
menuErrorCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error processing menu updates:', err);
|
||||
}
|
||||
res.render("admin/header/index", {
|
||||
layout: "layouts/main",
|
||||
title: "Header Management",
|
||||
user: req.session.user,
|
||||
data: data,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error loading header management:", error);
|
||||
res.status(500).render("page/error", {
|
||||
title: "Error",
|
||||
message: "Failed to load header management page",
|
||||
});
|
||||
}
|
||||
|
||||
let flashMsg = 'Header updated successfully.';
|
||||
if (menuUpdateCount > 0) {
|
||||
flashMsg += ` Updated ${menuUpdateCount} menu items.`;
|
||||
}
|
||||
if (menuErrorCount > 0) {
|
||||
req.flash('error_msg', `Updated ${menuUpdateCount} items, but failed to update ${menuErrorCount} items. Check logs.`);
|
||||
} else {
|
||||
req.flash('success_msg', flashMsg);
|
||||
}
|
||||
|
||||
// Redirect back to the active tab
|
||||
const activeTab = req.body.activeTab || 'topbar';
|
||||
res.redirect(`/admin/header?activeTab=${activeTab}`);
|
||||
} catch (error) {
|
||||
console.error('Error updating header:', error);
|
||||
req.flash('error_msg', 'Error updating header: ' + error.message);
|
||||
res.redirect('/admin/header');
|
||||
}
|
||||
};
|
||||
|
||||
// Update menu structure (order and parent)
|
||||
exports.updateMenu = async (req, res) => {
|
||||
try {
|
||||
const { updates } = req.body;
|
||||
|
||||
if (!updates || !Array.isArray(updates)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
error: 'Invalid updates data'
|
||||
});
|
||||
// Admin: Get all headers (API)
|
||||
exports.getAll = async (req, res) => {
|
||||
try {
|
||||
const headers = await Header.find().sort({ order: 1 });
|
||||
res.json({
|
||||
success: true,
|
||||
data: headers,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const Menu = require('../models/menuHeader');
|
||||
|
||||
// Update each menu item
|
||||
for (const update of updates) {
|
||||
const { menuid, title, order, type, parent, fetch, isActive } = update;
|
||||
|
||||
console.log(menuid, title, order, type, parent, fetch, isActive);
|
||||
|
||||
const updateData = {
|
||||
title: title,
|
||||
order: order,
|
||||
type: type,
|
||||
parent: parent
|
||||
};
|
||||
|
||||
// Add fetch field if provided (for level type menus)
|
||||
if (fetch !== undefined) {
|
||||
updateData.fetch = fetch;
|
||||
}
|
||||
|
||||
// Add isActive field if provided
|
||||
if (isActive !== undefined) {
|
||||
updateData.isActive = isActive;
|
||||
}
|
||||
|
||||
await Menu.findOneAndUpdate(
|
||||
{ menuid: menuid },
|
||||
updateData,
|
||||
{ new: true }
|
||||
);
|
||||
// Admin: Get single header
|
||||
exports.show = async (req, res) => {
|
||||
try {
|
||||
const header = await Header.findById(req.params.id);
|
||||
if (!header) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "Header not found",
|
||||
});
|
||||
}
|
||||
res.json({
|
||||
success: true,
|
||||
data: header,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Error updating menu structure:', error);
|
||||
req.flash('error_msg', 'Error updating menu structure: ' + error.message);
|
||||
res.redirect('/admin/header');
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Admin: Create header
|
||||
exports.store = async (req, res) => {
|
||||
try {
|
||||
const { top, offcanvas, menu, logo, ctaButton, status, order } = req.body;
|
||||
|
||||
const header = new Header({
|
||||
top,
|
||||
offcanvas,
|
||||
menu,
|
||||
logo,
|
||||
ctaButton,
|
||||
status: status || "active",
|
||||
order: order || 1,
|
||||
});
|
||||
|
||||
await header.save();
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: "Header created successfully",
|
||||
data: header,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Admin: Update header
|
||||
exports.update = async (req, res) => {
|
||||
try {
|
||||
let { top, topbarJson, offcanvas, menu, logo, ctaButton, status, order } = req.body;
|
||||
|
||||
console.log("=== UPDATE REQUEST RECEIVED ===");
|
||||
console.log("Raw body:", JSON.stringify(req.body, null, 2));
|
||||
console.log("topbarJson type:", typeof topbarJson);
|
||||
console.log("topbarJson value:", topbarJson);
|
||||
|
||||
// Nếu có topbarJson, parse nó
|
||||
if (topbarJson && typeof topbarJson === "string") {
|
||||
try {
|
||||
const parsedData = JSON.parse(topbarJson);
|
||||
console.log("✓ Parsed topbarJson successfully:", parsedData);
|
||||
// Chuyển đổi từ topbarData sang top format
|
||||
top = {
|
||||
phone: parsedData.contactInfo?.phone || "",
|
||||
email: parsedData.contactInfo?.email || "",
|
||||
location: parsedData.contactInfo?.location || "",
|
||||
socialLinks: parsedData.socialLinks || [],
|
||||
};
|
||||
console.log("✓ Converted to top object:", top);
|
||||
} catch (e) {
|
||||
console.error("✗ Error parsing topbarJson:", e.message);
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Invalid JSON in topbarJson: " + e.message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Nếu không có id, tìm header đầu tiên hoặc tạo mới
|
||||
let headerId = req.params.id;
|
||||
|
||||
if (!headerId) {
|
||||
// Tìm header đầu tiên
|
||||
let header = await Header.findOne().sort({ order: 1 });
|
||||
if (!header) {
|
||||
console.log("No existing header found, creating new one");
|
||||
// Tạo header mới nếu chưa có
|
||||
header = new Header({
|
||||
top,
|
||||
offcanvas,
|
||||
menu,
|
||||
logo: logo ? { light: logo } : {},
|
||||
ctaButton,
|
||||
status: status || "active",
|
||||
order: order || 1,
|
||||
});
|
||||
await header.save();
|
||||
console.log("✓ Header created:", header._id);
|
||||
return res.json({
|
||||
success: true,
|
||||
message: "Header created successfully",
|
||||
data: header,
|
||||
});
|
||||
}
|
||||
headerId = header._id;
|
||||
console.log("✓ Found existing header:", headerId);
|
||||
}
|
||||
|
||||
// Chuẩn bị dữ liệu logo - merge với dữ liệu cũ
|
||||
let logoData = {};
|
||||
if (logo) {
|
||||
// Nếu có logo mới, lấy dữ liệu cũ và update light
|
||||
const existingHeader = await Header.findById(headerId);
|
||||
logoData = {
|
||||
light: logo,
|
||||
dark: existingHeader?.logo?.dark || "",
|
||||
alt: existingHeader?.logo?.alt || "",
|
||||
};
|
||||
}
|
||||
|
||||
const updateData = {
|
||||
top,
|
||||
offcanvas,
|
||||
menu,
|
||||
ctaButton,
|
||||
status,
|
||||
order,
|
||||
};
|
||||
|
||||
if (logo) {
|
||||
updateData.logo = logoData;
|
||||
}
|
||||
|
||||
console.log("Preparing to update header with data:", JSON.stringify(updateData, null, 2));
|
||||
|
||||
const updatedHeader = await Header.findByIdAndUpdate(headerId, updateData, { new: true, runValidators: true });
|
||||
|
||||
if (!updatedHeader) {
|
||||
console.error("✗ Header not found with ID:", headerId);
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "Header not found",
|
||||
});
|
||||
}
|
||||
|
||||
console.log("✓ Header updated successfully:", updatedHeader._id);
|
||||
console.log("Updated header data:", JSON.stringify(updatedHeader, null, 2));
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Header updated successfully",
|
||||
data: updatedHeader,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("✗ Error updating header:", error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Admin: Update status
|
||||
exports.updateStatus = async (req, res) => {
|
||||
try {
|
||||
const { status } = req.body;
|
||||
|
||||
if (!["active", "inactive"].includes(status)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Invalid status",
|
||||
});
|
||||
}
|
||||
|
||||
const header = await Header.findByIdAndUpdate(req.params.id, { status }, { new: true });
|
||||
|
||||
if (!header) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "Header not found",
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Header status updated",
|
||||
data: header,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Admin: Delete header
|
||||
exports.destroy = async (req, res) => {
|
||||
try {
|
||||
const header = await Header.findByIdAndDelete(req.params.id);
|
||||
|
||||
if (!header) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "Header not found",
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Header deleted successfully",
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Public API: Get active header
|
||||
exports.api = async (req, res) => {
|
||||
try {
|
||||
const header = await Header.findOne({ status: "active" }).sort({ order: 1 });
|
||||
|
||||
if (!header) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "No active header found",
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: header,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Public API: Get menu tree structure
|
||||
exports.getMenuTreeAPI = async (req, res) => {
|
||||
try {
|
||||
const header = await Header.findOne({ status: "active" }).sort({ order: 1 });
|
||||
|
||||
if (!header || !header.menu) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "No active menu found",
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: header.menu,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
321
controllers/socialLinkController.js
Normal file
321
controllers/socialLinkController.js
Normal file
@@ -0,0 +1,321 @@
|
||||
const Header = require("../models/header");
|
||||
|
||||
// Get all social links
|
||||
exports.getAll = async (req, res) => {
|
||||
try {
|
||||
const header = await Header.findOne({ status: "active" }).sort({ order: 1 });
|
||||
|
||||
if (!header) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "No active header found",
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: header.top?.socialLinks || [],
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Get single social link by platform
|
||||
exports.show = async (req, res) => {
|
||||
try {
|
||||
const { platform } = req.params;
|
||||
const header = await Header.findOne({ status: "active" }).sort({ order: 1 });
|
||||
|
||||
if (!header) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "No active header found",
|
||||
});
|
||||
}
|
||||
|
||||
const socialLink = header.top?.socialLinks?.find((link) => link.platform === platform);
|
||||
|
||||
if (!socialLink) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "Social link not found",
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: socialLink,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Create social link
|
||||
exports.store = async (req, res) => {
|
||||
try {
|
||||
let { platform, url, icon } = req.body;
|
||||
|
||||
// Convert platform to lowercase
|
||||
platform = platform.toLowerCase().trim();
|
||||
url = url.trim();
|
||||
icon = icon ? icon.trim() : null;
|
||||
|
||||
console.log("Creating social link:", { platform, url, icon });
|
||||
|
||||
// Validate required fields
|
||||
if (!platform || !url) {
|
||||
console.log("Validation failed: platform or url missing");
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Platform and URL are required",
|
||||
});
|
||||
}
|
||||
|
||||
// Validate platform is in enum
|
||||
const validPlatforms = ["linkedin", "twitter", "instagram", "youtube", "facebook"];
|
||||
if (!validPlatforms.includes(platform)) {
|
||||
console.log("Invalid platform:", platform);
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `Invalid platform. Must be one of: ${validPlatforms.join(", ")}`,
|
||||
});
|
||||
}
|
||||
|
||||
// Find header
|
||||
let header = await Header.findOne({ status: "active" }).sort({ order: 1 });
|
||||
|
||||
if (!header) {
|
||||
console.log("No active header found");
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "No active header found",
|
||||
});
|
||||
}
|
||||
|
||||
console.log("Found header:", header._id);
|
||||
|
||||
// Check if platform already exists
|
||||
const existingLink = header.top?.socialLinks?.find((link) => link.platform === platform);
|
||||
|
||||
if (existingLink) {
|
||||
console.log("Platform already exists:", platform);
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: `Social link for ${platform} already exists`,
|
||||
});
|
||||
}
|
||||
|
||||
// Add new social link
|
||||
if (!header.top) {
|
||||
header.top = {};
|
||||
}
|
||||
if (!header.top.socialLinks) {
|
||||
header.top.socialLinks = [];
|
||||
}
|
||||
|
||||
// Calculate next order number
|
||||
const maxOrder =
|
||||
header.top.socialLinks.length > 0 ? Math.max(...header.top.socialLinks.map((link) => link.order || 0)) : 0;
|
||||
|
||||
header.top.socialLinks.push({
|
||||
platform,
|
||||
url,
|
||||
icon: icon || `fa-brands fa-${platform}`,
|
||||
order: maxOrder + 1,
|
||||
});
|
||||
|
||||
console.log("Saving header with new social link");
|
||||
await header.save();
|
||||
|
||||
console.log("Social link created successfully");
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: "Social link created successfully",
|
||||
data: header.top.socialLinks[header.top.socialLinks.length - 1],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error creating social link:", error);
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Update social link
|
||||
exports.update = async (req, res) => {
|
||||
try {
|
||||
let { platform } = req.params;
|
||||
let { url, icon } = req.body;
|
||||
|
||||
// Convert to lowercase
|
||||
platform = platform.toLowerCase().trim();
|
||||
url = url.trim();
|
||||
icon = icon ? icon.trim() : null;
|
||||
|
||||
// Validate required fields
|
||||
if (!url) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "URL is required",
|
||||
});
|
||||
}
|
||||
|
||||
// Find header
|
||||
const header = await Header.findOne({ status: "active" }).sort({ order: 1 });
|
||||
|
||||
if (!header) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "No active header found",
|
||||
});
|
||||
}
|
||||
|
||||
// Find and update social link
|
||||
const socialLink = header.top?.socialLinks?.find((link) => link.platform === platform);
|
||||
|
||||
if (!socialLink) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "Social link not found",
|
||||
});
|
||||
}
|
||||
|
||||
socialLink.url = url;
|
||||
if (icon) {
|
||||
socialLink.icon = icon;
|
||||
}
|
||||
|
||||
await header.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Social link updated successfully",
|
||||
data: socialLink,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Delete social link
|
||||
exports.destroy = async (req, res) => {
|
||||
try {
|
||||
let { platform } = req.params;
|
||||
|
||||
// Convert to lowercase
|
||||
platform = platform.toLowerCase().trim();
|
||||
|
||||
console.log("Deleting social link:", platform);
|
||||
|
||||
// Find header
|
||||
const header = await Header.findOne({ status: "active" }).sort({ order: 1 });
|
||||
|
||||
if (!header) {
|
||||
console.log("No active header found");
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "No active header found",
|
||||
});
|
||||
}
|
||||
|
||||
// Find and remove social link
|
||||
const index = header.top?.socialLinks?.findIndex((link) => link.platform === platform);
|
||||
|
||||
if (index === -1 || index === undefined) {
|
||||
console.log("Social link not found:", platform);
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "Social link not found",
|
||||
});
|
||||
}
|
||||
|
||||
const deletedLink = header.top.socialLinks.splice(index, 1);
|
||||
|
||||
console.log("Saving header after delete");
|
||||
await header.save();
|
||||
|
||||
console.log("Social link deleted successfully");
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Social link deleted successfully",
|
||||
data: deletedLink[0],
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error deleting social link:", error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Bulk update social links (used for reordering and batch updates)
|
||||
exports.bulkUpdate = async (req, res) => {
|
||||
try {
|
||||
const { socialLinks } = req.body;
|
||||
|
||||
if (!Array.isArray(socialLinks)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "socialLinks must be an array",
|
||||
});
|
||||
}
|
||||
|
||||
// Find header
|
||||
let header = await Header.findOne({ status: "active" }).sort({ order: 1 });
|
||||
|
||||
if (!header) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "No active header found",
|
||||
});
|
||||
}
|
||||
|
||||
// Validate all social links
|
||||
for (const link of socialLinks) {
|
||||
if (!link.platform || !link.url) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "Each social link must have platform and url",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Update social links with order field
|
||||
if (!header.top) {
|
||||
header.top = {};
|
||||
}
|
||||
|
||||
header.top.socialLinks = socialLinks.map((link, index) => ({
|
||||
platform: link.platform,
|
||||
url: link.url,
|
||||
icon: link.icon || `fa-brands fa-${link.platform}`,
|
||||
order: link.order || index + 1, // Use provided order or calculate from index
|
||||
}));
|
||||
|
||||
await header.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: "Social links updated successfully",
|
||||
data: header.top.socialLinks,
|
||||
});
|
||||
} catch (error) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user