feature/fitlien-828 #118
@ -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,checkExpiredMemberships } from './notifications';
|
export { processNotificationOnCreate, checkExpiredMemberships } 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';
|
||||||
@ -21,3 +21,13 @@ export {
|
|||||||
esslGetUserDetails, esslUpdateUser,
|
esslGetUserDetails, esslUpdateUser,
|
||||||
esslDeleteUser, esslGetEmployeePunchLogs
|
esslDeleteUser, esslGetEmployeePunchLogs
|
||||||
} from './dooraccess';
|
} from './dooraccess';
|
||||||
|
|
||||||
|
// Add member cache functions
|
||||||
|
export {
|
||||||
|
generateMemberCache,
|
||||||
|
updateTrainerAssignmentCache,
|
||||||
|
updateTimeSlotCache,
|
||||||
|
getCachedMembers,
|
||||||
|
rebuildGymCachee,
|
||||||
|
batchRebuildCaches
|
||||||
|
} from './memberCache';
|
||||||
8
functions/src/memberCache/index.ts
Normal file
8
functions/src/memberCache/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export {
|
||||||
|
generateMemberCache,
|
||||||
|
updateTrainerAssignmentCache,
|
||||||
|
updateTimeSlotCache,
|
||||||
|
getCachedMembers,
|
||||||
|
rebuildGymCachee,
|
||||||
|
batchRebuildCaches
|
||||||
|
} from './memberCache';
|
||||||
525
functions/src/memberCache/memberCache.ts
Normal file
525
functions/src/memberCache/memberCache.ts
Normal file
@ -0,0 +1,525 @@
|
|||||||
|
import { onDocumentWritten } from "firebase-functions/v2/firestore";
|
||||||
|
import { onCall, HttpsError } from "firebase-functions/v2/https";
|
||||||
|
import { getLogger, getAdmin } from "../shared/config";
|
||||||
|
import * as admin from "firebase-admin";
|
||||||
|
|
||||||
|
const app = getAdmin();
|
||||||
|
const logger = getLogger();
|
||||||
|
|
||||||
|
const CACHE_FOLDER = 'gym_member_cache';
|
||||||
|
|
||||||
|
interface MembershipData {
|
||||||
|
gymId?: string;
|
||||||
|
status?: string;
|
||||||
|
subscription?: {
|
||||||
|
hasPersonalTraining?: boolean;
|
||||||
|
frequency?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
isPartialPayment?: boolean;
|
||||||
|
remainingAmount?: number;
|
||||||
|
createdAt?: admin.firestore.Timestamp;
|
||||||
|
updatedAt?: admin.firestore.Timestamp;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClientFields {
|
||||||
|
fields?: { [key: string]: string };
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PaymentData {
|
||||||
|
dateTimestamp?: admin.firestore.Timestamp;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TrainerAssignment {
|
||||||
|
id?: string;
|
||||||
|
membershipId?: string;
|
||||||
|
createdAt?: admin.firestore.Timestamp;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TimeSlot {
|
||||||
|
id?: string;
|
||||||
|
membershipId?: string;
|
||||||
|
startTime?: admin.firestore.Timestamp;
|
||||||
|
endTime?: admin.firestore.Timestamp;
|
||||||
|
createdAt?: admin.firestore.Timestamp;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CacheEntry {
|
||||||
|
membershipId: string;
|
||||||
|
memberData: {
|
||||||
|
daysUntilExpiry?: number | null;
|
||||||
|
hasPartialPayment: boolean;
|
||||||
|
createdAt?: string | null;
|
||||||
|
updatedAt?: string | null;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
fields: { [key: string]: string };
|
||||||
|
renewalDate?: string | null;
|
||||||
|
trainerAssignments: any[];
|
||||||
|
timeSlots: any[];
|
||||||
|
lastUpdated: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface JsonCacheData {
|
||||||
|
gymId: string;
|
||||||
|
members: CacheEntry[];
|
||||||
|
totalMembers: number;
|
||||||
|
lastUpdated: string;
|
||||||
|
cacheVersion: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateMemberCache = onDocumentWritten(
|
||||||
|
{
|
||||||
|
region: "#{SERVICES_RGN}#",
|
||||||
|
document: "memberships/{membershipId}",
|
||||||
|
},
|
||||||
|
async (event) => {
|
||||||
|
try {
|
||||||
|
const membershipId = event.params.membershipId;
|
||||||
|
const membershipData = event.data?.after?.exists
|
||||||
|
? event.data.after.data() as MembershipData
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (!membershipData) {
|
||||||
|
await rebuildGymCacheFromDeletion(membershipId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const gymId = membershipData.gymId;
|
||||||
|
if (!gymId) {
|
||||||
|
logger.warn(`No gymId found for membership ${membershipId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await rebuildGymCache(gymId);
|
||||||
|
|
||||||
|
logger.info(`JSON cache updated for gym ${gymId} after member ${membershipId} change`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error updating member cache:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateTrainerAssignmentCache = onDocumentWritten(
|
||||||
|
{
|
||||||
|
region: "#{SERVICES_RGN}#",
|
||||||
|
document: "personal_trainer_assignments/{assignmentId}",
|
||||||
|
},
|
||||||
|
async (event) => {
|
||||||
|
try {
|
||||||
|
const assignmentData = event.data?.after?.exists
|
||||||
|
? event.data.after.data() as TrainerAssignment
|
||||||
|
: null;
|
||||||
|
const oldAssignmentData = event.data?.before?.exists
|
||||||
|
? event.data.before.data() as TrainerAssignment
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const gymIds = new Set<string>();
|
||||||
|
|
||||||
|
if (assignmentData?.membershipId) {
|
||||||
|
const gymId = await getGymIdFromMembershipId(assignmentData.membershipId);
|
||||||
|
if (gymId) gymIds.add(gymId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldAssignmentData?.membershipId) {
|
||||||
|
const gymId = await getGymIdFromMembershipId(oldAssignmentData.membershipId);
|
||||||
|
if (gymId) gymIds.add(gymId);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const gymId of gymIds) {
|
||||||
|
await rebuildGymCache(gymId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gymIds.size > 0) {
|
||||||
|
logger.info(`Updated trainer assignment cache for ${gymIds.size} gym(s)`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error updating trainer assignment cache:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
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(
|
||||||
|
{
|
||||||
|
region: "#{SERVICES_RGN}#",
|
||||||
|
},
|
||||||
|
async (request) => {
|
||||||
|
try {
|
||||||
|
const { gymId, forceRefresh } = request.data;
|
||||||
|
|
||||||
|
if (!gymId) {
|
||||||
|
throw new HttpsError('invalid-argument', 'gymId is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = `${CACHE_FOLDER}/${gymId}.json`;
|
||||||
|
const file = app.storage().bucket().file(fileName);
|
||||||
|
|
||||||
|
let fileExists = false;
|
||||||
|
let fileAge = 0;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [metadata] = await file.getMetadata();
|
||||||
|
fileExists = true;
|
||||||
|
fileAge = Date.now() - new Date(metadata.timeCreated!).getTime();
|
||||||
|
} catch (error) {
|
||||||
|
fileExists = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fiveMinutes = 5 * 60 * 1000;
|
||||||
|
if (forceRefresh || !fileExists || fileAge > fiveMinutes) {
|
||||||
|
logger.info(`Rebuilding cache for gym ${gymId} - forceRefresh: ${forceRefresh}, fileExists: ${fileExists}, fileAge: ${fileAge}ms`);
|
||||||
|
await rebuildGymCache(gymId);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [fileBuffer] = await file.download();
|
||||||
|
const jsonData: JsonCacheData = JSON.parse(fileBuffer.toString());
|
||||||
|
logger.info(`Retrieved ${jsonData.members?.length || 0} members from cache for gym ${gymId}`);
|
||||||
|
return jsonData.members || [];
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error reading cache file for gym ${gymId}:`, error);
|
||||||
|
|
||||||
|
await rebuildGymCache(gymId);
|
||||||
|
const [fileBuffer] = await file.download();
|
||||||
|
const jsonData: JsonCacheData = JSON.parse(fileBuffer.toString());
|
||||||
|
return jsonData.members || [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error getting cached members:', error);
|
||||||
|
throw new HttpsError('internal', 'Error retrieving cached data');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const rebuildGymCachee = onCall(
|
||||||
|
{
|
||||||
|
region: "#{SERVICES_RGN}#",
|
||||||
|
},
|
||||||
|
async (request) => {
|
||||||
|
const { gymId } = request.data;
|
||||||
|
|
||||||
|
if (!gymId) {
|
||||||
|
throw new HttpsError('invalid-argument', 'gymId is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
await rebuildGymCache(gymId);
|
||||||
|
return { success: true, message: `JSON cache rebuilt for gym ${gymId}` };
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const batchRebuildCaches = onCall(
|
||||||
|
{
|
||||||
|
region: "#{SERVICES_RGN}#",
|
||||||
|
},
|
||||||
|
async (request) => {
|
||||||
|
try {
|
||||||
|
const { gymIds } = request.data;
|
||||||
|
|
||||||
|
if (!gymIds || !Array.isArray(gymIds)) {
|
||||||
|
throw new HttpsError('invalid-argument', 'gymIds array is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: Array<{ gymId: string; status: string; error?: string }> = [];
|
||||||
|
|
||||||
|
for (const gymId of gymIds) {
|
||||||
|
try {
|
||||||
|
await rebuildGymCache(gymId);
|
||||||
|
results.push({ gymId, status: 'success' });
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
results.push({ gymId, status: 'error', error: errorMessage });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Batch rebuild completed for ${gymIds.length} gyms`);
|
||||||
|
return { results };
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||||
|
throw new HttpsError('internal', errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
async function rebuildGymCache(gymId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
logger.info(`Starting JSON cache rebuild for gym: ${gymId}`);
|
||||||
|
|
||||||
|
const membershipsSnapshot = await app
|
||||||
|
.firestore()
|
||||||
|
.collection('memberships')
|
||||||
|
.where('gymId', '==', gymId)
|
||||||
|
.orderBy('createdAt', 'desc')
|
||||||
|
.get();
|
||||||
|
|
||||||
|
const members: CacheEntry[] = [];
|
||||||
|
const batchSize = 10;
|
||||||
|
|
||||||
|
for (let i = 0; i < membershipsSnapshot.docs.length; i += batchSize) {
|
||||||
|
const batch = membershipsSnapshot.docs.slice(i, i + batchSize);
|
||||||
|
const batchPromises = batch.map(async (doc) => {
|
||||||
|
try {
|
||||||
|
return await generateCacheEntry(doc.id, doc.data() as MembershipData);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error processing member ${doc.id}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const batchResults = await Promise.all(batchPromises);
|
||||||
|
const validResults = batchResults.filter((member): member is CacheEntry => member !== null);
|
||||||
|
members.push(...validResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonData: JsonCacheData = {
|
||||||
|
gymId,
|
||||||
|
members,
|
||||||
|
totalMembers: members.length,
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
cacheVersion: '1.0'
|
||||||
|
};
|
||||||
|
|
||||||
|
const fileName = `${CACHE_FOLDER}/${gymId}.json`;
|
||||||
|
const file = app.storage().bucket().file(fileName);
|
||||||
|
|
||||||
|
await file.save(JSON.stringify(jsonData, null, 2), {
|
||||||
|
metadata: {
|
||||||
|
contentType: 'application/json',
|
||||||
|
metadata: {
|
||||||
|
gymId: gymId,
|
||||||
|
totalMembers: members.length.toString(),
|
||||||
|
generatedAt: new Date().toISOString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.info(`JSON cache saved for gym ${gymId} with ${members.length} members`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error rebuilding JSON cache for gym ${gymId}:`, error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateCacheEntry(membershipId: string, membershipData: MembershipData): Promise<CacheEntry> {
|
||||||
|
try {
|
||||||
|
let fields: { [key: string]: string } = {};
|
||||||
|
try {
|
||||||
|
const clientFieldsSnapshot = await app
|
||||||
|
.firestore()
|
||||||
|
.collection('client_profiles')
|
||||||
|
.where('membershipId', '==', membershipId)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (!clientFieldsSnapshot.empty) {
|
||||||
|
const fieldDoc = clientFieldsSnapshot.docs[0];
|
||||||
|
const fieldData = fieldDoc.data() as ClientFields;
|
||||||
|
fields = fieldData.fields || {};
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error getting fields for ${membershipId}:`, error);
|
||||||
|
}
|
||||||
|
let renewalDate: Date | null = null;
|
||||||
|
let daysUntilExpiry: number | null = null;
|
||||||
|
|
||||||
|
if (membershipData.subscription) {
|
||||||
|
try {
|
||||||
|
const paymentsSnapshot = await app
|
||||||
|
.firestore()
|
||||||
|
.collection('membership_payments')
|
||||||
|
.where('membershipId', '==', membershipId)
|
||||||
|
.orderBy('dateTimestamp', 'desc')
|
||||||
|
.limit(1)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
if (!paymentsSnapshot.empty) {
|
||||||
|
const latestPayment = paymentsSnapshot.docs[0].data() as PaymentData;
|
||||||
|
if (latestPayment.dateTimestamp) {
|
||||||
|
const paymentDate = latestPayment.dateTimestamp.toDate();
|
||||||
|
renewalDate = calculateRenewalDateTimeFromPayment(membershipData.subscription, paymentDate);
|
||||||
|
|
||||||
|
if (renewalDate && membershipData.status === 'ACTIVE') {
|
||||||
|
const now = new Date();
|
||||||
|
const difference = Math.floor((renewalDate.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
|
||||||
|
daysUntilExpiry = difference > 0 ? difference : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error getting renewal date for ${membershipId}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isPartial = membershipData.isPartialPayment === true;
|
||||||
|
const remaining = membershipData.remainingAmount || 0;
|
||||||
|
const hasPartialPayment = isPartial && remaining > 0;
|
||||||
|
|
||||||
|
let trainerAssignments: any[] = [];
|
||||||
|
let timeSlots: any[] = [];
|
||||||
|
|
||||||
|
if (membershipData.subscription?.hasPersonalTraining) {
|
||||||
|
try {
|
||||||
|
const assignmentsSnapshot = await app
|
||||||
|
.firestore()
|
||||||
|
.collection('personal_trainer_assignments')
|
||||||
|
.where('membershipId', '==', membershipId)
|
||||||
|
.get();
|
||||||
|
|
||||||
|
trainerAssignments = assignmentsSnapshot.docs.map(doc => {
|
||||||
|
const data = doc.data() as TrainerAssignment;
|
||||||
|
return {
|
||||||
|
id: doc.id,
|
||||||
|
...data,
|
||||||
|
createdAt: data.createdAt ? data.createdAt.toDate().toISOString() : null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
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) {
|
||||||
|
logger.error(`Error getting trainer data for ${membershipId}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheEntry: CacheEntry = {
|
||||||
|
membershipId,
|
||||||
|
memberData: {
|
||||||
|
...membershipData,
|
||||||
|
daysUntilExpiry,
|
||||||
|
hasPartialPayment,
|
||||||
|
createdAt: membershipData.createdAt ? membershipData.createdAt.toDate().toISOString() : null,
|
||||||
|
updatedAt: membershipData.updatedAt ? membershipData.updatedAt.toDate().toISOString() : null
|
||||||
|
},
|
||||||
|
fields,
|
||||||
|
renewalDate: renewalDate ? renewalDate.toISOString() : null,
|
||||||
|
trainerAssignments,
|
||||||
|
timeSlots,
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
|
||||||
|
return cacheEntry;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error generating cache entry:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getGymIdFromMembershipId(membershipId: string): Promise<string | null> {
|
||||||
|
try {
|
||||||
|
const membershipDoc = await app.firestore().collection('memberships').doc(membershipId).get();
|
||||||
|
if (membershipDoc.exists) {
|
||||||
|
const data = membershipDoc.data() as MembershipData;
|
||||||
|
return data.gymId || null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error getting gym ID for membership ${membershipId}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function rebuildGymCacheFromDeletion(membershipId: string): Promise<void> {
|
||||||
|
try {
|
||||||
|
const [files] = await app.storage().bucket().getFiles({
|
||||||
|
prefix: `${CACHE_FOLDER}/`,
|
||||||
|
delimiter: '/'
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const file of files) {
|
||||||
|
if (file.name.endsWith('.json')) {
|
||||||
|
try {
|
||||||
|
const [fileBuffer] = await file.download();
|
||||||
|
const jsonData: JsonCacheData = JSON.parse(fileBuffer.toString());
|
||||||
|
|
||||||
|
const memberExists = jsonData.members?.some(member =>
|
||||||
|
member.membershipId === membershipId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (memberExists) {
|
||||||
|
const gymId = file.name.replace(`${CACHE_FOLDER}/`, '').replace('.json', '');
|
||||||
|
logger.info(`Rebuilding cache for gym ${gymId} after member ${membershipId} deletion`);
|
||||||
|
await rebuildGymCache(gymId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error checking cache file ${file.name}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('Error handling membership deletion:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateRenewalDateTimeFromPayment(subscription: { frequency?: string }, paymentDate: Date): Date {
|
||||||
|
const frequency = subscription.frequency || 'Monthly';
|
||||||
|
|
||||||
|
switch (frequency) {
|
||||||
|
case 'Monthly':
|
||||||
|
return new Date(paymentDate.getFullYear(), paymentDate.getMonth() + 1, paymentDate.getDate());
|
||||||
|
case 'Quarterly':
|
||||||
|
return new Date(paymentDate.getFullYear(), paymentDate.getMonth() + 3, paymentDate.getDate());
|
||||||
|
case 'Half-yearly':
|
||||||
|
return new Date(paymentDate.getFullYear(), paymentDate.getMonth() + 6, paymentDate.getDate());
|
||||||
|
case 'Yearly':
|
||||||
|
return new Date(paymentDate.getFullYear() + 1, paymentDate.getMonth(), paymentDate.getDate());
|
||||||
|
default:
|
||||||
|
return new Date(Date.now() + (30 * 24 * 60 * 60 * 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user