Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 82 additions & 79 deletions src/groups/membership.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,58 +7,86 @@ const user = require('../user');
const cache = require('../cache');

module.exports = function (Groups) {
Groups.getMembers = async function (groupName, start, stop) {
return await db.getSortedSetRevRange(`group:${groupName}:members`, start, stop);
// CONSTANTS
// Refactored with assistance from OpenAI's GPT-5 (ChatGPT).

const isMemberOfEphemeralGroup = (uid, groupName) =>
(groupName === 'guests' && parseInt(uid, 10) === 0) ||
(groupName === 'spiders' && parseInt(uid, 10) === -1);

const getCached = (key) => {
const value = Groups.cache.get(key);
return value !== undefined ? value : null;
};

const setCached = (key, value) => Groups.cache.set(key, value);

const filterNonCachedUids = (uids, groupName, cachedData) =>
uids.filter((uid) => {
const key = `${uid}:${groupName}`;
const cached = getCached(key);
if (cached !== null) cachedData[key] = cached;
return cached === null;
});

const filterNonCachedGroups = (uid, groups, cachedData) =>
groups.filter((groupName) => {
const key = `${uid}:${groupName}`;
const cached = getCached(key);
if (cached !== null) cachedData[key] = cached;
return cached === null;
});

const getGroupMembers = async (groupNames) => {
return await db.getSortedSetsMembers(groupNames.map(name => `group:${name}:members`));
};

// FUNCTIONS
Groups.getMembers = async (groupName, start, stop) =>
await db.getSortedSetRevRange(`group:${groupName}:members`, start, stop);

Groups.getMemberUsers = async function (groupNames, start, stop) {
async function get(groupName) {
const uids = await Groups.getMembers(groupName, start, stop);
return await user.getUsersFields(uids, ['uid', 'username', 'picture', 'userslug']);
return await user.getUsersWithFields(uids, ['uid', 'username', 'picture', 'userslug']);
}
return await Promise.all(groupNames.map(name => get(name)));
};

Groups.getMembersOfGroups = async function (groupNames) {
return await db.getSortedSetsMembers(groupNames.map(name => `group:${name}:members`));
};
Groups.getMembersOfGroups = async groupNames =>
await getGroupMembers(groupNames);

Groups.isMember = async function (uid, groupName) {
if (!uid || parseInt(uid, 10) <= 0 || !groupName) {
if (!uid || parseInt(uid, 10) <= 0 || !groupName) {
return isMemberOfEphemeralGroup(uid, groupName);
}

const cacheKey = `${uid}:${groupName}`;
let isMember = Groups.cache.get(cacheKey);
if (isMember !== undefined) {
return isMember;
}
let isMember = getCached(cacheKey);
if (isMember !== null) return isMember;

isMember = await db.isSortedSetMember(`group:${groupName}:members`, uid);
Groups.cache.set(cacheKey, isMember);
setCached(cacheKey, isMember);
return isMember;
};

Groups.isMembers = async function (uids, groupName) {
if (!groupName || !uids.length) {
return uids.map(() => false);
}

if (groupName === 'guests' || groupName === 'spiders') {
return uids.map(uid => isMemberOfEphemeralGroup(uid, groupName));
}

const cachedData = {};
const nonCachedUids = uids.filter(uid => filterNonCached(cachedData, uid, groupName));

if (!nonCachedUids.length) {
return uids.map(uid => cachedData[`${uid}:${groupName}`]);
const nonCachedUids = filterNonCachedUids(uids, groupName, cachedData);

if (nonCachedUids.length) {
const isMembers = await db.isSortedSetMembers(`group:${groupName}:members`, nonCachedUids);
nonCachedUids.forEach((uid, i) => {
const key = `${uid}:${groupName}`;
cachedData[key] = isMembers[i];
setCached(key, isMembers[i]);
});
}

const isMembers = await db.isSortedSetMembers(`group:${groupName}:members`, nonCachedUids);
nonCachedUids.forEach((uid, index) => {
cachedData[`${uid}:${groupName}`] = isMembers[index];
Groups.cache.set(`${uid}:${groupName}`, isMembers[index]);
});
return uids.map(uid => cachedData[`${uid}:${groupName}`]);
};

Expand All @@ -67,35 +95,20 @@ module.exports = function (Groups) {
return groups.map(groupName => isMemberOfEphemeralGroup(uid, groupName));
}
const cachedData = {};
const nonCachedGroups = groups.filter(groupName => filterNonCached(cachedData, uid, groupName));

if (!nonCachedGroups.length) {
return groups.map(groupName => cachedData[`${uid}:${groupName}`]);
const nonCachedGroups = filterNonCachedGroups(uid, groups, cachedData);

if (nonCachedGroups.length) {
const sets = nonCachedGroups.map(groupName => `group:${groupName}:members`);
const isMembers = await db.isMemberOfSortedSets(sets, uid);
nonCachedGroups.forEach((groupName, index) => {
const key = `${uid}:${groupName}`;
cachedData[key] = isMembers[index];
setCached(key, isMembers[index]);
});
}
const nonCachedGroupsMemberSets = nonCachedGroups.map(groupName => `group:${groupName}:members`);
const isMembers = await db.isMemberOfSortedSets(nonCachedGroupsMemberSets, uid);
nonCachedGroups.forEach((groupName, index) => {
cachedData[`${uid}:${groupName}`] = isMembers[index];
Groups.cache.set(`${uid}:${groupName}`, isMembers[index]);
});

return groups.map(groupName => cachedData[`${uid}:${groupName}`]);
};

function isMemberOfEphemeralGroup(uid, groupName) {
return (groupName === 'guests' && parseInt(uid, 10) === 0) ||
(groupName === 'spiders' && parseInt(uid, 10) === -1);
}

function filterNonCached(cachedData, uid, groupName) {
const isMember = Groups.cache.get(`${uid}:${groupName}`);
const isInCache = isMember !== undefined;
if (isInCache) {
cachedData[`${uid}:${groupName}`] = isMember;
}
return !isInCache;
}

Groups.isMemberOfAny = async function (uid, groups) {
if (!Array.isArray(groups) || !groups.length) {
return false;
Expand All @@ -104,10 +117,8 @@ module.exports = function (Groups) {
return isMembers.includes(true);
};

Groups.getMemberCount = async function (groupName) {
const count = await db.getObjectField(`group:${groupName}`, 'memberCount');
return parseInt(count, 10);
};
Groups.getMemberCount = async groupName =>
parseInt(await db.getObjectField(`group:${groupName}`, 'memberCount'), 10);

Groups.isMemberOfGroupList = async function (uid, groupListKey) {
let groupNames = await getGroupNames(groupListKey);
Expand All @@ -122,34 +133,28 @@ module.exports = function (Groups) {

Groups.isMemberOfGroupsList = async function (uid, groupListKeys) {
const members = await getGroupNames(groupListKeys);

let uniqueGroups = _.uniq(_.flatten(members));
uniqueGroups = Groups.removeEphemeralGroups(uniqueGroups);


const isMembers = await Groups.isMemberOfGroups(uid, uniqueGroups);
const isGroupMember = _.zipObject(uniqueGroups, isMembers);


return members.map(groupNames => !!groupNames.find(name => isGroupMember[name]));

};

Groups.isMembersOfGroupList = async function (uids, groupListKey) {
const results = uids.map(() => false);

let groupNames = await getGroupNames(groupListKey);
groupNames = Groups.removeEphemeralGroups(groupNames);
if (!groupNames.length) {
return results;
return uids.map(() => false);
}
const isGroupMembers = await Promise.all(groupNames.map(name => Groups.isMembers(uids, name)));

isGroupMembers.forEach((isMembers) => {
results.forEach((isMember, index) => {
if (!isMember && isMembers[index]) {
results[index] = true;
}
});
});
return results;
return uids.map((_, index) => isGroupMembers.some(isMembers => isMembers[index]));
};

async function getGroupNames(keys) {
Expand All @@ -158,23 +163,21 @@ module.exports = function (Groups) {

const cachedData = {};
const nonCachedKeys = keys.filter((groupName) => {
const groupMembers = cache.get(`group:${groupName}:members`);
const isInCache = groupMembers !== undefined;
if (isInCache) {
cachedData[groupName] = groupMembers;
const cached = cache.get(`group:${groupName}:members`);
if (cached !== undefined) {
cachedData[groupName] = cached;
}
return !isInCache;
return cached === undefined;
});

if (!nonCachedKeys.length) {
return isArray ? keys.map(groupName => cachedData[groupName]) : cachedData[keys[0]];
if (nonCachedKeys.length) {
const groupMembers = await getGroupMembers(nonCachedKeys);
nonCachedKeys.forEach((groupName, index) => {
cachedData[groupName] = groupMembers[index];
cache.set(`group:${groupName}:members`, groupMembers[index]);
});
}
const groupMembers = await db.getSortedSetsMembers(nonCachedKeys.map(name => `group:${name}:members`));

nonCachedKeys.forEach((groupName, index) => {
cachedData[groupName] = groupMembers[index];
cache.set(`group:${groupName}:members`, groupMembers[index]);
});
return isArray ? keys.map(groupName => cachedData[groupName]) : cachedData[keys[0]];
}
};
};