ClientsLoadingFix #117

Merged
dhanshas merged 4 commits from ClientsLoadingFix into dev 2025-09-18 12:21:33 +00:00
3 changed files with 49 additions and 96 deletions

View File

@ -22,12 +22,10 @@ export {
esslDeleteUser, esslGetEmployeePunchLogs esslDeleteUser, esslGetEmployeePunchLogs
} from './dooraccess'; } from './dooraccess';
// Add member cache functions
export { export {
generateMemberCache, generateMemberCache,
updateTrainerAssignmentCache, updateTrainerAssignmentCache,
updateTimeSlotCache,
getCachedMembers, getCachedMembers,
rebuildGymCachee, rebuildGymCache,
batchRebuildCaches batchRebuildCaches
} from './memberCache'; } from './memberCache';

View File

@ -1,8 +1,7 @@
export { export {
generateMemberCache, generateMemberCache,
updateTrainerAssignmentCache, updateTrainerAssignmentCache,
updateTimeSlotCache,
getCachedMembers, getCachedMembers,
rebuildGymCachee, rebuildGymCache,
batchRebuildCaches batchRebuildCaches
} from './memberCache'; } from './memberCache';

View File

@ -10,6 +10,7 @@ const CACHE_FOLDER = 'gym_member_cache';
interface MembershipData { interface MembershipData {
gymId?: string; gymId?: string;
userId?: string;
status?: string; status?: string;
subscription?: { subscription?: {
hasPersonalTraining?: boolean; hasPersonalTraining?: boolean;
@ -36,20 +37,13 @@ interface PaymentData {
interface TrainerAssignment { interface TrainerAssignment {
id?: string; id?: string;
membershipId?: string; membershipId?: string;
createdAt?: admin.firestore.Timestamp; timeSlot?: any[];
[key: string]: any;
}
interface TimeSlot {
id?: string;
membershipId?: string;
startTime?: admin.firestore.Timestamp;
endTime?: admin.firestore.Timestamp;
createdAt?: admin.firestore.Timestamp; createdAt?: admin.firestore.Timestamp;
[key: string]: any; [key: string]: any;
} }
interface CacheEntry { interface CacheEntry {
userId: string;
membershipId: string; membershipId: string;
memberData: { memberData: {
daysUntilExpiry?: number | null; daysUntilExpiry?: number | null;
@ -81,7 +75,7 @@ export const generateMemberCache = onDocumentWritten(
async (event) => { async (event) => {
try { try {
const membershipId = event.params.membershipId; const membershipId = event.params.membershipId;
const membershipData = event.data?.after?.exists const membershipData = event.data?.after?.exists
? event.data.after.data() as MembershipData ? event.data.after.data() as MembershipData
: null; : null;
@ -96,7 +90,7 @@ export const generateMemberCache = onDocumentWritten(
return; return;
} }
await rebuildGymCache(gymId); await rebuildGymCachee(gymId);
logger.info(`JSON cache updated for gym ${gymId} after member ${membershipId} change`); logger.info(`JSON cache updated for gym ${gymId} after member ${membershipId} change`);
} catch (error) { } catch (error) {
@ -132,7 +126,7 @@ export const updateTrainerAssignmentCache = onDocumentWritten(
} }
for (const gymId of gymIds) { for (const gymId of gymIds) {
await rebuildGymCache(gymId); await rebuildGymCachee(gymId);
} }
if (gymIds.size > 0) { if (gymIds.size > 0) {
@ -144,45 +138,6 @@ export const updateTrainerAssignmentCache = onDocumentWritten(
} }
); );
export const updateTimeSlotCache = onDocumentWritten(
{
region: "#{SERVICES_RGN}#",
document: "scheduled_trainings/{slotId}",
},
async (event) => {
try {
const slotData = event.data?.after?.exists
? event.data.after.data() as TimeSlot
: null;
const oldSlotData = event.data?.before?.exists
? event.data.before.data() as TimeSlot
: null;
const gymIds = new Set<string>();
if (slotData?.membershipId) {
const gymId = await getGymIdFromMembershipId(slotData.membershipId);
if (gymId) gymIds.add(gymId);
}
if (oldSlotData?.membershipId) {
const gymId = await getGymIdFromMembershipId(oldSlotData.membershipId);
if (gymId) gymIds.add(gymId);
}
for (const gymId of gymIds) {
await rebuildGymCache(gymId);
}
if (gymIds.size > 0) {
logger.info(`Updated time slot cache for ${gymIds.size} gym(s)`);
}
} catch (error) {
logger.error('Error updating time slot cache:', error);
}
}
);
export const getCachedMembers = onCall( export const getCachedMembers = onCall(
{ {
region: "#{SERVICES_RGN}#", region: "#{SERVICES_RGN}#",
@ -212,7 +167,7 @@ export const getCachedMembers = onCall(
const fiveMinutes = 5 * 60 * 1000; const fiveMinutes = 5 * 60 * 1000;
if (forceRefresh || !fileExists || fileAge > fiveMinutes) { if (forceRefresh || !fileExists || fileAge > fiveMinutes) {
logger.info(`Rebuilding cache for gym ${gymId} - forceRefresh: ${forceRefresh}, fileExists: ${fileExists}, fileAge: ${fileAge}ms`); logger.info(`Rebuilding cache for gym ${gymId} - forceRefresh: ${forceRefresh}, fileExists: ${fileExists}, fileAge: ${fileAge}ms`);
await rebuildGymCache(gymId); await rebuildGymCachee(gymId);
} }
try { try {
@ -223,7 +178,7 @@ export const getCachedMembers = onCall(
} catch (error) { } catch (error) {
logger.error(`Error reading cache file for gym ${gymId}:`, error); logger.error(`Error reading cache file for gym ${gymId}:`, error);
await rebuildGymCache(gymId); await rebuildGymCachee(gymId);
const [fileBuffer] = await file.download(); const [fileBuffer] = await file.download();
const jsonData: JsonCacheData = JSON.parse(fileBuffer.toString()); const jsonData: JsonCacheData = JSON.parse(fileBuffer.toString());
return jsonData.members || []; return jsonData.members || [];
@ -235,7 +190,7 @@ export const getCachedMembers = onCall(
} }
); );
export const rebuildGymCachee = onCall( export const rebuildGymCache = onCall(
{ {
region: "#{SERVICES_RGN}#", region: "#{SERVICES_RGN}#",
}, },
@ -246,7 +201,7 @@ export const rebuildGymCachee = onCall(
throw new HttpsError('invalid-argument', 'gymId is required'); throw new HttpsError('invalid-argument', 'gymId is required');
} }
await rebuildGymCache(gymId); await rebuildGymCachee(gymId);
return { success: true, message: `JSON cache rebuilt for gym ${gymId}` }; return { success: true, message: `JSON cache rebuilt for gym ${gymId}` };
} }
); );
@ -267,7 +222,7 @@ export const batchRebuildCaches = onCall(
for (const gymId of gymIds) { for (const gymId of gymIds) {
try { try {
await rebuildGymCache(gymId); await rebuildGymCachee(gymId);
results.push({ gymId, status: 'success' }); results.push({ gymId, status: 'success' });
} catch (error) { } catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error); const errorMessage = error instanceof Error ? error.message : String(error);
@ -284,7 +239,7 @@ export const batchRebuildCaches = onCall(
} }
); );
async function rebuildGymCache(gymId: string): Promise<void> { async function rebuildGymCachee(gymId: string): Promise<void> {
try { try {
logger.info(`Starting JSON cache rebuild for gym: ${gymId}`); logger.info(`Starting JSON cache rebuild for gym: ${gymId}`);
@ -302,7 +257,13 @@ async function rebuildGymCache(gymId: string): Promise<void> {
const batch = membershipsSnapshot.docs.slice(i, i + batchSize); const batch = membershipsSnapshot.docs.slice(i, i + batchSize);
const batchPromises = batch.map(async (doc) => { const batchPromises = batch.map(async (doc) => {
try { try {
return await generateCacheEntry(doc.id, doc.data() as MembershipData); const membershipData = doc.data() as MembershipData;
const userId = membershipData.userId;
if (!userId) {
logger.warn(`Skipping member with ID ${doc.id} due to missing userId`);
return null;
}
return await generateCacheEntry(userId, doc.id, membershipData);
} catch (error) { } catch (error) {
logger.error(`Error processing member ${doc.id}:`, error); logger.error(`Error processing member ${doc.id}:`, error);
return null; return null;
@ -343,24 +304,30 @@ async function rebuildGymCache(gymId: string): Promise<void> {
} }
} }
async function generateCacheEntry(membershipId: string, membershipData: MembershipData): Promise<CacheEntry> { async function generateCacheEntry(userId: string, membershipId: string, membershipData: MembershipData): Promise<CacheEntry> {
try { try {
let fields: { [key: string]: string } = {}; let fields: { [key: string]: string } = {};
try { try {
const clientFieldsSnapshot = await app const clientFieldsSnapshot = await app
.firestore() .firestore()
.collection('client_profiles') .collection('client_profiles')
.where('membershipId', '==', membershipId) .where('uid', '==', userId)
.get(); .get();
if (!clientFieldsSnapshot.empty) { if (!clientFieldsSnapshot.empty) {
const fieldDoc = clientFieldsSnapshot.docs[0]; const fieldDoc = clientFieldsSnapshot.docs[0];
const fieldData = fieldDoc.data() as ClientFields; const fieldData = fieldDoc.data() as ClientFields;
fields = fieldData.fields || {}; fields = {
'first-name': fieldData.fields?.['first-name'] || '',
'email': fieldData.fields?.['email'] || '',
'phone-number': fieldData.fields?.['phone-number'] || '',
'alternate-contact': fieldData.fields?.['alternate-contact'] || '',
};
} }
} catch (error) { } catch (error) {
logger.error(`Error getting fields for ${membershipId}:`, error); logger.error(`Error getting fields for user ${userId}:`, error);
} }
let renewalDate: Date | null = null; let renewalDate: Date | null = null;
let daysUntilExpiry: number | null = null; let daysUntilExpiry: number | null = null;
@ -388,7 +355,7 @@ async function generateCacheEntry(membershipId: string, membershipData: Membersh
} }
} }
} catch (error) { } catch (error) {
logger.error(`Error getting renewal date for ${membershipId}:`, error); logger.error(`Error getting renewal date for membership ${membershipId}:`, error);
} }
} }
@ -407,42 +374,31 @@ async function generateCacheEntry(membershipId: string, membershipData: Membersh
.where('membershipId', '==', membershipId) .where('membershipId', '==', membershipId)
.get(); .get();
trainerAssignments = assignmentsSnapshot.docs.map(doc => { assignmentsSnapshot.docs.forEach(doc => {
const data = doc.data() as TrainerAssignment; const data = doc.data() as TrainerAssignment;
return { if (data.timeSlot && Array.isArray(data.timeSlot)) {
id: doc.id, timeSlots.push(...data.timeSlot);
...data, }
createdAt: data.createdAt ? data.createdAt.toDate().toISOString() : null trainerAssignments.push({ id: doc.id });
};
});
const timeSlotsSnapshot = await app
.firestore()
.collection('scheduled_trainings')
.where('membershipId', '==', membershipId)
.get();
timeSlots = timeSlotsSnapshot.docs.map(doc => {
const data = doc.data() as TimeSlot;
return {
id: doc.id,
...data,
startTime: data.startTime ? data.startTime.toDate().toISOString() : null,
endTime: data.endTime ? data.endTime.toDate().toISOString() : null,
createdAt: data.createdAt ? data.createdAt.toDate().toISOString() : null
};
}); });
} catch (error) { } catch (error) {
logger.error(`Error getting trainer data for ${membershipId}:`, error); logger.error(`Error getting trainer data for membership ${membershipId}:`, error);
} }
} }
const cacheEntry: CacheEntry = { const cacheEntry: CacheEntry = {
userId,
membershipId, membershipId,
memberData: { memberData: {
...membershipData, status: membershipData.status,
daysUntilExpiry, subscription: {
hasPersonalTraining: membershipData.subscription?.hasPersonalTraining,
frequency: membershipData.subscription?.frequency,
},
remainingAmount: membershipData.remainingAmount,
isPartialPayment: membershipData.isPartialPayment,
hasPartialPayment, hasPartialPayment,
daysUntilExpiry,
createdAt: membershipData.createdAt ? membershipData.createdAt.toDate().toISOString() : null, createdAt: membershipData.createdAt ? membershipData.createdAt.toDate().toISOString() : null,
updatedAt: membershipData.updatedAt ? membershipData.updatedAt.toDate().toISOString() : null updatedAt: membershipData.updatedAt ? membershipData.updatedAt.toDate().toISOString() : null
}, },
@ -494,7 +450,7 @@ async function rebuildGymCacheFromDeletion(membershipId: string): Promise<void>
if (memberExists) { if (memberExists) {
const gymId = file.name.replace(`${CACHE_FOLDER}/`, '').replace('.json', ''); const gymId = file.name.replace(`${CACHE_FOLDER}/`, '').replace('.json', '');
logger.info(`Rebuilding cache for gym ${gymId} after member ${membershipId} deletion`); logger.info(`Rebuilding cache for gym ${gymId} after member ${membershipId} deletion`);
await rebuildGymCache(gymId); await rebuildGymCachee(gymId);
break; break;
} }
} catch (error) { } catch (error) {