const Category = require('../models/categoryModel'); // Get all categories (optionally filter by parent to get root categories or subcategories) exports.getAllCategories = async (req, res) => { try { const { parentId } = req.query; // Build filter - if parentId is 'root' or null, get root categories let filter = {}; if (parentId === 'root' || parentId === 'null') { filter.parent = null; } else if (parentId) { filter.parent = parentId; } const categories = await Category.find(filter) .populate('parent', 'nameAr nameEn slug') .lean(); res.status(200).json({ status: 'success', results: categories.length, data: { categories, }, }); } catch (err) { res.status(500).json({ status: 'fail', message: err.message, }); } }; // Get single category with parent and children exports.getCategory = async (req, res) => { try { const category = await Category.findById(req.params.id) .populate('parent', 'nameAr nameEn slug') .populate('children', 'nameAr nameEn slug level') .lean(); if (!category) { return res.status(404).json({ status: 'fail', message: 'No category found with that ID', }); } res.status(200).json({ status: 'success', data: { category }, }); } catch (err) { res.status(400).json({ status: 'fail', message: err.message, }); } }; // Create a new category exports.createCategory = async (req, res) => { try { const lastCategory = await Category.findOne().sort({ id: -1 }); const autoId = lastCategory ? lastCategory.id + 1 : 1; const newCategory = await Category.create({ // eslint-disable-next-line node/no-unsupported-features/es-syntax ...req.body, id: req.body.id || autoId, }); res.status(201).json({ status: 'success', data: { category: newCategory }, }); } catch (err) { res.status(400).json({ status: 'fail', message: err.message, }); } }; // Update a category exports.updateCategory = async (req, res) => { try { const updatedCategory = await Category.findByIdAndUpdate( req.params.id, req.body, { new: true, runValidators: true }, ); if (!updatedCategory) { return res.status(404).json({ status: 'fail', message: 'No category found with that ID', }); } res.status(200).json({ status: 'success', data: { category: updatedCategory }, }); } catch (err) { res.status(400).json({ status: 'fail', message: err.message, }); } }; // Delete a category exports.deleteCategory = async (req, res) => { try { // First check if category has children const childrenCount = await Category.countDocuments({ parent: req.params.id, }); if (childrenCount > 0) { return res.status(400).json({ status: 'fail', message: 'Cannot delete category with subcategories. Delete subcategories first.', }); } const category = await Category.findByIdAndDelete(req.params.id); if (!category) { return res.status(404).json({ status: 'fail', message: 'No category found with that ID', }); } res.status(204).json({ status: 'success', data: null, }); } catch (err) { res.status(400).json({ status: 'fail', message: err.message, }); } }; // Get all subcategories of a specific category exports.getSubcategories = async (req, res) => { try { const subcategories = await Category.find({ parent: req.params.id }).lean(); res.status(200).json({ status: 'success', results: subcategories.length, data: { subcategories, }, }); } catch (err) { res.status(400).json({ status: 'fail', message: err.message, }); } }; // Get full category tree (hierarchical structure) exports.getCategoryTree = async (req, res) => { try { // Fetch all categories once to build tree in memory const allCategories = await Category.find().lean(); const categoryMap = {}; allCategories.forEach((cat) => { categoryMap[cat._id.toString()] = { ...cat, children: [] }; }); const tree = []; allCategories.forEach((cat) => { const catObj = categoryMap[cat._id.toString()]; if (cat.parent) { const parentId = cat.parent.toString(); if (categoryMap[parentId]) { categoryMap[parentId].children.push(catObj); } } else { tree.push(catObj); } }); res.status(200).json({ status: 'success', results: tree.length, data: { categories: tree, }, }); } catch (err) { res.status(500).json({ status: 'fail', message: err.message, }); } };