From d5342a40ba800e266fa5dc1b8a9d1e239386e328 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 17:11:59 -0400 Subject: [PATCH] feat: #7743,groups/index,join --- src/groups/index.js | 317 ++++++++++++++++---------------------------- src/groups/join.js | 149 +++++++++------------ 2 files changed, 176 insertions(+), 290 deletions(-) diff --git a/src/groups/index.js b/src/groups/index.js index 4a19b4e0d3..141b1ddaf8 100644 --- a/src/groups/index.js +++ b/src/groups/index.js @@ -1,13 +1,11 @@ 'use strict'; -var async = require('async'); +const user = require('../user'); +const db = require('../database'); +const plugins = require('../plugins'); +const utils = require('../utils'); -var user = require('../user'); -var db = require('../database'); -var plugins = require('../plugins'); -var utils = require('../utils'); - -var Groups = module.exports; +const Groups = module.exports; require('./data')(Groups); require('./create')(Groups); @@ -52,237 +50,146 @@ Groups.isPrivilegeGroup = function (groupName) { return isPrivilegeGroupRegex.test(groupName); }; -Groups.getGroupsFromSet = function (set, uid, start, stop, callback) { - async.waterfall([ - function (next) { - if (set === 'groups:visible:name') { - db.getSortedSetRangeByLex(set, '-', '+', start, stop - start + 1, next); - } else { - db.getSortedSetRevRange(set, start, stop, next); - } - }, - function (groupNames, next) { - if (set === 'groups:visible:name') { - groupNames = groupNames.map(function (name) { - return name.split(':')[1]; - }); - } +Groups.getGroupsFromSet = async function (set, uid, start, stop) { + let groupNames; + if (set === 'groups:visible:name') { + groupNames = await db.getSortedSetRangeByLex(set, '-', '+', start, stop - start + 1); + } else { + groupNames = await db.getSortedSetRevRange(set, start, stop); + } + if (set === 'groups:visible:name') { + groupNames = groupNames.map(name => name.split(':')[1]); + } - Groups.getGroupsAndMembers(groupNames, next); - }, - ], callback); + return await Groups.getGroupsAndMembers(groupNames); }; -Groups.getNonPrivilegeGroups = function (set, start, stop, callback) { - async.waterfall([ - function (next) { - db.getSortedSetRevRange(set, start, stop, next); - }, - function (groupNames, next) { - groupNames = groupNames.concat(Groups.ephemeralGroups).filter(groupName => !Groups.isPrivilegeGroup(groupName)); - Groups.getGroupsData(groupNames, next); - }, - ], callback); +Groups.getNonPrivilegeGroups = async function (set, start, stop) { + let groupNames = await db.getSortedSetRevRange(set, start, stop); + groupNames = groupNames.concat(Groups.ephemeralGroups).filter(groupName => !Groups.isPrivilegeGroup(groupName)); + return await Groups.getGroupsData(groupNames); }; -Groups.getGroups = function (set, start, stop, callback) { - db.getSortedSetRevRange(set, start, stop, callback); +Groups.getGroups = async function (set, start, stop) { + return await db.getSortedSetRevRange(set, start, stop); }; -Groups.getGroupsAndMembers = function (groupNames, callback) { - async.waterfall([ - function (next) { - async.parallel({ - groups: function (next) { - Groups.getGroupsData(groupNames, next); - }, - members: function (next) { - Groups.getMemberUsers(groupNames, 0, 3, next); - }, - }, next); - }, - function (data, next) { - data.groups.forEach(function (group, index) { - if (group) { - group.members = data.members[index] || []; - group.truncated = group.memberCount > group.members.length; - } - }); - next(null, data.groups); - }, - ], callback); +Groups.getGroupsAndMembers = async function (groupNames) { + const [groups, members] = await Promise.all([ + Groups.getGroupsData(groupNames), + Groups.getMemberUsers(groupNames, 0, 3), + ]); + groups.forEach(function (group, index) { + if (group) { + group.members = members[index] || []; + group.truncated = group.memberCount > group.members.length; + } + }); + return groups; }; -Groups.get = function (groupName, options, callback) { +Groups.get = async function (groupName, options) { if (!groupName) { - return callback(new Error('[[error:invalid-group]]')); + throw new Error('[[error:invalid-group]]'); } - var stop = -1; + let stop = -1; - var results; - async.waterfall([ - function (next) { - async.parallel({ - base: function (next) { - Groups.getGroupData(groupName, next); - }, - members: function (next) { - if (options.truncateUserList) { - stop = (parseInt(options.userListCount, 10) || 4) - 1; - } - - Groups.getOwnersAndMembers(groupName, options.uid, 0, stop, next); - }, - pending: function (next) { - Groups.getUsersFromSet('group:' + groupName + ':pending', ['username', 'userslug', 'picture'], next); - }, - invited: function (next) { - Groups.getUsersFromSet('group:' + groupName + ':invited', ['username', 'userslug', 'picture'], next); - }, - isMember: async.apply(Groups.isMember, options.uid, groupName), - isPending: async.apply(Groups.isPending, options.uid, groupName), - isInvited: async.apply(Groups.isInvited, options.uid, groupName), - isOwner: async.apply(Groups.ownership.isOwner, options.uid, groupName), - }, next); - }, - function (_results, next) { - results = _results; - if (!results.base) { - return callback(null, null); - } - plugins.fireHook('filter:parse.raw', results.base.description, next); - }, - function (descriptionParsed, next) { - var groupData = results.base; - - groupData.descriptionParsed = descriptionParsed; - groupData.members = results.members; - groupData.membersNextStart = stop + 1; - groupData.pending = results.pending.filter(Boolean); - groupData.invited = results.invited.filter(Boolean); - - groupData.isMember = results.isMember; - groupData.isPending = results.isPending; - groupData.isInvited = results.isInvited; - groupData.isOwner = results.isOwner; - - plugins.fireHook('filter:group.get', { group: groupData }, next); - }, - function (results, next) { - next(null, results.group); - }, - ], callback); -}; - -Groups.getOwners = function (groupName, callback) { - db.getSetMembers('group:' + groupName + ':owners', callback); -}; - -Groups.getOwnersAndMembers = function (groupName, uid, start, stop, callback) { - async.waterfall([ - function (next) { - async.parallel({ - owners: function (next) { - async.waterfall([ - function (next) { - db.getSetMembers('group:' + groupName + ':owners', next); - }, - function (uids, next) { - user.getUsers(uids, uid, next); - }, - ], next); - }, - members: function (next) { - user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop, next); - }, - }, next); - }, - function (results, next) { - var ownerUids = []; - results.owners.forEach(function (user) { - if (user) { - user.isOwner = true; - ownerUids.push(user.uid.toString()); - } - }); + if (options.truncateUserList) { + stop = (parseInt(options.userListCount, 10) || 4) - 1; + } - results.members = results.members.filter(function (user) { - return user && user.uid && !ownerUids.includes(user.uid.toString()); - }); - results.members = results.owners.concat(results.members); + const [groupData, members, pending, invited, isMember, isPending, isInvited, isOwner] = await Promise.all([ + Groups.getGroupData(groupName), + Groups.getOwnersAndMembers(groupName, options.uid, 0, stop), + Groups.getUsersFromSet('group:' + groupName + ':pending', ['username', 'userslug', 'picture']), + Groups.getUsersFromSet('group:' + groupName + ':invited', ['username', 'userslug', 'picture']), + Groups.isMember(options.uid, groupName), + Groups.isPending(options.uid, groupName), + Groups.isInvited(options.uid, groupName), + Groups.ownership.isOwner(options.uid, groupName), + ]); + + if (!groupData) { + return null; + } + const descriptionParsed = await plugins.fireHook('filter:parse.raw', groupData.description); + groupData.descriptionParsed = descriptionParsed; + groupData.members = members; + groupData.membersNextStart = stop + 1; + groupData.pending = pending.filter(Boolean); + groupData.invited = invited.filter(Boolean); + groupData.isMember = isMember; + groupData.isPending = isPending; + groupData.isInvited = isInvited; + groupData.isOwner = isOwner; + const results = await plugins.fireHook('filter:group.get', { group: groupData }); + return results.group; +}; + +Groups.getOwners = async function (groupName) { + return await db.getSetMembers('group:' + groupName + ':owners'); +}; + +Groups.getOwnersAndMembers = async function (groupName, uid, start, stop) { + const ownerUids = await db.getSetMembers('group:' + groupName + ':owners'); + const [owners, members] = await Promise.all([ + user.getUsers(ownerUids, uid), + user.getUsersFromSet('group:' + groupName + ':members', uid, start, stop), + ]); + owners.forEach(function (user) { + if (user) { + user.isOwner = true; + } + }); - next(null, results.members); - }, - ], callback); + const nonOwners = members.filter(user => user && user.uid && !ownerUids.includes(user.uid.toString())); + return owners.concat(nonOwners); }; -Groups.getByGroupslug = function (slug, options, callback) { - async.waterfall([ - function (next) { - db.getObjectField('groupslug:groupname', slug, next); - }, - function (groupName, next) { - if (!groupName) { - return next(new Error('[[error:no-group]]')); - } - Groups.get(groupName, options, next); - }, - ], callback); +Groups.getByGroupslug = async function (slug, options) { + const groupName = await db.getObjectField('groupslug:groupname', slug); + if (!groupName) { + throw new Error('[[error:no-group]]'); + } + return await Groups.get(groupName, options); }; -Groups.getGroupNameByGroupSlug = function (slug, callback) { - db.getObjectField('groupslug:groupname', slug, callback); +Groups.getGroupNameByGroupSlug = async function (slug) { + return await db.getObjectField('groupslug:groupname', slug); }; -Groups.isPrivate = function (groupName, callback) { - isFieldOn(groupName, 'private', callback); +Groups.isPrivate = async function (groupName) { + return await isFieldOn(groupName, 'private'); }; -Groups.isHidden = function (groupName, callback) { - isFieldOn(groupName, 'hidden', callback); +Groups.isHidden = async function (groupName) { + return await isFieldOn(groupName, 'hidden'); }; -function isFieldOn(groupName, field, callback) { - async.waterfall([ - function (next) { - db.getObjectField('group:' + groupName, field, next); - }, - function (value, next) { - next(null, parseInt(value, 10) === 1); - }, - ], callback); +async function isFieldOn(groupName, field) { + const value = await db.getObjectField('group:' + groupName, field); + return parseInt(value, 10) === 1; } -Groups.exists = function (name, callback) { +Groups.exists = async function (name) { if (Array.isArray(name)) { - var slugs = name.map(groupName => utils.slugify(groupName)); - async.waterfall([ - async.apply(db.isSortedSetMembers, 'groups:createtime', name), - function (isMembersOfRealGroups, next) { - const isMembersOfEphemeralGroups = slugs.map(slug => Groups.ephemeralGroups.includes(slug)); - const exists = name.map((n, index) => isMembersOfRealGroups[index] || isMembersOfEphemeralGroups[index]); - next(null, exists); - }, - ], callback); - } else { - var slug = utils.slugify(name); - async.waterfall([ - async.apply(db.isSortedSetMember, 'groups:createtime', name), - function (isMemberOfRealGroups, next) { - const isMemberOfEphemeralGroups = Groups.ephemeralGroups.includes(slug); - next(null, isMemberOfRealGroups || isMemberOfEphemeralGroups); - }, - ], callback); + const slugs = name.map(groupName => utils.slugify(groupName)); + const isMembersOfRealGroups = await db.isSortedSetMembers('groups:createtime', name); + const isMembersOfEphemeralGroups = slugs.map(slug => Groups.ephemeralGroups.includes(slug)); + return name.map((n, index) => isMembersOfRealGroups[index] || isMembersOfEphemeralGroups[index]); } + const slug = utils.slugify(name); + const isMemberOfRealGroups = await db.isSortedSetMember('groups:createtime', name); + const isMemberOfEphemeralGroups = Groups.ephemeralGroups.includes(slug); + return isMemberOfRealGroups || isMemberOfEphemeralGroups; }; -Groups.existsBySlug = function (slug, callback) { +Groups.existsBySlug = async function (slug) { if (Array.isArray(slug)) { - db.isObjectFields('groupslug:groupname', slug, callback); - } else { - db.isObjectField('groupslug:groupname', slug, callback); + return await db.isObjectFields('groupslug:groupname', slug); } + return await db.isObjectField('groupslug:groupname', slug); }; Groups.async = require('../promisify')(Groups); diff --git a/src/groups/join.js b/src/groups/join.js index 4b553ba22d..a7b5e18351 100644 --- a/src/groups/join.js +++ b/src/groups/join.js @@ -8,117 +8,96 @@ const user = require('../user'); const plugins = require('../plugins'); module.exports = function (Groups) { - Groups.join = function (groupNames, uid, callback) { - callback = callback || function () {}; - + Groups.join = async function (groupNames, uid) { if (!groupNames) { - return callback(new Error('[[error:invalid-data]]')); + throw new Error('[[error:invalid-data]]'); } if (Array.isArray(groupNames) && !groupNames.length) { - return setImmediate(callback); + return; } if (!Array.isArray(groupNames)) { groupNames = [groupNames]; } if (!uid) { - return callback(new Error('[[error:invalid-uid]]')); + throw new Error('[[error:invalid-uid]]'); } - var isAdmin; - async.waterfall([ - function (next) { - async.parallel({ - isMembers: async.apply(Groups.isMemberOfGroups, uid, groupNames), - exists: async.apply(Groups.exists, groupNames), - isAdmin: async.apply(user.isAdministrator, uid), - }, next); - }, - function (results, next) { - isAdmin = results.isAdmin; - - var groupsToCreate = groupNames.filter(function (groupName, index) { - return groupName && !results.exists[index]; - }); - groupNames = groupNames.filter(function (groupName, index) { - return !results.isMembers[index]; - }); + const [isMembers, exists, isAdmin] = await Promise.all([ + Groups.isMemberOfGroups(uid, groupNames), + Groups.exists(groupNames), + user.isAdministrator(uid), + ]); - if (!groupNames.length) { - return callback(); - } + const groupsToCreate = groupNames.filter((groupName, index) => groupName && !exists[index]); + const groupsToJoin = groupNames.filter((groupName, index) => !isMembers[index]); - createNonExistingGroups(groupsToCreate, next); - }, - function (next) { - var tasks = [ - async.apply(db.sortedSetsAdd, groupNames.map(groupName => 'group:' + groupName + ':members'), Date.now(), uid), - async.apply(db.incrObjectField, groupNames.map(groupName => 'group:' + groupName), 'memberCount'), - ]; - if (isAdmin) { - tasks.push(async.apply(db.setsAdd, groupNames.map(groupName => 'group:' + groupName + ':owners'), uid)); - } + if (!groupsToJoin.length) { + return; + } + await createNonExistingGroups(groupsToCreate); - async.parallel(tasks, next); - }, - function (results, next) { - Groups.clearCache(uid, groupNames); - Groups.getGroupsFields(groupNames, ['name', 'hidden', 'memberCount'], next); - }, - function (groupData, next) { - var visibleGroups = groupData.filter(groupData => groupData && !groupData.hidden); - - if (visibleGroups.length) { - db.sortedSetAdd('groups:visible:memberCount', visibleGroups.map(groupData => groupData.memberCount), visibleGroups.map(groupData => groupData.name), next); - } else { - next(); - } - }, - function (next) { - setGroupTitleIfNotSet(groupNames, uid, next); - }, - function (next) { - plugins.fireHook('action:group.join', { - groupNames: groupNames, - uid: uid, - }); - next(); - }, - ], callback); + const promises = [ + db.sortedSetsAdd(groupsToJoin.map(groupName => 'group:' + groupName + ':members'), Date.now(), uid), + db.incrObjectField(groupsToJoin.map(groupName => 'group:' + groupName), 'memberCount'), + ]; + if (isAdmin) { + promises.push(db.setsAdd(groupsToJoin.map(groupName => 'group:' + groupName + ':owners'), uid)); + } + + await Promise.all(promises); + + Groups.clearCache(uid, groupsToJoin); + + const groupData = await Groups.getGroupsFields(groupsToJoin, ['name', 'hidden', 'memberCount']); + const visibleGroups = groupData.filter(groupData => groupData && !groupData.hidden); + + if (visibleGroups.length) { + await db.sortedSetAdd('groups:visible:memberCount', + visibleGroups.map(groupData => groupData.memberCount), + visibleGroups.map(groupData => groupData.name) + ); + } + + await setGroupTitleIfNotSet(groupsToJoin, uid); + + plugins.fireHook('action:group.join', { + groupNames: groupsToJoin, + uid: uid, + }); }; - function createNonExistingGroups(groupsToCreate, callback) { + async function createNonExistingGroups(groupsToCreate) { if (!groupsToCreate.length) { - return setImmediate(callback); + return; } - async.eachSeries(groupsToCreate, function (groupName, next) { - Groups.create({ - name: groupName, - hidden: 1, - }, function (err) { + + await async.eachSeries(groupsToCreate, async function (groupName) { + try { + await Groups.create({ + name: groupName, + hidden: 1, + }); + } catch (err) { if (err && err.message !== '[[error:group-already-exists]]') { winston.error('[groups.join] Could not create new hidden group', err); - return next(err); + throw err; } - next(); - }); - }, callback); + } + }); } - function setGroupTitleIfNotSet(groupNames, uid, callback) { - groupNames = groupNames.filter(function (groupName) { - return groupName !== 'registered-users' && !Groups.isPrivilegeGroup(groupName); - }); + async function setGroupTitleIfNotSet(groupNames, uid) { + groupNames = groupNames.filter(groupName => groupName !== 'registered-users' && !Groups.isPrivilegeGroup(groupName)); if (!groupNames.length) { - return callback(); + return; } - db.getObjectField('user:' + uid, 'groupTitle', function (err, currentTitle) { - if (err || currentTitle || currentTitle === '') { - return callback(err); - } + const currentTitle = await db.getObjectField('user:' + uid, 'groupTitle'); + if (currentTitle || currentTitle === '') { + return; + } - user.setUserField(uid, 'groupTitle', JSON.stringify(groupNames), callback); - }); + await user.setUserField(uid, 'groupTitle', JSON.stringify(groupNames)); } };