From a39ca51e061fcd87ffbea6a077610f08c6e1998e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 18 Jul 2019 19:20:17 -0400 Subject: [PATCH] feat: #7743, groups/index, invite, leave,membership --- src/groups/index.js | 1 + src/groups/invite.js | 105 ++++++++++ src/groups/leave.js | 180 ++++++++--------- src/groups/membership.js | 408 +++++++++------------------------------ 4 files changed, 270 insertions(+), 424 deletions(-) create mode 100644 src/groups/invite.js diff --git a/src/groups/index.js b/src/groups/index.js index 141b1ddaf8..756e0d19ca 100644 --- a/src/groups/index.js +++ b/src/groups/index.js @@ -11,6 +11,7 @@ require('./data')(Groups); require('./create')(Groups); require('./delete')(Groups); require('./update')(Groups); +require('./invite')(Groups); require('./membership')(Groups); require('./ownership')(Groups); require('./search')(Groups); diff --git a/src/groups/invite.js b/src/groups/invite.js new file mode 100644 index 0000000000..364cf0f6b8 --- /dev/null +++ b/src/groups/invite.js @@ -0,0 +1,105 @@ +'use strict'; + +const db = require('../database'); +const user = require('../user'); +const utils = require('../utils'); +const plugins = require('../plugins'); +const notifications = require('../notifications'); + +module.exports = function (Groups) { + Groups.requestMembership = async function (groupName, uid) { + await inviteOrRequestMembership(groupName, uid, 'request'); + const username = await user.getUserField(uid, 'username'); + const [notification, owners] = await Promise.all([ + notifications.create({ + type: 'group-request-membership', + bodyShort: '[[groups:request.notification_title, ' + username + ']]', + bodyLong: '[[groups:request.notification_text, ' + username + ', ' + groupName + ']]', + nid: 'group:' + groupName + ':uid:' + uid + ':request', + path: '/groups/' + utils.slugify(groupName), + from: uid, + }), + Groups.getOwners(groupName), + ]); + + await notifications.push(notification, owners); + }; + + Groups.acceptMembership = async function (groupName, uid) { + await db.setsRemove(['group:' + groupName + ':pending', 'group:' + groupName + ':invited'], uid); + await Groups.join(groupName, uid); + }; + + Groups.rejectMembership = async function (groupNames, uid) { + if (!Array.isArray(groupNames)) { + groupNames = [groupNames]; + } + const sets = []; + groupNames.forEach(function (groupName) { + sets.push('group:' + groupName + ':pending', 'group:' + groupName + ':invited'); + }); + await db.setsRemove(sets, uid); + }; + + Groups.invite = async function (groupName, uid) { + await inviteOrRequestMembership(groupName, uid, 'invite'); + const notification = await notifications.create({ + type: 'group-invite', + bodyShort: '[[groups:invited.notification_title, ' + groupName + ']]', + bodyLong: '', + nid: 'group:' + groupName + ':uid:' + uid + ':invite', + path: '/groups/' + utils.slugify(groupName), + }); + await notifications.push(notification, [uid]); + }; + + async function inviteOrRequestMembership(groupName, uid, type) { + if (!(parseInt(uid, 10) > 0)) { + throw new Error('[[error:not-logged-in]]'); + } + + const [exists, isMember, isPending, isInvited] = await Promise.all([ + Groups.exists(groupName), + Groups.isMember(uid, groupName), + Groups.isPending(uid, groupName), + Groups.isInvited(uid, groupName), + ]); + + if (!exists) { + throw new Error('[[error:no-group]]'); + } else if (isMember || (type === 'invite' && isInvited)) { + return; + } else if (type === 'request' && isPending) { + throw new Error('[[error:group-already-requested]]'); + } + + const set = type === 'invite' ? 'group:' + groupName + ':invited' : 'group:' + groupName + ':pending'; + await db.setAdd(set, uid); + const hookName = type === 'invite' ? 'action:group.inviteMember' : 'action:group.requestMembership'; + plugins.fireHook(hookName, { + groupName: groupName, + uid: uid, + }); + } + + Groups.isInvited = async function (uid, groupName) { + if (!(parseInt(uid, 10) > 0)) { + return false; + } + return await db.isSetMember('group:' + groupName + ':invited', uid); + }; + + Groups.isPending = async function (uid, groupName) { + if (!(parseInt(uid, 10) > 0)) { + return false; + } + return await db.isSetMember('group:' + groupName + ':pending', uid); + }; + + Groups.getPending = async function (groupName) { + if (!groupName) { + return []; + } + return await db.getSetMembers('group:' + groupName + ':pending'); + }; +}; diff --git a/src/groups/leave.js b/src/groups/leave.js index ad5d8565e8..719039ba6b 100644 --- a/src/groups/leave.js +++ b/src/groups/leave.js @@ -1,123 +1,99 @@ 'use strict'; -const async = require('async'); - const db = require('../database'); const user = require('../user'); const plugins = require('../plugins'); module.exports = function (Groups) { - Groups.leave = function (groupNames, uid, callback) { - callback = callback || function () {}; - + Groups.leave = async function (groupNames, uid) { if (Array.isArray(groupNames) && !groupNames.length) { - return setImmediate(callback); + return; } if (!Array.isArray(groupNames)) { groupNames = [groupNames]; } - async.waterfall([ - function (next) { - async.parallel({ - isMembers: async.apply(Groups.isMemberOfGroups, uid, groupNames), - exists: async.apply(Groups.exists, groupNames), - }, next); - }, - function (result, next) { - groupNames = groupNames.filter(function (groupName, index) { - return result.isMembers[index] && result.exists[index]; - }); - - if (!groupNames.length) { - return callback(); - } - - async.parallel([ - async.apply(db.sortedSetRemove, groupNames.map(groupName => 'group:' + groupName + ':members'), uid), - async.apply(db.setRemove, groupNames.map(groupName => 'group:' + groupName + ':owners'), uid), - async.apply(db.decrObjectField, groupNames.map(groupName => 'group:' + groupName), 'memberCount'), - ], next); - }, - function (results, next) { - Groups.clearCache(uid, groupNames); - Groups.getGroupsFields(groupNames, ['name', 'hidden', 'memberCount'], next); - }, - function (groupData, next) { - if (!groupData) { - return callback(); - } - var tasks = []; - - var emptyPrivilegeGroups = groupData.filter(function (groupData) { - return groupData && Groups.isPrivilegeGroup(groupData.name) && groupData.memberCount === 0; - }); - if (emptyPrivilegeGroups.length) { - tasks.push(async.apply(Groups.destroy, emptyPrivilegeGroups)); - } - - var visibleGroups = groupData.filter(groupData => groupData && !groupData.hidden); - if (visibleGroups.length) { - tasks.push(async.apply(db.sortedSetAdd, 'groups:visible:memberCount', visibleGroups.map(groupData => groupData.memberCount), visibleGroups.map(groupData => groupData.name))); - } - - async.parallel(tasks, function (err) { - next(err); - }); - }, - function (next) { - clearGroupTitleIfSet(groupNames, uid, next); - }, - function (next) { - plugins.fireHook('action:group.leave', { - groupNames: groupNames, - uid: uid, - }); - next(); - }, - ], callback); - }; + const [isMembers, exists] = await Promise.all([ + Groups.isMemberOfGroups(uid, groupNames), + Groups.exists(groupNames), + ]); + + const groupsToLeave = groupNames.filter((groupName, index) => isMembers[index] && exists[index]); + if (!groupsToLeave.length) { + return; + } + + await Promise.all([ + db.sortedSetRemove(groupsToLeave.map(groupName => 'group:' + groupName + ':members'), uid), + db.setRemove(groupsToLeave.map(groupName => 'group:' + groupName + ':owners'), uid), + db.decrObjectField(groupsToLeave.map(groupName => 'group:' + groupName), 'memberCount'), + ]); - function clearGroupTitleIfSet(groupNames, uid, callback) { - groupNames = groupNames.filter(function (groupName) { - return groupName !== 'registered-users' && !Groups.isPrivilegeGroup(groupName); + Groups.clearCache(uid, groupsToLeave); + + const groupData = await Groups.getGroupsFields(groupsToLeave, ['name', 'hidden', 'memberCount']); + if (!groupData) { + return; + } + + const emptyPrivilegeGroups = groupData.filter(g => g && Groups.isPrivilegeGroup(g.name) && g.memberCount === 0); + const visibleGroups = groupData.filter(g => g && !g.hidden); + + const promises = []; + if (emptyPrivilegeGroups.length) { + promises.push(Groups.destroy, emptyPrivilegeGroups); + } + if (visibleGroups.length) { + promises.push(db.sortedSetAdd, 'groups:visible:memberCount', + visibleGroups.map(groupData => groupData.memberCount), + visibleGroups.map(groupData => groupData.name) + ); + } + + await Promise.all(promises); + + await clearGroupTitleIfSet(groupsToLeave, uid); + + plugins.fireHook('action:group.leave', { + groupNames: groupsToLeave, + uid: uid, }); + }; + + async function clearGroupTitleIfSet(groupNames, uid) { + groupNames = groupNames.filter(groupName => groupName !== 'registered-users' && !Groups.isPrivilegeGroup(groupName)); if (!groupNames.length) { - return callback(); + return; + } + const userData = await user.getUserData(uid); + if (!userData) { + return; + } + + const newTitleArray = userData.groupTitleArray.filter(groupTitle => !groupNames.includes(groupTitle)); + if (newTitleArray.length) { + await db.setObjectField('user:' + uid, 'groupTitle', JSON.stringify(newTitleArray)); + } else { + await db.deleteObjectField('user:' + uid, 'groupTitle'); } - async.waterfall([ - function (next) { - user.getUserData(uid, next); - }, - function (userData, next) { - var newTitleArray = userData.groupTitleArray.filter(function (groupTitle) { - return !groupNames.includes(groupTitle); - }); - - if (newTitleArray.length) { - db.setObjectField('user:' + uid, 'groupTitle', JSON.stringify(newTitleArray), next); - } else { - db.deleteObjectField('user:' + uid, 'groupTitle', next); - } - }, - ], callback); } - Groups.leaveAllGroups = function (uid, callback) { - async.waterfall([ - function (next) { - db.getSortedSetRange('groups:createtime', 0, -1, next); - }, - function (groups, next) { - async.parallel([ - function (next) { - Groups.leave(groups, uid, next); - }, - function (next) { - Groups.rejectMembership(groups, uid, next); - }, - ], next); - }, - ], callback); + Groups.leaveAllGroups = async function (uid) { + const groups = await db.getSortedSetRange('groups:createtime', 0, -1); + await Promise.all([ + Groups.leave(groups, uid), + Groups.rejectMembership(groups, uid), + ]); + }; + + Groups.kick = async function (uid, groupName, isOwner) { + if (isOwner) { + // If the owners set only contains one member, error out! + const numOwners = await db.setCount('group:' + groupName + ':owners'); + if (numOwners <= 1) { + throw new Error('[[error:group-needs-owner]]'); + } + } + await Groups.leave(groupName, uid); }; }; diff --git a/src/groups/membership.js b/src/groups/membership.js index 910601a4db..a746148c15 100644 --- a/src/groups/membership.js +++ b/src/groups/membership.js @@ -1,234 +1,91 @@ 'use strict'; -var async = require('async'); -var _ = require('lodash'); +const _ = require('lodash'); -var user = require('../user'); -var utils = require('../utils'); -var plugins = require('../plugins'); -var notifications = require('../notifications'); -var db = require('../database'); +const db = require('../database'); +const user = require('../user'); module.exports = function (Groups) { - Groups.requestMembership = function (groupName, uid, callback) { - async.waterfall([ - async.apply(inviteOrRequestMembership, groupName, uid, 'request'), - function (next) { - user.getUserField(uid, 'username', next); - }, - function (username, next) { - async.parallel({ - notification: function (next) { - notifications.create({ - type: 'group-request-membership', - bodyShort: '[[groups:request.notification_title, ' + username + ']]', - bodyLong: '[[groups:request.notification_text, ' + username + ', ' + groupName + ']]', - nid: 'group:' + groupName + ':uid:' + uid + ':request', - path: '/groups/' + utils.slugify(groupName), - from: uid, - }, next); - }, - owners: function (next) { - Groups.getOwners(groupName, next); - }, - }, next); - }, - function (results, next) { - if (!results.notification || !results.owners.length) { - return next(); - } - notifications.push(results.notification, results.owners, next); - }, - ], callback); - }; - - Groups.acceptMembership = function (groupName, uid, callback) { - async.waterfall([ - async.apply(db.setsRemove, ['group:' + groupName + ':pending', 'group:' + groupName + ':invited'], uid), - async.apply(Groups.join, groupName, uid), - ], callback); + Groups.getMembers = async function (groupName, start, stop) { + return await db.getSortedSetRevRange('group:' + groupName + ':members', start, stop); }; - Groups.rejectMembership = function (groupNames, uid, callback) { - if (!Array.isArray(groupNames)) { - groupNames = [groupNames]; + 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']); } - var sets = []; - groupNames.forEach(function (groupName) { - sets.push('group:' + groupName + ':pending', 'group:' + groupName + ':invited'); - }); - - db.setsRemove(sets, uid, callback); + return await Promise.all(groupNames.map(name => get(name))); }; - Groups.invite = function (groupName, uid, callback) { - async.waterfall([ - async.apply(inviteOrRequestMembership, groupName, uid, 'invite'), - async.apply(notifications.create, { - type: 'group-invite', - bodyShort: '[[groups:invited.notification_title, ' + groupName + ']]', - bodyLong: '', - nid: 'group:' + groupName + ':uid:' + uid + ':invite', - path: '/groups/' + utils.slugify(groupName), - }), - function (notification, next) { - notifications.push(notification, [uid], next); - }, - ], callback); + Groups.getMembersOfGroups = async function (groupNames) { + return await db.getSortedSetsMembers(groupNames.map(name => 'group:' + name + ':members')); }; - function inviteOrRequestMembership(groupName, uid, type, callback) { - if (!(parseInt(uid, 10) > 0)) { - return callback(new Error('[[error:not-logged-in]]')); - } - var hookName = type === 'invite' ? 'action:group.inviteMember' : 'action:group.requestMembership'; - var set = type === 'invite' ? 'group:' + groupName + ':invited' : 'group:' + groupName + ':pending'; - - async.waterfall([ - function (next) { - async.parallel({ - exists: async.apply(Groups.exists, groupName), - isMember: async.apply(Groups.isMember, uid, groupName), - isPending: async.apply(Groups.isPending, uid, groupName), - isInvited: async.apply(Groups.isInvited, uid, groupName), - }, next); - }, - function (checks, next) { - if (!checks.exists) { - return next(new Error('[[error:no-group]]')); - } else if (checks.isMember) { - return callback(); - } else if (type === 'invite' && checks.isInvited) { - return callback(); - } else if (type === 'request' && checks.isPending) { - return next(new Error('[[error:group-already-requested]]')); - } - - db.setAdd(set, uid, next); - }, - function (next) { - plugins.fireHook(hookName, { - groupName: groupName, - uid: uid, - }); - next(); - }, - ], callback); - } - - Groups.getMembers = function (groupName, start, stop, callback) { - db.getSortedSetRevRange('group:' + groupName + ':members', start, stop, callback); - }; - - Groups.getMemberUsers = function (groupNames, start, stop, callback) { - async.map(groupNames, function (groupName, next) { - async.waterfall([ - function (next) { - Groups.getMembers(groupName, start, stop, next); - }, - function (uids, next) { - user.getUsersFields(uids, ['uid', 'username', 'picture', 'userslug'], next); - }, - ], next); - }, callback); - }; - - Groups.getMembersOfGroups = function (groupNames, callback) { - db.getSortedSetsMembers(groupNames.map(name => 'group:' + name + ':members'), callback); - }; - - Groups.isMember = function (uid, groupName, callback) { + Groups.isMember = async function (uid, groupName) { if (!uid || parseInt(uid, 10) <= 0 || !groupName) { - return setImmediate(callback, null, false); + return false; } - var cacheKey = uid + ':' + groupName; - var isMember = Groups.cache.get(cacheKey); + const cacheKey = uid + ':' + groupName; + let isMember = Groups.cache.get(cacheKey); if (isMember !== undefined) { Groups.cache.hits += 1; - return setImmediate(callback, null, isMember); + return isMember; } Groups.cache.misses += 1; - async.waterfall([ - function (next) { - db.isSortedSetMember('group:' + groupName + ':members', uid, next); - }, - function (isMember, next) { - Groups.cache.set(cacheKey, isMember); - next(null, isMember); - }, - ], callback); + isMember = await db.isSortedSetMember('group:' + groupName + ':members', uid); + Groups.cache.set(cacheKey, isMember); + return isMember; }; - Groups.isMembers = function (uids, groupName, callback) { - var cachedData = {}; - function getFromCache(next) { - setImmediate(next, null, uids.map(uid => cachedData[uid + ':' + groupName])); - } + Groups.isMembers = async function (uids, groupName) { if (!groupName || !uids.length) { - return setImmediate(callback, null, uids.map(() => false)); + return uids.map(() => false); } if (groupName === 'guests') { - return setImmediate(callback, null, uids.map(uid => parseInt(uid, 10) === 0)); + return uids.map(uid => parseInt(uid, 10) === 0); } - var nonCachedUids = uids.filter(uid => filterNonCached(cachedData, uid, groupName)); + const cachedData = {}; + const nonCachedUids = uids.filter(uid => filterNonCached(cachedData, uid, groupName)); if (!nonCachedUids.length) { - return getFromCache(callback); + return uids.map(uid => cachedData[uid + ':' + groupName]); } - async.waterfall([ - function (next) { - db.isSortedSetMembers('group:' + groupName + ':members', nonCachedUids, next); - }, - function (isMembers, next) { - nonCachedUids.forEach(function (uid, index) { - cachedData[uid + ':' + groupName] = isMembers[index]; - Groups.cache.set(uid + ':' + groupName, isMembers[index]); - }); - - getFromCache(next); - }, - ], callback); + const isMembers = await db.isSortedSetMembers('group:' + groupName + ':members', nonCachedUids); + nonCachedUids.forEach(function (uid, index) { + cachedData[uid + ':' + groupName] = isMembers[index]; + Groups.cache.set(uid + ':' + groupName, isMembers[index]); + }); + return uids.map(uid => cachedData[uid + ':' + groupName]); }; - Groups.isMemberOfGroups = function (uid, groups, callback) { - var cachedData = {}; - function getFromCache(next) { - setImmediate(next, null, groups.map(groupName => cachedData[uid + ':' + groupName])); - } - + Groups.isMemberOfGroups = async function (uid, groups) { if (!uid || parseInt(uid, 10) <= 0 || !groups.length) { - return callback(null, groups.map(groupName => groupName === 'guests')); + return groups.map(groupName => groupName === 'guests'); } - - var nonCachedGroups = groups.filter(groupName => filterNonCached(cachedData, uid, groupName)); + const cachedData = {}; + const nonCachedGroups = groups.filter(groupName => filterNonCached(cachedData, uid, groupName)); if (!nonCachedGroups.length) { - return getFromCache(callback); + return groups.map(groupName => cachedData[uid + ':' + groupName]); } + const nonCachedGroupsMemberSets = nonCachedGroups.map(groupName => 'group:' + groupName + ':members'); + const isMembers = await db.isMemberOfSortedSets(nonCachedGroupsMemberSets, uid); + nonCachedGroups.forEach(function (groupName, index) { + cachedData[uid + ':' + groupName] = isMembers[index]; + Groups.cache.set(uid + ':' + groupName, isMembers[index]); + }); - async.waterfall([ - function (next) { - const nonCachedGroupsMemberSets = nonCachedGroups.map(groupName => 'group:' + groupName + ':members'); - db.isMemberOfSortedSets(nonCachedGroupsMemberSets, uid, next); - }, - function (isMembers, next) { - nonCachedGroups.forEach(function (groupName, index) { - cachedData[uid + ':' + groupName] = isMembers[index]; - Groups.cache.set(uid + ':' + groupName, isMembers[index]); - }); - - getFromCache(next); - }, - ], callback); + return groups.map(groupName => cachedData[uid + ':' + groupName]); }; function filterNonCached(cachedData, uid, groupName) { - var isMember = Groups.cache.get(uid + ':' + groupName); - var isInCache = isMember !== undefined; + const isMember = Groups.cache.get(uid + ':' + groupName); + const isInCache = isMember !== undefined; if (isInCache) { Groups.cache.hits += 1; cachedData[uid + ':' + groupName] = isMember; @@ -238,155 +95,62 @@ module.exports = function (Groups) { return !isInCache; } - Groups.isMemberOfAny = function (uid, groups, callback) { + Groups.isMemberOfAny = async function (uid, groups) { if (!groups.length) { - return setImmediate(callback, null, false); + return false; } - async.waterfall([ - function (next) { - Groups.isMemberOfGroups(uid, groups, next); - }, - function (isMembers, next) { - next(null, isMembers.some(isMember => !!isMember)); - }, - ], callback); - }; - - Groups.getMemberCount = function (groupName, callback) { - async.waterfall([ - function (next) { - db.getObjectField('group:' + groupName, 'memberCount', next); - }, - function (count, next) { - next(null, parseInt(count, 10)); - }, - ], callback); + const isMembers = await Groups.isMemberOfGroups(uid, groups); + return isMembers.includes(true); }; - Groups.isMemberOfGroupList = function (uid, groupListKey, callback) { - async.waterfall([ - function (next) { - db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, next); - }, - function (groupNames, next) { - groupNames = Groups.removeEphemeralGroups(groupNames); - if (!groupNames.length) { - return callback(null, false); - } - - Groups.isMemberOfGroups(uid, groupNames, next); - }, - function (isMembers, next) { - next(null, isMembers.includes(true)); - }, - ], callback); + Groups.getMemberCount = async function (groupName) { + const count = await db.getObjectField('group:' + groupName, 'memberCount'); + return parseInt(count, 10); }; - Groups.isMemberOfGroupsList = function (uid, groupListKeys, callback) { - var uniqueGroups; - var members; - async.waterfall([ - function (next) { - const sets = groupListKeys.map(groupName => 'group:' + groupName + ':members'); - db.getSortedSetsMembers(sets, next); - }, - function (_members, next) { - members = _members; - uniqueGroups = _.uniq(_.flatten(members)); - uniqueGroups = Groups.removeEphemeralGroups(uniqueGroups); - - Groups.isMemberOfGroups(uid, uniqueGroups, next); - }, - function (isMembers, next) { - var map = {}; - - uniqueGroups.forEach(function (groupName, index) { - map[groupName] = isMembers[index]; - }); - - var result = members.map(function (groupNames) { - for (var i = 0; i < groupNames.length; i += 1) { - if (map[groupNames[i]]) { - return true; - } - } - return false; - }); + Groups.isMemberOfGroupList = async function (uid, groupListKey) { + let groupNames = await db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1); + groupNames = Groups.removeEphemeralGroups(groupNames); + if (!groupNames.length) { + return false; + } - next(null, result); - }, - ], callback); + const isMembers = await Groups.isMemberOfGroups(uid, groupNames); + return isMembers.includes(true); }; - Groups.isMembersOfGroupList = function (uids, groupListKey, callback) { - var groupNames; - var results = uids.map(() => false); - - async.waterfall([ - function (next) { - db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, next); - }, - function (_groupNames, next) { - groupNames = Groups.removeEphemeralGroups(_groupNames); + Groups.isMemberOfGroupsList = async function (uid, groupListKeys) { + const sets = groupListKeys.map(groupName => 'group:' + groupName + ':members'); + const members = await db.getSortedSetsMembers(sets); - if (groupNames.length === 0) { - return callback(null, results); - } + let uniqueGroups = _.uniq(_.flatten(members)); + uniqueGroups = Groups.removeEphemeralGroups(uniqueGroups); - async.map(groupNames, function (groupName, next) { - Groups.isMembers(uids, groupName, next); - }, next); - }, - function (isGroupMembers, next) { - isGroupMembers.forEach(function (isMembers) { - results.forEach(function (isMember, index) { - if (!isMember && isMembers[index]) { - results[index] = true; - } - }); - }); - next(null, results); - }, - ], callback); - }; + const isMembers = await Groups.isMemberOfGroups(uid, uniqueGroups); + const isGroupMember = _.zipObject(uniqueGroups, isMembers); - Groups.isInvited = function (uid, groupName, callback) { - if (!(parseInt(uid, 10) > 0)) { - return setImmediate(callback, null, false); - } - db.isSetMember('group:' + groupName + ':invited', uid, callback); + return members.map(function (groupNames) { + return !!groupNames.find(name => isGroupMember[name]); + }); }; - Groups.isPending = function (uid, groupName, callback) { - if (!(parseInt(uid, 10) > 0)) { - return setImmediate(callback, null, false); - } - db.isSetMember('group:' + groupName + ':pending', uid, callback); - }; + Groups.isMembersOfGroupList = async function (uids, groupListKey) { + const results = uids.map(() => false); - Groups.getPending = function (groupName, callback) { - if (!groupName) { - return setImmediate(callback, null, []); + let groupNames = await db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1); + groupNames = Groups.removeEphemeralGroups(groupNames); + if (!groupNames.length) { + return results; } - db.getSetMembers('group:' + groupName + ':pending', callback); - }; + const isGroupMembers = await Promise.all(groupNames.map(name => Groups.isMembers(uids, name))); - Groups.kick = function (uid, groupName, isOwner, callback) { - if (isOwner) { - // If the owners set only contains one member, error out! - async.waterfall([ - function (next) { - db.setCount('group:' + groupName + ':owners', next); - }, - function (numOwners, next) { - if (numOwners <= 1) { - return next(new Error('[[error:group-needs-owner]]')); - } - Groups.leave(groupName, uid, next); - }, - ], callback); - } else { - Groups.leave(groupName, uid, callback); - } + isGroupMembers.forEach(function (isMembers) { + results.forEach(function (isMember, index) { + if (!isMember && isMembers[index]) { + results[index] = true; + } + }); + }); + return results; }; };