From 5c81ae30161b86bf0caf2464601aee344a4c03e3 Mon Sep 17 00:00:00 2001 From: Sharon Dcruz Date: Fri, 1 Aug 2025 12:24:04 +0530 Subject: [PATCH] Changes Updated --- functions/src/index.ts | 2 +- functions/src/notifications/index.ts | 2 + .../membershipStatusNotifications.ts | 178 ++++++++++++++++++ .../src/notifications/processNotification.ts | 2 +- 4 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 functions/src/notifications/membershipStatusNotifications.ts diff --git a/functions/src/index.ts b/functions/src/index.ts index 1156209..fb48fbe 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -13,7 +13,7 @@ export * from './shared/config'; export { sendEmailSES } from './email'; export { sendSMSMessage } from './sms'; export { accessFile } from './storage'; -export { processNotificationOnCreate } from './notifications'; +export { processNotificationOnCreate,processMembershipStatusChange } from './notifications'; export * from './payments'; export { getPlaceDetails, getPlacesAutocomplete } from './places'; export { registerClient } from './users'; diff --git a/functions/src/notifications/index.ts b/functions/src/notifications/index.ts index c9fed3e..eb9cb4d 100644 --- a/functions/src/notifications/index.ts +++ b/functions/src/notifications/index.ts @@ -1 +1,3 @@ export { processNotificationOnCreate } from './processNotification'; +export { processMembershipStatusChange } from "./membershipStatusNotifications"; + diff --git a/functions/src/notifications/membershipStatusNotifications.ts b/functions/src/notifications/membershipStatusNotifications.ts new file mode 100644 index 0000000..21b7a79 --- /dev/null +++ b/functions/src/notifications/membershipStatusNotifications.ts @@ -0,0 +1,178 @@ +import { onDocumentUpdated } from "firebase-functions/v2/firestore"; +import { getLogger } from "../shared/config"; +import { getAdmin } from "../shared/config"; +import * as admin from "firebase-admin"; + +const app = getAdmin(); +const logger = getLogger(); + +interface MembershipData { + id?: string; + userId: string; + gymId: string; + status: string; + subscription?: { + name: string; + duration: number; + }; + createdAt: admin.firestore.FieldValue; + updatedAt: admin.firestore.FieldValue; +} + +interface ClientFields { + [key: string]: string | undefined; + 'first-name'?: string; +} + +export const processMembershipStatusChange = onDocumentUpdated( + { + region: "#{SERVICES_RGN}#", + document: "memberships/{membershipId}", + }, + async (event) => { + try { + const membershipId = event.params.membershipId; + const beforeData = event.data?.before?.data() as MembershipData; + const afterData = event.data?.after?.data() as MembershipData; + + if (!beforeData || !afterData) { + logger.error(`No data found for membership ${membershipId}`); + return; + } + + // Check if status changed to EXPIRED + if (beforeData.status !== "EXPIRED" && afterData.status === "EXPIRED") { + logger.info(`Membership ${membershipId} status changed to EXPIRED. Sending notification...`); + + await sendPlanExpiredNotification(membershipId, afterData); + } + + } catch (error) { + logger.error("Error processing membership status change:", error); + } + } +); + +async function sendPlanExpiredNotification( + membershipId: string, + membershipData: MembershipData +): Promise { + try { + const clientName = await getClientName(membershipId, membershipData.userId); + + const gymOwnerId = await getGymOwnerId(membershipData.gymId); + + const gymName = await getGymName(membershipData.gymId); + + const expiryDate = new Date(); + const formattedExpiryDate = expiryDate.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + + // Create notification document + const notificationData = { + senderId: "system", + recipientId: gymOwnerId, + type: "plan_expired", + notificationSent: false, + timestamp: admin.firestore.FieldValue.serverTimestamp(), + read: false, + data: { + title: "Plan Expired", + message: `The plan ${membershipData.subscription?.name || 'Unknown Plan'} for client ${clientName} has expired.`, + planName: membershipData.subscription?.name || 'Unknown Plan', + clientName: clientName, + membershipId: membershipId, + gymName: gymName, + formattedExpiryDate: formattedExpiryDate, + expiryDate: admin.firestore.Timestamp.fromDate(expiryDate), + }, + }; + + const notificationRef = await app + .firestore() + .collection("notifications") + .add(notificationData); + + logger.info(`Plan expired notification created with ID: ${notificationRef.id} for gym owner ${gymOwnerId}, membership ${membershipId}`); + } catch (error) { + logger.error(`Error sending plan expired notification for membership ${membershipId}:`, error); + throw error; + } +} + +async function getClientName(membershipId: string, clientId: string): Promise { + try { + const clientProfileDoc = await app + .firestore() + .collection("client_profiles") + .doc(clientId) + .get(); + + if (!clientProfileDoc.exists) { + logger.warn(`Client profile not found for clientId: ${clientId}`); + return "Unknown Client"; + } + + const clientData = clientProfileDoc.data(); + const fields = clientData?.fields as ClientFields; + + if (fields) { + const firstName = fields['first-name'] || ''; + return firstName || "Unknown Client"; + } + + return "Unknown Client"; + } catch (error) { + logger.error(`Error getting client name for membership ${membershipId}:`, error); + return "Unknown Client"; + } +} + +async function getGymOwnerId(gymId: string): Promise { + try { + const gymDoc = await app + .firestore() + .collection("gyms") + .doc(gymId) + .get(); + + if (!gymDoc.exists) { + throw new Error(`Gym not found: ${gymId}`); + } + + const gymData = gymDoc.data(); + const userId = gymData?.userId; + + if (!userId) { + throw new Error(`No userId found in gym document: ${gymId}`); + } + + return userId; + } catch (error) { + logger.error(`Error getting gym owner ID for gym ${gymId}:`, error); + throw error; + } +} + +async function getGymName(gymId: string): Promise { + try { + const gymDoc = await app + .firestore() + .collection("gyms") + .doc(gymId) + .get(); + + if (!gymDoc.exists) { + return "Unknown Gym"; + } + + const gymData = gymDoc.data(); + return gymData?.name || gymData?.gymName || "Unknown Gym"; + } catch (error) { + logger.error(`Error getting gym name for gym ${gymId}:`, error); + return "Unknown Gym"; + } +} diff --git a/functions/src/notifications/processNotification.ts b/functions/src/notifications/processNotification.ts index 5d3c9b0..d232395 100644 --- a/functions/src/notifications/processNotification.ts +++ b/functions/src/notifications/processNotification.ts @@ -240,7 +240,7 @@ function prepareNotificationMessage( 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}.`; + `The plan ${notification.data?.planName} for client ${notification.data?.clientName} has expired.`; break; case "schedule_update":