require('dotenv').config(); const cron = require('node-cron'); const logger = require('./logger.services'); const { getDueNotifications, updateNotificationStatus } = require('./notification-scheduler.services'); const { sendNotification } = require('./firebase.services'); const { PrismaClient: CMSClient } = require("../../prisma/clients/cms"); const { localTime } = require('./time.services'); const prisma = new CMSClient(); /** * Process and send a single due notification * @param {Object} notification - The notification record */ async function processDueNotification(notification) { const startTime = Date.now(); try { const userToken = await prisma.usersToken.findFirst({ where: { UserID_UT: notification.UserID_AIN } }); if (!userToken || !userToken.Token_UT) { logger.warn(`No valid FCM token for user ${notification.UserID_AIN}`); await updateNotificationStatus(notification.UUID_AIN, { SentStatus_AIN: 'failed', ErrorMessage_AIN: 'No valid FCM token', FailedAt_AIN: localTime(new Date()) }); return { success: false, reason: 'No FCM token' }; } const messageId = await sendNotification( userToken.Token_UT, notification.GeneratedTitle_AIN, notification.GeneratedDesc_AIN, { source: 'scheduled-notification', aiNotificationId: notification.UUID_AIN, scheduledDelivery: 'true', confidence: notification.PredictedConfidence_AIN?.toString() || '0', pattern: notification.UserEngagementPattern_AIN || 'unknown' } ); await updateNotificationStatus(notification.UUID_AIN, { SentStatus_AIN: 'sent', SentAt_AIN: localTime(new Date()), FCMMessageId_AIN: messageId, ProcessingTime_AIN: Date.now() - startTime }); logger.info(`Scheduled notification sent to ${notification.UserID_AIN}`, { notificationId: notification.UUID_AIN, messageId, scheduledFor: notification.ScheduledAt_AIN, confidence: notification.PredictedConfidence_AIN, pattern: notification.UserEngagementPattern_AIN }); return { success: true, messageId }; } catch (error) { logger.error(`Failed to send scheduled notification ${notification.UUID_AIN}:`, error); await updateNotificationStatus(notification.UUID_AIN, { SentStatus_AIN: 'failed', ErrorMessage_AIN: error.message, FailedAt_AIN: localTime(new Date()), ProcessingTime_AIN: Date.now() - startTime }); return { success: false, error: error.message }; } } /** * Main processor function - checks and sends all due notifications */ async function processScheduledNotifications() { try { logger.info('🔄 Checking for due scheduled notifications...'); const dueNotifications = await getDueNotifications(); if (dueNotifications.length === 0) { logger.info('✓ No notifications due at this time'); return { processed: 0, sent: 0, failed: 0 }; } logger.info(`Found ${dueNotifications.length} notifications to process`); const results = { processed: 0, sent: 0, failed: 0, details: [] }; for (const notification of dueNotifications) { results.processed++; const result = await processDueNotification(notification); if (result.success) { results.sent++; } else { results.failed++; } results.details.push({ notificationId: notification.UUID_AIN, userId: notification.UserID_AIN, success: result.success, messageId: result.messageId, error: result.error, reason: result.reason }); await new Promise(resolve => setTimeout(resolve, 500)); } logger.info(`Scheduled notification processing complete:`, { processed: results.processed, sent: results.sent, failed: results.failed }); return results; } catch (error) { logger.error('Error in scheduled notification processor:', error); throw error; } } /** * Start the cron job for scheduled notifications * Runs every 5 minutes */ function startScheduledNotificationCron() { const cronExpression = '*/5 * * * *'; const job = cron.schedule(cronExpression, async () => { logger.info('🕐 Scheduled notification cron job triggered'); try { await processScheduledNotifications(); } catch (error) { logger.error('Cron job error:', error); } }, { scheduled: true, timezone: process.env.TIMEZONE || "Asia/Jakarta" }); logger.info(`✅ Scheduled notification cron started (runs every 5 minutes)`); return job; } /** * Manual trigger for testing */ async function manualTriggerScheduledProcessor() { logger.info('🔧 Manual trigger: Processing scheduled notifications'); return await processScheduledNotifications(); } module.exports = { startScheduledNotificationCron, processScheduledNotifications, manualTriggerScheduledProcessor, processDueNotification };