feat: #7743 finish groups

v1.18.x
Barış Soner Uşaklı 6 years ago
parent a39ca51e06
commit 72def7dfa6

@ -1,118 +1,85 @@
'use strict'; 'use strict';
var async = require('async'); const user = require('../user');
const db = require('./../database');
var user = require('../user');
var db = require('./../database');
module.exports = function (Groups) { module.exports = function (Groups) {
Groups.search = function (query, options, callback) { Groups.search = async function (query, options) {
if (!query) { 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(); groupNames = groupNames.filter(name => name.toLowerCase().includes(query) && name !== 'administrators' && !Groups.isPrivilegeGroup(name));
async.waterfall([ groupNames = groupNames.slice(0, 100);
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);
}
Groups.sort(options.sort, groupsData, next); let groupsData;
}, if (options.showMembers) {
], callback); 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) { switch (strategy) {
case 'count': case 'count':
groups = groups.sort(function (a, b) { groups.sort((a, b) => a.slug > b.slug)
return a.slug > b.slug; .sort((a, b) => b.memberCount - a.memberCount);
}).sort(function (a, b) {
return b.memberCount - a.memberCount;
});
break; break;
case 'date': case 'date':
groups = groups.sort(function (a, b) { groups.sort((a, b) => b.createtime - a.createtime);
return b.createtime - a.createtime;
});
break; break;
case 'alpha': // intentional fall-through case 'alpha': // intentional fall-through
default: default:
groups = groups.sort(function (a, b) { groups.sort((a, b) => (a.slug > b.slug ? 1 : -1));
return a.slug > b.slug ? 1 : -1;
});
} }
next(null, groups); return groups;
}; };
Groups.searchMembers = function (data, callback) { Groups.searchMembers = async function (data) {
if (!data.query) { if (!data.query) {
Groups.getOwnersAndMembers(data.groupName, data.uid, 0, 19, function (err, users) { const users = await Groups.getOwnersAndMembers(data.groupName, data.uid, 0, 19);
callback(err, { users: users }); return { users: users };
});
return;
} }
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); data.paginate = false;
}, const results = await user.search(data);
function (isMembers, next) {
results.users = results.users.filter(function (user, index) { let uids = results.users.map(user => user && user.uid);
return isMembers[index]; const isMembers = await Groups.isMembers(uids, data.groupName);
});
var uids = results.users.map(function (user) { results.users = results.users.filter((user, index) => isMembers[index]);
return user && user.uid;
}); uids = results.users.map(user => user && user.uid);
Groups.ownership.isOwners(uids, data.groupName, next); const isOwners = await Groups.ownership.isOwners(uids, data.groupName);
},
function (isOwners, next) { results.users.forEach(function (user, index) {
results.users.forEach(function (user, index) { if (user) {
if (user) { user.isOwner = isOwners[index];
user.isOwner = isOwners[index]; }
} });
});
results.users.sort(function (a, b) { results.users.sort(function (a, b) {
if (a.isOwner && !b.isOwner) { if (a.isOwner && !b.isOwner) {
return -1; return -1;
} else if (!a.isOwner && b.isOwner) { } else if (!a.isOwner && b.isOwner) {
return 1; return 1;
} }
return 0; return 0;
}); });
next(null, results); return results;
},
], callback);
}; };
}; };

@ -1,337 +1,244 @@
'use strict'; 'use strict';
var async = require('async'); const winston = require('winston');
var winston = require('winston');
var plugins = require('../plugins'); const plugins = require('../plugins');
var utils = require('../utils'); const utils = require('../utils');
var db = require('../database'); const db = require('../database');
var user = require('../user'); const user = require('../user');
const batch = require('../batch');
module.exports = function (Groups) { module.exports = function (Groups) {
Groups.update = function (groupName, values, callback) { Groups.update = async function (groupName, values) {
callback = callback || function () {}; const exists = await db.exists('group:' + groupName);
if (!exists) {
async.waterfall([ throw new Error('[[error:no-group]]');
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 || '';
}
if (values.hasOwnProperty('userTitleEnabled')) { const result = await plugins.fireHook('filter:group.update', {
payload.userTitleEnabled = values.userTitleEnabled ? '1' : '0'; groupName: groupName,
} values: values,
});
values = result.values;
if (values.hasOwnProperty('hidden')) { const payload = {
payload.hidden = values.hidden ? '1' : '0'; description: values.description || '',
} icon: values.icon || '',
labelColor: values.labelColor || '#000000',
textColor: values.textColor || '#ffffff',
};
if (values.hasOwnProperty('private')) { if (values.hasOwnProperty('userTitle')) {
payload.private = values.private ? '1' : '0'; payload.userTitle = values.userTitle || '';
} }
if (values.hasOwnProperty('disableJoinRequests')) { if (values.hasOwnProperty('userTitleEnabled')) {
payload.disableJoinRequests = values.disableJoinRequests ? '1' : '0'; payload.userTitleEnabled = values.userTitleEnabled ? '1' : '0';
} }
async.series([
async.apply(checkNameChange, groupName, values.name), if (values.hasOwnProperty('hidden')) {
function (next) { payload.hidden = values.hidden ? '1' : '0';
if (values.hasOwnProperty('private')) { }
updatePrivacy(groupName, values.private, next);
} else { if (values.hasOwnProperty('private')) {
next(); payload.private = values.private ? '1' : '0';
} }
},
function (next) { if (values.hasOwnProperty('disableJoinRequests')) {
if (values.hasOwnProperty('hidden')) { payload.disableJoinRequests = values.disableJoinRequests ? '1' : '0';
updateVisibility(groupName, values.hidden, next); }
} else {
next(); await checkNameChange(groupName, values.name);
} if (values.hasOwnProperty('private')) {
}, await updatePrivacy(groupName, values.private);
async.apply(db.setObject, 'group:' + groupName, payload), }
async.apply(Groups.renameGroup, groupName, values.name),
], next); if (values.hasOwnProperty('hidden')) {
}, await updateVisibility(groupName, values.hidden);
function (result, next) { }
plugins.fireHook('action:group.update', { await db.setObject('group:' + groupName, payload);
name: groupName, await Groups.renameGroup(groupName, values.name);
values: values,
}); plugins.fireHook('action:group.update', {
next(); name: groupName,
}, values: values,
], callback); });
}; };
function updateVisibility(groupName, hidden, callback) { async function updateVisibility(groupName, hidden) {
if (hidden) { if (hidden) {
async.parallel([ await db.sortedSetRemoveBulk([
async.apply(db.sortedSetsRemove, ['groups:visible:createtime', 'groups:visible:memberCount'], groupName), ['groups:visible:createtime', groupName],
async.apply(db.sortedSetRemove, 'groups:visible:name', groupName.toLowerCase() + ':' + groupName), ['groups:visible:memberCount', groupName],
], callback); ['groups:visible:name', groupName.toLowerCase() + ':' + groupName],
} else { ]);
async.waterfall([ return;
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);
} }
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) { Groups.hide = async function (groupName) {
showHide(groupName, 'hidden', callback); await showHide(groupName, 'hidden');
}; };
Groups.show = function (groupName, callback) { Groups.show = async function (groupName) {
showHide(groupName, 'show', callback); await showHide(groupName, 'show');
}; };
function showHide(groupName, hidden, callback) { async function showHide(groupName, hidden) {
hidden = hidden === 'hidden'; hidden = hidden === 'hidden';
callback = callback || function () {}; await Promise.all([
async.parallel([ db.setObjectField('group:' + groupName, 'hidden', hidden ? 1 : 0),
async.apply(db.setObjectField, 'group:' + groupName, 'hidden', hidden ? 1 : 0), updateVisibility(groupName, hidden),
async.apply(updateVisibility, groupName, hidden), ]);
], function (err) {
callback(err);
});
} }
function updatePrivacy(groupName, isPrivate, callback) { async function updatePrivacy(groupName, isPrivate) {
async.waterfall([ const groupData = await Groups.getGroupFields(groupName, ['private']);
function (next) { const currentlyPrivate = groupData.private === 1;
Groups.getGroupFields(groupName, ['private'], next); if (!currentlyPrivate || currentlyPrivate === isPrivate) {
}, return;
function (currentValue, next) { }
var currentlyPrivate = currentValue.private === 1; const pendingUids = await db.getSetMembers('group:' + groupName + ':pending');
if (!currentlyPrivate || currentlyPrivate === isPrivate) { if (!pendingUids.length) {
return callback(); return;
} }
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);
});
}
function checkNameChange(currentName, newName, callback) { winston.verbose('[groups.update] Group is now public, automatically adding ' + pendingUids.length + ' new members, who were pending prior.');
if (currentName === newName) {
return setImmediate(callback); for (const uid of pendingUids) {
/* eslint-disable no-await-in-loop */
await Groups.join(groupName, uid);
} }
var currentSlug = utils.slugify(currentName); await db.delete('group:' + groupName + ':pending');
var newSlug = utils.slugify(newName); }
if (currentSlug === newSlug) {
return setImmediate(callback); async function checkNameChange(currentName, newName) {
const currentSlug = utils.slugify(currentName);
const newSlug = utils.slugify(newName);
if (currentName === newName || currentSlug === newSlug) {
return;
} }
async.waterfall([ const [group, exists] = await Promise.all([
function (next) { Groups.getGroupData(currentName),
async.parallel({ Groups.existsBySlug(newSlug),
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]]'));
}
if (!results.group) { if (exists) {
return next(new Error('[[error:no-group]]')); throw new Error('[[error:group-already-exists]]');
} }
if (results.group.system) { if (!group) {
return next(new Error('[[error:not-allowed-to-rename-system-group]]')); throw new Error('[[error:no-group]]');
} }
next(); if (group.system) {
}, throw new Error('[[error:not-allowed-to-rename-system-group]]');
], callback); }
} }
Groups.renameGroup = function (oldName, newName, callback) { Groups.renameGroup = async function (oldName, newName) {
if (oldName === newName || !newName || newName.length === 0) { if (oldName === newName || !newName || String(newName).length === 0) {
return setImmediate(callback); 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); const exists = await Groups.exists(newName);
}, if (exists) {
function (exists, next) { throw new Error('[[error:group-already-exists]]');
if (exists) { }
return callback(new Error('[[error:group-already-exists]]'));
} await updateMemberGroupTitles(oldName, newName);
async.series([ await updateNavigationItems(oldName, newName);
async.apply(updateMemberGroupTitles, oldName, newName), await updateWidgets(oldName, newName);
async.apply(updateNavigationItems, oldName, newName), await db.setObject('group:' + oldName, { name: newName, slug: utils.slugify(newName) });
async.apply(updateWidgets, oldName, newName), await db.deleteObjectField('groupslug:groupname', group.slug);
async.apply(db.setObjectField, 'group:' + oldName, 'name', newName), await db.setObjectField('groupslug:groupname', utils.slugify(newName), newName);
async.apply(db.setObjectField, 'group:' + oldName, 'slug', utils.slugify(newName)),
async.apply(db.deleteObjectField, 'groupslug:groupname', group.slug), const allGroups = await db.getSortedSetRange('groups:createtime', 0, -1);
async.apply(db.setObjectField, 'groupslug:groupname', utils.slugify(newName), newName), const keys = allGroups.map(group => 'group:' + group + ':members');
function (next) { await renameGroupsMember(keys, oldName, newName);
db.getSortedSetRange('groups:createtime', 0, -1, function (err, groups) {
if (err) { await db.rename('group:' + oldName, 'group:' + newName);
return next(err); await db.rename('group:' + oldName + ':members', 'group:' + newName + ':members');
} await db.rename('group:' + oldName + ':owners', 'group:' + newName + ':owners');
const keys = groups.map(group => 'group:' + group + ':members'); await db.rename('group:' + oldName + ':pending', 'group:' + newName + ':pending');
renameGroupsMember(keys, oldName, newName, next); await db.rename('group:' + oldName + ':invited', 'group:' + newName + ':invited');
}); await db.rename('group:' + oldName + ':member:pids', 'group:' + newName + ':member:pids');
},
async.apply(db.rename, 'group:' + oldName, 'group:' + newName), await renameGroupsMember(['groups:createtime', 'groups:visible:createtime', 'groups:visible:memberCount'], oldName, newName);
async.apply(db.rename, 'group:' + oldName + ':members', 'group:' + newName + ':members'), await renameGroupsMember(['groups:visible:name'], oldName.toLowerCase() + ':' + oldName, newName.toLowerCase() + ':' + newName);
async.apply(db.rename, 'group:' + oldName + ':owners', 'group:' + newName + ':owners'),
async.apply(db.rename, 'group:' + oldName + ':pending', 'group:' + newName + ':pending'), plugins.fireHook('action:group.rename', {
async.apply(db.rename, 'group:' + oldName + ':invited', 'group:' + newName + ':invited'), old: oldName,
async.apply(db.rename, 'group:' + oldName + ':member:pids', 'group:' + newName + ':member:pids'), new: newName,
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);
}); });
Groups.resetCache();
}; };
function updateMemberGroupTitles(oldName, newName, callback) { async function updateMemberGroupTitles(oldName, newName) {
const batch = require('../batch'); await batch.processSortedSet('group:' + oldName + ':members', async function (uids) {
batch.processSortedSet('group:' + oldName + ':members', function (uids, next) { let usersData = await user.getUsersData(uids);
async.waterfall([ usersData = usersData.filter(userData => userData && userData.groupTitleArray.includes(oldName));
function (next) {
user.getUsersData(uids, next); usersData.forEach(function (userData) {
}, userData.newTitleArray = userData.groupTitleArray.map(oldTitle => (oldTitle === oldName ? newName : oldTitle));
function (usersData, next) { });
usersData = usersData.filter(userData => userData && userData.groupTitleArray.includes(oldName));
async.each(usersData, function (userData, next) { await Promise.all(usersData.map(u => user.setUserField(u.uid, 'groupTitle', JSON.stringify(u.newTitleArray))));
const newTitleArray = userData.groupTitleArray.map(oldTitle => (oldTitle === oldName ? newName : oldTitle)); }, {});
user.setUserField(userData.uid, 'groupTitle', JSON.stringify(newTitleArray), next);
}, next);
},
], next);
}, callback);
} }
function renameGroupsMember(keys, oldName, newName, callback) { async function renameGroupsMember(keys, oldName, newName) {
var scores; const isMembers = await db.isMemberOfSortedSets(keys, oldName);
async.waterfall([ keys = keys.filter((key, index) => isMembers[index]);
function (next) { if (!keys.length) {
db.isMemberOfSortedSets(keys, oldName, next); return;
}, }
function (isMembers, next) { const scores = await db.sortedSetsScore(keys, oldName);
keys = keys.filter((key, index) => isMembers[index]); await db.sortedSetsRemove(keys, oldName);
if (!keys.length) { await db.sortedSetsAdd(keys, scores, newName);
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);
} }
function updateNavigationItems(oldName, newName, callback) { async function updateNavigationItems(oldName, newName) {
const navigation = require('../navigation/admin'); 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([ await navigation.save(navItems);
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);
} }
function updateWidgets(oldName, newName, callback) { async function updateWidgets(oldName, newName) {
const admin = require('../widgets/admin'); const admin = require('../widgets/admin');
const widgets = require('../widgets'); const widgets = require('../widgets');
async.waterfall([
admin.get, const data = await admin.get();
function (data, next) {
async.eachSeries(data.areas, function (area, next) { data.areas.forEach(function (area) {
if (!area.data.length) { area.widgets = area.data;
return setImmediate(next); area.widgets.forEach(function (widget) {
} if (widget && widget.data && Array.isArray(widget.data.groups) && widget.data.groups.includes(oldName)) {
area.widgets = area.data; widget.data.groups.splice(widget.data.groups.indexOf(oldName), 1, newName);
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);
widgets.setArea(area, next); }
}, next); }
},
], callback);
} }
}; };

@ -78,3 +78,5 @@ function getAvailable(callback) {
plugins.fireHook('filter:navigation.available', core, callback); plugins.fireHook('filter:navigation.available', core, callback);
} }
require('../promisify')(admin);

@ -38,3 +38,5 @@ navigation.get = function (uid, callback) {
}, },
], callback); ], callback);
}; };
require('../promisify')(navigation);

@ -115,3 +115,5 @@ function buildTemplatesFromAreas(areas) {
}); });
return templates; return templates;
} }
require('../promisify')(admin);

@ -269,3 +269,5 @@ widgets.resetTemplate = function (template, callback) {
widgets.resetTemplates = function (templates, callback) { widgets.resetTemplates = function (templates, callback) {
async.eachSeries(templates, widgets.resetTemplate, callback); async.eachSeries(templates, widgets.resetTemplate, callback);
}; };
require('../promisify')(widgets);

Loading…
Cancel
Save