import Event from '../model/event.model'; import User from '../model/user.model'; import sendMail from '../util/mailer.util'; import Queue from 'bull'; import { REDIS_URL } from '../config/env.config'; // Create Bull queues for async job processing const emailQueue = new Queue('email-notifications', REDIS_URL || 'redis://localhost:6379'); const reminderQueue = new Queue('event-reminders', REDIS_URL || 'redis://localhost:6379'); // ==================== EMAIL QUEUE PROCESSOR ==================== emailQueue.process(async (job) => { const { to, subject, text, html } = job.data; await sendMail({ to, subject, text, html }); }); // ==================== REMINDER QUEUE PROCESSOR ==================== reminderQueue.process(async (job) => { const { eventId } = job.data; await processEventReminder(eventId); }); // ==================== QUEUE EVENT REMINDER (Stateless) ==================== export const queueEventReminder = async (eventId: string, sendAt: Date) => { const delay = sendAt.getTime() - Date.now(); if (delay > 0) { await reminderQueue.add( { eventId }, { delay, attempts: 3, backoff: { type: 'exponential', delay: 2000 } } ); } }; // ==================== PROCESS EVENT REMINDER ==================== async function processEventReminder(eventId: string) { try { const event = await Event.findById(eventId) .populate('participants.user', 'email username') .populate('organization', 'username'); if (!event || event.reminderSent) return; for (const participant of event.participants) { const user = participant.user as any; await emailQueue.add({ to: user.email, subject: `Reminder: ${event.title} - Tomorrow`, text: `Reminder: ${event.title} starts tomorrow at ${event.startDate.toLocaleString()}`, html: generateReminderHTML(event, user) }); } event.reminderSent = true; await event.save(); console.log(`Reminder sent for event: ${event.title}`); } catch (err) { console.error('Error sending reminder:', err); throw err; // Bull will retry } } function generateReminderHTML(event: any, user: any): string { return `

Event Reminder

Hi ${user.username},

This is a reminder that you're registered for:

${event.title}

Code: ${event.code}

Start: ${event.startDate.toLocaleString()}

`; } // ==================== SCHEDULE ALL UPCOMING REMINDERS (Run on startup) ==================== export async function scheduleUpcomingReminders() { const now = new Date(); const sevenDaysFromNow = new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000); const upcomingEvents = await Event.find({ status: 'published', reminderSent: false, startDate: { $gte: now, $lte: sevenDaysFromNow } }); for (const event of upcomingEvents) { const reminderTime = new Date(event.startDate.getTime() - 24 * 60 * 60 * 1000); await queueEventReminder(event._id.toString(), reminderTime); } console.log(`Scheduled ${upcomingEvents.length} event reminders`); } export const sendEventUpdateNotification = async (eventId: string, updateMessage: string) => { const event = await Event.findById(eventId).populate('participants.user', 'email username'); if (!event) return; for (const participant of event.participants) { const user = participant.user as any; await emailQueue.add({ to: user.email, subject: `Update: ${event.title}`, text: updateMessage, html: `

Event Update

${updateMessage}

` }); } }; export const sendWaitlistPromotionNotification = async (eventId: string, userId: string) => { const event = await Event.findById(eventId); const user = await User.findById(userId); if (!event || !user) return; await emailQueue.add({ to: user.email, subject: `Spot Available: ${event.title}`, text: `Good news! A spot has opened up for ${event.title}`, html: `

Good News!

You've been registered for ${event.title}

` }); };