feature/IO-3096-GlobalNotifications - Checkpoint, socket to email to bodyshop mapping.

This commit is contained in:
Dave Richer
2025-02-18 11:02:46 -05:00
parent a15f86cc4e
commit c02c36c548
4 changed files with 123 additions and 128 deletions

View File

@@ -73,7 +73,20 @@ const applyRedisHelpers = ({ pubClient, app, logger }) => {
try {
await pubClient.rpush(`socket:${socketId}:${key}`, JSON.stringify(newItem));
} catch (error) {
logger.log(`Error adding item to the end of the list for socket ${socketId}: ${error}`, "ERROR", "redis");
let userEmail = "unknown";
let socketMappings = {};
try {
const userData = await getSessionData(socketId, "user");
if (userData && userData.email) {
userEmail = userData.email;
socketMappings = await getUserSocketMapping(userEmail);
}
} catch (sessionError) {
logger.log(`Failed to fetch session data for socket ${socketId}: ${sessionError}`, "ERROR", "redis");
}
const mappingString = JSON.stringify(socketMappings, null, 2);
const errorMessage = `Error adding item to the end of the list for socket ${socketId}: ${error}. User: ${userEmail}, Socket Mappings: ${mappingString}`;
logger.log(errorMessage, "ERROR", "redis");
}
};
@@ -121,23 +134,81 @@ const applyRedisHelpers = ({ pubClient, app, logger }) => {
}
};
const addUserSocketMapping = async (email, socketId) => {
// Using a Redis set allows a user to have multiple active socket ids.
console.log(`Adding socket ${socketId} to user ${email}`);
return pubClient.sadd(`user:${email}:sockets`, socketId);
const addUserSocketMapping = async (email, socketId, bodyshopId) => {
const userKey = `user:${email}`;
const bodyshopKey = `${userKey}:bodyshops:${bodyshopId}`;
try {
logger.log(`Adding socket ${socketId} to user ${email} for bodyshop ${bodyshopId}`, "debug", "redis");
// Mark the bodyshop as associated with the user in the hash
await pubClient.hset(userKey, `bodyshops:${bodyshopId}`, "1");
// Add the socket ID to the bodyshop-specific set
await pubClient.sadd(bodyshopKey, socketId);
// Set TTL to 24 hours for both keys
await pubClient.expire(userKey, 86400);
await pubClient.expire(bodyshopKey, 86400);
} catch (error) {
logger.log(`Error adding socket mapping for ${email} (bodyshop ${bodyshopId}): ${error}`, "ERROR", "redis");
}
};
const removeUserSocketMapping = async (email, socketId) => {
console.log(`Removing socket ${socketId} from user ${email}`);
return pubClient.srem(`user:${email}:sockets`, socketId);
const refreshUserSocketTTL = async (email, bodyshopId) => {
const userKey = `user:${email}`;
const bodyshopKey = `${userKey}:bodyshops:${bodyshopId}`;
try {
const userExists = await pubClient.exists(userKey);
if (userExists) {
await pubClient.expire(userKey, 86400);
}
const bodyshopExists = await pubClient.exists(bodyshopKey);
if (bodyshopExists) {
await pubClient.expire(bodyshopKey, 86400);
logger.log(`Refreshed TTL for ${email} bodyshop ${bodyshopId} socket mapping`, "debug", "redis");
}
} catch (error) {
logger.log(`Error refreshing TTL for ${email} (bodyshop ${bodyshopId}): ${error}`, "ERROR", "redis");
}
};
const removeUserSocketMapping = async (email, socketId, bodyshopId) => {
const userKey = `user:${email}`;
const bodyshopKey = `${userKey}:bodyshops:${bodyshopId}`;
try {
logger.log(`Removing socket ${socketId} from user ${email} for bodyshop ${bodyshopId}`, "DEBUG", "redis");
await pubClient.srem(bodyshopKey, socketId);
// Refresh TTL if there are still sockets, or let it expire
const remainingSockets = await pubClient.scard(bodyshopKey);
if (remainingSockets > 0) {
await pubClient.expire(bodyshopKey, 86400);
} else {
// Optionally remove the bodyshop field from the hash if no sockets remain
await pubClient.hdel(userKey, `bodyshops:${bodyshopId}`);
}
// Refresh user key TTL if there are still bodyshops
const remainingBodyshops = await pubClient.hlen(userKey);
if (remainingBodyshops > 0) {
await pubClient.expire(userKey, 86400);
}
} catch (error) {
logger.log(`Error removing socket mapping for ${email} (bodyshop ${bodyshopId}): ${error}`, "ERROR", "redis");
}
};
const getUserSocketMapping = async (email) => {
const key = `user:${email}:sockets`;
const userKey = `user:${email}`;
try {
return await pubClient.smembers(key);
// Get all bodyshop fields from the hash
const bodyshops = await pubClient.hkeys(userKey);
const result = {};
for (const bodyshopField of bodyshops) {
const bodyshopId = bodyshopField.split("bodyshops:")[1];
const bodyshopKey = `${userKey}:bodyshops:${bodyshopId}`;
const socketIds = await pubClient.smembers(bodyshopKey);
const ttl = await pubClient.ttl(bodyshopKey);
result[bodyshopId] = { socketIds, ttl };
}
return result;
} catch (error) {
console.error(`Error retrieving socket IDs for ${email}:`, error);
console.error(`Error retrieving socket mappings for ${email}:`, error);
throw error;
}
};
@@ -157,7 +228,8 @@ const applyRedisHelpers = ({ pubClient, app, logger }) => {
getUsersInRoom,
addUserSocketMapping,
removeUserSocketMapping,
getUserSocketMapping
getUserSocketMapping,
refreshUserSocketTTL
};
Object.assign(module.exports, api);
@@ -167,86 +239,6 @@ const applyRedisHelpers = ({ pubClient, app, logger }) => {
next();
});
// Demo to show how all the helper functions work
// const demoSessionData = async () => {
// const socketId = "testSocketId";
//
// // 1. Test setSessionData and getSessionData
// await setSessionData(socketId, "field1", "Hello, Redis!");
// const field1Value = await getSessionData(socketId, "field1");
// console.log("Retrieved single field value:", field1Value);
//
// // 2. Test setMultipleSessionData and getMultipleSessionData
// await setMultipleSessionData(socketId, { field2: "Second Value", field3: "Third Value" });
// const multipleFields = await getMultipleSessionData(socketId, ["field2", "field3"]);
// console.log("Retrieved multiple field values:", multipleFields);
//
// // 3. Test setMultipleFromArraySessionData
// await setMultipleFromArraySessionData(socketId, [
// ["field4", "Fourth Value"],
// ["field5", "Fifth Value"]
// ]);
//
// // Retrieve and log all fields
// const allFields = await getMultipleSessionData(socketId, ["field1", "field2", "field3", "field4", "field5"]);
// console.log("Retrieved all field values:", allFields);
//
// // 4. Test list functions
// // Add item to the end of a Redis list
// await addItemToEndOfList(socketId, "logEvents", { event: "Log Event 1", timestamp: new Date() });
// await addItemToEndOfList(socketId, "logEvents", { event: "Log Event 2", timestamp: new Date() });
//
// // Add item to the beginning of a Redis list
// await addItemToBeginningOfList(socketId, "logEvents", { event: "First Log Event", timestamp: new Date() });
//
// // Retrieve the entire list
// const logEventsData = await pubClient.lrange(`socket:${socketId}:logEvents`, 0, -1);
// const logEvents = logEventsData.map((item) => JSON.parse(item));
// console.log("Log Events List:", logEvents);
//
// // 5. Test clearList
// await clearList(socketId, "logEvents");
// console.log("Log Events List cleared.");
//
// // Retrieve the list after clearing to confirm it's empty
// const logEventsAfterClear = await pubClient.lrange(`socket:${socketId}:logEvents`, 0, -1);
// console.log("Log Events List after clearing:", logEventsAfterClear); // Should be an empty array
//
// // 6. Test clearSessionData
// await clearSessionData(socketId);
// console.log("Session data cleared.");
//
// // 7. Test room functions
// const roomName = "testRoom";
// const user1 = { id: 1, name: "Alice" };
// const user2 = { id: 2, name: "Bob" };
//
// // Add users to room
// await addUserToRoom(roomName, user1);
// await addUserToRoom(roomName, user2);
//
// // Get users in room
// const usersInRoom = await getUsersInRoom(roomName);
// console.log(`Users in room ${roomName}:`, usersInRoom);
//
// // Remove a user from room
// await removeUserFromRoom(roomName, user1);
//
// // Get users in room after removal
// const usersInRoomAfterRemoval = await getUsersInRoom(roomName);
// console.log(`Users in room ${roomName} after removal:`, usersInRoomAfterRemoval);
//
// // Clean up: remove remaining users from room
// await removeUserFromRoom(roomName, user2);
//
// // Verify room is empty
// const usersInRoomAfterCleanup = await getUsersInRoom(roomName);
// console.log(`Users in room ${roomName} after cleanup:`, usersInRoomAfterCleanup); // Should be empty
// };
// if (process.env.NODE_ENV === "development") {
// demoSessionData();
// }
return api;
};