feature/fitlien-828 #124
| @ -5,6 +5,7 @@ import * as admin from "firebase-admin"; | |||||||
| const app = getAdmin(); | const app = getAdmin(); | ||||||
| const logger = getLogger(); | const logger = getLogger(); | ||||||
| const kTrainerRole = "Trainer"; | const kTrainerRole = "Trainer"; | ||||||
|  | const CACHE_FOLDER = "gym_member_cache"; | ||||||
| 
 | 
 | ||||||
| interface MembershipData { | interface MembershipData { | ||||||
|   id?: string; |   id?: string; | ||||||
| @ -44,6 +45,183 @@ interface PersonalTrainerAssign { | |||||||
|   gymId: string; |   gymId: string; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | interface MinimalCacheEntry { | ||||||
|  |   membershipId: string; | ||||||
|  |   userId: string; | ||||||
|  |   status: string; | ||||||
|  |   displayName: string; | ||||||
|  |   email?: string | null; | ||||||
|  |   phoneNumber?: string | null; | ||||||
|  |   alternateContact?: string | null; | ||||||
|  |   renewalDate?: string | null; | ||||||
|  |   expirationDate?: string | null; | ||||||
|  |   createdAt?: string | null; | ||||||
|  |   daysUntilExpiry?: number | null; | ||||||
|  |   hasPersonalTraining: boolean; | ||||||
|  |   hasPartialPayment: boolean; | ||||||
|  |   remainingAmount: number; | ||||||
|  |   subscriptionId?: string | null; | ||||||
|  |   lastUpdated: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | interface MinimalJsonCacheData { | ||||||
|  |   gymId: string; | ||||||
|  |   members: MinimalCacheEntry[]; | ||||||
|  |   totalMembers: number; | ||||||
|  |   lastUpdated: string; | ||||||
|  |   cacheVersion: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function updateCacheForMembership(gymId: string, membershipId: string): Promise<void> { | ||||||
|  |   try { | ||||||
|  |     const fileName = `${CACHE_FOLDER}/${gymId}.json`; | ||||||
|  |     const file = app.storage().bucket().file(fileName); | ||||||
|  | 
 | ||||||
|  |     let existingData: MinimalJsonCacheData | null = null; | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |       const [fileBuffer] = await file.download(); | ||||||
|  |       existingData = JSON.parse(fileBuffer.toString()); | ||||||
|  |     } catch (error) { | ||||||
|  |       logger.warn(`Could not load existing cache for gym ${gymId}, skipping cache update: ${error}`); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!existingData || !existingData.members) { | ||||||
|  |       logger.warn(`Invalid cache data for gym ${gymId}, skipping cache update`); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const membershipDoc = await app | ||||||
|  |       .firestore() | ||||||
|  |       .collection("memberships") | ||||||
|  |       .doc(membershipId) | ||||||
|  |       .get(); | ||||||
|  | 
 | ||||||
|  |     if (!membershipDoc.exists) { | ||||||
|  |       logger.warn(`Membership ${membershipId} not found, skipping cache update`); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const membershipData = membershipDoc.data(); | ||||||
|  |     const updatedEntry = await generateMinimalCacheEntry( | ||||||
|  |       membershipData!.userId, | ||||||
|  |       membershipId, | ||||||
|  |       membershipData!, | ||||||
|  |       gymId | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     const memberIndex = existingData.members.findIndex( | ||||||
|  |       member => member.membershipId === membershipId | ||||||
|  |     ); | ||||||
|  | 
 | ||||||
|  |     if (memberIndex >= 0) { | ||||||
|  |       existingData.members[memberIndex] = updatedEntry; | ||||||
|  |     } else { | ||||||
|  |       existingData.members.push(updatedEntry); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     existingData.lastUpdated = new Date().toISOString(); | ||||||
|  |     existingData.totalMembers = existingData.members.length; | ||||||
|  | 
 | ||||||
|  |     await file.save(JSON.stringify(existingData, null, 2), { | ||||||
|  |       metadata: { | ||||||
|  |         contentType: "application/json", | ||||||
|  |         metadata: { | ||||||
|  |           gymId: gymId, | ||||||
|  |           totalMembers: existingData.totalMembers.toString(), | ||||||
|  |           generatedAt: existingData.lastUpdated, | ||||||
|  |           cacheVersion: existingData.cacheVersion, | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     logger.info(`Cache updated for membership ${membershipId} in gym ${gymId}`); | ||||||
|  |   } catch (error) { | ||||||
|  |     logger.error(`Error updating cache for membership ${membershipId}:`, error); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | async function generateMinimalCacheEntry( | ||||||
|  |   userId: string, | ||||||
|  |   membershipId: string, | ||||||
|  |   membershipData: any, | ||||||
|  |   gymId: string | ||||||
|  | ): Promise<MinimalCacheEntry> { | ||||||
|  |   try { | ||||||
|  |     let firstName = ""; | ||||||
|  |     let lastName = ""; | ||||||
|  |     let email = ""; | ||||||
|  |     let phoneNumber = ""; | ||||||
|  |     let alternateContact = ""; | ||||||
|  | 
 | ||||||
|  |     try { | ||||||
|  |       const clientFieldsSnapshot = await app | ||||||
|  |         .firestore() | ||||||
|  |         .collection("client_profiles") | ||||||
|  |         .where("uid", "==", userId) | ||||||
|  |         .get(); | ||||||
|  | 
 | ||||||
|  |       if (!clientFieldsSnapshot.empty) { | ||||||
|  |         const fieldDoc = clientFieldsSnapshot.docs[0]; | ||||||
|  |         const fieldData = fieldDoc.data(); | ||||||
|  | 
 | ||||||
|  |         const fields = fieldData.fields || {}; | ||||||
|  |         firstName = fields["first-name"] || fieldData["first-name"] || ""; | ||||||
|  |         lastName = fields["last-name"] || fieldData["last-name"] || ""; | ||||||
|  |         email = fields["email"] || fieldData["email"] || ""; | ||||||
|  |         phoneNumber = fields["phone-number"] || fieldData["phone-number"] || ""; | ||||||
|  |         alternateContact = | ||||||
|  |           fields["alternate-contact"] || fieldData["alternate-contact"] || ""; | ||||||
|  |       } | ||||||
|  |     } catch (error) { | ||||||
|  |       logger.error(`Error getting fields for user ${userId}:`, error); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const daysUntilExpiry = membershipData.daysUntilExpiry || null; | ||||||
|  | 
 | ||||||
|  |     const displayName = | ||||||
|  |       firstName.length === 0 | ||||||
|  |         ? "Unknown" | ||||||
|  |         : lastName.length === 0 | ||||||
|  |         ? firstName | ||||||
|  |         : `${firstName} ${lastName}`; | ||||||
|  | 
 | ||||||
|  |     const isPartial = membershipData.isPartialPayment === true; | ||||||
|  |     const remaining = membershipData.remainingAmount || 0; | ||||||
|  |     const hasPartialPayment = isPartial && remaining > 0; | ||||||
|  | 
 | ||||||
|  |     const minimalEntry: MinimalCacheEntry = { | ||||||
|  |       membershipId, | ||||||
|  |       userId, | ||||||
|  |       status: membershipData.status || "N/A", | ||||||
|  |       displayName, | ||||||
|  |       email: email || null, | ||||||
|  |       phoneNumber: phoneNumber || null, | ||||||
|  |       alternateContact: alternateContact || null, | ||||||
|  |       renewalDate: null, | ||||||
|  |       expirationDate: membershipData.expirationDate | ||||||
|  |         ? membershipData.expirationDate.toDate().toISOString() | ||||||
|  |         : null, | ||||||
|  |       createdAt: membershipData.createdAt | ||||||
|  |         ? membershipData.createdAt.toDate().toISOString() | ||||||
|  |         : null, | ||||||
|  |       daysUntilExpiry, | ||||||
|  |       hasPersonalTraining: | ||||||
|  |         membershipData.subscription?.hasPersonalTraining === true, | ||||||
|  |       hasPartialPayment, | ||||||
|  |       remainingAmount: remaining, | ||||||
|  |       subscriptionId: membershipData.subscriptionId || null, | ||||||
|  |       lastUpdated: new Date().toISOString(), | ||||||
|  |     }; | ||||||
|  | 
 | ||||||
|  |     return minimalEntry; | ||||||
|  |   } catch (error) { | ||||||
|  |     logger.error("Error generating minimal cache entry:", error); | ||||||
|  |     throw error; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| export const checkExpiredMemberships = onSchedule( | export const checkExpiredMemberships = onSchedule( | ||||||
|   { |   { | ||||||
|     schedule: "0 8,14,20 * * *", |     schedule: "0 8,14,20 * * *", | ||||||
| @ -150,6 +328,7 @@ async function findExpiredMembershipsWithoutExpiryDate(): Promise< | |||||||
|     throw error; |     throw error; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | 
 | ||||||
| async function updateExpiryDateForExpiredMembership( | async function updateExpiryDateForExpiredMembership( | ||||||
|   membershipId: string, |   membershipId: string, | ||||||
|   membershipData: MembershipData |   membershipData: MembershipData | ||||||
| @ -184,6 +363,8 @@ async function updateExpiryDateForExpiredMembership( | |||||||
|         updatedAt: admin.firestore.FieldValue.serverTimestamp(), |         updatedAt: admin.firestore.FieldValue.serverTimestamp(), | ||||||
|       }); |       }); | ||||||
| 
 | 
 | ||||||
|  |     await updateCacheForMembership(membershipData.gymId, membershipId); | ||||||
|  | 
 | ||||||
|     logger.info( |     logger.info( | ||||||
|       `Updated expiry date for expired membership ${membershipId}: ${expiryDate.toISOString()}` |       `Updated expiry date for expired membership ${membershipId}: ${expiryDate.toISOString()}` | ||||||
|     ); |     ); | ||||||
| @ -513,6 +694,8 @@ async function updateDaysUntilExpiryForAllMemberships(): Promise<void> { | |||||||
|               .doc(doc.id) |               .doc(doc.id) | ||||||
|               .update(updateData); |               .update(updateData); | ||||||
| 
 | 
 | ||||||
|  |             await updateCacheForMembership(data.gymId, doc.id); | ||||||
|  | 
 | ||||||
|             logger.info( |             logger.info( | ||||||
|               `Updated membership ${doc.id} with daysUntilExpiry: ${daysUntilExpiry}` |               `Updated membership ${doc.id} with daysUntilExpiry: ${daysUntilExpiry}` | ||||||
|             ); |             ); | ||||||
| @ -665,6 +848,8 @@ async function processExpiredMembership( | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     await updateCacheForMembership(membershipData.gymId, membershipId); | ||||||
|  | 
 | ||||||
|     logger.info(`Marked membership ${membershipId} as EXPIRED`); |     logger.info(`Marked membership ${membershipId} as EXPIRED`); | ||||||
| 
 | 
 | ||||||
|     await sendPlanExpiredNotification(membershipId, membershipData); |     await sendPlanExpiredNotification(membershipId, membershipData); | ||||||
| @ -697,6 +882,8 @@ async function processExpiringMembership( | |||||||
|           expirationDate: admin.firestore.Timestamp.fromDate(expiryDate), |           expirationDate: admin.firestore.Timestamp.fromDate(expiryDate), | ||||||
|           updatedAt: admin.firestore.FieldValue.serverTimestamp(), |           updatedAt: admin.firestore.FieldValue.serverTimestamp(), | ||||||
|         }); |         }); | ||||||
|  | 
 | ||||||
|  |       await updateCacheForMembership(membershipData.gymId, membershipId); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     await sendPlanExpiringNotification(membershipId, membershipData); |     await sendPlanExpiringNotification(membershipId, membershipData); | ||||||
| @ -1067,4 +1254,4 @@ async function getGymName(gymId: string): Promise<string> { | |||||||
|     logger.error(`Error getting gym name for gym ${gymId}:`, error); |     logger.error(`Error getting gym name for gym ${gymId}:`, error); | ||||||
|     return "Unknown Gym"; |     return "Unknown Gym"; | ||||||
|   } |   } | ||||||
| } | } | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user