180 lines
5.5 KiB
JavaScript
180 lines
5.5 KiB
JavaScript
|
|
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
|
|
};
|