feature/fitlien-828 #124
| @ -5,6 +5,7 @@ import * as admin from "firebase-admin"; | ||||
| const app = getAdmin(); | ||||
| const logger = getLogger(); | ||||
| const kTrainerRole = "Trainer"; | ||||
| const CACHE_FOLDER = "gym_member_cache"; | ||||
| 
 | ||||
| interface MembershipData { | ||||
|   id?: string; | ||||
| @ -44,6 +45,183 @@ interface PersonalTrainerAssign { | ||||
|   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( | ||||
|   { | ||||
|     schedule: "0 8,14,20 * * *", | ||||
| @ -150,6 +328,7 @@ async function findExpiredMembershipsWithoutExpiryDate(): Promise< | ||||
|     throw error; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| async function updateExpiryDateForExpiredMembership( | ||||
|   membershipId: string, | ||||
|   membershipData: MembershipData | ||||
| @ -184,6 +363,8 @@ async function updateExpiryDateForExpiredMembership( | ||||
|         updatedAt: admin.firestore.FieldValue.serverTimestamp(), | ||||
|       }); | ||||
| 
 | ||||
|     await updateCacheForMembership(membershipData.gymId, membershipId); | ||||
| 
 | ||||
|     logger.info( | ||||
|       `Updated expiry date for expired membership ${membershipId}: ${expiryDate.toISOString()}` | ||||
|     ); | ||||
| @ -513,6 +694,8 @@ async function updateDaysUntilExpiryForAllMemberships(): Promise<void> { | ||||
|               .doc(doc.id) | ||||
|               .update(updateData); | ||||
| 
 | ||||
|             await updateCacheForMembership(data.gymId, doc.id); | ||||
| 
 | ||||
|             logger.info( | ||||
|               `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`); | ||||
| 
 | ||||
|     await sendPlanExpiredNotification(membershipId, membershipData); | ||||
| @ -697,6 +882,8 @@ async function processExpiringMembership( | ||||
|           expirationDate: admin.firestore.Timestamp.fromDate(expiryDate), | ||||
|           updatedAt: admin.firestore.FieldValue.serverTimestamp(), | ||||
|         }); | ||||
| 
 | ||||
|       await updateCacheForMembership(membershipData.gymId, membershipId); | ||||
|     } | ||||
| 
 | ||||
|     await sendPlanExpiringNotification(membershipId, membershipData); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user