diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 041ba878df..7b5f9a6839 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -189,3 +189,5 @@ groupsController.uploadCover = function (req, res, next) { res.json([{ url: image.url }]); }); }; + +require('../promisify')(groupsController); diff --git a/src/socket.io/groups.js b/src/socket.io/groups.js index 505c79b04f..d90b536bd5 100644 --- a/src/socket.io/groups.js +++ b/src/socket.io/groups.js @@ -1,7 +1,5 @@ 'use strict'; -var async = require('async'); - var groups = require('../groups'); var meta = require('../meta'); var user = require('../user'); @@ -12,356 +10,293 @@ var privileges = require('../privileges'); var SocketGroups = module.exports; -SocketGroups.before = function (socket, method, data, next) { +SocketGroups.before = async (socket, method, data) => { if (!data) { - return next(new Error('[[error:invalid-data]]')); + throw new Error('[[error:invalid-data]]'); } - next(); }; -SocketGroups.join = function (socket, data, callback) { +SocketGroups.join = async (socket, data) => { if (socket.uid <= 0) { - return callback(new Error('[[error:invalid-uid]]')); + throw new Error('[[error:invalid-uid]]'); } if (data.groupName === 'administrators' || groups.isPrivilegeGroup(data.groupName)) { - return callback(new Error('[[error:not-allowed]]')); + throw new Error('[[error:not-allowed]]'); + } + + const exists = await groups.exists(data.groupName); + if (!exists) { + throw new Error('[[error:no-group]]'); } - async.waterfall([ - function (next) { - groups.exists(data.groupName, next); - }, - function (exists, next) { - if (!exists) { - return next(new Error('[[error:no-group]]')); - } - - if (!meta.config.allowPrivateGroups) { - groups.join(data.groupName, socket.uid, callback); - return; - } - - async.parallel({ - isAdmin: async.apply(user.isAdministrator, socket.uid), - groupData: async.apply(groups.getGroupData, data.groupName), - }, next); - }, - function (results, next) { - if (results.groupData.private && results.groupData.disableJoinRequests) { - return next(new Error('[[error:join-requests-disabled]]')); - } - - if (!results.groupData.private || results.isAdmin) { - groups.join(data.groupName, socket.uid, next); - } else { - groups.requestMembership(data.groupName, socket.uid, next); - } - }, - ], callback); + if (!meta.config.allowPrivateGroups) { + await groups.join(data.groupName, socket.uid); + return; + } + + const results = await utils.promiseParallel({ + isAdmin: await user.isAdministrator(socket.uid), + groupData: await groups.getGroupData(data.groupName), + }); + + if (results.groupData.private && results.groupData.disableJoinRequests) { + throw new Error('[[error:join-requests-disabled]]'); + } + + if (!results.groupData.private || results.isAdmin) { + await groups.join(data.groupName, socket.uid); + } else { + await groups.requestMembership(data.groupName, socket.uid); + } }; -SocketGroups.leave = function (socket, data, callback) { +SocketGroups.leave = async (socket, data) => { if (socket.uid <= 0) { - return callback(new Error('[[error:invalid-uid]]')); + throw new Error('[[error:invalid-uid]]'); } if (data.groupName === 'administrators') { - return callback(new Error('[[error:cant-remove-self-as-admin]]')); + throw new Error('[[error:cant-remove-self-as-admin]]'); } - groups.leave(data.groupName, socket.uid, callback); + await groups.leave(data.groupName, socket.uid); }; -SocketGroups.addMember = isOwner(function (socket, data, callback) { +SocketGroups.addMember = async (socket, data) => { + await isOwner(socket, data); if (data.groupName === 'administrators' || groups.isPrivilegeGroup(data.groupName)) { - return callback(new Error('[[error:not-allowed]]')); + throw new Error('[[error:not-allowed]]'); + } + await groups.join(data.groupName, data.uid); +}; + +async function isOwner(socket, data) { + const results = await utils.promiseParallel({ + isAdmin: await user.isAdministrator(socket.uid), + isGlobalModerator: await user.isGlobalModerator(socket.uid), + isOwner: await groups.ownership.isOwner(socket.uid, data.groupName), + group: await groups.getGroupData(data.groupName), + }); + + var isOwner = results.isOwner || results.isAdmin || (results.isGlobalModerator && !results.group.system); + if (!isOwner) { + throw new Error('[[error:no-privileges]]'); } - groups.join(data.groupName, data.uid, callback); -}); - -function isOwner(next) { - return function (socket, data, callback) { - async.parallel({ - isAdmin: async.apply(user.isAdministrator, socket.uid), - isGlobalModerator: async.apply(user.isGlobalModerator, socket.uid), - isOwner: async.apply(groups.ownership.isOwner, socket.uid, data.groupName), - group: async.apply(groups.getGroupData, data.groupName), - }, function (err, results) { - if (err) { - return callback(err); - } - var isOwner = results.isOwner || results.isAdmin || (results.isGlobalModerator && !results.group.system); - if (!isOwner) { - return callback(new Error('[[error:no-privileges]]')); - } - next(socket, data, callback); - }); - }; } -function isInvited(next) { - return function (socket, data, callback) { - groups.isInvited(socket.uid, data.groupName, function (err, invited) { - if (err || !invited) { - return callback(err || new Error('[[error:not-invited]]')); - } - next(socket, data, callback); - }); - }; +async function isInvited(socket, data) { + const invited = await groups.isInvited(socket.uid, data.groupName); + if (!invited) { + throw new Error('[[error:not-invited]]'); + } } -SocketGroups.grant = isOwner(function (socket, data, callback) { - groups.ownership.grant(data.toUid, data.groupName, callback); -}); - -SocketGroups.rescind = isOwner(function (socket, data, callback) { - groups.ownership.rescind(data.toUid, data.groupName, callback); -}); - -SocketGroups.accept = isOwner(function (socket, data, callback) { - async.waterfall([ - function (next) { - groups.acceptMembership(data.groupName, data.toUid, next); - }, - function (next) { - events.log({ - type: 'accept-membership', - uid: socket.uid, - ip: socket.ip, - groupName: data.groupName, - targetUid: data.toUid, - }); - setImmediate(next); - }, - ], callback); -}); - -SocketGroups.reject = isOwner(function (socket, data, callback) { - async.waterfall([ - function (next) { - groups.rejectMembership(data.groupName, data.toUid, next); - }, - function (next) { - events.log({ - type: 'reject-membership', - uid: socket.uid, - ip: socket.ip, - groupName: data.groupName, - targetUid: data.toUid, - }); - setImmediate(next); - }, - ], callback); -}); - -SocketGroups.acceptAll = isOwner(function (socket, data, callback) { - acceptRejectAll(SocketGroups.accept, socket, data, callback); -}); - -SocketGroups.rejectAll = isOwner(function (socket, data, callback) { - acceptRejectAll(SocketGroups.reject, socket, data, callback); -}); - -function acceptRejectAll(method, socket, data, callback) { - async.waterfall([ - function (next) { - groups.getPending(data.groupName, next); - }, - function (uids, next) { - async.each(uids, function (uid, next) { - method(socket, { groupName: data.groupName, toUid: uid }, next); - }, next); - }, - ], callback); +SocketGroups.grant = async (socket, data) => { + await isOwner(socket, data); + await groups.ownership.grant(data.toUid, data.groupName); +}; + +SocketGroups.rescind = async (socket, data) => { + await isOwner(socket, data); + await groups.ownership.rescind(data.toUid, data.groupName); +}; + +SocketGroups.accept = async (socket, data) => { + await isOwner(socket, data); + await groups.acceptMembership(data.groupName, data.toUid); + events.log({ + type: 'accept-membership', + uid: socket.uid, + ip: socket.ip, + groupName: data.groupName, + targetUid: data.toUid, + }); +}; + +SocketGroups.reject = async (socket, data) => { + await isOwner(socket, data); + await groups.rejectMembership(data.groupName, data.toUid); + events.log({ + type: 'reject-membership', + uid: socket.uid, + ip: socket.ip, + groupName: data.groupName, + targetUid: data.toUid, + }); +}; + +SocketGroups.acceptAll = async (socket, data) => { + await isOwner(socket, data); + await acceptRejectAll(SocketGroups.accept, socket, data); +}; + +SocketGroups.rejectAll = async (socket, data) => { + await isOwner(socket, data); + await acceptRejectAll(SocketGroups.reject, socket, data); +}; + +async function acceptRejectAll(method, socket, data) { + const uids = groups.getPending(data.groupName); + await new Promise(uids.forEach(async (uid) => { + await method(socket, { groupName: data.groupName, toUid: uid }); + })); } -SocketGroups.issueInvite = isOwner(function (socket, data, callback) { - groups.invite(data.groupName, data.toUid, callback); -}); +SocketGroups.issueInvite = async (socket, data) => { + await isOwner(socket, data); + await groups.invite(data.groupName, data.toUid); +}; -SocketGroups.issueMassInvite = isOwner(function (socket, data, callback) { +SocketGroups.issueMassInvite = async (socket, data) => { + await isOwner(socket, data); if (!data || !data.usernames || !data.groupName) { - return callback(new Error('[[error:invalid-data]]')); + throw new Error('[[error:invalid-data]]'); } var usernames = String(data.usernames).split(','); usernames = usernames.map(function (username) { return username && username.trim(); }); - async.waterfall([ - function (next) { - user.getUidsByUsernames(usernames, next); - }, - function (uids, next) { - uids = uids.filter(function (uid) { - return !!uid && parseInt(uid, 10); - }); + let uids = await user.getUidsByUsernames(usernames); + uids = uids.filter(function (uid) { + return !!uid && parseInt(uid, 10); + }); - async.eachSeries(uids, function (uid, next) { - groups.invite(data.groupName, uid, next); - }, next); - }, - ], callback); -}); + // eslint-disable-next-line guard-for-in + for (const i in uids) { + // eslint-disable-next-line no-await-in-loop + await groups.invite(data.groupName, uids[i]); + } +}; -SocketGroups.rescindInvite = isOwner(function (socket, data, callback) { - groups.rejectMembership(data.groupName, data.toUid, callback); -}); +SocketGroups.rescindInvite = async (socket, data) => { + await isOwner(socket, data); + await groups.rejectMembership(data.groupName, data.toUid); +}; -SocketGroups.acceptInvite = isInvited(function (socket, data, callback) { - groups.acceptMembership(data.groupName, socket.uid, callback); -}); +SocketGroups.acceptInvite = async (socket, data) => { + await isInvited(socket, data); + await groups.acceptMembership(data.groupName, socket.uid); +}; -SocketGroups.rejectInvite = isInvited(function (socket, data, callback) { - groups.rejectMembership(data.groupName, socket.uid, callback); -}); +SocketGroups.rejectInvite = async (socket, data) => { + await isInvited(socket, data); + await groups.rejectMembership(data.groupName, socket.uid); +}; -SocketGroups.update = isOwner(function (socket, data, callback) { - groups.update(data.groupName, data.values, callback); -}); +SocketGroups.update = async (socket, data) => { + await isOwner(socket, data); + await groups.update(data.groupName, data.values); +}; -SocketGroups.kick = isOwner(function (socket, data, callback) { +SocketGroups.kick = async (socket, data) => { + await isOwner(socket, data); if (socket.uid === parseInt(data.uid, 10)) { - return callback(new Error('[[error:cant-kick-self]]')); + throw new Error('[[error:cant-kick-self]]'); } - async.waterfall([ - function (next) { - groups.ownership.isOwner(data.uid, data.groupName, next); - }, - function (isOwner, next) { - groups.kick(data.uid, data.groupName, isOwner, next); - }, - ], callback); -}); - -SocketGroups.create = function (socket, data, callback) { + const isOwnerBit = await groups.ownership.isOwner(data.uid, data.groupName); + await groups.kick(data.uid, data.groupName, isOwnerBit); +}; + +SocketGroups.create = async (socket, data) => { if (!socket.uid) { - return callback(new Error('[[error:no-privileges]]')); + throw new Error('[[error:no-privileges]]'); } else if (groups.isPrivilegeGroup(data.name)) { - return callback(new Error('[[error:invalid-group-name]]')); + throw new Error('[[error:invalid-group-name]]'); } - async.waterfall([ - function (next) { - privileges.global.can('group:create', socket.uid, next); - }, - function (canCreate, next) { - if (!canCreate) { - return next(new Error('[[error:no-privileges]]')); - } - data.ownerUid = socket.uid; - groups.create(data, next); - }, - ], callback); + const canCreate = await privileges.global.can('group:create', socket.uid); + if (!canCreate) { + throw new Error('[[error:no-privileges]]'); + } + data.ownerUid = socket.uid; + await groups.create(data); }; -SocketGroups.delete = isOwner(function (socket, data, callback) { - if (data.groupName === 'administrators' || - data.groupName === 'registered-users' || - data.groupName === 'guests' || - data.groupName === 'Global Moderators') { - return callback(new Error('[[error:not-allowed]]')); +SocketGroups.delete = async (socket, data) => { + await isOwner(socket, data); + if ( + data.groupName === 'administrators' || data.groupName === 'registered-users' || + data.groupName === 'guests' || data.groupName === 'Global Moderators' + ) { + throw new Error('[[error:not-allowed]]'); } - groups.destroy(data.groupName, callback); -}); + await groups.destroy(data.groupName); +}; -SocketGroups.search = function (socket, data, callback) { +SocketGroups.search = async (socket, data) => { data.options = data.options || {}; if (!data.query) { var groupsPerPage = 15; - groupsController.getGroupsFromSet(socket.uid, data.options.sort, 0, groupsPerPage - 1, function (err, data) { - callback(err, !err ? data.groups : null); - }); - return; + const groups = await groupsController.getGroupsFromSet(socket.uid, data.options.sort, 0, groupsPerPage - 1); + return groups.groups; } - groups.search(data.query, data.options, callback); + await groups.search(data.query, data.options); }; -SocketGroups.loadMore = function (socket, data, callback) { +SocketGroups.loadMore = async (socket, data) => { if (!data.sort || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { - return callback(new Error('[[error:invalid-data]]')); + throw new Error('[[error:invalid-data]]'); } var groupsPerPage = 9; var start = parseInt(data.after, 10); var stop = start + groupsPerPage - 1; - groupsController.getGroupsFromSet(socket.uid, data.sort, start, stop, callback); + await groupsController.getGroupsFromSet(socket.uid, data.sort, start, stop); }; -SocketGroups.searchMembers = function (socket, data, callback) { +SocketGroups.searchMembers = async (socket, data) => { data.uid = socket.uid; - groups.searchMembers(data, callback); + await groups.searchMembers(data); }; -SocketGroups.loadMoreMembers = function (socket, data, callback) { +SocketGroups.loadMoreMembers = async (socket, data) => { if (!data.groupName || !utils.isNumber(data.after) || parseInt(data.after, 10) < 0) { - return callback(new Error('[[error:invalid-data]]')); + throw new Error('[[error:invalid-data]]'); } data.after = parseInt(data.after, 10); - async.waterfall([ - function (next) { - user.getUsersFromSet('group:' + data.groupName + ':members', socket.uid, data.after, data.after + 9, next); - }, - function (users, next) { - next(null, { - users: users, - nextStart: data.after + 10, - }); - }, - ], callback); + const users = user.getUsersFromSet('group:' + data.groupName + ':members', socket.uid, data.after, data.after + 9); + return { + users: users, + nextStart: data.after + 10, + }; }; SocketGroups.cover = {}; -SocketGroups.cover.update = function (socket, data, callback) { +SocketGroups.cover.update = async (socket, data) => { if (!socket.uid) { - return callback(new Error('[[error:no-privileges]]')); + throw new Error('[[error:no-privileges]]'); } - async.waterfall([ - function (next) { - canModifyGroup(socket.uid, data.groupName, next); - }, - function (next) { - groups.updateCover(socket.uid, data, next); - }, - ], callback); + await canModifyGroup(socket.uid, data.groupName); + await groups.updateCover(socket.uid, data); }; -SocketGroups.cover.remove = function (socket, data, callback) { +SocketGroups.cover.remove = async (socket, data) => { if (!socket.uid) { - return callback(new Error('[[error:no-privileges]]')); + throw new Error('[[error:no-privileges]]'); } - async.waterfall([ - function (next) { - canModifyGroup(socket.uid, data.groupName, next); - }, - function (next) { - groups.removeCover(data, next); - }, - ], callback); + await canModifyGroup(socket.uid, data.groupName); + await groups.removeCover(socket.uid, data); }; -function canModifyGroup(uid, groupName, callback) { - async.waterfall([ - function (next) { - async.parallel({ - isOwner: async.apply(groups.ownership.isOwner, uid, groupName), - isAdminOrGlobalMod: async.apply(user.isAdminOrGlobalMod, uid), - }, next); - }, - function (results, next) { - if (!results.isOwner && !results.isAdminOrGlobalMod) { - return next(new Error('[[error:no-privileges]]')); - } - next(); - }, - ], callback); +async function canModifyGroup(uid, groupName) { + const results = await utils.promiseParallel({ + isOwner: groups.ownership.isOwner(uid, groupName), + isAdminOrGlobalMod: user.isAdminOrGlobalMod(uid), + }); + + if (!results.isOwner && !results.isAdminOrGlobalMod) { + throw new Error('[[error:no-privileges]]'); + } } + +require('../promisify')(SocketGroups);