diff --git a/functions/src/notifications/processNotification.ts b/functions/src/notifications/processNotification.ts index ada5636..a9a5563 100644 --- a/functions/src/notifications/processNotification.ts +++ b/functions/src/notifications/processNotification.ts @@ -9,22 +9,12 @@ const logger = getLogger(); interface NotificationData { senderId?: string; recipientId?: string; - notificationSent?: boolean; - userId?: string; - clientId?: string; - invitorId?: string; - phoneNumber?: string; - message?: string; + ownerId?: string; type?: string; - status?: string; - gymName?: string; - trainerName?: string; - membershipId?: string; - subscriptionName?: string; - name?: string; - clientEmail?: string; - invitationId?: string; - [key: string]: any; + notificationSent?: boolean; + timestamp?: admin.firestore.FieldValue; + read?: boolean; + data?: { [key: string]: any }; } export const processNotificationOnCreate = onDocumentCreated( @@ -67,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); @@ -90,53 +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}`); - } 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.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.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 { @@ -162,142 +154,164 @@ async function getFCMTokenFromUserDoc(userId: string): Promise { 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 = { +): 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) 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.status === "ACCEPTED" + notification.data?.title || + (notification.data?.status === "ACCEPTED" ? "Trainer Request Accepted" - : "Trainer Request Update"; + : "Trainer Request Update"); body = - notification.message || + notification.data?.message || `${ - notification.trainerName - } has ${notification.status?.toLowerCase()} your request`; - data.trainerName = notification.trainerName || ""; - data.status = notification.status || ""; + notification.data?.trainerName + } has ${notification.data?.status?.toLowerCase()} your request`; break; case "trainer_assignment": - title = "New Client Assignment"; + title = notification.data?.title || "New Client Assignment"; body = - notification.message || - `You have been assigned to ${notification.name}`; - data.clientName = notification.name || ""; - data.membershipId = notification.membershipId || ""; + notification.data?.message || + `You have been assigned to ${notification.data?.name}`; break; case "trainer_assigned_to_client": - title = "Trainer Assigned"; + title = notification.data?.title || "Trainer Assigned"; body = - notification.message || - `${notification.trainerName} has been assigned as your trainer`; - data.trainerName = notification.trainerName || ""; - data.membershipId = notification.membershipId || ""; + notification.data?.message || + `${notification.data?.trainerName} has been assigned as your trainer`; break; case "trainer_update_owner": - title = "Trainer Schedule Update"; - body = notification.message || "A trainer has updated their schedule"; - data.membershipId = notification.membershipId || ""; + title = notification.data?.title || "Trainer Schedule Update"; + body = + notification.data?.message || "A trainer has updated their schedule"; break; case "trainer_update_client": - title = "Schedule Update"; - body = notification.message || "Your training schedule has been updated"; - data.membershipId = notification.membershipId || ""; + 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 = "Plan Renewal"; + title = notification.data?.title || "Plan Renewal"; body = - notification.message || - `Plan ${notification.subscriptionName} has been renewed`; - data.planName = notification.subscriptionName || ""; - data.membershipId = notification.membershipId || ""; + notification.data?.message || + `Plan ${notification.data?.subscriptionName} has been renewed`; break; case "plan_assigned": - title = "New Plan Assigned"; + title = notification.data?.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 || ""; + notification.data?.message || + `You have been assigned ${notification.data?.subscriptionName} at ${notification.data?.gymName}`; break; case "schedule_update": - title = "Schedule Update"; - body = notification.message || "Your training schedule has been updated"; - data.gymName = notification.gymName || ""; + 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 = "Attendance Dispute"; + title = notification.data?.title || "Attendance Dispute"; body = - notification.message || - `${notification.name} has disputed an attendance record`; - data.disputedBy = notification.name || ""; - data.membershipId = notification.membershipId || ""; + 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 = 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"); - 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"; + 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 = 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; } - 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.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: {