feat: #7743,groups/index,join

This commit is contained in:
Barış Soner Uşaklı
2019-07-18 17:11:59 -04:00
parent 87b1148fa8
commit d5342a40ba
2 changed files with 183 additions and 297 deletions

View File

@@ -1,13 +1,11 @@
'use strict'; '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'); const Groups = module.exports;
var db = require('../database');
var plugins = require('../plugins');
var utils = require('../utils');
var Groups = module.exports;
require('./data')(Groups); require('./data')(Groups);
require('./create')(Groups); require('./create')(Groups);
@@ -52,237 +50,146 @@ Groups.isPrivilegeGroup = function (groupName) {
return isPrivilegeGroupRegex.test(groupName); return isPrivilegeGroupRegex.test(groupName);
}; };
Groups.getGroupsFromSet = function (set, uid, start, stop, callback) { Groups.getGroupsFromSet = async function (set, uid, start, stop) {
async.waterfall([ let groupNames;
function (next) { if (set === 'groups:visible:name') {
if (set === 'groups:visible:name') { groupNames = await db.getSortedSetRangeByLex(set, '-', '+', start, stop - start + 1);
db.getSortedSetRangeByLex(set, '-', '+', start, stop - start + 1, next); } else {
} else { groupNames = await db.getSortedSetRevRange(set, start, stop);
db.getSortedSetRevRange(set, start, stop, next); }
} if (set === 'groups:visible:name') {
}, groupNames = groupNames.map(name => name.split(':')[1]);
function (groupNames, next) {
if (set === 'groups:visible:name') {
groupNames = groupNames.map(function (name) {
return name.split(':')[1];
});
}
Groups.getGroupsAndMembers(groupNames, next);
},
], callback);
};
Groups.getNonPrivilegeGroups = function (set, start, stop, callback) {
async.waterfall([
function (next) {
db.getSortedSetRevRange(set, start, stop, next);
},
function (groupNames, next) {
groupNames = groupNames.concat(Groups.ephemeralGroups).filter(groupName => !Groups.isPrivilegeGroup(groupName));
Groups.getGroupsData(groupNames, next);
},
], callback);
};
Groups.getGroups = function (set, start, stop, callback) {
db.getSortedSetRevRange(set, start, stop, callback);
};
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) {
if (group) {
group.members = data.members[index] || [];
group.truncated = group.memberCount > group.members.length;
}
});
next(null, data.groups);
},
], callback);
};
Groups.get = function (groupName, options, callback) {
if (!groupName) {
return callback(new Error('[[error:invalid-group]]'));
} }
var stop = -1; return await Groups.getGroupsAndMembers(groupNames);
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);
}
plugins.fireHook('filter:parse.raw', results.base.description, next);
},
function (descriptionParsed, next) {
var groupData = results.base;
groupData.descriptionParsed = descriptionParsed;
groupData.members = results.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);
}; };
Groups.getOwners = function (groupName, callback) { Groups.getNonPrivilegeGroups = async function (set, start, stop) {
db.getSetMembers('group:' + groupName + ':owners', callback); let groupNames = await db.getSortedSetRevRange(set, start, stop);
groupNames = groupNames.concat(Groups.ephemeralGroups).filter(groupName => !Groups.isPrivilegeGroup(groupName));
return await Groups.getGroupsData(groupNames);
}; };
Groups.getOwnersAndMembers = function (groupName, uid, start, stop, callback) { Groups.getGroups = async function (set, start, stop) {
async.waterfall([ return await db.getSortedSetRevRange(set, start, stop);
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) {
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);
}; };
Groups.getByGroupslug = function (slug, options, callback) { Groups.getGroupsAndMembers = async function (groupNames) {
async.waterfall([ const [groups, members] = await Promise.all([
function (next) { Groups.getGroupsData(groupNames),
db.getObjectField('groupslug:groupname', slug, next); Groups.getMemberUsers(groupNames, 0, 3),
}, ]);
function (groupName, next) { groups.forEach(function (group, index) {
if (!groupName) { if (group) {
return next(new Error('[[error:no-group]]')); group.members = members[index] || [];
} group.truncated = group.memberCount > group.members.length;
Groups.get(groupName, options, next); }
}, });
], callback); return groups;
}; };
Groups.getGroupNameByGroupSlug = function (slug, callback) { Groups.get = async function (groupName, options) {
db.getObjectField('groupslug:groupname', slug, callback); if (!groupName) {
throw new Error('[[error:invalid-group]]');
}
let stop = -1;
if (options.truncateUserList) {
stop = (parseInt(options.userListCount, 10) || 4) - 1;
}
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;
}
const descriptionParsed = await plugins.fireHook('filter:parse.raw', groupData.description);
groupData.descriptionParsed = descriptionParsed;
groupData.members = members;
groupData.membersNextStart = stop + 1;
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.isPrivate = function (groupName, callback) { Groups.getOwners = async function (groupName) {
isFieldOn(groupName, 'private', callback); return await db.getSetMembers('group:' + groupName + ':owners');
}; };
Groups.isHidden = function (groupName, callback) { Groups.getOwnersAndMembers = async function (groupName, uid, start, stop) {
isFieldOn(groupName, 'hidden', callback); 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;
}
});
const nonOwners = members.filter(user => user && user.uid && !ownerUids.includes(user.uid.toString()));
return owners.concat(nonOwners);
}; };
function isFieldOn(groupName, field, callback) { Groups.getByGroupslug = async function (slug, options) {
async.waterfall([ const groupName = await db.getObjectField('groupslug:groupname', slug);
function (next) { if (!groupName) {
db.getObjectField('group:' + groupName, field, next); throw new Error('[[error:no-group]]');
}, }
function (value, next) { return await Groups.get(groupName, options);
next(null, parseInt(value, 10) === 1); };
},
], callback); Groups.getGroupNameByGroupSlug = async function (slug) {
return await db.getObjectField('groupslug:groupname', slug);
};
Groups.isPrivate = async function (groupName) {
return await isFieldOn(groupName, 'private');
};
Groups.isHidden = async function (groupName) {
return await isFieldOn(groupName, 'hidden');
};
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)) { if (Array.isArray(name)) {
var slugs = name.map(groupName => utils.slugify(groupName)); const slugs = name.map(groupName => utils.slugify(groupName));
async.waterfall([ const isMembersOfRealGroups = await db.isSortedSetMembers('groups:createtime', name);
async.apply(db.isSortedSetMembers, 'groups:createtime', name), const isMembersOfEphemeralGroups = slugs.map(slug => Groups.ephemeralGroups.includes(slug));
function (isMembersOfRealGroups, next) { return name.map((n, index) => isMembersOfRealGroups[index] || isMembersOfEphemeralGroups[index]);
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);
} }
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)) { if (Array.isArray(slug)) {
db.isObjectFields('groupslug:groupname', slug, callback); return await db.isObjectFields('groupslug:groupname', slug);
} else {
db.isObjectField('groupslug:groupname', slug, callback);
} }
return await db.isObjectField('groupslug:groupname', slug);
}; };
Groups.async = require('../promisify')(Groups); Groups.async = require('../promisify')(Groups);

View File

@@ -8,117 +8,96 @@ const user = require('../user');
const plugins = require('../plugins'); const plugins = require('../plugins');
module.exports = function (Groups) { module.exports = function (Groups) {
Groups.join = function (groupNames, uid, callback) { Groups.join = async function (groupNames, uid) {
callback = callback || function () {};
if (!groupNames) { if (!groupNames) {
return callback(new Error('[[error:invalid-data]]')); throw new Error('[[error:invalid-data]]');
} }
if (Array.isArray(groupNames) && !groupNames.length) { if (Array.isArray(groupNames) && !groupNames.length) {
return setImmediate(callback); return;
} }
if (!Array.isArray(groupNames)) { if (!Array.isArray(groupNames)) {
groupNames = [groupNames]; groupNames = [groupNames];
} }
if (!uid) { if (!uid) {
return callback(new Error('[[error:invalid-uid]]')); throw 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) { const [isMembers, exists, isAdmin] = await Promise.all([
return groupName && !results.exists[index]; Groups.isMemberOfGroups(uid, groupNames),
}); Groups.exists(groupNames),
user.isAdministrator(uid),
]);
groupNames = groupNames.filter(function (groupName, index) { const groupsToCreate = groupNames.filter((groupName, index) => groupName && !exists[index]);
return !results.isMembers[index]; const groupsToJoin = groupNames.filter((groupName, index) => !isMembers[index]);
});
if (!groupNames.length) { if (!groupsToJoin.length) {
return callback(); return;
} }
await createNonExistingGroups(groupsToCreate);
createNonExistingGroups(groupsToCreate, next); const promises = [
}, db.sortedSetsAdd(groupsToJoin.map(groupName => 'group:' + groupName + ':members'), Date.now(), uid),
function (next) { db.incrObjectField(groupsToJoin.map(groupName => 'group:' + groupName), 'memberCount'),
var tasks = [ ];
async.apply(db.sortedSetsAdd, groupNames.map(groupName => 'group:' + groupName + ':members'), Date.now(), uid), if (isAdmin) {
async.apply(db.incrObjectField, groupNames.map(groupName => 'group:' + groupName), 'memberCount'), promises.push(db.setsAdd(groupsToJoin.map(groupName => 'group:' + groupName + ':owners'), uid));
]; }
if (isAdmin) {
tasks.push(async.apply(db.setsAdd, groupNames.map(groupName => 'group:' + groupName + ':owners'), uid));
}
async.parallel(tasks, next); await Promise.all(promises);
},
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);
if (visibleGroups.length) { Groups.clearCache(uid, groupsToJoin);
db.sortedSetAdd('groups:visible:memberCount', visibleGroups.map(groupData => groupData.memberCount), visibleGroups.map(groupData => groupData.name), next);
} else { const groupData = await Groups.getGroupsFields(groupsToJoin, ['name', 'hidden', 'memberCount']);
next(); const visibleGroups = groupData.filter(groupData => groupData && !groupData.hidden);
}
}, if (visibleGroups.length) {
function (next) { await db.sortedSetAdd('groups:visible:memberCount',
setGroupTitleIfNotSet(groupNames, uid, next); visibleGroups.map(groupData => groupData.memberCount),
}, visibleGroups.map(groupData => groupData.name)
function (next) { );
plugins.fireHook('action:group.join', { }
groupNames: groupNames,
uid: uid, await setGroupTitleIfNotSet(groupsToJoin, uid);
});
next(); plugins.fireHook('action:group.join', {
}, groupNames: groupsToJoin,
], callback); uid: uid,
});
}; };
function createNonExistingGroups(groupsToCreate, callback) { async function createNonExistingGroups(groupsToCreate) {
if (!groupsToCreate.length) { if (!groupsToCreate.length) {
return setImmediate(callback); return;
} }
async.eachSeries(groupsToCreate, function (groupName, next) {
Groups.create({ await async.eachSeries(groupsToCreate, async function (groupName) {
name: groupName, try {
hidden: 1, await Groups.create({
}, function (err) { name: groupName,
hidden: 1,
});
} catch (err) {
if (err && err.message !== '[[error:group-already-exists]]') { if (err && err.message !== '[[error:group-already-exists]]') {
winston.error('[groups.join] Could not create new hidden group', err); winston.error('[groups.join] Could not create new hidden group', err);
return next(err); throw err;
} }
next(); }
}); });
}, callback);
} }
function setGroupTitleIfNotSet(groupNames, uid, callback) { async function setGroupTitleIfNotSet(groupNames, uid) {
groupNames = groupNames.filter(function (groupName) { groupNames = groupNames.filter(groupName => groupName !== 'registered-users' && !Groups.isPrivilegeGroup(groupName));
return groupName !== 'registered-users' && !Groups.isPrivilegeGroup(groupName);
});
if (!groupNames.length) { if (!groupNames.length) {
return callback(); return;
} }
db.getObjectField('user:' + uid, 'groupTitle', function (err, currentTitle) { const currentTitle = await db.getObjectField('user:' + uid, 'groupTitle');
if (err || currentTitle || currentTitle === '') { if (currentTitle || currentTitle === '') {
return callback(err); return;
} }
user.setUserField(uid, 'groupTitle', JSON.stringify(groupNames), callback); await user.setUserField(uid, 'groupTitle', JSON.stringify(groupNames));
});
} }
}; };