csa-backend-test/app/services/firebase.services.js

202 lines
5.8 KiB
JavaScript

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,
};