Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
271470e
MER-125-Club-Meetings-Infra
himekatie Feb 24, 2026
6543e66
linking task list
himekatie Feb 24, 2026
f01b0a1
Merge branch 'main' of github.com:Study-Compass/Study-Compass into ME…
himekatie Feb 27, 2026
d0dc42e
MER-145 Register MeetingConfig, MeetingMinutes, RecurringMeetingRule …
himekatie Feb 27, 2026
33be715
MER-146, MER-153 Add meetingService with resolveRequiredAttendees for…
himekatie Feb 27, 2026
d445acf
MER-147 Add recurringMeetingService to generate Event instances from …
himekatie Feb 27, 2026
cac4b5b
MER-147, MER-151 Add Inngest cron for recurring meeting generation an…
himekatie Feb 27, 2026
5121794
MER-148, MER-150, MER-152, MER-154 Add meeting routes (RSVP, minutes,…
himekatie Feb 27, 2026
7e27ede
MER-148 updating plan
himekatie Feb 27, 2026
c2c6f2c
MER-125-Club-Meetings Added Meetings componenets and changed the club…
himekatie Apr 3, 2026
a43eea1
MER-125-Club-Meetings Added tab container and the information within …
himekatie Apr 17, 2026
1fbcf76
MER-125-Club-Meetings Added Header Container for Upcomming and past m…
himekatie Apr 17, 2026
d5c336b
MER-125-Club-Meetings made different files for a lot of things in Mee…
himekatie Apr 26, 2026
c664365
MER-125-Club-Meetings added the Meeting section so when a meeting is …
himekatie Apr 28, 2026
9532e3a
MER-125-Club-Meetings Changed attendance button for past meetings, it…
himekatie Apr 28, 2026
7b2d49c
MER-125-Club-Meetings Added the reminders tab in the meeting information
himekatie Apr 28, 2026
d20eaea
MER-125-Club-Meetings Added Attedance tab to make Meeting.jsx more clear
himekatie Apr 28, 2026
0dce776
MER-125-Club-Meetings Added Reminders tab to make Meeting.jsx more clear
himekatie Apr 28, 2026
c1ad6a1
MER-125-Club-Meetings Changed some design to look better and closer t…
himekatie Apr 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ function createApp() {
const qrRoutes = require('./routes/qrRoutes.js');
const eventAnalyticsRoutes = require('./routes/eventAnalyticsRoutes.js');
const orgEventManagementRoutes = require('./routes/orgEventManagementRoutes.js');
const meetingRoutes = require('./routes/meetingRoutes.js');
const formRoutes = require('./routes/formRoutes.js');
const inngestRoutes = require('./routes/inngestRoutes.js');
const inngestServe = require('./inngest/serve.js');
Expand Down Expand Up @@ -140,6 +141,7 @@ function createApp() {
app.use('/org-management', orgManagementRoutes);
app.use('/org-invites', orgInviteRoutes);
app.use('/org-messages', orgMessageRoutes);
app.use('/org-event-management', meetingRoutes);
app.use('/org-event-management', orgEventManagementRoutes);
app.use('/admin', roomRoutes);
app.use(adminRoutes);
Expand Down
35 changes: 35 additions & 0 deletions backend/inngest/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,44 @@ const sendTestConnectionEvent = async (testId, message, callbackUrl) => {
}
};

/**
* Send meeting reminder event (manual or from cron)
* @param {string} eventId - Event ID
* @param {string} school - Tenant/school subdomain
*/
const sendMeetingReminderEvent = async (eventId, school = 'rpi') => {
try {
await inngest.send({
name: 'meeting/reminder.send',
data: { eventId, school },
});
console.log(`Meeting reminder event sent for event ${eventId}`);
} catch (error) {
console.error('Error sending meeting reminder event:', error);
}
};

/**
* Trigger recurring meeting instance generation (manual or cron)
* @param {string} school - Tenant/school subdomain
*/
const sendRecurringMeetingGenerateEvent = async (school = 'rpi') => {
try {
await inngest.send({
name: 'meeting/recurring.generate',
data: { school },
});
console.log(`Recurring meeting generate event sent for school ${school}`);
} catch (error) {
console.error('Error sending recurring meeting generate event:', error);
}
};

module.exports = {
sendUserRegisteredEvent,
sendRoomCheckoutEvent,
sendRoomCheckinEvent,
sendTestConnectionEvent,
sendMeetingReminderEvent,
sendRecurringMeetingGenerateEvent,
};
104 changes: 104 additions & 0 deletions backend/inngest/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,114 @@ const testInngestDashboard = inngest.createFunction(
}
);

// Function: Scheduled recurring meeting generation (runs daily at 2 AM, or on manual event)
const scheduledRecurringMeetingGeneration = inngest.createFunction(
{ id: 'scheduled-recurring-meeting-generation' },
[
{ cron: '0 2 * * *' },
{ event: 'meeting/recurring.generate' }
],
async ({ event, step }) => {
const school = event?.data?.school || 'rpi';

const result = await step.run('generate-recurring-instances', async () => {
const { connectToDatabase } = require('../connectionsManager');
const getModels = require('../services/getModelService');
const recurringMeetingService = require('../services/recurringMeetingService');

const db = await connectToDatabase(school);
const req = { db, school };

const { RecurringMeetingRule } = getModels(req, 'RecurringMeetingRule');
const rules = await RecurringMeetingRule.find({ isActive: true }).lean();

let totalCreated = 0;
for (const rule of rules) {
const created = await recurringMeetingService.generateUpcomingInstances(
{ ...rule, _id: rule._id },
req,
{ maxOccurrences: 5 }
);
totalCreated += created.length;
}

return { totalCreated, rulesProcessed: rules.length };
});

return { success: true, ...result };
}
);

// Function: Meeting reminder cron (runs every 30 minutes)
const meetingReminderCron = inngest.createFunction(
{ id: 'meeting-reminder-cron' },
{ cron: '*/30 * * * *' },
async ({ step }) => {
const school = 'rpi';

const result = await step.run('send-meeting-reminders', async () => {
const { connectToDatabase } = require('../connectionsManager');
const getModels = require('../services/getModelService');
const NotificationService = require('../services/notificationService');

const db = await connectToDatabase(school || 'rpi');
const req = { db, school };

const { Event, MeetingConfig, OrgMember } = getModels(req, 'Event', 'MeetingConfig', 'OrgMember');
const configs = await MeetingConfig.find({
'reminderConfig.enabled': true
}).lean();

const now = new Date();
let remindersSent = 0;

for (const config of configs) {
const eventDoc = await Event.findById(config.eventId);
if (!eventDoc) continue;

const leadMs = (config.reminderConfig?.leadTimeMinutes || 60 * 24) * 60 * 1000;
const windowStart = new Date(eventDoc.start_time.getTime() - leadMs);
const windowEnd = new Date(eventDoc.start_time.getTime());

if (now >= windowStart && now <= windowEnd) {
const orgId = eventDoc.hostingId?.toString();
const requiredRoles = config.requiredRoles || ['members'];
const members = await OrgMember.find({ org_id: orgId, status: 'active' })
.populate('user_id', '_id')
.lean();

const models = { Notification: db.model('Notification', require('../schemas/notification'), 'notifications') };
const notificationService = NotificationService.withModels(models);
const recipients = members.map((m) => ({ id: m.user_id?._id || m.user_id, model: 'User' }));

for (const r of recipients) {
await notificationService.createNotification({
recipient: r.id,
recipientModel: 'User',
title: 'Meeting Reminder',
message: `${eventDoc.name} starts at ${eventDoc.start_time.toLocaleString()}`,
type: 'reminder',
channels: config.reminderConfig?.channels || ['in_app'],
metadata: { eventId: eventDoc._id }
});
}
remindersSent++;
}
}

return { remindersSent };
});

return { success: true, ...result };
}
);

// Export all functions
module.exports = {
processRoomCheckout,
autoCheckoutAfterDelay,
testInngestConnection,
testInngestDashboard,
scheduledRecurringMeetingGeneration,
meetingReminderCron,
};
Loading