diff --git a/src/controllers/jobsController.js b/src/controllers/jobsController.js index 3f1cb73ae..09d64d546 100644 --- a/src/controllers/jobsController.js +++ b/src/controllers/jobsController.js @@ -5,35 +5,49 @@ const JobPositionCategory = require('../models/jobPositionCategory'); UTILS ============================================================ */ -// Prevent regex injection / crashes -const escapeRegex = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +const escapeRegex = (text = '') => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -// Build query (REUSABLE) -const buildJobQuery = ({ search = '', category = '', position = '' }) => { +const parseCategory = (category) => { + if (!category) return []; + + if (Array.isArray(category)) return category; + + try { + const parsed = JSON.parse(category); + return Array.isArray(parsed) ? parsed : []; + } catch { + return category + .split(',') + .map((c) => c.trim()) + .filter(Boolean); + } +}; + +const buildConditions = ({ search, category, position }) => { const conditions = []; - /* SEARCH FILTER */ - if (search && search.trim() !== '') { - const safeSearch = escapeRegex(search.trim()); + /* SEARCH */ + if (search?.trim()) { + const safe = escapeRegex(search.trim()); conditions.push({ $or: [ - { title: { $regex: safeSearch, $options: 'i' } }, - { description: { $regex: safeSearch, $options: 'i' } }, + { title: { $regex: safe, $options: 'i' } }, + { description: { $regex: safe, $options: 'i' } }, ], }); } - /* POSITION FILTER (uses title) */ - if (position && position.trim() !== '') { - const safePosition = escapeRegex(position.trim()); + /* POSITION */ + if (position?.trim()) { + const safe = escapeRegex(position.trim()); conditions.push({ - title: { $regex: `^${safePosition}`, $options: 'i' }, + title: { $regex: `^${safe}`, $options: 'i' }, }); } - /* CATEGORY FILTER */ - if (category && category.trim() !== '') { - const categoryList = category.split(',').map((c) => c.trim()); + /* CATEGORY */ + const categoryList = parseCategory(category); + if (categoryList.length > 0) { conditions.push({ category: { $in: categoryList } }); } @@ -41,8 +55,9 @@ const buildJobQuery = ({ search = '', category = '', position = '' }) => { }; /* ============================================================ - INTERNAL: MAIN PAGINATION + FILTERING LOGIC + PAGINATION CONTROLLER ============================================================ */ + const paginationForJobs = async (req, res) => { const { page = 1, limit = 18, search = '', category = '', position = '' } = req.query; @@ -50,12 +65,12 @@ const paginationForJobs = async (req, res) => { const pageNumber = Math.max(1, parseInt(page, 10)); const limitNumber = Math.max(1, parseInt(limit, 10)); - const query = buildJobQuery({ search, category, position }); + const query = buildConditions({ search, category, position }); const totalJobs = await Job.countDocuments(query); const totalPages = Math.max(1, Math.ceil(totalJobs / limitNumber)); - const pageNum = pageNumber > totalPages ? 1 : pageNumber; + const pageNum = Math.min(pageNumber, totalPages); const jobs = await Job.find(query) .sort({ displayOrder: 1, featured: -1, datePosted: -1, title: 1 }) @@ -84,19 +99,17 @@ const paginationForJobs = async (req, res) => { } }; -/* ============================================================ - EXPORT: GET JOBS WITH PAGINATION - ============================================================ */ const getJobs = (req, res) => paginationForJobs(req, res); /* ============================================================ JOB SUMMARIES ============================================================ */ + const getJobSummaries = async (req, res) => { const { search = '', category = '', position = '' } = req.query; try { - const query = buildJobQuery({ search, category, position }); + const query = buildConditions({ search, category, position }); const jobs = await Job.find(query) .select('title category location description datePosted featured jobDetailsLink') @@ -105,7 +118,11 @@ const getJobSummaries = async (req, res) => { const totalJobs = jobs.length; - res.json({ success: true, jobs, totalJobs }); + res.json({ + success: true, + jobs, + totalJobs, + }); } catch (error) { res.status(500).json({ success: false, @@ -118,8 +135,10 @@ const getJobSummaries = async (req, res) => { /* ============================================================ OTHER CONTROLLERS ============================================================ */ + const getJobTitleSuggestions = async (req, res) => { const { query = '' } = req.query; + try { const suggestions = await Job.find({ title: { $regex: query, $options: 'i' }, @@ -138,16 +157,11 @@ const resetJobsFilters = async (req, res) => { const pageNumber = Math.max(1, parseInt(page, 10)); const limitNumber = Math.max(1, parseInt(limit, 10)); - const sortCriteria = { - displayOrder: 1, - featured: -1, - datePosted: -1, - title: 1, - }; - const totalJobs = await Job.countDocuments({}); + const totalPages = Math.ceil(totalJobs / limitNumber); + const jobs = await Job.find({}) - .sort(sortCriteria) + .sort({ displayOrder: 1, featured: -1, datePosted: -1, title: 1 }) .skip((pageNumber - 1) * limitNumber) .limit(limitNumber); @@ -155,15 +169,18 @@ const resetJobsFilters = async (req, res) => { jobs, pagination: { totalJobs, - totalPages: Math.ceil(totalJobs / limitNumber), + totalPages, currentPage: pageNumber, limit: limitNumber, - hasNextPage: pageNumber < Math.ceil(totalJobs / limitNumber), + hasNextPage: pageNumber < totalPages, hasPreviousPage: pageNumber > 1, }, }); } catch (error) { - res.status(500).json({ error: 'Failed to reset filters', details: error.message }); + res.status(500).json({ + error: 'Failed to reset filters', + details: error.message, + }); } }; @@ -189,12 +206,17 @@ const getPositions = async (req, res) => { const getJobById = async (req, res) => { const { id } = req.params; + try { const job = await Job.findById(id); if (!job) return res.status(404).json({ error: 'Job not found' }); + res.json(job); } catch (error) { - res.status(500).json({ error: 'Failed to fetch job', details: error.message }); + res.status(500).json({ + error: 'Failed to fetch job', + details: error.message, + }); } }; @@ -202,7 +224,7 @@ const createJob = async (req, res) => { const { title, category, description, imageUrl, location, applyLink, jobDetailsLink } = req.body; try { - const highestOrderJob = await Job.findOne().sort({ displayOrder: -1 }).limit(1); + const highestOrderJob = await Job.findOne().sort({ displayOrder: -1 }); const newDisplayOrder = highestOrderJob ? highestOrderJob.displayOrder + 1 : 0; const newJob = new Job({ @@ -219,7 +241,10 @@ const createJob = async (req, res) => { const savedJob = await newJob.save(); res.status(201).json(savedJob); } catch (error) { - res.status(500).json({ error: 'Failed to create job', details: error.message }); + res.status(500).json({ + error: 'Failed to create job', + details: error.message, + }); } }; @@ -229,9 +254,13 @@ const updateJob = async (req, res) => { try { const updatedJob = await Job.findByIdAndUpdate(id, req.body, { new: true }); if (!updatedJob) return res.status(404).json({ error: 'Job not found' }); + res.json(updatedJob); } catch (error) { - res.status(500).json({ error: 'Failed to update job', details: error.message }); + res.status(500).json({ + error: 'Failed to update job', + details: error.message, + }); } }; @@ -241,9 +270,13 @@ const deleteJob = async (req, res) => { try { const deletedJob = await Job.findByIdAndDelete(id); if (!deletedJob) return res.status(404).json({ error: 'Job not found' }); + res.json({ message: 'Job deleted successfully' }); } catch (error) { - res.status(500).json({ error: 'Failed to delete job', details: error.message }); + res.status(500).json({ + error: 'Failed to delete job', + details: error.message, + }); } }; @@ -251,8 +284,9 @@ const reorderJobs = async (req, res) => { const { jobIds } = req.body; try { - if (!Array.isArray(jobIds) || jobIds.length === 0) + if (!Array.isArray(jobIds) || jobIds.length === 0) { return res.status(400).json({ error: 'Invalid job order data' }); + } const updateOperations = jobIds.map((jobId, index) => ({ updateOne: { @@ -271,10 +305,17 @@ const reorderJobs = async (req, res) => { jobs, }); } catch (error) { - res.status(500).json({ error: 'Failed to reorder jobs', details: error.message }); + res.status(500).json({ + error: 'Failed to reorder jobs', + details: error.message, + }); } }; +/* ============================================================ + EXPORTS + ============================================================ */ + module.exports = { getJobs, getJobTitleSuggestions,