Changes Updated #74
@ -13,7 +13,7 @@ export * from './shared/config';
|
|||||||
export { sendEmailSES } from './email';
|
export { sendEmailSES } from './email';
|
||||||
export { sendSMSMessage } from './sms';
|
export { sendSMSMessage } from './sms';
|
||||||
export { accessFile } from './storage';
|
export { accessFile } from './storage';
|
||||||
export { processNotificationOnCreate } from './notifications';
|
export { processNotificationOnCreate,processMembershipStatusChange } from './notifications';
|
||||||
export * from './payments';
|
export * from './payments';
|
||||||
export { getPlaceDetails, getPlacesAutocomplete } from './places';
|
export { getPlaceDetails, getPlacesAutocomplete } from './places';
|
||||||
export { registerClient } from './users';
|
export { registerClient } from './users';
|
||||||
|
|||||||
@ -1 +1,3 @@
|
|||||||
export { processNotificationOnCreate } from './processNotification';
|
export { processNotificationOnCreate } from './processNotification';
|
||||||
|
export { processMembershipStatusChange } from "./membershipStatusNotifications";
|
||||||
|
|
||||||
|
|||||||
178
functions/src/notifications/membershipStatusNotifications.ts
Normal file
178
functions/src/notifications/membershipStatusNotifications.ts
Normal file
@ -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<void> {
|
||||||
|
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<string> {
|
||||||
|
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<string> {
|
||||||
|
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<string> {
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -240,7 +240,7 @@ function prepareNotificationMessage(
|
|||||||
title = notification.data?.title || "Plan Expired";
|
title = notification.data?.title || "Plan Expired";
|
||||||
body =
|
body =
|
||||||
notification.data?.message ||
|
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;
|
break;
|
||||||
|
|
||||||
case "schedule_update":
|
case "schedule_update":
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user