mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: #7743,groups/index,join
This commit is contained in:
@@ -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) {
|
||||
Groups.getGroupsFromSet = async function (set, uid, start, stop) {
|
||||
let groupNames;
|
||||
if (set === 'groups:visible:name') {
|
||||
db.getSortedSetRangeByLex(set, '-', '+', start, stop - start + 1, next);
|
||||
groupNames = await db.getSortedSetRangeByLex(set, '-', '+', start, stop - start + 1);
|
||||
} else {
|
||||
db.getSortedSetRevRange(set, start, stop, next);
|
||||
groupNames = await db.getSortedSetRevRange(set, start, stop);
|
||||
}
|
||||
},
|
||||
function (groupNames, next) {
|
||||
if (set === 'groups:visible:name') {
|
||||
groupNames = groupNames.map(function (name) {
|
||||
return name.split(':')[1];
|
||||
});
|
||||
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) {
|
||||
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));
|
||||
Groups.getGroupsData(groupNames, next);
|
||||
},
|
||||
], callback);
|
||||
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) {
|
||||
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 = data.members[index] || [];
|
||||
group.members = members[index] || [];
|
||||
group.truncated = group.memberCount > group.members.length;
|
||||
}
|
||||
});
|
||||
next(null, data.groups);
|
||||
},
|
||||
], callback);
|
||||
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);
|
||||
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;
|
||||
}
|
||||
plugins.fireHook('filter:parse.raw', results.base.description, next);
|
||||
},
|
||||
function (descriptionParsed, next) {
|
||||
var groupData = results.base;
|
||||
|
||||
const descriptionParsed = await plugins.fireHook('filter:parse.raw', groupData.description);
|
||||
groupData.descriptionParsed = descriptionParsed;
|
||||
groupData.members = results.members;
|
||||
groupData.members = 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);
|
||||
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 = function (groupName, callback) {
|
||||
db.getSetMembers('group:' + groupName + ':owners', callback);
|
||||
Groups.getOwners = async function (groupName) {
|
||||
return await db.getSetMembers('group:' + groupName + ':owners');
|
||||
};
|
||||
|
||||
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) {
|
||||
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;
|
||||
ownerUids.push(user.uid.toString());
|
||||
}
|
||||
});
|
||||
|
||||
results.members = results.members.filter(function (user) {
|
||||
return user && user.uid && !ownerUids.includes(user.uid.toString());
|
||||
});
|
||||
results.members = results.owners.concat(results.members);
|
||||
|
||||
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) {
|
||||
Groups.getByGroupslug = async function (slug, options) {
|
||||
const groupName = await db.getObjectField('groupslug:groupname', slug);
|
||||
if (!groupName) {
|
||||
return next(new Error('[[error:no-group]]'));
|
||||
throw new Error('[[error:no-group]]');
|
||||
}
|
||||
Groups.get(groupName, options, next);
|
||||
},
|
||||
], callback);
|
||||
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 slugs = name.map(groupName => utils.slugify(groupName));
|
||||
const isMembersOfRealGroups = await db.isSortedSetMembers('groups:createtime', name);
|
||||
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);
|
||||
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);
|
||||
|
||||
@@ -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]]'));
|
||||
}
|
||||
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];
|
||||
});
|
||||
|
||||
if (!groupNames.length) {
|
||||
return callback();
|
||||
throw new Error('[[error:invalid-uid]]');
|
||||
}
|
||||
|
||||
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'),
|
||||
const [isMembers, exists, isAdmin] = await Promise.all([
|
||||
Groups.isMemberOfGroups(uid, groupNames),
|
||||
Groups.exists(groupNames),
|
||||
user.isAdministrator(uid),
|
||||
]);
|
||||
|
||||
const groupsToCreate = groupNames.filter((groupName, index) => groupName && !exists[index]);
|
||||
const groupsToJoin = groupNames.filter((groupName, index) => !isMembers[index]);
|
||||
|
||||
if (!groupsToJoin.length) {
|
||||
return;
|
||||
}
|
||||
await createNonExistingGroups(groupsToCreate);
|
||||
|
||||
const promises = [
|
||||
db.sortedSetsAdd(groupsToJoin.map(groupName => 'group:' + groupName + ':members'), Date.now(), uid),
|
||||
db.incrObjectField(groupsToJoin.map(groupName => 'group:' + groupName), 'memberCount'),
|
||||
];
|
||||
if (isAdmin) {
|
||||
tasks.push(async.apply(db.setsAdd, groupNames.map(groupName => 'group:' + groupName + ':owners'), uid));
|
||||
promises.push(db.setsAdd(groupsToJoin.map(groupName => 'group:' + groupName + ':owners'), uid));
|
||||
}
|
||||
|
||||
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);
|
||||
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) {
|
||||
db.sortedSetAdd('groups:visible:memberCount', visibleGroups.map(groupData => groupData.memberCount), visibleGroups.map(groupData => groupData.name), next);
|
||||
} else {
|
||||
next();
|
||||
await db.sortedSetAdd('groups:visible:memberCount',
|
||||
visibleGroups.map(groupData => groupData.memberCount),
|
||||
visibleGroups.map(groupData => groupData.name)
|
||||
);
|
||||
}
|
||||
},
|
||||
function (next) {
|
||||
setGroupTitleIfNotSet(groupNames, uid, next);
|
||||
},
|
||||
function (next) {
|
||||
|
||||
await setGroupTitleIfNotSet(groupsToJoin, uid);
|
||||
|
||||
plugins.fireHook('action:group.join', {
|
||||
groupNames: groupNames,
|
||||
groupNames: groupsToJoin,
|
||||
uid: uid,
|
||||
});
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
function createNonExistingGroups(groupsToCreate, callback) {
|
||||
async function createNonExistingGroups(groupsToCreate) {
|
||||
if (!groupsToCreate.length) {
|
||||
return setImmediate(callback);
|
||||
return;
|
||||
}
|
||||
async.eachSeries(groupsToCreate, function (groupName, next) {
|
||||
Groups.create({
|
||||
|
||||
await async.eachSeries(groupsToCreate, async function (groupName) {
|
||||
try {
|
||||
await Groups.create({
|
||||
name: groupName,
|
||||
hidden: 1,
|
||||
}, function (err) {
|
||||
});
|
||||
} 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));
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user