notification-issue #72

Merged
dhanshas merged 7 commits from notification-issue into dev 2025-07-29 07:46:19 +00:00
Showing only changes of commit ebac135d08 - Show all commits

View File

@ -12,31 +12,9 @@ interface NotificationData {
ownerId?: string; ownerId?: string;
type?: string; type?: string;
notificationSent?: boolean; notificationSent?: boolean;
userId?: string; timestamp?: admin.firestore.FieldValue;
clientId?: string;
invitorId?: string;
phoneNumber?: string;
message?: string;
status?: string;
gymName?: string;
trainerName?: string;
membershipId?: string;
subscriptionName?: string;
name?: string;
clientEmail?: string;
invitationId?: string;
gymId?: string;
trainerId?: string;
timeSlots?: Array<{[key: string]: any}>;
timestamp?: any;
read?: boolean; read?: boolean;
title?: string; data?: { [key: string]: any };
updatedBy?: string;
oldTimeSlot?: string;
newTimeSlot?: string;
formattedDate?: string;
logTime?: string;
[key: string]: any;
} }
export const processNotificationOnCreate = onDocumentCreated( export const processNotificationOnCreate = onDocumentCreated(
@ -79,10 +57,7 @@ export const processNotificationOnCreate = onDocumentCreated(
const message = prepareNotificationMessage(notification, fcmToken); const message = prepareNotificationMessage(notification, fcmToken);
try { try {
const fcmResponse = await app.messaging().send({ const fcmResponse = await app.messaging().send(message);
...message,
token: fcmToken,
});
logger.info(`FCM notification sent successfully: ${fcmResponse}`); logger.info(`FCM notification sent successfully: ${fcmResponse}`);
await markNotificationAsSent(notificationId); await markNotificationAsSent(notificationId);
@ -102,57 +77,58 @@ export const processNotificationOnCreate = onDocumentCreated(
async function getUserAndFCMToken( async function getUserAndFCMToken(
notification: NotificationData notification: NotificationData
): Promise<{ userId: string | null; fcmToken: string | null }> { ): Promise<{ userId: string | null; fcmToken: string | null }> {
let userId: string | null = null; let targetUserId: string | null = null;
let fcmToken: string | null = null; let fcmToken: string | null = null;
if (notification.recipientId) { if (notification.recipientId) {
userId = notification.recipientId; targetUserId = notification.recipientId;
fcmToken = await getFCMTokenFromUserDoc(userId); logger.info(`Using top-level recipientId: ${targetUserId}`);
logger.info(`Using recipientId: ${userId}`);
} else if (notification.ownerId) { } else if (notification.ownerId) {
userId = notification.ownerId; targetUserId = notification.ownerId;
fcmToken = await getFCMTokenFromUserDoc(userId); logger.info(`Using top-level ownerId: ${targetUserId}`);
logger.info(`Using ownerId: ${userId}`); } else if (notification.data?.userId) {
} else if (notification.userId) { targetUserId = notification.data.userId;
userId = notification.userId; logger.info(`Using data.userId: ${targetUserId}`);
fcmToken = await getFCMTokenFromUserDoc(userId); } else if (notification.data?.clientId) {
logger.info(`Using userId: ${userId}`); targetUserId = notification.data.clientId;
} else if (notification.clientId) { logger.info(`Using data.clientId: ${targetUserId}`);
userId = notification.clientId; } else if (notification.data?.invitorId) {
fcmToken = await getFCMTokenFromUserDoc(userId); targetUserId = notification.data.invitorId;
logger.info(`Using clientId: ${userId}`); logger.info(`Using data.invitorId: ${targetUserId}`);
} else if (notification.invitorId) { } else if (notification.data?.phoneNumber) {
userId = notification.invitorId; logger.info(
fcmToken = await getFCMTokenFromUserDoc(userId); `Looking up user by phone number from data: ${notification.data.phoneNumber}`
logger.info(`Using invitorId: ${userId}`); );
} else if (notification.phoneNumber) {
logger.info(`Looking up user by phone number: ${notification.phoneNumber}`);
const userQuery = await app const userQuery = await app
.firestore() .firestore()
.collection("users") .collection("users")
.where("phoneNumber", "==", notification.phoneNumber) .where("phoneNumber", "==", notification.data.phoneNumber)
.limit(1) .limit(1)
.get(); .get();
if (!userQuery.empty) { if (!userQuery.empty) {
const userDoc = userQuery.docs[0]; const userDoc = userQuery.docs[0];
userId = userDoc.id; targetUserId = userDoc.id;
fcmToken = userDoc.data()?.fcmToken; fcmToken = userDoc.data()?.fcmToken;
logger.info(`Found user by phone: ${userId}`); logger.info(`Found user by phone: ${targetUserId}`);
} else { } else {
logger.warn( logger.warn(
`No user found with phone number: ${notification.phoneNumber}` `No user found with phone number from data: ${notification.data.phoneNumber}`
); );
} }
} else { } else {
logger.error("No valid user identifier found in notification"); logger.error("No valid user identifier found in notification or its data");
} }
if (userId && !fcmToken) { if (targetUserId && !fcmToken) {
logger.warn(`User ${userId} found but no FCM token available`); fcmToken = await getFCMTokenFromUserDoc(targetUserId);
} }
return { userId, fcmToken }; if (targetUserId && !fcmToken) {
logger.warn(`User ${targetUserId} found but no FCM token available`);
}
return { userId: targetUserId, fcmToken };
} }
async function getFCMTokenFromUserDoc(userId: string): Promise<string | null> { async function getFCMTokenFromUserDoc(userId: string): Promise<string | null> {
@ -178,135 +154,146 @@ async function getFCMTokenFromUserDoc(userId: string): Promise<string | null> {
function prepareNotificationMessage( function prepareNotificationMessage(
notification: NotificationData, notification: NotificationData,
fcmToken: string fcmToken: string
): admin.messaging.Message { ): admin.messaging.TokenMessage {
let title = notification.title || "New Notification"; let title = notification.data?.title || "New Notification";
let body = notification.message || "You have a new notification"; let body = notification.data?.message || "You have a new notification";
let data: Record<string, string> = {
let fcmData: Record<string, string> = {
type: notification.type || "general", type: notification.type || "general",
notificationId: "notification_" + Date.now(), notificationId: "notification_" + Date.now().toString(),
}; };
if (notification.senderId) data.senderId = notification.senderId; if (notification.senderId) fcmData.senderId = notification.senderId;
if (notification.recipientId) data.recipientId = notification.recipientId; if (notification.recipientId) fcmData.recipientId = notification.recipientId;
if (notification.ownerId) data.ownerId = notification.ownerId; if (notification.ownerId) fcmData.ownerId = notification.ownerId;
if (notification.userId) data.userId = notification.userId; if (notification.read !== undefined) fcmData.read = String(notification.read);
if (notification.clientId) data.clientId = notification.clientId;
if (notification.gymId) data.gymId = notification.gymId; if (notification.data) {
if (notification.trainerId) data.trainerId = notification.trainerId; for (const key in notification.data) {
if (notification.membershipId) data.membershipId = notification.membershipId; if (Object.prototype.hasOwnProperty.call(notification.data, key)) {
if (notification.invitationId) data.invitationId = notification.invitationId; const value = notification.data[key];
if (notification.phoneNumber) data.phoneNumber = notification.phoneNumber; if (typeof value === "object" && value !== null) {
if (notification.gymName) data.gymName = notification.gymName; fcmData[key] = JSON.stringify(value);
if (notification.trainerName) data.trainerName = notification.trainerName; } else {
if (notification.subscriptionName) data.subscriptionName = notification.subscriptionName; fcmData[key] = String(value);
if (notification.name) data.name = notification.name; }
if (notification.clientEmail) data.clientEmail = notification.clientEmail; }
if (notification.status) data.status = notification.status; }
if (notification.updatedBy) data.updatedBy = notification.updatedBy; }
if (notification.oldTimeSlot) data.oldTimeSlot = notification.oldTimeSlot;
if (notification.newTimeSlot) data.newTimeSlot = notification.newTimeSlot;
if (notification.formattedDate) data.formattedDate = notification.formattedDate;
if (notification.logTime) data.logTime = notification.logTime;
if (notification.timeSlots) data.timeSlots = JSON.stringify(notification.timeSlots);
switch (notification.type) { switch (notification.type) {
case "trainer_response": case "trainer_response":
title = notification.title || title =
(notification.status === "ACCEPTED" notification.data?.title ||
(notification.data?.status === "ACCEPTED"
? "Trainer Request Accepted" ? "Trainer Request Accepted"
: "Trainer Request Update"); : "Trainer Request Update");
body = body =
notification.message || notification.data?.message ||
`${ `${
notification.trainerName notification.data?.trainerName
} has ${notification.status?.toLowerCase()} your request`; } has ${notification.data?.status?.toLowerCase()} your request`;
break; break;
case "trainer_assignment": case "trainer_assignment":
title = notification.title || "New Client Assignment"; title = notification.data?.title || "New Client Assignment";
body = body =
notification.message || notification.data?.message ||
`You have been assigned to ${notification.name}`; `You have been assigned to ${notification.data?.name}`;
break; break;
case "trainer_assigned_to_client": case "trainer_assigned_to_client":
title = notification.title || "Trainer Assigned"; title = notification.data?.title || "Trainer Assigned";
body = body =
notification.message || notification.data?.message ||
`${notification.trainerName} has been assigned as your trainer`; `${notification.data?.trainerName} has been assigned as your trainer`;
break; break;
case "trainer_update_owner": case "trainer_update_owner":
title = notification.title || "Trainer Schedule Update"; title = notification.data?.title || "Trainer Schedule Update";
body = notification.message || "A trainer has updated their schedule"; body =
notification.data?.message || "A trainer has updated their schedule";
break; break;
case "trainer_update_client": case "trainer_update_client":
title = notification.title || "Schedule Update"; title = notification.data?.title || "Schedule Update";
body = notification.message || "Your training schedule has been updated"; body =
notification.data?.message || "Your training schedule has been updated";
if (notification.data?.oldTimeSlot && notification.data?.newTimeSlot) {
body += ` from ${notification.data.oldTimeSlot} to ${notification.data.newTimeSlot}`;
if (notification.data?.formattedDate) {
body += ` on ${notification.data.formattedDate}`;
}
}
break; break;
case "plan_renewal": case "plan_renewal":
title = notification.title || "Plan Renewal"; title = notification.data?.title || "Plan Renewal";
body = body =
notification.message || notification.data?.message ||
`Plan ${notification.subscriptionName} has been renewed`; `Plan ${notification.data?.subscriptionName} has been renewed`;
break; break;
case "plan_assigned": case "plan_assigned":
title = notification.title || "New Plan Assigned"; title = notification.data?.title || "New Plan Assigned";
body = body =
notification.message || notification.data?.message ||
`You have been assigned ${notification.subscriptionName} at ${notification.gymName}`; `You have been assigned ${notification.data?.subscriptionName} at ${notification.data?.gymName}`;
break; break;
case "schedule_update": case "schedule_update":
title = notification.title || "Schedule Update"; title = notification.data?.title || "Schedule Update";
body = notification.message || "Your training schedule has been updated"; body =
if (notification.oldTimeSlot && notification.newTimeSlot) { notification.data?.message || "Your training schedule has been updated";
body += ` from ${notification.oldTimeSlot} to ${notification.newTimeSlot}`; if (notification.data?.oldTimeSlot && notification.data?.newTimeSlot) {
if (notification.formattedDate) { body += ` from ${notification.data.oldTimeSlot} to ${notification.data.newTimeSlot}`;
body += ` on ${notification.formattedDate}`; if (notification.data?.formattedDate) {
body += ` on ${notification.data.formattedDate}`;
} }
} }
break; break;
case "attendance_dispute": case "attendance_dispute":
title = notification.title || "Attendance Dispute"; title = notification.data?.title || "Attendance Dispute";
body = body =
notification.message || notification.data?.message ||
`${notification.name} has disputed an attendance record`; `${notification.data?.name} has disputed an attendance record`;
if (notification.logTime) { if (notification.data?.logTime) {
body += ` for ${notification.logTime}`; body += ` for ${notification.data.logTime}`;
} }
break; break;
case "day_pass_entry": case "day_pass_entry":
const isAccepted = notification.status === "ACCEPTED"; const isAccepted = notification.data?.status === "ACCEPTED";
title = notification.title || (isAccepted ? "Day Pass Approved" : "Day Pass Denied"); title =
notification.data?.title ||
(isAccepted ? "Day Pass Approved" : "Day Pass Denied");
body = body =
notification.message || notification.data?.message ||
(isAccepted (isAccepted
? "Your day pass has been approved" ? "Your day pass has been approved"
: "Your day pass has been denied"); : "Your day pass has been denied");
break; break;
case "client_invitations": case "client_invitations":
if (notification.userId || notification.invitorId) { if (notification.data?.userId || notification.data?.invitorId) {
const isAccept = notification.status === "ACCEPTED"; const isAccept = notification.data?.status === "ACCEPTED";
title = notification.title || (isAccept ? "Invitation Accepted" : "Invitation Rejected"); title =
notification.data?.title ||
(isAccept ? "Invitation Accepted" : "Invitation Rejected");
body = body =
notification.message || notification.data?.message ||
(isAccept (isAccept
? `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been accepted` ? `The invitation for ${notification.data?.subscriptionName} you shared with ${notification.data?.name} has been accepted`
: `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been rejected`); : `The invitation for ${notification.data?.subscriptionName} you shared with ${notification.data?.name} has been rejected`);
} else if (notification.phoneNumber) { } else if (notification.data?.phoneNumber) {
const invitationStatus = getInvitationStatus(notification.status); const invitationStatus = getInvitationStatus(notification.data?.status);
title = notification.title || getInvitationTitle(invitationStatus); title =
notification.data?.title || getInvitationTitle(invitationStatus);
body = body =
notification.message || notification.data?.message ||
getInvitationBody(invitationStatus, notification.name); getInvitationBody(invitationStatus, notification.data?.name);
data.status = invitationStatus; fcmData.status = invitationStatus;
} }
break; break;
@ -314,16 +301,17 @@ function prepareNotificationMessage(
logger.info( logger.info(
`Using default handling for notification type: ${notification.type}` `Using default handling for notification type: ${notification.type}`
); );
title = notification.title || title =
notification.data?.title ||
(notification.type (notification.type
? `${notification.type.replace("_", " ").toUpperCase()}` ? `${notification.type.replace("_", " ").toUpperCase()}`
: "Notification"); : "Notification");
break; break;
} }
const notificationMessage: admin.messaging.Message = { const notificationMessage: admin.messaging.TokenMessage = {
notification: { title, body }, notification: { title, body },
data, data: fcmData,
android: { android: {
priority: "high", priority: "high",
notification: { notification: {
@ -417,4 +405,4 @@ async function updateNotificationWithError(
} catch (updateError) { } catch (updateError) {
logger.error(`Error updating notification with error: ${updateError}`); logger.error(`Error updating notification with error: ${updateError}`);
} }
} }