Skip to content

Commit 87a6772

Browse files
Merge pull request #2082 from OneCommunityGlobal/Abhinav-Kitchen-Inventory-Management-Create-Backend-API-endpoints-to-get-events-to-the-calendar
Abhinav kitchen inventory management create backend api endpoints to get events to the calendar
2 parents ecdfdcf + 56d1b86 commit 87a6772

14 files changed

Lines changed: 1213 additions & 1741 deletions

src/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ app.use(Sentry.Handlers.requestHandler());
1515
require('./startup/compression')(app);
1616
require('./startup/cors')(app);
1717
require('./startup/bodyParser')(app);
18-
require('./startup/session')(app); // Add session before middleware and routes
18+
require('./startup/session')(app); // Add session before middleware and routes
1919

2020
app.use('/api/test', testRoutes);
2121

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
const calendarController = function (CalendarEvent, ProcessingProject) {
2+
const getCalendarEvents = async (req, res) => {
3+
try {
4+
const { month, year, module: moduleFilter } = req.query;
5+
6+
if (!month || !year) {
7+
return res.status(400).send({ error: 'Month and year query parameters are required.' });
8+
}
9+
10+
const monthNum = parseInt(month, 10);
11+
const yearNum = parseInt(year, 10);
12+
13+
if (Number.isNaN(monthNum) || Number.isNaN(yearNum) || monthNum < 1 || monthNum > 12) {
14+
return res.status(400).send({ error: 'Invalid month or year value.' });
15+
}
16+
17+
// Build date range: start of month to end of month
18+
const startDate = new Date(yearNum, monthNum - 1, 1);
19+
const endDate = new Date(yearNum, monthNum, 0, 23, 59, 59, 999);
20+
21+
// Build query from validated primitives only — never spread user-controlled data
22+
const safeQuery = {
23+
scheduled_date: { $gte: startDate, $lte: endDate },
24+
};
25+
26+
const validModules = ['garden', 'orchard', 'animals', 'kitchen'];
27+
28+
if (moduleFilter && !validModules.includes(moduleFilter)) {
29+
return res.status(400).send({
30+
error: `Invalid module. Must be one of: ${validModules.join(', ')}`,
31+
});
32+
}
33+
34+
let events = [];
35+
36+
if (moduleFilter === 'kitchen') {
37+
// Query kitchen events from ProcessingProject collection
38+
const kitchenProjects = await ProcessingProject.find(safeQuery).sort({
39+
scheduled_date: 1,
40+
});
41+
events = kitchenProjects.map((project) => ({
42+
_id: project._id,
43+
title: project.item_name,
44+
module: 'kitchen',
45+
event_type: project.process_name,
46+
scheduled_date: project.scheduled_date,
47+
description: '',
48+
assigned_to: '',
49+
related_item: project.item_name,
50+
status: 'scheduled',
51+
}));
52+
} else if (moduleFilter) {
53+
// Look up the module from the whitelist — never use raw user value in query
54+
const safeModule = validModules.find((m) => m === moduleFilter);
55+
events = await CalendarEvent.find({ ...safeQuery, module: safeModule }).sort({
56+
scheduled_date: 1,
57+
});
58+
} else {
59+
// No filter: query all modules in parallel
60+
const [calendarEvents, kitchenProjects] = await Promise.all([
61+
CalendarEvent.find(safeQuery).sort({ scheduled_date: 1 }),
62+
ProcessingProject.find(safeQuery).sort({ scheduled_date: 1 }),
63+
]);
64+
65+
const normalizedKitchen = kitchenProjects.map((project) => ({
66+
_id: project._id,
67+
title: project.item_name,
68+
module: 'kitchen',
69+
event_type: project.process_name,
70+
scheduled_date: project.scheduled_date,
71+
description: '',
72+
assigned_to: '',
73+
related_item: project.item_name,
74+
status: 'scheduled',
75+
}));
76+
77+
events = [...calendarEvents, ...normalizedKitchen];
78+
79+
// Sort merged results by scheduled_date
80+
events.sort((a, b) => new Date(a.scheduled_date) - new Date(b.scheduled_date));
81+
}
82+
83+
return res.status(200).send({ events });
84+
} catch (err) {
85+
// eslint-disable-next-line no-console
86+
console.error('Error fetching calendar events:', err);
87+
return res.status(500).send({ error: 'Internal Server Error' });
88+
}
89+
};
90+
91+
const createEvent = async (req, res) => {
92+
try {
93+
const {
94+
title,
95+
module: eventModule,
96+
event_type,
97+
scheduled_date,
98+
description,
99+
assigned_to,
100+
related_item,
101+
status,
102+
} = req.body;
103+
104+
if (!title || !eventModule || !event_type || !scheduled_date) {
105+
return res.status(400).send({
106+
error: 'Title, module, event_type, and scheduled_date are required.',
107+
});
108+
}
109+
110+
const validModules = ['garden', 'orchard', 'animals', 'kitchen'];
111+
if (!validModules.includes(eventModule)) {
112+
return res.status(400).send({
113+
error: `Invalid module. Must be one of: ${validModules.join(', ')}`,
114+
});
115+
}
116+
117+
const event = new CalendarEvent({
118+
title,
119+
module: eventModule,
120+
event_type,
121+
scheduled_date,
122+
description,
123+
assigned_to,
124+
related_item,
125+
status,
126+
});
127+
128+
await event.save();
129+
return res.status(201).send(event);
130+
} catch (err) {
131+
// eslint-disable-next-line no-console
132+
console.error('Error creating calendar event:', err);
133+
return res.status(500).send({ error: 'Internal Server Error' });
134+
}
135+
};
136+
137+
const deleteEvent = async (req, res) => {
138+
try {
139+
const { id } = req.params;
140+
141+
const event = await CalendarEvent.findById(id);
142+
if (!event) {
143+
return res.status(404).send({ error: 'Event not found.' });
144+
}
145+
146+
await CalendarEvent.findByIdAndDelete(id);
147+
return res.status(200).send({ message: 'Event successfully deleted.' });
148+
} catch (err) {
149+
// eslint-disable-next-line no-console
150+
console.error('Error deleting calendar event:', err);
151+
return res.status(500).send({ error: 'Internal Server Error' });
152+
}
153+
};
154+
155+
return {
156+
getCalendarEvents,
157+
createEvent,
158+
deleteEvent,
159+
};
160+
};
161+
162+
module.exports = calendarController;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const mongoose = require('mongoose');
2+
3+
const processingProjectController = function (ProcessingProject) {
4+
const postProject = async (req, res) => {
5+
try {
6+
const {
7+
item_name,
8+
process_name,
9+
quantity,
10+
unit,
11+
batches,
12+
supplies_quantity,
13+
supplies_type,
14+
scheduled_date,
15+
priority,
16+
} = req.body;
17+
18+
if (!item_name || !process_name || !quantity) {
19+
return res
20+
.status(400)
21+
.send({ error: 'Item name, process name, and quantity are required.' });
22+
}
23+
24+
const project = new ProcessingProject({
25+
item_name,
26+
process_name,
27+
quantity,
28+
unit,
29+
batches,
30+
supplies_quantity,
31+
supplies_type,
32+
scheduled_date,
33+
priority,
34+
});
35+
36+
await project.save();
37+
res.status(201).send(project);
38+
} catch (err) {
39+
console.error(err);
40+
res.status(500).send({ error: 'Internal Server Error' });
41+
}
42+
};
43+
44+
const getProjects = async (req, res) => {
45+
try {
46+
// Get all projects, sorted by scheduled_date ascending (upcoming first)
47+
// If user wants "current and upcoming", we technically might want to filter scheduled_date >= today
48+
// But based on the request "get all the current and upcoming projects", returning all sorted is a good start.
49+
const projects = await ProcessingProject.find().sort({ scheduled_date: 1 });
50+
51+
res.status(200).send(projects);
52+
} catch (err) {
53+
console.error(err);
54+
res.status(500).send({ error: 'Internal Server Error' });
55+
}
56+
};
57+
58+
return {
59+
postProject,
60+
getProjects,
61+
};
62+
};
63+
64+
module.exports = processingProjectController;

src/controllers/progressController.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,10 @@ const progressController = function () {
210210
}
211211

212212
// Check if progress record already exists
213-
const existingProgress = await Progress.findOne({ studentId: sanitizedStudentId, atomId: sanitizedAtomId });
213+
const existingProgress = await Progress.findOne({
214+
studentId: sanitizedStudentId,
215+
atomId: sanitizedAtomId,
216+
});
214217
if (existingProgress) {
215218
return res
216219
.status(400)
@@ -532,7 +535,9 @@ const progressController = function () {
532535
.select('name description difficulty moleculeType subjectId');
533536

534537
// Find unearned atoms (atoms not in progress records)
535-
const progressAtomIds = new Set(progressRecords.map((p) => p.atomId?._id.toString()).filter(Boolean));
538+
const progressAtomIds = new Set(
539+
progressRecords.map((p) => p.atomId?._id.toString()).filter(Boolean),
540+
);
536541
const unearnedAtoms = allAtoms
537542
.filter((atom) => !progressAtomIds.has(atom._id.toString()))
538543
.map((atom) => ({

0 commit comments

Comments
 (0)