From 9c5da57f9724d301b5ad9c4847aa9fe84cd60613 Mon Sep 17 00:00:00 2001 From: Sharon Dcruz Date: Fri, 25 Jul 2025 11:48:49 +0530 Subject: [PATCH 1/6] Changes Updated --- .../src/notifications/processNotification.ts | 512 ++++++++++++------ 1 file changed, 342 insertions(+), 170 deletions(-) diff --git a/functions/src/notifications/processNotification.ts b/functions/src/notifications/processNotification.ts index b995b87..ada5636 100644 --- a/functions/src/notifications/processNotification.ts +++ b/functions/src/notifications/processNotification.ts @@ -1,222 +1,394 @@ import { onDocumentCreated } from "firebase-functions/v2/firestore"; import { getLogger } from "../shared/config"; import { getAdmin } from "../shared/config"; -import * as admin from 'firebase-admin'; +import * as admin from "firebase-admin"; const app = getAdmin(); const logger = getLogger(); interface NotificationData { - notificationSent?: boolean; - userId?: string; - clientId?: string; - invitorId?: string; - phoneNumber?: string; - message?: string; - type?: string; - status?: string; - gymName?: string; - trainerName?: string; - membershipId?: string; - subscriptionName?: string; - name?: string; - clientEmail?: string; - invitationId?: string; - [key: string]: any; + senderId?: string; + recipientId?: string; + notificationSent?: boolean; + userId?: string; + clientId?: string; + invitorId?: string; + phoneNumber?: string; + message?: string; + type?: string; + status?: string; + gymName?: string; + trainerName?: string; + membershipId?: string; + subscriptionName?: string; + name?: string; + clientEmail?: string; + invitationId?: string; + [key: string]: any; } -export const processNotificationOnCreate = onDocumentCreated({ - region: '#{SERVICES_RGN}#', - document: 'notifications/{notificationId}' -}, async (event) => { +export const processNotificationOnCreate = onDocumentCreated( + { + region: "#{SERVICES_RGN}#", + document: "notifications/{notificationId}", + }, + async (event) => { try { - const notificationSnapshot = event.data; - const notificationId = event.params.notificationId; + const notificationSnapshot = event.data; + const notificationId = event.params.notificationId; - if (!notificationSnapshot) { - logger.error(`No data found for notification ${notificationId}`); - return; - } + if (!notificationSnapshot) { + logger.error(`No data found for notification ${notificationId}`); + return; + } - const notification = notificationSnapshot.data() as NotificationData; - if (notification.notificationSent === true) { - logger.info(`Notification ${notificationId} already sent, skipping.`); - return; - } + const notification = notificationSnapshot.data() as NotificationData; - const { fcmToken } = await getUserAndFCMToken(notification); - if (!fcmToken) { - logger.error(`FCM token not found for notification ${notificationId}`); - await updateNotificationWithError(notificationId, 'FCM token not found for user'); - return; - } + if (notification.notificationSent === true) { + logger.info(`Notification ${notificationId} already sent, skipping.`); + return; + } - const message = prepareNotificationMessage(notification, fcmToken); - try { - const fcmResponse = await app.messaging().send({ - ...message, - token: fcmToken - }); + logger.info( + `Processing notification ${notificationId} of type: ${notification.type}` + ); - logger.info(`FCM notification sent successfully: ${fcmResponse}`); - await markNotificationAsSent(notificationId); + const { userId, fcmToken } = await getUserAndFCMToken(notification); + if (!fcmToken) { + logger.error( + `FCM token not found for notification ${notificationId}, user: ${userId}` + ); + await updateNotificationWithError( + notificationId, + "FCM token not found for user" + ); + return; + } - } catch (error) { - logger.error(`Error sending notification ${notificationId}:`, error); - await updateNotificationWithError(notificationId, error instanceof Error ? error.message : String(error)); - } + const message = prepareNotificationMessage(notification, fcmToken); + try { + const fcmResponse = await app.messaging().send({ + ...message, + token: fcmToken, + }); + + logger.info(`FCM notification sent successfully: ${fcmResponse}`); + await markNotificationAsSent(notificationId); + } catch (error) { + logger.error(`Error sending notification ${notificationId}:`, error); + await updateNotificationWithError( + notificationId, + error instanceof Error ? error.message : String(error) + ); + } } catch (error) { - logger.error('Error processing notification:', error); + logger.error("Error processing notification:", error); } -}); + } +); -async function getUserAndFCMToken(notification: NotificationData): Promise<{ userId: string | null; fcmToken: string | null }> { - let userId: string | null = null; - let fcmToken: string | null = null; +async function getUserAndFCMToken( + notification: NotificationData +): Promise<{ userId: string | null; fcmToken: string | null }> { + let userId: string | null = null; + let fcmToken: string | null = null; - if (notification.userId) { - userId = notification.userId; - fcmToken = await getFCMTokenFromUserDoc(userId); - } else if (notification.clientId) { - userId = notification.clientId; - fcmToken = await getFCMTokenFromUserDoc(userId); - } else if (notification.invitorId) { - userId = notification.invitorId; - fcmToken = await getFCMTokenFromUserDoc(userId); - } else if (notification.phoneNumber) { - const userQuery = await app - .firestore() - .collection('users') - .where('phoneNumber', '==', notification.phoneNumber) - .limit(1) - .get(); + if (notification.recipientId) { + userId = notification.recipientId; + fcmToken = await getFCMTokenFromUserDoc(userId); + logger.info(`Using recipientId: ${userId}`); + } else if (notification.userId) { + userId = notification.userId; + fcmToken = await getFCMTokenFromUserDoc(userId); + logger.info(`Using userId: ${userId}`); + } else if (notification.clientId) { + userId = notification.clientId; + fcmToken = await getFCMTokenFromUserDoc(userId); + logger.info(`Using clientId: ${userId}`); + } else if (notification.invitorId) { + userId = notification.invitorId; + fcmToken = await getFCMTokenFromUserDoc(userId); + logger.info(`Using invitorId: ${userId}`); + } else if (notification.phoneNumber) { + logger.info(`Looking up user by phone number: ${notification.phoneNumber}`); + const userQuery = await app + .firestore() + .collection("users") + .where("phoneNumber", "==", notification.phoneNumber) + .limit(1) + .get(); - if (!userQuery.empty) { - const userDoc = userQuery.docs[0]; - userId = userDoc.id; - fcmToken = userDoc.data()?.fcmToken; - } + if (!userQuery.empty) { + const userDoc = userQuery.docs[0]; + userId = userDoc.id; + fcmToken = userDoc.data()?.fcmToken; + logger.info(`Found user by phone: ${userId}`); + } else { + logger.warn( + `No user found with phone number: ${notification.phoneNumber}` + ); } + } else { + logger.error("No valid user identifier found in notification"); + } - return { userId, fcmToken }; + if (userId && !fcmToken) { + logger.warn(`User ${userId} found but no FCM token available`); + } + + return { userId, fcmToken }; } async function getFCMTokenFromUserDoc(userId: string): Promise { - const userDoc = await app.firestore().collection('users').doc(userId).get(); - return userDoc.exists ? userDoc.data()?.fcmToken : null; + try { + const userDoc = await app.firestore().collection("users").doc(userId).get(); + if (userDoc.exists) { + const userData = userDoc.data(); + const fcmToken = userData?.fcmToken; + if (!fcmToken) { + logger.warn(`User ${userId} exists but has no FCM token`); + } + return fcmToken; + } else { + logger.warn(`User document not found: ${userId}`); + return null; + } + } catch (error) { + logger.error(`Error fetching user ${userId}:`, error); + return null; + } } -function prepareNotificationMessage(notification: NotificationData, fcmToken: string): admin.messaging.Message { - let title = 'New Notification'; - let body = notification.message || 'You have a new notification'; - let data: Record = { - type: notification.type || 'general', - }; +function prepareNotificationMessage( + notification: NotificationData, + fcmToken: string +): admin.messaging.Message { + let title = "New Notification"; + let body = notification.message || "You have a new notification"; + let data: Record = { + type: notification.type || "general", + notificationId: "notification_" + Date.now(), + }; - switch (notification.type) { - case 'day_pass_entry': - const isAccepted = notification.status === 'ACCEPTED'; - title = isAccepted ? 'Day Pass Approved' : 'Day Pass Denied'; - body = notification.message || (isAccepted ? - 'Your day pass has been approved' : - 'Your day pass has been denied'); - data.gymName = notification.gymName || ''; - break; + switch (notification.type) { + case "trainer_response": + title = + notification.status === "ACCEPTED" + ? "Trainer Request Accepted" + : "Trainer Request Update"; + body = + notification.message || + `${ + notification.trainerName + } has ${notification.status?.toLowerCase()} your request`; + data.trainerName = notification.trainerName || ""; + data.status = notification.status || ""; + break; - case 'trainer_assigned_to_client': - title = 'Trainer Assigned'; - body = notification.message || `${notification.trainerName} has been assigned as your trainer`; - data.trainerName = notification.trainerName || ''; - data.membershipId = notification.membershipId || ''; - break; + case "trainer_assignment": + title = "New Client Assignment"; + body = + notification.message || + `You have been assigned to ${notification.name}`; + data.clientName = notification.name || ""; + data.membershipId = notification.membershipId || ""; + break; - case 'client_invitations': - if (notification.userId || notification.invitorId) { - const isAccept = notification.status === 'ACCEPTED'; - title = isAccept ? 'Invitation Accepted' : 'Invitation Rejected'; - body = notification.message || (isAccept ? - `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been accepted` : - `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been rejected`); - } else if (notification.phoneNumber) { - const invitationStatus = getInvitationStatus(notification.status); - title = getInvitationTitle(invitationStatus); - body = notification.message || getInvitationBody(invitationStatus, notification.name); - data.status = invitationStatus; - } - data.gymName = notification.gymName || ''; - data.clientEmail = notification.clientEmail || ''; - data.clientName = notification.name || ''; - data.invitationId = notification.invitationId || ''; - data.subscriptionName = notification.subscriptionName || ''; - break; + case "trainer_assigned_to_client": + title = "Trainer Assigned"; + body = + notification.message || + `${notification.trainerName} has been assigned as your trainer`; + data.trainerName = notification.trainerName || ""; + data.membershipId = notification.membershipId || ""; + break; - default: - logger.info(`Using default handling for notification type: ${notification.type}`); - break; - } + case "trainer_update_owner": + title = "Trainer Schedule Update"; + body = notification.message || "A trainer has updated their schedule"; + data.membershipId = notification.membershipId || ""; + break; - const notificationMessage: admin.messaging.Message = { - notification: { title, body }, - data, - android: { - priority: 'high', - notification: { - channelId: 'notifications_channel', - priority: 'high', - defaultSound: true, - defaultVibrateTimings: true, - icon: '@mipmap/ic_launcher', - clickAction: 'FLUTTER_NOTIFICATION_CLICK', - }, + case "trainer_update_client": + title = "Schedule Update"; + body = notification.message || "Your training schedule has been updated"; + data.membershipId = notification.membershipId || ""; + break; + + case "plan_renewal": + title = "Plan Renewal"; + body = + notification.message || + `Plan ${notification.subscriptionName} has been renewed`; + data.planName = notification.subscriptionName || ""; + data.membershipId = notification.membershipId || ""; + break; + + case "plan_assigned": + title = "New Plan Assigned"; + body = + notification.message || + `You have been assigned ${notification.subscriptionName} at ${notification.gymName}`; + data.planName = notification.subscriptionName || ""; + data.gymName = notification.gymName || ""; + data.membershipId = notification.membershipId || ""; + break; + + case "schedule_update": + title = "Schedule Update"; + body = notification.message || "Your training schedule has been updated"; + data.gymName = notification.gymName || ""; + break; + + case "attendance_dispute": + title = "Attendance Dispute"; + body = + notification.message || + `${notification.name} has disputed an attendance record`; + data.disputedBy = notification.name || ""; + data.membershipId = notification.membershipId || ""; + break; + + case "day_pass_entry": + const isAccepted = notification.status === "ACCEPTED"; + title = isAccepted ? "Day Pass Approved" : "Day Pass Denied"; + body = + notification.message || + (isAccepted + ? "Your day pass has been approved" + : "Your day pass has been denied"); + data.gymName = notification.gymName || ""; + data.status = notification.status || ""; + break; + + case "client_invitations": + if (notification.userId || notification.invitorId) { + const isAccept = notification.status === "ACCEPTED"; + title = isAccept ? "Invitation Accepted" : "Invitation Rejected"; + body = + notification.message || + (isAccept + ? `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been accepted` + : `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been rejected`); + } else if (notification.phoneNumber) { + const invitationStatus = getInvitationStatus(notification.status); + title = getInvitationTitle(invitationStatus); + body = + notification.message || + getInvitationBody(invitationStatus, notification.name); + data.status = invitationStatus; + } + data.gymName = notification.gymName || ""; + data.clientEmail = notification.clientEmail || ""; + data.clientName = notification.name || ""; + data.invitationId = notification.invitationId || ""; + data.subscriptionName = notification.subscriptionName || ""; + break; + + default: + logger.info( + `Using default handling for notification type: ${notification.type}` + ); + title = notification.type + ? `${notification.type.replace("_", " ").toUpperCase()}` + : "Notification"; + break; + } + + const notificationMessage: admin.messaging.Message = { + notification: { title, body }, + data, + android: { + priority: "high", + notification: { + channelId: "notifications_channel", + priority: "high", + defaultSound: true, + defaultVibrateTimings: true, + icon: "@mipmap/ic_launcher", + clickAction: "FLUTTER_NOTIFICATION_CLICK", + }, + }, + apns: { + payload: { + aps: { + sound: "default", + badge: 1, }, - apns: { - payload: { - aps: { - sound: 'default', - badge: 1, - }, - }, - }, - token: fcmToken, - }; - return notificationMessage; + }, + }, + token: fcmToken, + }; + + logger.info(`Prepared notification: ${title} - ${body}`); + return notificationMessage; } function getInvitationStatus(status?: string): string { - if (status === 'ACCEPTED') return 'accepted'; - if (status === 'REJECTED') return 'rejected'; - if (status === 'PENDING') return 'pending'; - return 'unknown'; + if (status === "ACCEPTED") return "accepted"; + if (status === "REJECTED") return "rejected"; + if (status === "PENDING") return "pending"; + return "unknown"; } function getInvitationTitle(status: string): string { - switch (status) { - case 'accepted': return 'Invitation Accepted'; - case 'rejected': return 'Invitation Rejected'; - case 'pending': return 'New Invitation'; - default: return 'Invitation Update'; - } + switch (status) { + case "accepted": + return "Invitation Accepted"; + case "rejected": + return "Invitation Rejected"; + case "pending": + return "New Invitation"; + default: + return "Invitation Update"; + } } function getInvitationBody(status: string, name?: string): string { - switch (status) { - case 'accepted': return `You have accepted the invitation from ${name}`; - case 'rejected': return `You have rejected the invitation from ${name}`; - case 'pending': return `You have a new invitation pending from ${name}`; - default: return 'There is an update to your invitation'; - } + switch (status) { + case "accepted": + return `You have accepted the invitation from ${name}`; + case "rejected": + return `You have rejected the invitation from ${name}`; + case "pending": + return `You have a new invitation pending from ${name}`; + default: + return "There is an update to your invitation"; + } } async function markNotificationAsSent(notificationId: string): Promise { - await app.firestore().collection('notifications').doc(notificationId).update({ + try { + await app + .firestore() + .collection("notifications") + .doc(notificationId) + .update({ notificationSent: true, - sentAt: app.firestore.FieldValue.serverTimestamp() - }); + sentAt: admin.firestore.FieldValue.serverTimestamp(), + }); + logger.info(`Notification ${notificationId} marked as sent`); + } catch (error) { + logger.error(`Error marking notification as sent: ${error}`); + } } -async function updateNotificationWithError(notificationId: string, error: string): Promise { - await app.firestore().collection('notifications').doc(notificationId).update({ +async function updateNotificationWithError( + notificationId: string, + error: string +): Promise { + try { + await app + .firestore() + .collection("notifications") + .doc(notificationId) + .update({ notificationError: error, - updatedAt: app.firestore.FieldValue.serverTimestamp() - }); + notificationSent: false, + updatedAt: admin.firestore.FieldValue.serverTimestamp(), + }); + logger.info(`Notification ${notificationId} marked with error: ${error}`); + } catch (updateError) { + logger.error(`Error updating notification with error: ${updateError}`); + } } -- 2.43.0 From d699e65fd018ac1587ad325f5bf35ae6202e78c0 Mon Sep 17 00:00:00 2001 From: Sharon Dcruz Date: Mon, 28 Jul 2025 16:17:33 +0530 Subject: [PATCH 2/6] Changes Updated for notification --- .../src/notifications/processNotification.ts | 112 +++++++++++------- 1 file changed, 69 insertions(+), 43 deletions(-) diff --git a/functions/src/notifications/processNotification.ts b/functions/src/notifications/processNotification.ts index ada5636..0f80d70 100644 --- a/functions/src/notifications/processNotification.ts +++ b/functions/src/notifications/processNotification.ts @@ -9,13 +9,14 @@ const logger = getLogger(); interface NotificationData { senderId?: string; recipientId?: string; + ownerId?: string; + type?: string; notificationSent?: boolean; userId?: string; clientId?: string; invitorId?: string; phoneNumber?: string; message?: string; - type?: string; status?: string; gymName?: string; trainerName?: string; @@ -24,6 +25,17 @@ interface NotificationData { name?: string; clientEmail?: string; invitationId?: string; + gymId?: string; + trainerId?: string; + timeSlots?: Array<{[key: string]: any}>; + timestamp?: any; + read?: boolean; + title?: string; + updatedBy?: string; + oldTimeSlot?: string; + newTimeSlot?: string; + formattedDate?: string; + logTime?: string; [key: string]: any; } @@ -97,6 +109,10 @@ async function getUserAndFCMToken( userId = notification.recipientId; fcmToken = await getFCMTokenFromUserDoc(userId); logger.info(`Using recipientId: ${userId}`); + } else if (notification.ownerId) { + userId = notification.ownerId; + fcmToken = await getFCMTokenFromUserDoc(userId); + logger.info(`Using ownerId: ${userId}`); } else if (notification.userId) { userId = notification.userId; fcmToken = await getFCMTokenFromUserDoc(userId); @@ -163,108 +179,122 @@ function prepareNotificationMessage( notification: NotificationData, fcmToken: string ): admin.messaging.Message { - let title = "New Notification"; + let title = notification.title || "New Notification"; let body = notification.message || "You have a new notification"; let data: Record = { type: notification.type || "general", notificationId: "notification_" + Date.now(), }; + if (notification.senderId) data.senderId = notification.senderId; + if (notification.recipientId) data.recipientId = notification.recipientId; + if (notification.ownerId) data.ownerId = notification.ownerId; + if (notification.userId) data.userId = notification.userId; + if (notification.clientId) data.clientId = notification.clientId; + if (notification.gymId) data.gymId = notification.gymId; + if (notification.trainerId) data.trainerId = notification.trainerId; + if (notification.membershipId) data.membershipId = notification.membershipId; + if (notification.invitationId) data.invitationId = notification.invitationId; + if (notification.phoneNumber) data.phoneNumber = notification.phoneNumber; + if (notification.gymName) data.gymName = notification.gymName; + if (notification.trainerName) data.trainerName = notification.trainerName; + if (notification.subscriptionName) data.subscriptionName = notification.subscriptionName; + 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) { case "trainer_response": - title = - notification.status === "ACCEPTED" + title = notification.title || + (notification.status === "ACCEPTED" ? "Trainer Request Accepted" - : "Trainer Request Update"; + : "Trainer Request Update"); body = notification.message || `${ notification.trainerName } has ${notification.status?.toLowerCase()} your request`; - data.trainerName = notification.trainerName || ""; - data.status = notification.status || ""; break; case "trainer_assignment": - title = "New Client Assignment"; + title = notification.title || "New Client Assignment"; body = notification.message || `You have been assigned to ${notification.name}`; - data.clientName = notification.name || ""; - data.membershipId = notification.membershipId || ""; break; case "trainer_assigned_to_client": - title = "Trainer Assigned"; + title = notification.title || "Trainer Assigned"; body = notification.message || `${notification.trainerName} has been assigned as your trainer`; - data.trainerName = notification.trainerName || ""; - data.membershipId = notification.membershipId || ""; break; case "trainer_update_owner": - title = "Trainer Schedule Update"; + title = notification.title || "Trainer Schedule Update"; body = notification.message || "A trainer has updated their schedule"; - data.membershipId = notification.membershipId || ""; break; case "trainer_update_client": - title = "Schedule Update"; + title = notification.title || "Schedule Update"; body = notification.message || "Your training schedule has been updated"; - data.membershipId = notification.membershipId || ""; break; case "plan_renewal": - title = "Plan Renewal"; + title = notification.title || "Plan Renewal"; body = notification.message || `Plan ${notification.subscriptionName} has been renewed`; - data.planName = notification.subscriptionName || ""; - data.membershipId = notification.membershipId || ""; break; case "plan_assigned": - title = "New Plan Assigned"; + title = notification.title || "New Plan Assigned"; body = notification.message || `You have been assigned ${notification.subscriptionName} at ${notification.gymName}`; - data.planName = notification.subscriptionName || ""; - data.gymName = notification.gymName || ""; - data.membershipId = notification.membershipId || ""; break; case "schedule_update": - title = "Schedule Update"; + title = notification.title || "Schedule Update"; body = notification.message || "Your training schedule has been updated"; - data.gymName = notification.gymName || ""; + if (notification.oldTimeSlot && notification.newTimeSlot) { + body += ` from ${notification.oldTimeSlot} to ${notification.newTimeSlot}`; + if (notification.formattedDate) { + body += ` on ${notification.formattedDate}`; + } + } break; case "attendance_dispute": - title = "Attendance Dispute"; + title = notification.title || "Attendance Dispute"; body = notification.message || `${notification.name} has disputed an attendance record`; - data.disputedBy = notification.name || ""; - data.membershipId = notification.membershipId || ""; + if (notification.logTime) { + body += ` for ${notification.logTime}`; + } break; case "day_pass_entry": const isAccepted = notification.status === "ACCEPTED"; - title = isAccepted ? "Day Pass Approved" : "Day Pass Denied"; + title = notification.title || (isAccepted ? "Day Pass Approved" : "Day Pass Denied"); body = notification.message || (isAccepted ? "Your day pass has been approved" : "Your day pass has been denied"); - data.gymName = notification.gymName || ""; - data.status = notification.status || ""; break; case "client_invitations": if (notification.userId || notification.invitorId) { const isAccept = notification.status === "ACCEPTED"; - title = isAccept ? "Invitation Accepted" : "Invitation Rejected"; + title = notification.title || (isAccept ? "Invitation Accepted" : "Invitation Rejected"); body = notification.message || (isAccept @@ -272,26 +302,22 @@ function prepareNotificationMessage( : `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been rejected`); } else if (notification.phoneNumber) { const invitationStatus = getInvitationStatus(notification.status); - title = getInvitationTitle(invitationStatus); + title = notification.title || getInvitationTitle(invitationStatus); body = notification.message || getInvitationBody(invitationStatus, notification.name); data.status = invitationStatus; } - data.gymName = notification.gymName || ""; - data.clientEmail = notification.clientEmail || ""; - data.clientName = notification.name || ""; - data.invitationId = notification.invitationId || ""; - data.subscriptionName = notification.subscriptionName || ""; break; default: logger.info( `Using default handling for notification type: ${notification.type}` ); - title = notification.type - ? `${notification.type.replace("_", " ").toUpperCase()}` - : "Notification"; + title = notification.title || + (notification.type + ? `${notification.type.replace("_", " ").toUpperCase()}` + : "Notification"); break; } @@ -391,4 +417,4 @@ async function updateNotificationWithError( } catch (updateError) { logger.error(`Error updating notification with error: ${updateError}`); } -} +} \ No newline at end of file -- 2.43.0 From ebac135d08801a46387fe1551bc4388a9e036345 Mon Sep 17 00:00:00 2001 From: Sharon Dcruz Date: Mon, 28 Jul 2025 17:37:45 +0530 Subject: [PATCH 3/6] Changes Updated --- .../src/notifications/processNotification.ts | 258 +++++++++--------- 1 file changed, 123 insertions(+), 135 deletions(-) diff --git a/functions/src/notifications/processNotification.ts b/functions/src/notifications/processNotification.ts index 0f80d70..a9a5563 100644 --- a/functions/src/notifications/processNotification.ts +++ b/functions/src/notifications/processNotification.ts @@ -12,31 +12,9 @@ interface NotificationData { ownerId?: string; type?: string; notificationSent?: boolean; - userId?: string; - 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; + timestamp?: admin.firestore.FieldValue; read?: boolean; - title?: string; - updatedBy?: string; - oldTimeSlot?: string; - newTimeSlot?: string; - formattedDate?: string; - logTime?: string; - [key: string]: any; + data?: { [key: string]: any }; } export const processNotificationOnCreate = onDocumentCreated( @@ -79,10 +57,7 @@ export const processNotificationOnCreate = onDocumentCreated( const message = prepareNotificationMessage(notification, fcmToken); try { - const fcmResponse = await app.messaging().send({ - ...message, - token: fcmToken, - }); + const fcmResponse = await app.messaging().send(message); logger.info(`FCM notification sent successfully: ${fcmResponse}`); await markNotificationAsSent(notificationId); @@ -102,57 +77,58 @@ export const processNotificationOnCreate = onDocumentCreated( async function getUserAndFCMToken( notification: NotificationData ): Promise<{ userId: string | null; fcmToken: string | null }> { - let userId: string | null = null; + let targetUserId: string | null = null; let fcmToken: string | null = null; if (notification.recipientId) { - userId = notification.recipientId; - fcmToken = await getFCMTokenFromUserDoc(userId); - logger.info(`Using recipientId: ${userId}`); + targetUserId = notification.recipientId; + logger.info(`Using top-level recipientId: ${targetUserId}`); } else if (notification.ownerId) { - userId = notification.ownerId; - fcmToken = await getFCMTokenFromUserDoc(userId); - logger.info(`Using ownerId: ${userId}`); - } else if (notification.userId) { - userId = notification.userId; - fcmToken = await getFCMTokenFromUserDoc(userId); - logger.info(`Using userId: ${userId}`); - } else if (notification.clientId) { - userId = notification.clientId; - fcmToken = await getFCMTokenFromUserDoc(userId); - logger.info(`Using clientId: ${userId}`); - } else if (notification.invitorId) { - userId = notification.invitorId; - fcmToken = await getFCMTokenFromUserDoc(userId); - logger.info(`Using invitorId: ${userId}`); - } else if (notification.phoneNumber) { - logger.info(`Looking up user by phone number: ${notification.phoneNumber}`); + targetUserId = notification.ownerId; + logger.info(`Using top-level ownerId: ${targetUserId}`); + } else if (notification.data?.userId) { + targetUserId = notification.data.userId; + logger.info(`Using data.userId: ${targetUserId}`); + } else if (notification.data?.clientId) { + targetUserId = notification.data.clientId; + logger.info(`Using data.clientId: ${targetUserId}`); + } else if (notification.data?.invitorId) { + targetUserId = notification.data.invitorId; + logger.info(`Using data.invitorId: ${targetUserId}`); + } else if (notification.data?.phoneNumber) { + logger.info( + `Looking up user by phone number from data: ${notification.data.phoneNumber}` + ); const userQuery = await app .firestore() .collection("users") - .where("phoneNumber", "==", notification.phoneNumber) + .where("phoneNumber", "==", notification.data.phoneNumber) .limit(1) .get(); if (!userQuery.empty) { const userDoc = userQuery.docs[0]; - userId = userDoc.id; + targetUserId = userDoc.id; fcmToken = userDoc.data()?.fcmToken; - logger.info(`Found user by phone: ${userId}`); + logger.info(`Found user by phone: ${targetUserId}`); } else { logger.warn( - `No user found with phone number: ${notification.phoneNumber}` + `No user found with phone number from data: ${notification.data.phoneNumber}` ); } } 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) { - logger.warn(`User ${userId} found but no FCM token available`); + if (targetUserId && !fcmToken) { + 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 { @@ -178,135 +154,146 @@ async function getFCMTokenFromUserDoc(userId: string): Promise { function prepareNotificationMessage( notification: NotificationData, fcmToken: string -): admin.messaging.Message { - let title = notification.title || "New Notification"; - let body = notification.message || "You have a new notification"; - let data: Record = { +): admin.messaging.TokenMessage { + let title = notification.data?.title || "New Notification"; + let body = notification.data?.message || "You have a new notification"; + + let fcmData: Record = { type: notification.type || "general", - notificationId: "notification_" + Date.now(), + notificationId: "notification_" + Date.now().toString(), }; - if (notification.senderId) data.senderId = notification.senderId; - if (notification.recipientId) data.recipientId = notification.recipientId; - if (notification.ownerId) data.ownerId = notification.ownerId; - if (notification.userId) data.userId = notification.userId; - if (notification.clientId) data.clientId = notification.clientId; - if (notification.gymId) data.gymId = notification.gymId; - if (notification.trainerId) data.trainerId = notification.trainerId; - if (notification.membershipId) data.membershipId = notification.membershipId; - if (notification.invitationId) data.invitationId = notification.invitationId; - if (notification.phoneNumber) data.phoneNumber = notification.phoneNumber; - if (notification.gymName) data.gymName = notification.gymName; - if (notification.trainerName) data.trainerName = notification.trainerName; - if (notification.subscriptionName) data.subscriptionName = notification.subscriptionName; - 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); + if (notification.senderId) fcmData.senderId = notification.senderId; + if (notification.recipientId) fcmData.recipientId = notification.recipientId; + if (notification.ownerId) fcmData.ownerId = notification.ownerId; + if (notification.read !== undefined) fcmData.read = String(notification.read); + + if (notification.data) { + for (const key in notification.data) { + if (Object.prototype.hasOwnProperty.call(notification.data, key)) { + const value = notification.data[key]; + if (typeof value === "object" && value !== null) { + fcmData[key] = JSON.stringify(value); + } else { + fcmData[key] = String(value); + } + } + } + } switch (notification.type) { case "trainer_response": - title = notification.title || - (notification.status === "ACCEPTED" + title = + notification.data?.title || + (notification.data?.status === "ACCEPTED" ? "Trainer Request Accepted" : "Trainer Request Update"); body = - notification.message || + notification.data?.message || `${ - notification.trainerName - } has ${notification.status?.toLowerCase()} your request`; + notification.data?.trainerName + } has ${notification.data?.status?.toLowerCase()} your request`; break; case "trainer_assignment": - title = notification.title || "New Client Assignment"; + title = notification.data?.title || "New Client Assignment"; body = - notification.message || - `You have been assigned to ${notification.name}`; + notification.data?.message || + `You have been assigned to ${notification.data?.name}`; break; case "trainer_assigned_to_client": - title = notification.title || "Trainer Assigned"; + title = notification.data?.title || "Trainer Assigned"; body = - notification.message || - `${notification.trainerName} has been assigned as your trainer`; + notification.data?.message || + `${notification.data?.trainerName} has been assigned as your trainer`; break; case "trainer_update_owner": - title = notification.title || "Trainer Schedule Update"; - body = notification.message || "A trainer has updated their schedule"; + title = notification.data?.title || "Trainer Schedule Update"; + body = + notification.data?.message || "A trainer has updated their schedule"; break; case "trainer_update_client": - title = notification.title || "Schedule Update"; - body = notification.message || "Your training schedule has been updated"; + title = notification.data?.title || "Schedule Update"; + 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; case "plan_renewal": - title = notification.title || "Plan Renewal"; + title = notification.data?.title || "Plan Renewal"; body = - notification.message || - `Plan ${notification.subscriptionName} has been renewed`; + notification.data?.message || + `Plan ${notification.data?.subscriptionName} has been renewed`; break; case "plan_assigned": - title = notification.title || "New Plan Assigned"; + title = notification.data?.title || "New Plan Assigned"; body = - notification.message || - `You have been assigned ${notification.subscriptionName} at ${notification.gymName}`; + notification.data?.message || + `You have been assigned ${notification.data?.subscriptionName} at ${notification.data?.gymName}`; break; case "schedule_update": - title = notification.title || "Schedule Update"; - body = notification.message || "Your training schedule has been updated"; - if (notification.oldTimeSlot && notification.newTimeSlot) { - body += ` from ${notification.oldTimeSlot} to ${notification.newTimeSlot}`; - if (notification.formattedDate) { - body += ` on ${notification.formattedDate}`; + title = notification.data?.title || "Schedule Update"; + 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; case "attendance_dispute": - title = notification.title || "Attendance Dispute"; + title = notification.data?.title || "Attendance Dispute"; body = - notification.message || - `${notification.name} has disputed an attendance record`; - if (notification.logTime) { - body += ` for ${notification.logTime}`; + notification.data?.message || + `${notification.data?.name} has disputed an attendance record`; + if (notification.data?.logTime) { + body += ` for ${notification.data.logTime}`; } break; case "day_pass_entry": - const isAccepted = notification.status === "ACCEPTED"; - title = notification.title || (isAccepted ? "Day Pass Approved" : "Day Pass Denied"); + const isAccepted = notification.data?.status === "ACCEPTED"; + title = + notification.data?.title || + (isAccepted ? "Day Pass Approved" : "Day Pass Denied"); body = - notification.message || + notification.data?.message || (isAccepted ? "Your day pass has been approved" : "Your day pass has been denied"); break; case "client_invitations": - if (notification.userId || notification.invitorId) { - const isAccept = notification.status === "ACCEPTED"; - title = notification.title || (isAccept ? "Invitation Accepted" : "Invitation Rejected"); + if (notification.data?.userId || notification.data?.invitorId) { + const isAccept = notification.data?.status === "ACCEPTED"; + title = + notification.data?.title || + (isAccept ? "Invitation Accepted" : "Invitation Rejected"); body = - notification.message || + notification.data?.message || (isAccept - ? `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been accepted` - : `The invitation for ${notification.subscriptionName} you shared with ${notification.name} has been rejected`); - } else if (notification.phoneNumber) { - const invitationStatus = getInvitationStatus(notification.status); - title = notification.title || getInvitationTitle(invitationStatus); + ? `The invitation for ${notification.data?.subscriptionName} you shared with ${notification.data?.name} has been accepted` + : `The invitation for ${notification.data?.subscriptionName} you shared with ${notification.data?.name} has been rejected`); + } else if (notification.data?.phoneNumber) { + const invitationStatus = getInvitationStatus(notification.data?.status); + title = + notification.data?.title || getInvitationTitle(invitationStatus); body = - notification.message || - getInvitationBody(invitationStatus, notification.name); - data.status = invitationStatus; + notification.data?.message || + getInvitationBody(invitationStatus, notification.data?.name); + fcmData.status = invitationStatus; } break; @@ -314,16 +301,17 @@ function prepareNotificationMessage( logger.info( `Using default handling for notification type: ${notification.type}` ); - title = notification.title || + title = + notification.data?.title || (notification.type ? `${notification.type.replace("_", " ").toUpperCase()}` : "Notification"); break; } - const notificationMessage: admin.messaging.Message = { + const notificationMessage: admin.messaging.TokenMessage = { notification: { title, body }, - data, + data: fcmData, android: { priority: "high", notification: { @@ -417,4 +405,4 @@ async function updateNotificationWithError( } catch (updateError) { logger.error(`Error updating notification with error: ${updateError}`); } -} \ No newline at end of file +} -- 2.43.0 From 3e4f26dff78a659411758f7965bc17ce75321384 Mon Sep 17 00:00:00 2001 From: Sharon Dcruz Date: Tue, 29 Jul 2025 12:52:55 +0530 Subject: [PATCH 4/6] Changes Updated --- functions/src/notifications/processNotification.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/functions/src/notifications/processNotification.ts b/functions/src/notifications/processNotification.ts index a9a5563..8d38599 100644 --- a/functions/src/notifications/processNotification.ts +++ b/functions/src/notifications/processNotification.ts @@ -10,6 +10,7 @@ interface NotificationData { senderId?: string; recipientId?: string; ownerId?: string; + trainerId?: string; type?: string; notificationSent?: boolean; timestamp?: admin.firestore.FieldValue; @@ -86,6 +87,11 @@ async function getUserAndFCMToken( } else if (notification.ownerId) { targetUserId = notification.ownerId; logger.info(`Using top-level ownerId: ${targetUserId}`); + + } else if (notification.trainerId) { + targetUserId = notification.trainerId; + logger.info(`Using top-level trainerId: ${targetUserId}`); + } else if (notification.data?.userId) { targetUserId = notification.data.userId; logger.info(`Using data.userId: ${targetUserId}`); @@ -166,6 +172,7 @@ function prepareNotificationMessage( if (notification.senderId) fcmData.senderId = notification.senderId; if (notification.recipientId) fcmData.recipientId = notification.recipientId; if (notification.ownerId) fcmData.ownerId = notification.ownerId; + if (notification.trainerId) fcmData.trainerId = notification.trainerId; if (notification.read !== undefined) fcmData.read = String(notification.read); if (notification.data) { @@ -185,7 +192,7 @@ function prepareNotificationMessage( case "trainer_response": title = notification.data?.title || - (notification.data?.status === "ACCEPTED" + (notification.data?.status === "accepted" ? "Trainer Request Accepted" : "Trainer Request Update"); body = @@ -199,14 +206,14 @@ function prepareNotificationMessage( title = notification.data?.title || "New Client Assignment"; body = notification.data?.message || - `You have been assigned to ${notification.data?.name}`; + `You have been assigned to train ${notification.data?.name}.`; break; case "trainer_assigned_to_client": title = notification.data?.title || "Trainer Assigned"; body = notification.data?.message || - `${notification.data?.trainerName} has been assigned as your trainer`; + `${notification.data?.trainerName} has been assigned as your trainer.`; break; case "trainer_update_owner": -- 2.43.0 From e7515d7d3a8199d63ba1941f585dcc52dacfe013 Mon Sep 17 00:00:00 2001 From: Sharon Dcruz Date: Tue, 29 Jul 2025 13:13:11 +0530 Subject: [PATCH 5/6] Changes Updated --- functions/src/notifications/processNotification.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/functions/src/notifications/processNotification.ts b/functions/src/notifications/processNotification.ts index 8d38599..e952fb0 100644 --- a/functions/src/notifications/processNotification.ts +++ b/functions/src/notifications/processNotification.ts @@ -9,8 +9,6 @@ const logger = getLogger(); interface NotificationData { senderId?: string; recipientId?: string; - ownerId?: string; - trainerId?: string; type?: string; notificationSent?: boolean; timestamp?: admin.firestore.FieldValue; @@ -84,14 +82,6 @@ async function getUserAndFCMToken( if (notification.recipientId) { targetUserId = notification.recipientId; logger.info(`Using top-level recipientId: ${targetUserId}`); - } else if (notification.ownerId) { - targetUserId = notification.ownerId; - logger.info(`Using top-level ownerId: ${targetUserId}`); - - } else if (notification.trainerId) { - targetUserId = notification.trainerId; - logger.info(`Using top-level trainerId: ${targetUserId}`); - } else if (notification.data?.userId) { targetUserId = notification.data.userId; logger.info(`Using data.userId: ${targetUserId}`); @@ -171,8 +161,6 @@ function prepareNotificationMessage( if (notification.senderId) fcmData.senderId = notification.senderId; if (notification.recipientId) fcmData.recipientId = notification.recipientId; - if (notification.ownerId) fcmData.ownerId = notification.ownerId; - if (notification.trainerId) fcmData.trainerId = notification.trainerId; if (notification.read !== undefined) fcmData.read = String(notification.read); if (notification.data) { -- 2.43.0 From d8dad8f1caad71b5c765c482811fbd644148f3eb Mon Sep 17 00:00:00 2001 From: Sharon Dcruz Date: Tue, 29 Jul 2025 18:53:31 +0530 Subject: [PATCH 6/6] Changes updated --- firestore.indexes.json | 36 +++++++++++++++++++ .../src/notifications/processNotification.ts | 7 ++++ 2 files changed, 43 insertions(+) diff --git a/firestore.indexes.json b/firestore.indexes.json index 5609cde..def7a3d 100644 --- a/firestore.indexes.json +++ b/firestore.indexes.json @@ -166,6 +166,42 @@ } ] }, + { + "collectionGroup": "notifications", + "queryScope": "COLLECTION", + "fields": [ + { + "fieldPath": "data.ownerId", + "order": "ASCENDING" + }, + { + "fieldPath": "timestamp", + "order": "DESCENDING" + }, + { + "fieldPath": "__name__", + "order": "DESCENDING" + } + ] + }, + { + "collectionGroup": "notifications", + "queryScope": "COLLECTION", + "fields": [ + { + "fieldPath": "data.trainerId", + "order": "ASCENDING" + }, + { + "fieldPath": "timestamp", + "order": "DESCENDING" + }, + { + "fieldPath": "__name__", + "order": "DESCENDING" + } + ] + }, { "collectionGroup": "workout_logs", "queryScope": "COLLECTION", diff --git a/functions/src/notifications/processNotification.ts b/functions/src/notifications/processNotification.ts index e952fb0..5d3c9b0 100644 --- a/functions/src/notifications/processNotification.ts +++ b/functions/src/notifications/processNotification.ts @@ -236,6 +236,13 @@ function prepareNotificationMessage( `You have been assigned ${notification.data?.subscriptionName} at ${notification.data?.gymName}`; break; + case "plan_expired": + title = notification.data?.title || "Plan Expired"; + body = + notification.data?.message || + `The plan ${notification.data?.planName} for client ${notification.data?.clientName} expired on ${notification.data?.formattedExpiryDate}.`; + break; + case "schedule_update": title = notification.data?.title || "Schedule Update"; body = -- 2.43.0