const admin = require("firebase-admin"); const { PrismaClient: CMSClient } = require("../../prisma/clients/cms"); const logger = require("./logger.services"); const { localTime } = require("./time.services.js"); const prisma = new CMSClient(); async function sendNotification(token, title, body, data = {}, imageUrl = null) { if (!token) throw new Error("Missing FCM token"); const stringData = {}; for (const [key, value] of Object.entries(data)) { stringData[key] = typeof value === 'string' ? value : JSON.stringify(value); } if (imageUrl) { stringData.image = imageUrl; } const message = { notification: { title, body }, data: stringData, token, }; if (imageUrl) { message.android = { notification: { imageUrl: imageUrl } }; } return await admin.messaging().send(message); } async function sendToTopic(title, body, data = {}, imageUrl = null) { if (!title || !body) throw new Error("Missing notification title or body"); const tokens = await prisma.usersToken.findMany({ select: { UserID_UT: true, Token_UT: true } }); const tokenList = tokens.filter(t => t.Token_UT); if (tokenList.length === 0) { return { successCount: 0, failureCount: 0, targetUsers: 0, deliveryDetails: [], message: "No tokens registered" }; } const stringData = {}; for (const [key, value] of Object.entries(data)) { stringData[key] = typeof value === 'string' ? value : JSON.stringify(value); } if (imageUrl) { stringData.image = imageUrl; } const chunkSize = 500; let successTotal = 0; let failureTotal = 0; const deliveryDetails = []; for (let i = 0; i < tokenList.length; i += chunkSize) { const tokensChunk = tokenList.slice(i, i + chunkSize); const tokensOnly = tokensChunk.map(t => t.Token_UT); const message = { notification: { title, body }, data: stringData, tokens: tokensOnly, }; if (imageUrl) { message.android = { notification: { imageUrl: imageUrl } }; } const response = await admin.messaging().sendEachForMulticast(message); successTotal += response.successCount; failureTotal += response.failureCount; response.responses.forEach((resp, index) => { const userToken = tokensChunk[index]; deliveryDetails.push({ userID: userToken.UserID_UT, token: userToken.Token_UT, success: resp.success, messageId: resp.messageId || null, error: resp.error ? { code: resp.error.code, message: resp.error.message } : null }); }); } logger.info(`Sent ${successTotal} notifications successfully and ${failureTotal} notifications failed`); return { successCount: successTotal, failureCount: failureTotal, targetUsers: tokenList.length, deliveryRate: tokenList.length > 0 ? ((successTotal / tokenList.length) * 100).toFixed(2) : 0, deliveryDetails: deliveryDetails }; } async function sendCampaignWithTracking(campaignID, title, body, data = {}, imageUrl = null) { try { const sentAt = new Date(); const users = await prisma.usersToken.findMany({ select: { UserID_UT: true, Token_UT: true } }); const validUsers = users.filter(u => u.Token_UT); if (validUsers.length === 0) { return { success: false, message: "No valid tokens found", targetUsers: 0, successCount: 0, failureCount: 0 }; } await prisma.campaignDelivery.createMany({ data: validUsers.map(user => ({ Campaign_CD: campaignID, UserID_CD: user.UserID_UT, Token_CD: user.Token_UT, Status_CD: 'pending', CreatedAt_CD: sentAt })) }); const result = await sendToTopic(title, body, { ...data, campaignID }, imageUrl); for (const delivery of result.deliveryDetails) { const updateData = { SentAt_CD: sentAt, UpdatedAt_CD: localTime(new Date()) }; if (delivery.success) { updateData.Status_CD = 'delivered'; updateData.DeliveredAt_CD = localTime(new Date()); updateData.ResponseData_CD = JSON.stringify({ messageId: delivery.messageId }); } else { updateData.Status_CD = 'failed'; updateData.FailedAt_CD = localTime(new Date()); updateData.ErrorMessage_CD = delivery.error ? delivery.error.message : 'Unknown error'; updateData.ResponseData_CD = JSON.stringify(delivery.error); } await prisma.campaignDelivery.updateMany({ where: { Campaign_CD: campaignID, UserID_CD: delivery.userID }, data: updateData }); } return { success: true, targetUsers: validUsers.length, successCount: result.successCount, failureCount: result.failureCount, deliveryRate: result.deliveryRate, sentAt: sentAt }; } catch (error) { logger.error(`Error in sendCampaignWithTracking: ${error}`); throw error; } } module.exports = { sendNotification, sendToTopic, sendCampaignWithTracking, };