const { Task, Notification, User } = require('../../models'); const { Op } = require('sequelize'); const { logError } = require('../../services/logService'); const { shouldSendInAppNotification, shouldSendTelegramNotification, } = require('../../utils/notificationPreferences'); /** * Service to check for due and overdue tasks * and create notifications for users */ /** * Check for tasks that are due soon or overdue * and create notifications for the task owners */ async function checkDueTasks() { try { const now = new Date(); const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000); const twoDaysAgo = new Date(now.getTime() - 2 * 24 * 60 * 60 * 1000); const dueTasks = await Task.findAll({ where: { due_date: { [Op.not]: null, [Op.lte]: tomorrow, }, status: { [Op.ne]: 2, }, }, include: [ { model: User, attributes: [ 'id', 'email', 'name', 'notification_preferences', ], }, ], }); if (dueTasks.length === 0) { return { success: true, tasksProcessed: 0, notificationsCreated: 0, }; } let notificationsCreated = 0; for (const task of dueTasks) { try { const dueDate = new Date(task.due_date); const isOverdue = dueDate < now; const notificationType = isOverdue ? 'task_overdue' : 'task_due_soon'; const level = isOverdue ? 'error' : 'warning'; // Check if user wants this notification if (!shouldSendInAppNotification(task.User, notificationType)) { continue; } // Check for existing notifications (including dismissed ones) // If a notification was dismissed, don't create it again const recentNotifications = await Notification.findAll({ where: { user_id: task.user_id, type: { [Op.in]: ['task_due_soon', 'task_overdue'], }, created_at: { [Op.gte]: twoDaysAgo, }, }, }); const existingNotification = recentNotifications.find( (notif) => notif.data?.taskUid === task.uid && notif.type === notificationType ); if (existingNotification) { // Skip if notification exists, even if it was dismissed // This prevents re-notifying users about tasks they've already dismissed continue; } const { title, message } = generateNotificationContent( task.name, dueDate, now, isOverdue ); // Build sources array based on user preferences const sources = []; if ( shouldSendTelegramNotification(task.User, notificationType) ) { sources.push('telegram'); } await Notification.createNotification({ userId: task.user_id, type: notificationType, title, message, level, sources, data: { taskUid: task.uid, taskName: task.name, dueDate: task.due_date, isOverdue, }, sentAt: new Date(), }); notificationsCreated++; } catch (error) { logError( `Error creating notification for task ${task.id}:`, error ); } } return { success: true, tasksProcessed: dueTasks.length, notificationsCreated, }; } catch (error) { logError('Error checking due tasks:', error); throw error; } } /** * Generate notification title and message based on task due date */ function generateNotificationContent(taskName, dueDate, now, isOverdue) { if (isOverdue) { const daysOverdue = Math.floor((now - dueDate) / (1000 * 60 * 60 * 24)); const title = 'Task is overdue'; let message; if (daysOverdue === 0) { message = `Your task "${taskName}" was due today`; } else if (daysOverdue === 1) { message = `Your task "${taskName}" was due yesterday`; } else { message = `Your task "${taskName}" was due ${daysOverdue} days ago`; } return { title, message }; } else { const hoursUntilDue = Math.floor((dueDate - now) / (1000 * 60 * 60)); const title = 'Task due soon'; let message; if (hoursUntilDue < 1) { message = `Your task "${taskName}" is due in less than 1 hour`; } else if (hoursUntilDue < 24) { message = `Your task "${taskName}" is due in ${hoursUntilDue} hours`; } else { message = `Your task "${taskName}" is due tomorrow`; } return { title, message }; } } module.exports = { checkDueTasks, };