Compare commits
29 Commits
main
...
feature/fi
| Author | SHA1 | Date | |
|---|---|---|---|
| 14e52eaa89 | |||
| 5ebd5d770b | |||
| 1896b3fb44 | |||
| 3baa70b0a0 | |||
| 7438431c11 | |||
| 84ab60d425 | |||
| f08bd7648b | |||
| 3cf3a56f4b | |||
| 365b65370f | |||
| 22b55cf660 | |||
| a8cb7dce8c | |||
| 7a6842b023 | |||
| 77cd616810 | |||
| b6d222f0c3 | |||
| 1b20e5efee | |||
| 44fdb9cd4d | |||
| bf94866fbf | |||
| 3b6bcdea9e | |||
| 21664f8ea6 | |||
| d492f660f5 | |||
| 28e9009d13 | |||
| 74e45dffb9 | |||
| 2963b23b61 | |||
| f56132dddd | |||
| 22b2f2adce | |||
| 0679992418 | |||
| b0b7193003 | |||
| 3ce4a9bdcb | |||
| 2ab220d1e0 |
@ -6,18 +6,25 @@ setGlobalOptions({
|
|||||||
timeoutSeconds: 540,
|
timeoutSeconds: 540,
|
||||||
minInstances: 0,
|
minInstances: 0,
|
||||||
maxInstances: 10,
|
maxInstances: 10,
|
||||||
concurrency: 80
|
concurrency: 80,
|
||||||
});
|
});
|
||||||
|
|
||||||
export * from './shared/config';
|
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 * from './payments';
|
|
||||||
export { getPlaceDetails, getPlacesAutocomplete } from './places';
|
|
||||||
export { registerClient } from './users';
|
|
||||||
export {
|
export {
|
||||||
esslGetUserDetails, esslUpdateUser,
|
processNotificationOnCreate,
|
||||||
esslDeleteUser, esslGetEmployeePunchLogs
|
checkExpiredMemberships,
|
||||||
} from './dooraccess';
|
} from "./notifications";
|
||||||
|
export * from "./payments";
|
||||||
|
export { getPlaceDetails, getPlacesAutocomplete } from "./places";
|
||||||
|
export { registerClient } from "./users";
|
||||||
|
export {
|
||||||
|
esslGetUserDetails,
|
||||||
|
esslUpdateUser,
|
||||||
|
esslDeleteUser,
|
||||||
|
esslGetEmployeePunchLogs,
|
||||||
|
} from "./dooraccess";
|
||||||
|
|
||||||
|
export { getMemberCache, updateMemberCache } from "./memberCache";
|
||||||
|
|||||||
1
functions/src/memberCache/index.ts
Normal file
1
functions/src/memberCache/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { getMemberCache, updateMemberCache } from "./memberCache";
|
||||||
446
functions/src/memberCache/memberCache.ts
Normal file
446
functions/src/memberCache/memberCache.ts
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
import { onRequest } 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;
|
||||||
|
userId?: string;
|
||||||
|
status?: string;
|
||||||
|
subscriptionId?: string;
|
||||||
|
subscription?: {
|
||||||
|
hasPersonalTraining?: boolean;
|
||||||
|
frequency?: string;
|
||||||
|
price?: number;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
isPartialPayment?: boolean;
|
||||||
|
remainingAmount?: number;
|
||||||
|
daysUntilExpiry?: number;
|
||||||
|
expirationDate?: admin.firestore.Timestamp;
|
||||||
|
createdAt?: admin.firestore.Timestamp;
|
||||||
|
updatedAt?: admin.firestore.Timestamp;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ClientFields {
|
||||||
|
fields?: { [key: string]: any };
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getMemberCache = onRequest(
|
||||||
|
{
|
||||||
|
region: "#{SERVICES_RGN}#",
|
||||||
|
cors: true,
|
||||||
|
},
|
||||||
|
async (req, res) => {
|
||||||
|
try {
|
||||||
|
if (req.method === "OPTIONS") {
|
||||||
|
res.set("Access-Control-Allow-Origin", "*");
|
||||||
|
res.set("Access-Control-Allow-Methods", "GET, POST");
|
||||||
|
res.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||||
|
res.status(204).send("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set("Access-Control-Allow-Origin", "*");
|
||||||
|
res.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||||
|
|
||||||
|
let requestData: any = {};
|
||||||
|
|
||||||
|
if (req.method === "GET") {
|
||||||
|
requestData = req.query;
|
||||||
|
} else if (req.method === "POST") {
|
||||||
|
requestData = req.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { gymId } = requestData;
|
||||||
|
|
||||||
|
if (!gymId) {
|
||||||
|
res.status(400).json({ error: "gymId is required" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fileName = `${CACHE_FOLDER}/${gymId}.json`;
|
||||||
|
const file = app.storage().bucket().file(fileName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [fileBuffer] = await file.download();
|
||||||
|
const jsonData: MinimalJsonCacheData = JSON.parse(
|
||||||
|
fileBuffer.toString()
|
||||||
|
);
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`Retrieved ${jsonData.totalMembers} members from cache for gym ${gymId}`
|
||||||
|
);
|
||||||
|
res.status(200).json(jsonData);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error reading cache file for gym ${gymId}:`, error);
|
||||||
|
res.status(404).json({
|
||||||
|
error: "Cache not found for this gym. Please update cache first.",
|
||||||
|
gymId,
|
||||||
|
members: [],
|
||||||
|
totalMembers: 0,
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
cacheVersion: "3.1",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error getting member cache:", error);
|
||||||
|
res.status(500).json({ error: "Error retrieving cached data" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const updateMemberCache = onRequest(
|
||||||
|
{
|
||||||
|
region: "#{SERVICES_RGN}#",
|
||||||
|
cors: true,
|
||||||
|
timeoutSeconds: 540,
|
||||||
|
},
|
||||||
|
async (req, res) => {
|
||||||
|
try {
|
||||||
|
if (req.method === "OPTIONS") {
|
||||||
|
res.set("Access-Control-Allow-Origin", "*");
|
||||||
|
res.set("Access-Control-Allow-Methods", "POST");
|
||||||
|
res.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||||
|
res.status(204).send("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.set("Access-Control-Allow-Origin", "*");
|
||||||
|
res.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||||
|
|
||||||
|
if (req.method !== "POST") {
|
||||||
|
res.status(405).json({ error: "Method not allowed. Use POST." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { gymId, membershipIds } = req.body;
|
||||||
|
let incrementalUpdate = req.body.incrementalUpdate || false;
|
||||||
|
|
||||||
|
if (!gymId) {
|
||||||
|
res.status(400).json({ error: "gymId is required" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`Starting cache ${
|
||||||
|
incrementalUpdate ? "incremental update" : "full refresh"
|
||||||
|
} for gym: ${gymId}`
|
||||||
|
);
|
||||||
|
|
||||||
|
let members: MinimalCacheEntry[] = [];
|
||||||
|
let existingData: MinimalJsonCacheData | null = null;
|
||||||
|
|
||||||
|
if (incrementalUpdate) {
|
||||||
|
try {
|
||||||
|
const fileName = `${CACHE_FOLDER}/${gymId}.json`;
|
||||||
|
const file = app.storage().bucket().file(fileName);
|
||||||
|
const [fileBuffer] = await file.download();
|
||||||
|
existingData = JSON.parse(fileBuffer.toString());
|
||||||
|
if (existingData) {
|
||||||
|
members = [...existingData.members];
|
||||||
|
logger.info(
|
||||||
|
`Loaded existing cache with ${members.length} members for incremental update`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.warn(
|
||||||
|
"Existing cache data is invalid, performing full refresh"
|
||||||
|
);
|
||||||
|
incrementalUpdate = false;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(
|
||||||
|
`Could not load existing cache for incremental update, performing full refresh: ${error}`
|
||||||
|
);
|
||||||
|
incrementalUpdate = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (incrementalUpdate && membershipIds && Array.isArray(membershipIds)) {
|
||||||
|
await updateSpecificMembers(gymId, membershipIds, members);
|
||||||
|
} else {
|
||||||
|
members = await fetchAllMinimalMembers(gymId);
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonData: MinimalJsonCacheData = {
|
||||||
|
gymId,
|
||||||
|
members,
|
||||||
|
totalMembers: members.length,
|
||||||
|
lastUpdated: new Date().toISOString(),
|
||||||
|
cacheVersion: "3.1",
|
||||||
|
};
|
||||||
|
|
||||||
|
await saveCacheToStorage(gymId, jsonData);
|
||||||
|
|
||||||
|
const updateType = incrementalUpdate
|
||||||
|
? "incremental update"
|
||||||
|
: "full refresh";
|
||||||
|
logger.info(
|
||||||
|
`Cache ${updateType} completed successfully for gym ${gymId} with ${members.length} members`
|
||||||
|
);
|
||||||
|
|
||||||
|
res.status(200).json({
|
||||||
|
success: true,
|
||||||
|
message: `Cache ${updateType} completed successfully for gym ${gymId}`,
|
||||||
|
totalMembers: members.length,
|
||||||
|
lastUpdated: jsonData.lastUpdated,
|
||||||
|
updateType: updateType,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error updating member cache:", error);
|
||||||
|
res.status(500).json({
|
||||||
|
error: "Error updating cache",
|
||||||
|
details: error instanceof Error ? error.message : String(error),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
async function fetchAllMinimalMembers(
|
||||||
|
gymId: string
|
||||||
|
): Promise<MinimalCacheEntry[]> {
|
||||||
|
const members: MinimalCacheEntry[] = [];
|
||||||
|
|
||||||
|
const membershipsSnapshot = await app
|
||||||
|
.firestore()
|
||||||
|
.collection("memberships")
|
||||||
|
.where("gymId", "==", gymId)
|
||||||
|
.orderBy("createdAt", "desc")
|
||||||
|
.get();
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 generateMinimalCacheEntry(userId, doc.id, membershipData, gymId);
|
||||||
|
} 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 MinimalCacheEntry => member !== null
|
||||||
|
);
|
||||||
|
members.push(...validResults);
|
||||||
|
}
|
||||||
|
|
||||||
|
return members;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateSpecificMembers(
|
||||||
|
gymId: string,
|
||||||
|
membershipIds: string[],
|
||||||
|
existingMembers: MinimalCacheEntry[]
|
||||||
|
): Promise<void> {
|
||||||
|
logger.info(`Updating ${membershipIds.length} specific members`);
|
||||||
|
|
||||||
|
const existingMembersMap = new Map<string, number>();
|
||||||
|
existingMembers.forEach((member, index) => {
|
||||||
|
existingMembersMap.set(member.membershipId, index);
|
||||||
|
});
|
||||||
|
|
||||||
|
const batchSize = 10;
|
||||||
|
for (let i = 0; i < membershipIds.length; i += batchSize) {
|
||||||
|
const batch = membershipIds.slice(i, i + batchSize);
|
||||||
|
|
||||||
|
const membershipDocs = await Promise.all(
|
||||||
|
batch.map(async (membershipId) => {
|
||||||
|
try {
|
||||||
|
const doc = await app
|
||||||
|
.firestore()
|
||||||
|
.collection("memberships")
|
||||||
|
.doc(membershipId)
|
||||||
|
.get();
|
||||||
|
return doc.exists ? { id: doc.id, data: doc.data() } : null;
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error fetching membership ${membershipId}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const batchPromises = membershipDocs
|
||||||
|
.filter((doc): doc is { id: string; data: any } => doc !== null)
|
||||||
|
.map(async (doc) => {
|
||||||
|
try {
|
||||||
|
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 generateMinimalCacheEntry(
|
||||||
|
userId,
|
||||||
|
doc.id,
|
||||||
|
membershipData,
|
||||||
|
gymId
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error processing member ${doc.id}:`, error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const batchResults = await Promise.all(batchPromises);
|
||||||
|
|
||||||
|
batchResults.forEach((updatedMember) => {
|
||||||
|
if (updatedMember) {
|
||||||
|
const existingIndex = existingMembersMap.get(
|
||||||
|
updatedMember.membershipId
|
||||||
|
);
|
||||||
|
if (existingIndex !== undefined) {
|
||||||
|
existingMembers[existingIndex] = updatedMember;
|
||||||
|
} else {
|
||||||
|
existingMembers.push(updatedMember);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveCacheToStorage(
|
||||||
|
gymId: string,
|
||||||
|
jsonData: MinimalJsonCacheData
|
||||||
|
): Promise<void> {
|
||||||
|
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: jsonData.totalMembers.toString(),
|
||||||
|
generatedAt: jsonData.lastUpdated,
|
||||||
|
cacheVersion: jsonData.cacheVersion,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function generateMinimalCacheEntry(
|
||||||
|
userId: string,
|
||||||
|
membershipId: string,
|
||||||
|
membershipData: MembershipData,
|
||||||
|
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() as ClientFields;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,6 +6,40 @@ const app = getAdmin();
|
|||||||
const logger = getLogger();
|
const logger = getLogger();
|
||||||
const kTrainerRole = "Trainer";
|
const kTrainerRole = "Trainer";
|
||||||
|
|
||||||
|
async function updateCacheForMembership(
|
||||||
|
gymId: string,
|
||||||
|
membershipId: string
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://updatemembercache-2k7djjvd3q-el.a.run.app/updateMemberCache`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
gymId: gymId,
|
||||||
|
incrementalUpdate: true,
|
||||||
|
membershipIds: [membershipId],
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
logger.info(
|
||||||
|
`Cache updated successfully for membership ${membershipId} in gym ${gymId}`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
logger.warn(
|
||||||
|
`Cache update failed for membership ${membershipId}: ${response.status}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(`Error updating cache for membership ${membershipId}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface MembershipData {
|
interface MembershipData {
|
||||||
id?: string;
|
id?: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
@ -150,6 +184,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 +219,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 +550,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}`
|
||||||
);
|
);
|
||||||
@ -535,7 +574,6 @@ async function updateDaysUntilExpiryForAllMemberships(): Promise<void> {
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function calculateDaysUntilExpiry(
|
async function calculateDaysUntilExpiry(
|
||||||
membershipId: string,
|
membershipId: string,
|
||||||
data: MembershipData
|
data: MembershipData
|
||||||
@ -665,6 +703,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 +737,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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user