feat: #7743 finish groups

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

@ -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;
};
};

@ -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);
}
}
}
};

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

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

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

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

Loading…
Cancel
Save