Website : rimsha.abasa.com
backdoor
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
var
/
www
/
mudeerapi.abasa.com
/
nodetest
/
src
/
services
/
Filename :
notificationService.js
back
Copy
import admin from 'firebase-admin'; import dotenv from 'dotenv'; import mongoose from 'mongoose'; dotenv.config(); // Initialize Firebase Admin SDK safely // Initialize Firebase Admin SDK safely (temporary disable) if (process.env.FIREBASE_SERVICE_ACCOUNT_KEY) { if (!admin.apps.length) { const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_KEY); admin.initializeApp({ credential: admin.credential.cert(serviceAccount), projectId: serviceAccount.project_id, }); } } else { console.log("⚠️ Firebase disabled temporarily (no service account provided)"); } // Helper function to convert all data values to strings for FCM compatibility const sanitizeDataForFCM = (data) => { if (!data || typeof data !== 'object') return {}; const sanitized = {}; for (const [key, value] of Object.entries(data)) { if (value !== null && value !== undefined) { sanitized[key] = String(value); } } return sanitized; }; // Core unified notification service export const sendNotificationService = async (options) => { try { const { // Content title, body, data = {}, template = null, templateData = {}, // Recipients recipientIds = null, recipientRoles = null, departmentId = null, excludeIds = [], includeIds = [], // Sender senderId = null, senderName = null, // Type/Category type = 'manual_notification', category = 'admin_message', // Control flags sendFCM = true } = options; const { user } = await import('../models/user.models.js'); const { Notification } = await import('../models/notification.model.js'); const { Department } = await import('../models/department.model.js'); let notificationContent = { title, body, type, category, data: sanitizeDataForFCM(data) }; // Use template if provided if (template) { const templates = await import('../template/notificationTemplates.js'); switch (template) { case 'login_notification': notificationContent = templates.loginNotificationTemplate( templateData.employeeName, templateData.employeeRole, templateData.department, templateData.loginTime ); break; case 'manual_notification': notificationContent = templates.manualNotificationTemplate( templateData.title, templateData.body, templateData.senderName, templateData.data || {} ); break; case 'department_announcement': notificationContent = templates.departmentAnnouncementTemplate( templateData.title, templateData.body, templateData.departmentName, templateData.senderName ); break; case 'system_alert': notificationContent = templates.systemAlertTemplate( templateData.title, templateData.body, templateData.priority, templateData.actionRequired ); break; } // Sanitize template data for FCM notificationContent.data = sanitizeDataForFCM(notificationContent.data); } // Resolve recipients let recipients = []; if (recipientIds && Array.isArray(recipientIds)) { recipients = await user.find({ _id: { $in: recipientIds.map(id => new mongoose.Types.ObjectId(id)) } }); } else if (recipientRoles && Array.isArray(recipientRoles)) { const query = { role: { $in: recipientRoles } }; // If departmentId is provided, we need to handle it specially // Department heads should be filtered by department // But admins and super_admins should be included regardless if (departmentId) { // Get users who match roles AND are either: // 1. Department head in the specified department // 2. Admin or super_admin (any department) recipients = await user.find({ role: { $in: recipientRoles }, $or: [ { role: 'department_head', department: new mongoose.Types.ObjectId(departmentId) }, { role: 'admin' }, { role: 'super_admin' } ] }); } else { recipients = await user.find(query); } } else if (departmentId) { recipients = await user.find({ department: new mongoose.Types.ObjectId(departmentId) }); } // Merge in additional recipients (e.g. self) when using role-based resolution if (includeIds && includeIds.length > 0) { const existingIds = new Set(recipients.map(r => r._id.toString())); const includeObjectIds = includeIds.filter(id => !existingIds.has(id.toString())); if (includeObjectIds.length > 0) { const includeUsers = await user.find({ _id: { $in: includeObjectIds.map(id => new mongoose.Types.ObjectId(id)) } }); recipients = recipients.concat(includeUsers); } } // Filter out excluded users if (excludeIds.length > 0) { const excludeObjectIds = excludeIds.map(id => id.toString()); recipients = recipients.filter(user => !excludeObjectIds.includes(user._id.toString())); } // Filter out deactivated users recipients = recipients.filter(user => user.isActive !== false); if (recipients.length === 0) { console.log('⚠️ No recipients found for notification'); return { success: true, message: 'No recipients found', totalRecipients: 0, savedToDb: 0, fcmSent: 0, fcmFailed: 0 }; } // Log recipients console.log(`📨 Found ${recipients.length} recipient(s):`); recipients.forEach((r, index) => { console.log(` ${index + 1}. ${r.first_name} ${r.last_name} (${r.email}) - ${r.role}`); }); let savedNotifications = []; // Save to database const notifications = recipients.map(recipient => ({ recipient: recipient._id, sender: senderId ? new mongoose.Types.ObjectId(senderId) : null, title: notificationContent.title, body: notificationContent.body, type: notificationContent.type, category: notificationContent.category, data: notificationContent.data, deliveryStatus: 'pending' })); console.log(`💾 Attempting to save ${notifications.length} notification(s) to database...`); console.log(` Type: ${notificationContent.type}`); console.log(` Category: ${notificationContent.category}`); try { savedNotifications = await Notification.insertMany(notifications); console.log(`✅ Successfully saved ${savedNotifications.length} notification(s) to database`); } catch (insertError) { console.error('❌ FAILED to insert notifications into database:'); console.error(' Error:', insertError.message); console.error(' Full error:', insertError); throw insertError; // Re-throw to be caught by outer catch } let fcmResult = { successCount: 0, failureCount: 0 }; // Per-recipient delivery status (recipientId -> { deliveryStatus, fcmResponse }) const recipientStatusMap = new Map(); for (const r of recipients) { recipientStatusMap.set(r._id.toString(), { deliveryStatus: 'failed', fcmResponse: { error: 'no_fcm_token' } }); } // Send FCM if enabled if (sendFCM) { const recipientsWithTokens = recipients.filter(r => r.fcmToken); if (recipientsWithTokens.length > 0) { if (recipientsWithTokens.length === 1) { const result = await sendNotification( recipientsWithTokens[0].fcmToken, notificationContent.title, notificationContent.body, notificationContent.data ); fcmResult.successCount = result.success ? 1 : 0; fcmResult.failureCount = result.success ? 0 : 1; recipientStatusMap.set(recipientsWithTokens[0]._id.toString(), { deliveryStatus: result.success ? 'sent' : 'failed', fcmResponse: { messageId: result.success ? (result.response || undefined) : undefined, error: result.success ? undefined : (result.error || 'Unknown error') } }); } else { const fcmTokens = recipientsWithTokens.map(r => r.fcmToken); const result = await sendNotificationToMultipleUsers( fcmTokens, notificationContent.title, notificationContent.body, notificationContent.data ); const batchResponse = result.response; if (batchResponse && batchResponse.responses) { fcmResult.successCount = batchResponse.successCount ?? 0; fcmResult.failureCount = batchResponse.failureCount ?? fcmTokens.length; batchResponse.responses.forEach((res, i) => { const recipientId = recipientsWithTokens[i]._id.toString(); recipientStatusMap.set(recipientId, { deliveryStatus: res.success ? 'sent' : 'failed', fcmResponse: { messageId: res.success ? (res.messageId || undefined) : undefined, error: res.success ? undefined : (res.error?.message || res.error?.code || 'FCM error') } }); }); } else { fcmResult.failureCount = fcmTokens.length; const batchError = result.error || 'FCM batch failed'; recipientsWithTokens.forEach((r) => { recipientStatusMap.set(r._id.toString(), { deliveryStatus: 'failed', fcmResponse: { error: batchError } }); }); } } } } // Update each notification with its recipient's delivery status await Notification.bulkWrite( savedNotifications.map((notif) => { const rec = recipientStatusMap.get(notif.recipient.toString()) || { deliveryStatus: 'failed', fcmResponse: { error: 'unknown' } }; return { updateOne: { filter: { _id: notif._id }, update: { $set: { deliveryStatus: rec.deliveryStatus, fcmResponse: rec.fcmResponse } } } }; }) ); return { success: true, totalRecipients: recipients.length, savedToDb: savedNotifications.length, fcmSent: fcmResult.successCount, fcmFailed: fcmResult.failureCount, notifications: savedNotifications }; } catch (error) { console.error('Error in sendNotificationService:', error); return { success: false, error: error.message, totalRecipients: 0, savedToDb: 0, fcmSent: 0, fcmFailed: 0 }; } }; // Original Firebase functions (maintained for backward compatibility) export const sendNotification = async (fcmToken, title, body, data = {}) => { try { const message = { notification: { title, body, }, data: sanitizeDataForFCM(data), token: fcmToken, }; const response = await admin.messaging().send(message); console.log('Notification sent successfully:', response); return { success: true, response }; } catch (error) { console.error('Error sending notification:', error); return { success: false, error: error.message }; } }; export const sendNotificationToMultipleUsers = async (fcmTokens, title, body, data = {}) => { try { const message = { notification: { title, body, }, data: sanitizeDataForFCM(data), tokens: fcmTokens, }; const response = await admin.messaging().sendEachForMulticast(message); console.log('Notifications sent successfully:', response); return { success: true, response }; } catch (error) { console.error('Error sending notifications:', error); return { success: false, error: error.message }; } }; // Hierarchical notification functions export const sendLoginNotification = async (userObj, minutesLate = null) => { try { // Check if user is active if (userObj.isActive === false) { return { success: false, error: 'User is deactivated. Cannot send login notification.' }; } const { Department } = await import('../models/department.model.js'); let departmentName = null; if (userObj.department) { const dept = await Department.findById(userObj.department); departmentName = dept?.name || null; } const loginTime = new Date().toLocaleString(); const userName = `${userObj.first_name} ${userObj.last_name}`; const templates = await import('../template/notificationTemplates.js'); const notificationContent = templates.hierarchicalLoginTemplate( userName, userObj.role, departmentName, loginTime, minutesLate ); // Super admin: themselves only if (userObj.role === 'super_admin') { return await sendNotificationService({ title: notificationContent.title, body: notificationContent.body, type: notificationContent.type, category: notificationContent.category, data: notificationContent.data, recipientIds: [userObj._id.toString()], senderId: userObj._id, sendFCM: true }); } const normalizedRole = userObj.role ? userObj.role.toLowerCase() : ''; const roleRecipients = { employee: ['admin', 'super_admin'], department_head: ['admin', 'super_admin'], admin: ['super_admin'] }; const recipientRoles = roleRecipients[normalizedRole] || []; const result = await sendNotificationService({ title: notificationContent.title, body: notificationContent.body, type: notificationContent.type, category: notificationContent.category, data: notificationContent.data, recipientRoles, includeIds: [userObj._id.toString()], senderId: userObj._id, sendFCM: true }); return result; } catch (error) { console.error('Error in sendLoginNotification:', error); return { success: false, error: error.message }; } }; export const sendCheckoutNotification = async (userObj, workingHours = null) => { try { // Check if user is active if (userObj.isActive === false) { return { success: false, error: 'User is deactivated. Cannot send checkout notification.' }; } const { Department } = await import('../models/department.model.js'); let departmentName = null; if (userObj.department) { const dept = await Department.findById(userObj.department); departmentName = dept?.name || null; } const checkoutTime = new Date().toLocaleString(); const userName = `${userObj.first_name} ${userObj.last_name}`; const templates = await import('../template/notificationTemplates.js'); const notificationContent = templates.checkoutNotificationTemplate( userName, userObj.role, departmentName, checkoutTime, workingHours ); if (userObj.role === 'super_admin') { return await sendNotificationService({ title: notificationContent.title, body: notificationContent.body, type: notificationContent.type, category: notificationContent.category, data: notificationContent.data, recipientIds: [userObj._id.toString()], senderId: userObj._id, sendFCM: true }); } const normalizedRole = userObj.role ? userObj.role.toLowerCase() : ''; const roleRecipients = { employee: ['admin', 'super_admin'], department_head: ['admin', 'super_admin'], admin: ['super_admin'] }; const recipientRoles = roleRecipients[normalizedRole] || []; const result = await sendNotificationService({ title: notificationContent.title, body: notificationContent.body, type: notificationContent.type, category: notificationContent.category, data: notificationContent.data, recipientRoles, includeIds: [userObj._id.toString()], senderId: userObj._id, sendFCM: true }); return result; } catch (error) { console.error('Error in sendCheckoutNotification:', error); return { success: false, error: error.message }; } }; export const sendLateArrivalNotification = async (userObj, minutesLate) => { try { if (userObj.isActive === false) { return { success: false, error: 'User is deactivated.' }; } const { Department } = await import('../models/department.model.js'); let departmentName = null; if (userObj.department) { const dept = await Department.findById(userObj.department); departmentName = dept?.name || null; } const userName = `${userObj.first_name} ${userObj.last_name}`; const expectedStartTime = userObj.start_time; const templates = await import('../template/notificationTemplates.js'); const notificationContent = templates.lateArrivalNotificationTemplate( userName, userObj.role, departmentName, expectedStartTime, minutesLate ); // Single call: self + hierarchy (user included via includeIds for non–super_admin) if (userObj.role === 'super_admin') { const result = await sendNotificationService({ title: notificationContent.title, body: notificationContent.body, type: notificationContent.type, category: notificationContent.category, data: notificationContent.data, recipientIds: [userObj._id.toString()], senderId: null, sendFCM: true }); return { success: true, totalRecipients: result.totalRecipients || 0, hierarchyNotifications: result.totalRecipients || 0, userNotified: result.totalRecipients > 0 }; } const normalizedRole = userObj.role ? userObj.role.toLowerCase() : ''; const roleRecipients = { employee: ['admin', 'super_admin'], department_head: ['admin', 'super_admin'], admin: ['super_admin'] }; const recipientRoles = roleRecipients[normalizedRole] || []; const result = await sendNotificationService({ title: notificationContent.title, body: notificationContent.body, type: notificationContent.type, category: notificationContent.category, data: notificationContent.data, recipientRoles, includeIds: [userObj._id.toString()], senderId: null, sendFCM: true }); return { success: true, totalRecipients: result.totalRecipients || 0, hierarchyNotifications: result.totalRecipients || 0, userNotified: true }; } catch (error) { console.error('Error in sendLateArrivalNotification:', error); return { success: false, error: error.message }; } }; // Convenience functions for common operations // export const sendLoginNotification = async (employeeName, employeeRole, department, loginTime) => { // return await sendNotificationService({ // template: 'login_notification', // templateData: { // employeeName, // employeeRole, // department, // loginTime // }, // recipientRoles: ['admin', 'super_admin'], // type: 'login_notification', // category: 'login_activity', // saveToDb: true, // sendFCM: true // }); // }; export const sendAttendanceAlert = async (employeeName, alertType, department, senderId = null) => { return await sendNotificationService({ title: `Attendance Alert: ${alertType}`, body: `${employeeName} from ${department || 'Unknown Department'} - ${alertType}`, recipientRoles: ['admin', 'super_admin'], type: 'attendance_alert', category: 'attendance', data: { employeeName, alertType, department, timestamp: new Date().toISOString() }, senderId, saveToDb: true, sendFCM: true }); }; export const sendSystemAlert = async (title, body, priority = 'normal', actionRequired = false, senderId = null) => { return await sendNotificationService({ template: 'system_alert', templateData: { title, body, priority, actionRequired }, recipientRoles: ['admin', 'super_admin'], type: 'system_alert', category: 'system', senderId, saveToDb: true, sendFCM: true }); }; export const sendDepartmentNotification = async (departmentId, title, body, senderName, senderId = null) => { const { Department } = await import('../models/department.model.js'); const department = await Department.findById(departmentId); return await sendNotificationService({ template: 'department_announcement', templateData: { title, body, departmentName: department ? department.name : 'Unknown Department', senderName }, departmentId, type: 'department_announcement', category: 'department', senderId, saveToDb: true, sendFCM: true }); }; export const sendReminder = async (recipientIds, title, body, data = {}, senderId = null) => { return await sendNotificationService({ title, body, recipientIds, type: 'reminder', category: 'personal', data, senderId, saveToDb: true, sendFCM: true }); }; // Backward compatibility function (deprecated but maintained) export const sendLoginNotificationToAdmins = async (employeeName, employeeRole, department, loginTime) => { console.log('⚠️ sendLoginNotificationToAdmins is deprecated. Use sendLoginNotification instead.'); return await sendLoginNotification(employeeName, employeeRole, department, loginTime); }; export default { sendNotificationService, sendNotification, sendNotificationToMultipleUsers, sendLoginNotification, sendCheckoutNotification, sendLateArrivalNotification, sendAttendanceAlert, sendSystemAlert, sendDepartmentNotification, sendReminder, sendLoginNotificationToAdmins };