From 72def7dfa628b48c803631e66efc38059bac7dbc 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 22:35:12 -0400 Subject: [PATCH] feat: #7743 finish groups --- src/groups/search.js | 145 +++++------- src/groups/update.js | 485 ++++++++++++++++------------------------ src/navigation/admin.js | 2 + src/navigation/index.js | 2 + src/widgets/admin.js | 2 + src/widgets/index.js | 2 + 6 files changed, 260 insertions(+), 378 deletions(-) diff --git a/src/groups/search.js b/src/groups/search.js index 62005ce308..45dbdbbc61 100644 --- a/src/groups/search.js +++ b/src/groups/search.js @@ -1,118 +1,85 @@ 'use strict'; -var async = require('async'); - -var user = require('../user'); -var db = require('./../database'); - +const user = require('../user'); +const db = require('./../database'); module.exports = function (Groups) { - Groups.search = function (query, options, callback) { + Groups.search = async function (query, options) { if (!query) { - return callback(null, []); + return []; + } + query = String(query).toLowerCase(); + let groupNames = await db.getSortedSetRange('groups:createtime', 0, -1); + if (!options.hideEphemeralGroups) { + groupNames = Groups.ephemeralGroups.concat(groupNames); } - query = query.toLowerCase(); - async.waterfall([ - async.apply(db.getSortedSetRange, 'groups:createtime', 0, -1), - function (groupNames, next) { - if (!options.hideEphemeralGroups) { - groupNames = Groups.ephemeralGroups.concat(groupNames); - } - groupNames = groupNames.filter(function (name) { - return name.toLowerCase().includes(query) && name !== 'administrators' && !Groups.isPrivilegeGroup(name); - }); - groupNames = groupNames.slice(0, 100); - if (options.showMembers) { - Groups.getGroupsAndMembers(groupNames, next); - } else { - Groups.getGroupsData(groupNames, next); - } - }, - function (groupsData, next) { - groupsData = groupsData.filter(Boolean); - if (options.filterHidden) { - groupsData = groupsData.filter(group => !group.hidden); - } + groupNames = groupNames.filter(name => name.toLowerCase().includes(query) && name !== 'administrators' && !Groups.isPrivilegeGroup(name)); + groupNames = groupNames.slice(0, 100); - Groups.sort(options.sort, groupsData, next); - }, - ], callback); + let groupsData; + if (options.showMembers) { + groupsData = await Groups.getGroupsAndMembers(groupNames); + } else { + groupsData = await Groups.getGroupsData(groupNames); + } + groupsData = groupsData.filter(Boolean); + if (options.filterHidden) { + groupsData = groupsData.filter(group => !group.hidden); + } + return Groups.sort(options.sort, groupsData); }; - Groups.sort = function (strategy, groups, next) { + Groups.sort = function (strategy, groups) { switch (strategy) { case 'count': - groups = groups.sort(function (a, b) { - return a.slug > b.slug; - }).sort(function (a, b) { - return b.memberCount - a.memberCount; - }); + groups.sort((a, b) => a.slug > b.slug) + .sort((a, b) => b.memberCount - a.memberCount); break; case 'date': - groups = groups.sort(function (a, b) { - return b.createtime - a.createtime; - }); + groups.sort((a, b) => b.createtime - a.createtime); break; case 'alpha': // intentional fall-through default: - groups = groups.sort(function (a, b) { - return a.slug > b.slug ? 1 : -1; - }); + groups.sort((a, b) => (a.slug > b.slug ? 1 : -1)); } - next(null, groups); + return groups; }; - Groups.searchMembers = function (data, callback) { + Groups.searchMembers = async function (data) { if (!data.query) { - Groups.getOwnersAndMembers(data.groupName, data.uid, 0, 19, function (err, users) { - callback(err, { users: users }); - }); - return; + const users = await Groups.getOwnersAndMembers(data.groupName, data.uid, 0, 19); + return { users: users }; } - var results; - async.waterfall([ - function (next) { - data.paginate = false; - user.search(data, next); - }, - function (_results, next) { - results = _results; - var uids = results.users.map(function (user) { - return user && user.uid; - }); - Groups.isMembers(uids, data.groupName, next); - }, - function (isMembers, next) { - results.users = results.users.filter(function (user, index) { - return isMembers[index]; - }); - var uids = results.users.map(function (user) { - return user && user.uid; - }); - Groups.ownership.isOwners(uids, data.groupName, next); - }, - function (isOwners, next) { - results.users.forEach(function (user, index) { - if (user) { - user.isOwner = isOwners[index]; - } - }); + data.paginate = false; + const results = await user.search(data); + + let uids = results.users.map(user => user && user.uid); + const isMembers = await Groups.isMembers(uids, data.groupName); + + results.users = results.users.filter((user, index) => isMembers[index]); + + uids = results.users.map(user => user && user.uid); + const isOwners = await Groups.ownership.isOwners(uids, data.groupName); + + results.users.forEach(function (user, index) { + if (user) { + user.isOwner = isOwners[index]; + } + }); - results.users.sort(function (a, b) { - if (a.isOwner && !b.isOwner) { - return -1; - } else if (!a.isOwner && b.isOwner) { - return 1; - } - return 0; - }); - next(null, results); - }, - ], callback); + results.users.sort(function (a, b) { + if (a.isOwner && !b.isOwner) { + return -1; + } else if (!a.isOwner && b.isOwner) { + return 1; + } + return 0; + }); + return results; }; }; diff --git a/src/groups/update.js b/src/groups/update.js index 31efd9a187..81c26c3171 100644 --- a/src/groups/update.js +++ b/src/groups/update.js @@ -1,337 +1,244 @@ 'use strict'; -var async = require('async'); -var winston = require('winston'); +const winston = require('winston'); -var plugins = require('../plugins'); -var utils = require('../utils'); -var db = require('../database'); -var user = require('../user'); +const plugins = require('../plugins'); +const utils = require('../utils'); +const db = require('../database'); +const user = require('../user'); +const batch = require('../batch'); module.exports = function (Groups) { - Groups.update = function (groupName, values, callback) { - callback = callback || function () {}; - - async.waterfall([ - function (next) { - db.exists('group:' + groupName, next); - }, - function (exists, next) { - if (!exists) { - return next(new Error('[[error:no-group]]')); - } - plugins.fireHook('filter:group.update', { - groupName: groupName, - values: values, - }, next); - }, - function (result, next) { - values = result.values; - - var payload = { - description: values.description || '', - icon: values.icon || '', - labelColor: values.labelColor || '#000000', - textColor: values.textColor || '#ffffff', - }; - - if (values.hasOwnProperty('userTitle')) { - payload.userTitle = values.userTitle || ''; - } + Groups.update = async function (groupName, values) { + const exists = await db.exists('group:' + groupName); + if (!exists) { + throw new Error('[[error:no-group]]'); + } - if (values.hasOwnProperty('userTitleEnabled')) { - payload.userTitleEnabled = values.userTitleEnabled ? '1' : '0'; - } + const result = await plugins.fireHook('filter:group.update', { + groupName: groupName, + values: values, + }); + values = result.values; - if (values.hasOwnProperty('hidden')) { - payload.hidden = values.hidden ? '1' : '0'; - } + const payload = { + description: values.description || '', + icon: values.icon || '', + labelColor: values.labelColor || '#000000', + textColor: values.textColor || '#ffffff', + }; - if (values.hasOwnProperty('private')) { - payload.private = values.private ? '1' : '0'; - } + if (values.hasOwnProperty('userTitle')) { + payload.userTitle = values.userTitle || ''; + } - if (values.hasOwnProperty('disableJoinRequests')) { - payload.disableJoinRequests = values.disableJoinRequests ? '1' : '0'; - } - async.series([ - async.apply(checkNameChange, groupName, values.name), - function (next) { - if (values.hasOwnProperty('private')) { - updatePrivacy(groupName, values.private, next); - } else { - next(); - } - }, - function (next) { - if (values.hasOwnProperty('hidden')) { - updateVisibility(groupName, values.hidden, next); - } else { - next(); - } - }, - async.apply(db.setObject, 'group:' + groupName, payload), - async.apply(Groups.renameGroup, groupName, values.name), - ], next); - }, - function (result, next) { - plugins.fireHook('action:group.update', { - name: groupName, - values: values, - }); - next(); - }, - ], callback); + if (values.hasOwnProperty('userTitleEnabled')) { + payload.userTitleEnabled = values.userTitleEnabled ? '1' : '0'; + } + + if (values.hasOwnProperty('hidden')) { + payload.hidden = values.hidden ? '1' : '0'; + } + + if (values.hasOwnProperty('private')) { + payload.private = values.private ? '1' : '0'; + } + + if (values.hasOwnProperty('disableJoinRequests')) { + payload.disableJoinRequests = values.disableJoinRequests ? '1' : '0'; + } + + await checkNameChange(groupName, values.name); + if (values.hasOwnProperty('private')) { + await updatePrivacy(groupName, values.private); + } + + if (values.hasOwnProperty('hidden')) { + await updateVisibility(groupName, values.hidden); + } + await db.setObject('group:' + groupName, payload); + await Groups.renameGroup(groupName, values.name); + + plugins.fireHook('action:group.update', { + name: groupName, + values: values, + }); }; - function updateVisibility(groupName, hidden, callback) { + async function updateVisibility(groupName, hidden) { if (hidden) { - async.parallel([ - async.apply(db.sortedSetsRemove, ['groups:visible:createtime', 'groups:visible:memberCount'], groupName), - async.apply(db.sortedSetRemove, 'groups:visible:name', groupName.toLowerCase() + ':' + groupName), - ], callback); - } else { - async.waterfall([ - function (next) { - db.getObjectFields('group:' + groupName, ['createtime', 'memberCount'], next); - }, - function (groupData, next) { - db.sortedSetAddBulk([ - ['groups:visible:createtime', groupData.createtime, groupName], - ['groups:visible:memberCount', groupData.memberCount, groupName], - ['groups:visible:name', 0, groupName.toLowerCase() + ':' + groupName], - ], next); - }, - ], callback); + await db.sortedSetRemoveBulk([ + ['groups:visible:createtime', groupName], + ['groups:visible:memberCount', groupName], + ['groups:visible:name', groupName.toLowerCase() + ':' + groupName], + ]); + return; } + const groupData = await db.getObjectFields('group:' + groupName, ['createtime', 'memberCount']); + await db.sortedSetAddBulk([ + ['groups:visible:createtime', groupData.createtime, groupName], + ['groups:visible:memberCount', groupData.memberCount, groupName], + ['groups:visible:name', 0, groupName.toLowerCase() + ':' + groupName], + ]); } - Groups.hide = function (groupName, callback) { - showHide(groupName, 'hidden', callback); + Groups.hide = async function (groupName) { + await showHide(groupName, 'hidden'); }; - Groups.show = function (groupName, callback) { - showHide(groupName, 'show', callback); + Groups.show = async function (groupName) { + await showHide(groupName, 'show'); }; - function showHide(groupName, hidden, callback) { + async function showHide(groupName, hidden) { hidden = hidden === 'hidden'; - callback = callback || function () {}; - async.parallel([ - async.apply(db.setObjectField, 'group:' + groupName, 'hidden', hidden ? 1 : 0), - async.apply(updateVisibility, groupName, hidden), - ], function (err) { - callback(err); - }); + await Promise.all([ + db.setObjectField('group:' + groupName, 'hidden', hidden ? 1 : 0), + updateVisibility(groupName, hidden), + ]); } - function updatePrivacy(groupName, isPrivate, callback) { - async.waterfall([ - function (next) { - Groups.getGroupFields(groupName, ['private'], next); - }, - function (currentValue, next) { - var currentlyPrivate = currentValue.private === 1; - if (!currentlyPrivate || currentlyPrivate === isPrivate) { - return callback(); - } - db.getSetMembers('group:' + groupName + ':pending', next); - }, - function (uids, next) { - if (!uids.length) { - return callback(); - } - var now = Date.now(); - winston.verbose('[groups.update] Group is now public, automatically adding ' + uids.length + ' new members, who were pending prior.'); - async.series([ - async.apply(db.sortedSetAdd, 'group:' + groupName + ':members', uids.map(() => now), uids), - async.apply(db.delete, 'group:' + groupName + ':pending'), - ], next); - }, - ], function (err) { - callback(err); - }); - } + async function updatePrivacy(groupName, isPrivate) { + const groupData = await Groups.getGroupFields(groupName, ['private']); + const currentlyPrivate = groupData.private === 1; + if (!currentlyPrivate || currentlyPrivate === isPrivate) { + return; + } + const pendingUids = await db.getSetMembers('group:' + groupName + ':pending'); + if (!pendingUids.length) { + return; + } - function checkNameChange(currentName, newName, callback) { - if (currentName === newName) { - return setImmediate(callback); + winston.verbose('[groups.update] Group is now public, automatically adding ' + pendingUids.length + ' new members, who were pending prior.'); + + for (const uid of pendingUids) { + /* eslint-disable no-await-in-loop */ + await Groups.join(groupName, uid); } - var currentSlug = utils.slugify(currentName); - var newSlug = utils.slugify(newName); - if (currentSlug === newSlug) { - return setImmediate(callback); + await db.delete('group:' + groupName + ':pending'); + } + + async function checkNameChange(currentName, newName) { + const currentSlug = utils.slugify(currentName); + const newSlug = utils.slugify(newName); + if (currentName === newName || currentSlug === newSlug) { + return; } - async.waterfall([ - function (next) { - async.parallel({ - group: function (next) { - Groups.getGroupData(currentName, next); - }, - exists: function (next) { - Groups.existsBySlug(newSlug, next); - }, - }, next); - }, - function (results, next) { - if (results.exists) { - return next(new Error('[[error:group-already-exists]]')); - } + const [group, exists] = await Promise.all([ + Groups.getGroupData(currentName), + Groups.existsBySlug(newSlug), + ]); - if (!results.group) { - return next(new Error('[[error:no-group]]')); - } + if (exists) { + throw new Error('[[error:group-already-exists]]'); + } - if (results.group.system) { - return next(new Error('[[error:not-allowed-to-rename-system-group]]')); - } + if (!group) { + throw new Error('[[error:no-group]]'); + } - next(); - }, - ], callback); + if (group.system) { + throw new Error('[[error:not-allowed-to-rename-system-group]]'); + } } - Groups.renameGroup = function (oldName, newName, callback) { - if (oldName === newName || !newName || newName.length === 0) { - return setImmediate(callback); + Groups.renameGroup = async function (oldName, newName) { + if (oldName === newName || !newName || String(newName).length === 0) { + return; + } + const group = await db.getObject('group:' + oldName); + if (!group) { + return; } - var group; - async.waterfall([ - function (next) { - db.getObject('group:' + oldName, next); - }, - function (_group, next) { - group = _group; - if (!group) { - return callback(); - } - Groups.exists(newName, next); - }, - function (exists, next) { - if (exists) { - return callback(new Error('[[error:group-already-exists]]')); - } - async.series([ - async.apply(updateMemberGroupTitles, oldName, newName), - async.apply(updateNavigationItems, oldName, newName), - async.apply(updateWidgets, oldName, newName), - async.apply(db.setObjectField, 'group:' + oldName, 'name', newName), - async.apply(db.setObjectField, 'group:' + oldName, 'slug', utils.slugify(newName)), - async.apply(db.deleteObjectField, 'groupslug:groupname', group.slug), - async.apply(db.setObjectField, 'groupslug:groupname', utils.slugify(newName), newName), - function (next) { - db.getSortedSetRange('groups:createtime', 0, -1, function (err, groups) { - if (err) { - return next(err); - } - const keys = groups.map(group => 'group:' + group + ':members'); - renameGroupsMember(keys, oldName, newName, next); - }); - }, - async.apply(db.rename, 'group:' + oldName, 'group:' + newName), - async.apply(db.rename, 'group:' + oldName + ':members', 'group:' + newName + ':members'), - async.apply(db.rename, 'group:' + oldName + ':owners', 'group:' + newName + ':owners'), - async.apply(db.rename, 'group:' + oldName + ':pending', 'group:' + newName + ':pending'), - async.apply(db.rename, 'group:' + oldName + ':invited', 'group:' + newName + ':invited'), - async.apply(db.rename, 'group:' + oldName + ':member:pids', 'group:' + newName + ':member:pids'), - - async.apply(renameGroupsMember, ['groups:createtime', 'groups:visible:createtime', 'groups:visible:memberCount'], oldName, newName), - async.apply(renameGroupsMember, ['groups:visible:name'], oldName.toLowerCase() + ':' + oldName, newName.toLowerCase() + ':' + newName), - function (next) { - plugins.fireHook('action:group.rename', { - old: oldName, - new: newName, - }); - Groups.resetCache(); - next(); - }, - ], next); - }, - ], function (err) { - callback(err); + const exists = await Groups.exists(newName); + if (exists) { + throw new Error('[[error:group-already-exists]]'); + } + + await updateMemberGroupTitles(oldName, newName); + await updateNavigationItems(oldName, newName); + await updateWidgets(oldName, newName); + await db.setObject('group:' + oldName, { name: newName, slug: utils.slugify(newName) }); + await db.deleteObjectField('groupslug:groupname', group.slug); + await db.setObjectField('groupslug:groupname', utils.slugify(newName), newName); + + const allGroups = await db.getSortedSetRange('groups:createtime', 0, -1); + const keys = allGroups.map(group => 'group:' + group + ':members'); + await renameGroupsMember(keys, oldName, newName); + + await db.rename('group:' + oldName, 'group:' + newName); + await db.rename('group:' + oldName + ':members', 'group:' + newName + ':members'); + await db.rename('group:' + oldName + ':owners', 'group:' + newName + ':owners'); + await db.rename('group:' + oldName + ':pending', 'group:' + newName + ':pending'); + await db.rename('group:' + oldName + ':invited', 'group:' + newName + ':invited'); + await db.rename('group:' + oldName + ':member:pids', 'group:' + newName + ':member:pids'); + + await renameGroupsMember(['groups:createtime', 'groups:visible:createtime', 'groups:visible:memberCount'], oldName, newName); + await renameGroupsMember(['groups:visible:name'], oldName.toLowerCase() + ':' + oldName, newName.toLowerCase() + ':' + newName); + + plugins.fireHook('action:group.rename', { + old: oldName, + new: newName, }); + Groups.resetCache(); }; - function updateMemberGroupTitles(oldName, newName, callback) { - const batch = require('../batch'); - batch.processSortedSet('group:' + oldName + ':members', function (uids, next) { - async.waterfall([ - function (next) { - user.getUsersData(uids, next); - }, - function (usersData, next) { - usersData = usersData.filter(userData => userData && userData.groupTitleArray.includes(oldName)); - async.each(usersData, function (userData, next) { - const newTitleArray = userData.groupTitleArray.map(oldTitle => (oldTitle === oldName ? newName : oldTitle)); - user.setUserField(userData.uid, 'groupTitle', JSON.stringify(newTitleArray), next); - }, next); - }, - ], next); - }, callback); + async function updateMemberGroupTitles(oldName, newName) { + await batch.processSortedSet('group:' + oldName + ':members', async function (uids) { + let usersData = await user.getUsersData(uids); + usersData = usersData.filter(userData => userData && userData.groupTitleArray.includes(oldName)); + + usersData.forEach(function (userData) { + userData.newTitleArray = userData.groupTitleArray.map(oldTitle => (oldTitle === oldName ? newName : oldTitle)); + }); + + await Promise.all(usersData.map(u => user.setUserField(u.uid, 'groupTitle', JSON.stringify(u.newTitleArray)))); + }, {}); } - function renameGroupsMember(keys, oldName, newName, callback) { - var scores; - async.waterfall([ - function (next) { - db.isMemberOfSortedSets(keys, oldName, next); - }, - function (isMembers, next) { - keys = keys.filter((key, index) => isMembers[index]); - if (!keys.length) { - return callback(); - } - db.sortedSetsScore(keys, oldName, next); - }, - function (_scores, next) { - scores = _scores; - db.sortedSetsRemove(keys, oldName, next); - }, - function (next) { - db.sortedSetsAdd(keys, scores, newName, next); - }, - ], callback); + async function renameGroupsMember(keys, oldName, newName) { + const isMembers = await db.isMemberOfSortedSets(keys, oldName); + keys = keys.filter((key, index) => isMembers[index]); + if (!keys.length) { + return; + } + const scores = await db.sortedSetsScore(keys, oldName); + await db.sortedSetsRemove(keys, oldName); + await db.sortedSetsAdd(keys, scores, newName); } - function updateNavigationItems(oldName, newName, callback) { + async function updateNavigationItems(oldName, newName) { const navigation = require('../navigation/admin'); + const navItems = await navigation.get(); + navItems.forEach(function (navItem) { + if (navItem && Array.isArray(navItem.groups) && navItem.groups.includes(oldName)) { + navItem.groups.splice(navItem.groups.indexOf(oldName), 1, newName); + } + }); - async.waterfall([ - navigation.get, - function (navItems, next) { - navItems.forEach(function (navItem) { - if (navItem && Array.isArray(navItem.groups) && navItem.groups.includes(oldName)) { - navItem.groups.splice(navItem.groups.indexOf(oldName), 1, newName); - } - }); - - navigation.save(navItems, next); - }, - ], callback); + await navigation.save(navItems); } - function updateWidgets(oldName, newName, callback) { + async function updateWidgets(oldName, newName) { const admin = require('../widgets/admin'); const widgets = require('../widgets'); - async.waterfall([ - admin.get, - function (data, next) { - async.eachSeries(data.areas, function (area, next) { - if (!area.data.length) { - return setImmediate(next); - } - area.widgets = area.data; - area.widgets.forEach(function (widget) { - if (widget && widget.data && Array.isArray(widget.data.groups) && widget.data.groups.includes(oldName)) { - widget.data.groups.splice(widget.data.groups.indexOf(oldName), 1, newName); - } - }); - - widgets.setArea(area, next); - }, next); - }, - ], callback); + + const data = await admin.get(); + + data.areas.forEach(function (area) { + area.widgets = area.data; + area.widgets.forEach(function (widget) { + if (widget && widget.data && Array.isArray(widget.data.groups) && widget.data.groups.includes(oldName)) { + widget.data.groups.splice(widget.data.groups.indexOf(oldName), 1, newName); + } + }); + }); + for (const area of data.areas) { + if (area.data.length) { + await widgets.setArea(area); + } + } } }; diff --git a/src/navigation/admin.js b/src/navigation/admin.js index 9d304e1ced..7722a99ac3 100644 --- a/src/navigation/admin.js +++ b/src/navigation/admin.js @@ -78,3 +78,5 @@ function getAvailable(callback) { plugins.fireHook('filter:navigation.available', core, callback); } + +require('../promisify')(admin); diff --git a/src/navigation/index.js b/src/navigation/index.js index 8a3577c68f..4e4dd9a760 100644 --- a/src/navigation/index.js +++ b/src/navigation/index.js @@ -38,3 +38,5 @@ navigation.get = function (uid, callback) { }, ], callback); }; + +require('../promisify')(navigation); diff --git a/src/widgets/admin.js b/src/widgets/admin.js index 82c0681900..40b53134af 100644 --- a/src/widgets/admin.js +++ b/src/widgets/admin.js @@ -115,3 +115,5 @@ function buildTemplatesFromAreas(areas) { }); return templates; } + +require('../promisify')(admin); diff --git a/src/widgets/index.js b/src/widgets/index.js index fcb9ada3f3..4fcef7fc2e 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -269,3 +269,5 @@ widgets.resetTemplate = function (template, callback) { widgets.resetTemplates = function (templates, callback) { async.eachSeries(templates, widgets.resetTemplate, callback); }; + +require('../promisify')(widgets);