From 218ff1d02e9a95a073877af4647070885e90655d Mon Sep 17 00:00:00 2001 From: Sharon Dcruz Date: Mon, 25 Aug 2025 09:50:08 +0000 Subject: [PATCH] notification-bug-fix (#110) Co-authored-by: Dhansh A S Reviewed-on: https://git.cosqnet.com/cosqnet/fitlien-services/pulls/110 Reviewed-by: Allen T J Co-authored-by: Sharon Dcruz Co-committed-by: Sharon Dcruz --- firestore.indexes.json | 193 +----------------- .../membershipStatusNotifications.ts | 102 +++++++-- 2 files changed, 86 insertions(+), 209 deletions(-) diff --git a/firestore.indexes.json b/firestore.indexes.json index 54f5709..2ddb5ce 100644 --- a/firestore.indexes.json +++ b/firestore.indexes.json @@ -1,195 +1,4 @@ { - "indexes": [ - { - "collectionGroup": "day_pass_bookings", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "userId", - "order": "ASCENDING" - }, - { - "fieldPath": "createdAt", - "order": "DESCENDING" - } - ] - }, - { - "collectionGroup": "day_pass_entries", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "bookingId", - "order": "ASCENDING" - }, - { - "fieldPath": "entryDate", - "order": "ASCENDING" - } - ] - }, - { - "collectionGroup": "gyms", - "queryScope": "COLLECTION_GROUP", - "fields": [ - { - "fieldPath": "userId", - "order": "ASCENDING" - }, - { - "fieldPath": "createdAt", - "order": "ASCENDING" - } - ] - }, - { - "collectionGroup": "gyms", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "userId", - "order": "ASCENDING" - }, - { - "fieldPath": "name", - "order": "ASCENDING" - } - ] - }, - { - "collectionGroup": "gyms", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "userId", - "order": "ASCENDING" - }, - { - "fieldPath": "createdAt", - "order": "DESCENDING" - } - ] - }, - { - "collectionGroup": "gyms", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "isApproved", - "order": "ASCENDING" - }, - { - "fieldPath": "createdAt", - "order": "ASCENDING" - } - ] - }, - { - "collectionGroup": "memberships", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "gymId", - "order": "ASCENDING" - }, - { - "fieldPath": "createdAt", - "order": "DESCENDING" - } - ] - }, - { - "collectionGroup": "notifications", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "type", - "order": "ASCENDING" - }, - { - "fieldPath": "userId", - "order": "ASCENDING" - }, - { - "fieldPath": "timestamp", - "order": "DESCENDING" - } - ] - }, - { - "collectionGroup": "notifications", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "recipientId", - "order": "ASCENDING" - }, - { - "fieldPath": "timestamp", - "order": "DESCENDING" - } - ] - }, - { - "collectionGroup": "workout_logs", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "user_id", - "order": "ASCENDING" - }, - { - "fieldPath": "date", - "order": "DESCENDING" - } - ] - }, - { - "collectionGroup": "workout_logs", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "user_id", - "order": "ASCENDING" - }, - { - "fieldPath": "date", - "order": "ASCENDING" - } - ] - }, - { - "collectionGroup": "workout_logs", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "user_id", - "order": "ASCENDING" - }, - { - "fieldPath": "start_time", - "order": "ASCENDING" - }, - { - "fieldPath": "date", - "order": "ASCENDING" - } - ] - }, - { - "collectionGroup": "terms_and_conditions", - "queryScope": "COLLECTION", - "fields": [ - { - "fieldPath": "normalizedName", - "order": "ASCENDING" - }, - { - "fieldPath": "userUid", - "order": "ASCENDING" - } - ] - } - ], + "indexes": [], "fieldOverrides": [] } \ No newline at end of file diff --git a/functions/src/notifications/membershipStatusNotifications.ts b/functions/src/notifications/membershipStatusNotifications.ts index 85799cf..e8c3b01 100644 --- a/functions/src/notifications/membershipStatusNotifications.ts +++ b/functions/src/notifications/membershipStatusNotifications.ts @@ -11,6 +11,7 @@ interface MembershipData { userId: string; gymId: string; status: string; + expirationDate?: admin.firestore.Timestamp; subscription?: { name: string; frequency: string; @@ -56,14 +57,16 @@ export const checkExpiredMemberships = onSchedule( await updateDaysUntilExpiryForAllMemberships(); const expiredMemberships = await findExpiredMemberships(); const expiringMemberships = await findMembershipsExpiringIn10Days(); + + const expiredMembershipsWithoutExpiryDate = await findExpiredMembershipsWithoutExpiryDate(); - if (expiredMemberships.length === 0 && expiringMemberships.length === 0) { - logger.info("No expired or expiring memberships found."); + if (expiredMemberships.length === 0 && expiringMemberships.length === 0 && expiredMembershipsWithoutExpiryDate.length === 0) { + logger.info("No expired, expiring, or unprocessed expired memberships found."); return; } logger.info( - `Found ${expiredMemberships.length} expired memberships and ${expiringMemberships.length} memberships expiring in 10 days to process.` + `Found ${expiredMemberships.length} expired memberships, ${expiringMemberships.length} memberships expiring in 10 days, and ${expiredMembershipsWithoutExpiryDate.length} expired memberships without expiry dates to process.` ); const expiredResults = await Promise.allSettled( @@ -74,21 +77,19 @@ export const checkExpiredMemberships = onSchedule( expiringMemberships.map((m) => processExpiringMembership(m.id, m.data)) ); - const expiredSuccessful = expiredResults.filter( - (r) => r.status === "fulfilled" - ).length; - const expiredFailed = expiredResults.filter( - (r) => r.status === "rejected" - ).length; - const expiringSuccessful = expiringResults.filter( - (r) => r.status === "fulfilled" - ).length; - const expiringFailed = expiringResults.filter( - (r) => r.status === "rejected" - ).length; + const updateResults = await Promise.allSettled( + expiredMembershipsWithoutExpiryDate.map((m) => updateExpiryDateForExpiredMembership(m.id, m.data)) + ); + + const expiredSuccessful = expiredResults.filter(r => r.status === "fulfilled").length; + const expiredFailed = expiredResults.filter(r => r.status === "rejected").length; + const expiringSuccessful = expiringResults.filter(r => r.status === "fulfilled").length; + const expiringFailed = expiringResults.filter(r => r.status === "rejected").length; + const updateSuccessful = updateResults.filter(r => r.status === "fulfilled").length; + const updateFailed = updateResults.filter(r => r.status === "rejected").length; logger.info( - `Completed processing. Expired - Success: ${expiredSuccessful}, Failed: ${expiredFailed}. Expiring - Success: ${expiringSuccessful}, Failed: ${expiringFailed}` + `Completed processing. Expired - Success: ${expiredSuccessful}, Failed: ${expiredFailed}. Expiring - Success: ${expiringSuccessful}, Failed: ${expiringFailed}. Updates - Success: ${updateSuccessful}, Failed: ${updateFailed}` ); } catch (error) { logger.error("Error in scheduled membership expiry check:", error); @@ -96,6 +97,72 @@ export const checkExpiredMemberships = onSchedule( } ); + + + +async function findExpiredMembershipsWithoutExpiryDate(): Promise< + Array<{ id: string; data: MembershipData }> +> { + try { + const snapshot = await app + .firestore() + .collection("memberships") + .where("status", "==", "EXPIRED") + .get(); + + const membershipsWithoutExpiryDate: Array<{ id: string; data: MembershipData }> = []; + + snapshot.docs.forEach((doc) => { + const data = doc.data() as MembershipData; + if (!data.expirationDate) { + membershipsWithoutExpiryDate.push({ id: doc.id, data }); + } + }); + + return membershipsWithoutExpiryDate; + } catch (error) { + logger.error("Error finding expired memberships without expiry date:", error); + throw error; + } +} +async function updateExpiryDateForExpiredMembership( + membershipId: string, + membershipData: MembershipData +): Promise { + try { + if (!membershipData.subscription || !membershipData.subscription.frequency) { + logger.warn(`Skipping membership ${membershipId} - no subscription data`); + return; + } + + const payments = await getPaymentsForMembership(membershipId); + if (payments.length === 0) { + logger.warn(`No payments found for membership ${membershipId}`); + return; + } + + const latestPayment = payments[0]; + const expiryDate = calculateExpiryDate( + latestPayment.dateTimestamp, + membershipData.subscription.frequency + ); + + await app + .firestore() + .collection("memberships") + .doc(membershipId) + .update({ + expirationDate: admin.firestore.Timestamp.fromDate(expiryDate), + updatedAt: admin.firestore.FieldValue.serverTimestamp(), + }); + + logger.info(`Updated expiry date for expired membership ${membershipId}: ${expiryDate.toISOString()}`); + } catch (error) { + logger.error(`Error updating expiry date for membership ${membershipId}:`, error); + throw error; + } +} + async function findExpiredMemberships(): Promise< Array<{ id: string; data: MembershipData }> > { @@ -923,6 +990,7 @@ async function sendPlanExpiringNotification( ); } } + async function getClientName( membershipId: string, clientId: string @@ -966,4 +1034,4 @@ async function getGymName(gymId: string): Promise { logger.error(`Error getting gym name for gym ${gymId}:`, error); return "Unknown Gym"; } -} +} \ No newline at end of file