You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nodebb/src/groups/membership.js

393 lines
11 KiB
JavaScript

'use strict';
8 years ago
var async = require('async');
var _ = require('lodash');
var user = require('../user');
var utils = require('../utils');
var plugins = require('../plugins');
var notifications = require('../notifications');
var db = require('../database');
module.exports = function (Groups) {
Groups.requestMembership = function (groupName, uid, callback) {
async.waterfall([
async.apply(inviteOrRequestMembership, groupName, uid, 'request'),
function (next) {
user.getUserField(uid, 'username', next);
},
function (username, next) {
async.parallel({
notification: function (next) {
notifications.create({
6 years ago
type: 'group-request-membership',
bodyShort: '[[groups:request.notification_title, ' + username + ']]',
bodyLong: '[[groups:request.notification_text, ' + username + ', ' + groupName + ']]',
nid: 'group:' + groupName + ':uid:' + uid + ':request',
9 years ago
path: '/groups/' + utils.slugify(groupName),
from: uid,
}, next);
},
owners: function (next) {
Groups.getOwners(groupName, next);
},
}, next);
},
function (results, next) {
if (!results.notification || !results.owners.length) {
return next();
}
notifications.push(results.notification, results.owners, next);
},
], callback);
};
Groups.acceptMembership = function (groupName, uid, callback) {
async.waterfall([
7 years ago
async.apply(db.setsRemove, ['group:' + groupName + ':pending', 'group:' + groupName + ':invited'], uid),
async.apply(Groups.join, groupName, uid),
], callback);
};
Groups.rejectMembership = function (groupNames, uid, callback) {
if (!Array.isArray(groupNames)) {
groupNames = [groupNames];
}
var sets = [];
groupNames.forEach(function (groupName) {
sets.push('group:' + groupName + ':pending', 'group:' + groupName + ':invited');
});
db.setsRemove(sets, uid, callback);
};
Groups.invite = function (groupName, uid, callback) {
async.waterfall([
async.apply(inviteOrRequestMembership, groupName, uid, 'invite'),
async.apply(notifications.create, {
type: 'group-invite',
bodyShort: '[[groups:invited.notification_title, ' + groupName + ']]',
bodyLong: '',
nid: 'group:' + groupName + ':uid:' + uid + ':invite',
path: '/groups/' + utils.slugify(groupName),
}),
function (notification, next) {
9 years ago
notifications.push(notification, [uid], next);
},
], callback);
};
function inviteOrRequestMembership(groupName, uid, type, callback) {
if (!(parseInt(uid, 10) > 0)) {
return callback(new Error('[[error:not-logged-in]]'));
}
var hookName = type === 'invite' ? 'action:group.inviteMember' : 'action:group.requestMembership';
var set = type === 'invite' ? 'group:' + groupName + ':invited' : 'group:' + groupName + ':pending';
async.waterfall([
function (next) {
async.parallel({
exists: async.apply(Groups.exists, groupName),
10 years ago
isMember: async.apply(Groups.isMember, uid, groupName),
isPending: async.apply(Groups.isPending, uid, groupName),
isInvited: async.apply(Groups.isInvited, uid, groupName),
}, next);
},
function (checks, next) {
if (!checks.exists) {
return next(new Error('[[error:no-group]]'));
} else if (checks.isMember) {
return callback();
10 years ago
} else if (type === 'invite' && checks.isInvited) {
return callback();
10 years ago
} else if (type === 'request' && checks.isPending) {
return next(new Error('[[error:group-already-requested]]'));
}
10 years ago
db.setAdd(set, uid, next);
},
function (next) {
10 years ago
plugins.fireHook(hookName, {
groupName: groupName,
uid: uid,
});
next();
},
], callback);
}
Groups.getMembers = function (groupName, start, stop, callback) {
db.getSortedSetRevRange('group:' + groupName + ':members', start, stop, callback);
};
Groups.getMemberUsers = function (groupNames, start, stop, callback) {
async.map(groupNames, function (groupName, next) {
8 years ago
async.waterfall([
function (next) {
Groups.getMembers(groupName, start, stop, next);
},
function (uids, next) {
user.getUsersFields(uids, ['uid', 'username', 'picture', 'userslug'], next);
},
], next);
}, callback);
};
Groups.getMembersOfGroups = function (groupNames, callback) {
db.getSortedSetsMembers(groupNames.map(name => 'group:' + name + ':members'), callback);
};
Groups.isMember = function (uid, groupName, callback) {
if (!uid || parseInt(uid, 10) <= 0 || !groupName) {
8 years ago
return setImmediate(callback, null, false);
}
9 years ago
var cacheKey = uid + ':' + groupName;
var isMember = Groups.cache.get(cacheKey);
7 years ago
if (isMember !== undefined) {
Groups.cache.hits += 1;
7 years ago
return setImmediate(callback, null, isMember);
}
Groups.cache.misses += 1;
async.waterfall([
function (next) {
db.isSortedSetMember('group:' + groupName + ':members', uid, next);
},
function (isMember, next) {
Groups.cache.set(cacheKey, isMember);
next(null, isMember);
},
], callback);
};
Groups.isMembers = function (uids, groupName, callback) {
7 years ago
var cachedData = {};
function getFromCache() {
6 years ago
setImmediate(callback, null, uids.map(uid => cachedData[uid + ':' + groupName]));
}
if (!groupName || !uids.length) {
6 years ago
return setImmediate(callback, null, uids.map(() => false));
}
6 years ago
if (groupName === 'guests') {
return setImmediate(callback, null, uids.map(uid => parseInt(uid, 10) === 0));
}
var nonCachedUids = uids.filter(uid => filterNonCached(cachedData, uid, groupName));
if (!nonCachedUids.length) {
return getFromCache(callback);
}
async.waterfall([
function (next) {
db.isSortedSetMembers('group:' + groupName + ':members', nonCachedUids, next);
},
function (isMembers, next) {
nonCachedUids.forEach(function (uid, index) {
7 years ago
cachedData[uid + ':' + groupName] = isMembers[index];
Groups.cache.set(uid + ':' + groupName, isMembers[index]);
});
getFromCache(next);
},
], callback);
};
Groups.isMemberOfGroups = function (uid, groups, callback) {
7 years ago
var cachedData = {};
function getFromCache(next) {
6 years ago
setImmediate(next, null, groups.map(groupName => cachedData[uid + ':' + groupName]));
}
if (!uid || parseInt(uid, 10) <= 0 || !groups.length) {
6 years ago
return callback(null, groups.map(groupName => groupName === 'guests'));
}
6 years ago
var nonCachedGroups = groups.filter(groupName => filterNonCached(cachedData, uid, groupName));
9 years ago
if (!nonCachedGroups.length) {
return getFromCache(callback);
}
async.waterfall([
function (next) {
6 years ago
const nonCachedGroupsMemberSets = nonCachedGroups.map(groupName => 'group:' + groupName + ':members');
db.isMemberOfSortedSets(nonCachedGroupsMemberSets, uid, next);
},
function (isMembers, next) {
nonCachedGroups.forEach(function (groupName, index) {
7 years ago
cachedData[uid + ':' + groupName] = isMembers[index];
Groups.cache.set(uid + ':' + groupName, isMembers[index]);
});
9 years ago
getFromCache(next);
},
], callback);
};
6 years ago
function filterNonCached(cachedData, uid, groupName) {
var isMember = Groups.cache.get(uid + ':' + groupName);
var isInCache = isMember !== undefined;
if (isInCache) {
Groups.cache.hits += 1;
cachedData[uid + ':' + groupName] = isMember;
} else {
Groups.cache.misses += 1;
}
return !isInCache;
}
Groups.isMemberOfAny = function (uid, groups, callback) {
if (!groups.length) {
return setImmediate(callback, null, false);
}
async.waterfall([
function (next) {
Groups.isMemberOfGroups(uid, groups, next);
},
function (isMembers, next) {
next(null, isMembers.some(isMember => !!isMember));
},
], callback);
};
Groups.getMemberCount = function (groupName, callback) {
8 years ago
async.waterfall([
function (next) {
db.getObjectField('group:' + groupName, 'memberCount', next);
},
function (count, next) {
next(null, parseInt(count, 10));
},
], callback);
};
Groups.isMemberOfGroupList = function (uid, groupListKey, callback) {
8 years ago
async.waterfall([
function (next) {
db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, next);
},
function (groupNames, next) {
groupNames = Groups.removeEphemeralGroups(groupNames);
if (!groupNames.length) {
8 years ago
return callback(null, false);
}
8 years ago
Groups.isMemberOfGroups(uid, groupNames, next);
},
function (isMembers, next) {
next(null, isMembers.includes(true));
8 years ago
},
], callback);
};
Groups.isMemberOfGroupsList = function (uid, groupListKeys, callback) {
8 years ago
var uniqueGroups;
var members;
async.waterfall([
function (next) {
const sets = groupListKeys.map(groupName => 'group:' + groupName + ':members');
8 years ago
db.getSortedSetsMembers(sets, next);
},
function (_members, next) {
members = _members;
uniqueGroups = _.uniq(_.flatten(members));
8 years ago
uniqueGroups = Groups.removeEphemeralGroups(uniqueGroups);
8 years ago
Groups.isMemberOfGroups(uid, uniqueGroups, next);
},
function (isMembers, next) {
var map = {};
uniqueGroups.forEach(function (groupName, index) {
map[groupName] = isMembers[index];
});
var result = members.map(function (groupNames) {
for (var i = 0; i < groupNames.length; i += 1) {
if (map[groupNames[i]]) {
return true;
}
}
return false;
});
8 years ago
next(null, result);
},
], callback);
};
Groups.isMembersOfGroupList = function (uids, groupListKey, callback) {
8 years ago
var groupNames;
6 years ago
var results = uids.map(() => false);
8 years ago
async.waterfall([
function (next) {
db.getSortedSetRange('group:' + groupListKey + ':members', 0, -1, next);
},
function (_groupNames, next) {
groupNames = Groups.removeEphemeralGroups(_groupNames);
8 years ago
if (groupNames.length === 0) {
return callback(null, results);
}
8 years ago
async.map(groupNames, function (groupName, next) {
Groups.isMembers(uids, groupName, next);
}, next);
},
function (isGroupMembers, next) {
isGroupMembers.forEach(function (isMembers) {
results.forEach(function (isMember, index) {
if (!isMember && isMembers[index]) {
results[index] = true;
}
});
});
8 years ago
next(null, results);
},
], callback);
};
Groups.isInvited = function (uid, groupName, callback) {
6 years ago
if (uid <= 0) {
8 years ago
return setImmediate(callback, null, false);
}
db.isSetMember('group:' + groupName + ':invited', uid, callback);
};
Groups.isPending = function (uid, groupName, callback) {
6 years ago
if (uid <= 0) {
8 years ago
return setImmediate(callback, null, false);
}
db.isSetMember('group:' + groupName + ':pending', uid, callback);
};
10 years ago
Groups.getPending = function (groupName, callback) {
10 years ago
if (!groupName) {
8 years ago
return setImmediate(callback, null, []);
10 years ago
}
db.getSetMembers('group:' + groupName + ':pending', callback);
};
Groups.kick = function (uid, groupName, isOwner, callback) {
if (isOwner) {
// If the owners set only contains one member, error out!
async.waterfall([
function (next) {
db.setCount('group:' + groupName + ':owners', next);
},
function (numOwners, next) {
if (numOwners <= 1) {
return next(new Error('[[error:group-needs-owner]]'));
}
9 years ago
Groups.leave(groupName, uid, next);
},
], callback);
} else {
Groups.leave(groupName, uid, callback);
}
};
};