add population to challenge.group, challenge.leader and group.leader

This commit is contained in:
Matteo Pagliazzi
2016-02-02 21:47:12 +01:00
parent 509dffd0c7
commit 7a5aa731db
13 changed files with 207 additions and 62 deletions

View File

@@ -27,22 +27,38 @@ describe('GET challenges/group/:groupId', () => {
challenge2 = await generateChallenge(user, group); challenge2 = await generateChallenge(user, group);
}); });
it('should return group challenges for non member', async () => { it('should return group challenges for non member with populated leader', async () => {
let challenges = await nonMember.get(`/challenges/groups/${publicGuild._id}`); let challenges = await nonMember.get(`/challenges/groups/${publicGuild._id}`);
let foundChallenge1 = _.find(challenges, { _id: challenge._id }); let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist; expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: publicGuild.leader._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist; expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: publicGuild.leader._id,
profile: {name: user.profile.name},
});
}); });
it('should return group challenges for member', async () => { it('should return group challenges for member with populated leader', async () => {
let challenges = await user.get(`/challenges/groups/${publicGuild._id}`); let challenges = await user.get(`/challenges/groups/${publicGuild._id}`);
let foundChallenge1 = _.find(challenges, { _id: challenge._id }); let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist; expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: publicGuild.leader._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist; expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: publicGuild.leader._id,
profile: {name: user.profile.name},
});
}); });
}); });
@@ -76,13 +92,21 @@ describe('GET challenges/group/:groupId', () => {
}); });
}); });
it('should return group challenges for member', async () => { it('should return group challenges for member with populated leader', async () => {
let challenges = await user.get(`/challenges/groups/${privateGuild._id}`); let challenges = await user.get(`/challenges/groups/${privateGuild._id}`);
let foundChallenge1 = _.find(challenges, { _id: challenge._id }); let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist; expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: privateGuild.leader._id,
profile: {name: user.profile.name},
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist; expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: privateGuild.leader._id,
profile: {name: user.profile.name},
});
}); });
}); });
}); });

View File

@@ -5,7 +5,7 @@ import {
} from '../../../../helpers/api-v3-integration.helper'; } from '../../../../helpers/api-v3-integration.helper';
describe('GET challenges/user', () => { describe('GET challenges/user', () => {
let user, member, nonMember, challenge, challenge2; let user, member, nonMember, challenge, challenge2, publicGuild;
before(async () => { before(async () => {
let { group, groupLeader, members } = await createAndPopulateGroup({ let { group, groupLeader, members } = await createAndPopulateGroup({
@@ -18,7 +18,7 @@ describe('GET challenges/user', () => {
}); });
user = groupLeader; user = groupLeader;
publicGuild = group;
member = members[0]; member = members[0];
nonMember = await generateUser(); nonMember = await generateUser();
@@ -33,6 +33,16 @@ describe('GET challenges/user', () => {
let foundChallenge = _.find(challenges, { _id: challenge._id }); let foundChallenge = _.find(challenges, { _id: challenge._id });
expect(foundChallenge).to.exist; expect(foundChallenge).to.exist;
expect(foundChallenge.leader).to.eql({
_id: publicGuild.leader._id,
profile: {name: user.profile.name},
});
expect(foundChallenge.group).to.eql({
_id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
});
}); });
it('should return challenges user has created', async () => { it('should return challenges user has created', async () => {
@@ -40,8 +50,28 @@ describe('GET challenges/user', () => {
let foundChallenge1 = _.find(challenges, { _id: challenge._id }); let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist; expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: publicGuild.leader._id,
profile: {name: user.profile.name},
});
expect(foundChallenge1.group).to.eql({
_id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist; expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: publicGuild.leader._id,
profile: {name: user.profile.name},
});
expect(foundChallenge2.group).to.eql({
_id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
});
}); });
it('should return challenges in user\'s group', async () => { it('should return challenges in user\'s group', async () => {
@@ -49,8 +79,28 @@ describe('GET challenges/user', () => {
let foundChallenge1 = _.find(challenges, { _id: challenge._id }); let foundChallenge1 = _.find(challenges, { _id: challenge._id });
expect(foundChallenge1).to.exist; expect(foundChallenge1).to.exist;
expect(foundChallenge1.leader).to.eql({
_id: publicGuild.leader._id,
profile: {name: user.profile.name},
});
expect(foundChallenge1.group).to.eql({
_id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
});
let foundChallenge2 = _.find(challenges, { _id: challenge2._id }); let foundChallenge2 = _.find(challenges, { _id: challenge2._id });
expect(foundChallenge2).to.exist; expect(foundChallenge2).to.exist;
expect(foundChallenge2.leader).to.eql({
_id: publicGuild.leader._id,
profile: {name: user.profile.name},
});
expect(foundChallenge2.group).to.eql({
_id: publicGuild._id,
type: publicGuild.type,
privacy: publicGuild.privacy,
name: publicGuild.name,
});
}); });
it('should not return challenges user doesn\'t have access to', async () => { it('should not return challenges user doesn\'t have access to', async () => {

View File

@@ -50,7 +50,7 @@ describe('PUT /challenges/:challengeId', () => {
let res = await user.put(`/challenges/${challenge._id}`, { let res = await user.put(`/challenges/${challenge._id}`, {
// ignored // ignored
prize: 33, prize: 33,
groupId: 'blabla', group: 'blabla',
memberCount: 33, memberCount: 33,
tasksOrder: 'new order', tasksOrder: 'new order',
official: true, official: true,
@@ -63,7 +63,7 @@ describe('PUT /challenges/:challengeId', () => {
}); });
expect(res.prize).to.equal(0); expect(res.prize).to.equal(0);
expect(res.groupId).to.equal(privateGuild._id); expect(res.group).to.equal(privateGuild._id);
expect(res.memberCount).to.equal(2); expect(res.memberCount).to.equal(2);
expect(res.tasksOrder).not.to.equal('new order'); expect(res.tasksOrder).not.to.equal('new order');
expect(res.official).to.equal(false); expect(res.official).to.equal(false);

View File

@@ -43,10 +43,6 @@ describe('GET /groups/:id', () => {
expect(group.leader._id).to.eql(leader._id); expect(group.leader._id).to.eql(leader._id);
expect(group.leader.profile.name).to.eql(leader.profile.name); expect(group.leader.profile.name).to.eql(leader.profile.name);
expect(group.leader.items).to.exist;
expect(group.leader.stats).to.exist;
expect(group.leader.achievements).to.exist;
expect(group.leader.contributor).to.exist;
}); });
}); });
}); });

View File

@@ -32,12 +32,17 @@ describe('POST /group', () => {
}); });
it('sets the group leader to the user who created the group', async () => { it('sets the group leader to the user who created the group', async () => {
await expect( let group = await user.post('/groups', {
user.post('/groups', { name: 'Test Public Guild',
name: 'Test Public Guild', type: 'guild',
type: 'guild', });
})
).to.eventually.have.property('leader', user._id); expect(group.leader).to.eql({
_id: user._id,
profile: {
name: user.profile.name,
},
});
}); });
}); });
@@ -86,6 +91,12 @@ describe('POST /group', () => {
expect(publicGuild.type).to.equal(groupType); expect(publicGuild.type).to.equal(groupType);
expect(publicGuild.memberCount).to.equal(1); expect(publicGuild.memberCount).to.equal(1);
expect(publicGuild.privacy).to.equal(groupPrivacy); expect(publicGuild.privacy).to.equal(groupPrivacy);
expect(publicGuild.leader).to.eql({
_id: user._id,
profile: {
name: user.profile.name,
},
});
}); });
}); });
@@ -106,6 +117,12 @@ describe('POST /group', () => {
expect(privateGuild.type).to.equal(groupType); expect(privateGuild.type).to.equal(groupType);
expect(privateGuild.memberCount).to.equal(1); expect(privateGuild.memberCount).to.equal(1);
expect(privateGuild.privacy).to.equal(groupPrivacy); expect(privateGuild.privacy).to.equal(groupPrivacy);
expect(privateGuild.leader).to.eql({
_id: user._id,
profile: {
name: user.profile.name,
},
});
}); });
it('deducts gems from user and adds them to guild bank', async () => { it('deducts gems from user and adds them to guild bank', async () => {
@@ -138,6 +155,12 @@ describe('POST /group', () => {
expect(party.name).to.equal(partyName); expect(party.name).to.equal(partyName);
expect(party.type).to.equal(partyType); expect(party.type).to.equal(partyType);
expect(party.memberCount).to.equal(1); expect(party.memberCount).to.equal(1);
expect(party.leader).to.eql({
_id: user._id,
profile: {
name: user.profile.name,
},
});
}); });
it('does not require gems to create a party', async () => { it('does not require gems to create a party', async () => {

View File

@@ -113,7 +113,7 @@ export async function createAndPopulateGroup (settings = {}) {
// optional details argument for the initial challenge creation and an // optional details argument for the initial challenge creation and an
// optional update argument which will update the challenge via the db // optional update argument which will update the challenge via the db
export async function generateChallenge (challengeCreator, group, details = {}, update = {}) { export async function generateChallenge (challengeCreator, group, details = {}, update = {}) {
details.groupId = group._id; details.group = group._id;
details.name = details.name || 'a challenge'; details.name = details.name || 'a challenge';
details.shortName = details.shortName || 'aChallenge'; details.shortName = details.shortName || 'aChallenge';
details.prize = details.prize || 0; details.prize = details.prize || 0;

View File

@@ -2,7 +2,10 @@ import { authWithHeaders } from '../../middlewares/api-v3/auth';
import _ from 'lodash'; import _ from 'lodash';
import cron from '../../middlewares/api-v3/cron'; import cron from '../../middlewares/api-v3/cron';
import { model as Challenge } from '../../models/challenge'; import { model as Challenge } from '../../models/challenge';
import { model as Group } from '../../models/group'; import {
model as Group,
basicFields as basicGroupFields,
} from '../../models/group';
import { import {
model as User, model as User,
nameFields, nameFields,
@@ -35,12 +38,12 @@ api.createChallenge = {
async handler (req, res) { async handler (req, res) {
let user = res.locals.user; let user = res.locals.user;
req.checkBody('groupId', res.t('groupIdRequired')).notEmpty(); req.checkBody('group', res.t('groupIdRequired')).notEmpty();
let validationErrors = req.validationErrors(); let validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors; if (validationErrors) throw validationErrors;
let groupId = req.body.groupId; let groupId = req.body.group;
let prize = req.body.prize; let prize = req.body.prize;
let group = await Group.getGroup({user, groupId, fields: '-chat', mustBeMember: true}); let group = await Group.getGroup({user, groupId, fields: '-chat', mustBeMember: true});
@@ -92,6 +95,17 @@ api.createChallenge = {
}), group.save()]); }), group.save()]);
let savedChal = results[0]; let savedChal = results[0];
// TODO Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
// await Q.ninvoke(savedChal, 'populate', ['leader', nameFields]); // doc.populate doesn't return a promise
let response = savedChal.toJSON();
response.leader = (await User.findById(response.leader).select(nameFields).exec()).toJSON({minimize: true});
response.group = {
_id: group._id,
name: group.name,
type: group.type,
privacy: group.privacy,
};
await savedChal.syncToUser(user); // (it also saves the user) await savedChal.syncToUser(user); // (it also saves the user)
res.respond(201, savedChal); res.respond(201, savedChal);
}, },
@@ -122,7 +136,7 @@ api.joinChallenge = {
if (!challenge) throw new NotFound(res.t('challengeNotFound')); if (!challenge) throw new NotFound(res.t('challengeNotFound'));
if (challenge.isMember(user)) throw new NotAuthorized(res.t('userAlreadyInChallenge')); if (challenge.isMember(user)) throw new NotAuthorized(res.t('userAlreadyInChallenge'));
let group = await Group.getGroup({user, groupId: challenge.groupId, fields: '_id type privacy', optionalMembership: true}); let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true});
if (!group || !challenge.hasAccess(user, group)) throw new NotFound(res.t('challengeNotFound')); if (!group || !challenge.hasAccess(user, group)) throw new NotFound(res.t('challengeNotFound'));
challenge.memberCount += 1; challenge.memberCount += 1;
@@ -158,7 +172,7 @@ api.leaveChallenge = {
let challenge = await Challenge.findOne({ _id: req.params.challengeId }); let challenge = await Challenge.findOne({ _id: req.params.challengeId });
if (!challenge) throw new NotFound(res.t('challengeNotFound')); if (!challenge) throw new NotFound(res.t('challengeNotFound'));
let group = await Group.getGroup({user, groupId: challenge.groupId, fields: '_id type privacy'}); let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy'});
if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound'));
if (!challenge.isMember(user)) throw new NotAuthorized(res.t('challengeMemberNotFound')); if (!challenge.isMember(user)) throw new NotAuthorized(res.t('challengeMemberNotFound'));
@@ -186,25 +200,32 @@ api.getUserChallenges = {
async handler (req, res) { async handler (req, res) {
let user = res.locals.user; let user = res.locals.user;
let groups = user.guilds.slice(0); // slice is used to clone the array so we don't modify it directly
if (user.party._id) groups.push(user.party._id);
groups.push('habitrpg'); // tavern challenges
let challenges = await Challenge.find({ let challenges = await Challenge.find({
$or: [ $or: [
{_id: {$in: user.challenges}}, // Challenges where the user is participating {_id: {$in: user.challenges}}, // Challenges where the user is participating
{groupId: {$in: groups}}, // Challenges in groups where I'm a member {group: {$in: user.getGroups()}}, // Challenges in groups where I'm a member
{leader: user._id}, // Challenges where I'm the leader {leader: user._id}, // Challenges where I'm the leader
], ],
_id: {$ne: '95533e05-1ff9-4e46-970b-d77219f199e9'}, // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug TODO revisit _id: {$ne: '95533e05-1ff9-4e46-970b-d77219f199e9'}, // remove the Spread the Word Challenge for now, will revisit when we fix the closing-challenge bug TODO revisit
}) })
.sort('-official -timestamp') .sort('-official -timestamp')
// TODO populate // .populate('group', basicGroupFields)
// .populate('group', '_id name type') // .populate('leader', nameFields)
// .populate('leader', 'profile.name')
.exec(); .exec();
res.respond(200, challenges); let resChals = challenges.map(challenge => challenge.toJSON());
// TODO Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
await Q.all(resChals.map((chal, index) => {
return Q.all([
User.findById(chal.leader).select(nameFields).exec(),
Group.findById(chal.group).select(basicGroupFields).exec(),
]).then(populatedData => {
resChals[index].leader = populatedData[0].toJSON({minimize: true});
resChals[index].group = populatedData[1].toJSON({minimize: true});
});
}));
res.respond(200, resChals);
}, },
}; };
@@ -234,14 +255,20 @@ api.getGroupChallenges = {
let group = await Group.getGroup({user, groupId}); let group = await Group.getGroup({user, groupId});
if (!group) throw new NotFound(res.t('groupNotFound')); if (!group) throw new NotFound(res.t('groupNotFound'));
let challenges = await Challenge.find({groupId}) let challenges = await Challenge.find({group: groupId})
.sort('-official -timestamp') .sort('-official -timestamp')
// TODO populate // .populate('leader', nameFields) // Only populate the leader as the group is implicit
// .populate('group', '_id name type')
// .populate('leader', 'profile.name')
.exec(); .exec();
res.respond(200, challenges); let resChals = challenges.map(challenge => challenge.toJSON());
// TODO Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
await Q.all(resChals.map((chal, index) => {
return User.findById(chal.leader).select(nameFields).exec().then(populatedLeader => {
resChals[index].leader = populatedLeader.toJSON({minimize: true});
});
}));
res.respond(200, resChals);
}, },
}; };
@@ -268,13 +295,21 @@ api.getChallenge = {
let user = res.locals.user; let user = res.locals.user;
let challengeId = req.params.challengeId; let challengeId = req.params.challengeId;
let challenge = await Challenge.findById(challengeId).exec(); let challenge = await Challenge.findById(challengeId)
// .populate('leader', nameFields) // don't populate the group as we'll fetch it manually later
.exec();
if (!challenge) throw new NotFound(res.t('challengeNotFound')); if (!challenge) throw new NotFound(res.t('challengeNotFound'));
let group = await Group.getGroup({user, groupId: challenge.groupId, fields: '_id type privacy', optionalMembership: true}); // Fetching basicGroupFields
let group = await Group.getGroup({user, groupId: challenge.group, fields: basicGroupFields, optionalMembership: true});
if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound'));
res.respond(200, challenge); let chalRes = challenge.toJSON();
chalRes.group = group.toJSON({minimize: true});
// TODO Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
chalRes.leader = (await User.findById(chalRes.leader).select(nameFields).exec()).toJSON({minimize: true});
res.respond(200, chalRes);
}, },
}; };
@@ -301,9 +336,9 @@ api.exportChallengeCsv = {
let user = res.locals.user; let user = res.locals.user;
let challengeId = req.params.challengeId; let challengeId = req.params.challengeId;
let challenge = await Challenge.findById(challengeId).select('_id groupId leader tasksOrder').exec(); let challenge = await Challenge.findById(challengeId).select('_id group leader tasksOrder').exec();
if (!challenge) throw new NotFound(res.t('challengeNotFound')); if (!challenge) throw new NotFound(res.t('challengeNotFound'));
let group = await Group.getGroup({user, groupId: challenge.groupId, fields: '_id type privacy', optionalMembership: true}); let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true});
if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound'));
// In v2 this used the aggregation framework to run some computation on MongoDB but then iterated through all // In v2 this used the aggregation framework to run some computation on MongoDB but then iterated through all
@@ -377,7 +412,7 @@ api.updateChallenge = {
let challenge = await Challenge.findById(challengeId).exec(); let challenge = await Challenge.findById(challengeId).exec();
if (!challenge) throw new NotFound(res.t('challengeNotFound')); if (!challenge) throw new NotFound(res.t('challengeNotFound'));
let group = await Group.getGroup({user, groupId: challenge.groupId, fields: '_id name type privacy', optionalMembership: true}); let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id name type privacy', optionalMembership: true});
if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound'));
if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyLeaderUpdateChal')); if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyLeaderUpdateChal'));
@@ -398,12 +433,12 @@ async function _closeChal (challenge, broken = {}) {
await Challenge.remove({_id: challenge._id}).exec(); await Challenge.remove({_id: challenge._id}).exec();
// Refund the leader if the challenge is closed and the group not the tavern // Refund the leader if the challenge is closed and the group not the tavern
if (challenge.groupId !== 'habitrpg' && brokenReason === 'CHALLENGE_DELETED') { if (challenge.group !== 'habitrpg' && brokenReason === 'CHALLENGE_DELETED') {
await User.update({_id: challenge.leader}, {$inc: {balance: challenge.prize / 4}}).exec(); await User.update({_id: challenge.leader}, {$inc: {balance: challenge.prize / 4}}).exec();
} }
// Update the challengeCount on the group // Update the challengeCount on the group
await Group.update({_id: challenge.groupId}, {$inc: {challengeCount: -1}}).exec(); await Group.update({_id: challenge.group}, {$inc: {challengeCount: -1}}).exec();
// Award prize to winner and notify // Award prize to winner and notify
if (winner) { if (winner) {

View File

@@ -5,8 +5,12 @@ import cron from '../../middlewares/api-v3/cron';
import { import {
INVITES_LIMIT, INVITES_LIMIT,
model as Group, model as Group,
basicFields as basicGroupFields,
} from '../../models/group'; } from '../../models/group';
import { model as User } from '../../models/user'; import {
model as User,
nameFields,
} from '../../models/user';
import { model as EmailUnsubscription } from '../../models/emailUnsubscription'; import { model as EmailUnsubscription } from '../../models/emailUnsubscription';
import { import {
NotFound, NotFound,
@@ -55,11 +59,14 @@ api.createGroup = {
let results = await Q.all([user.save(), group.save()]); let results = await Q.all([user.save(), group.save()]);
let savedGroup = results[1]; let savedGroup = results[1];
// TODO Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
// await Q.ninvoke(savedGroup, 'populate', ['leader', nameFields]); // doc.populate doesn't return a promise
let response = savedGroup.toJSON();
response.leader = (await User.findById(response.leader).select(nameFields).exec()).toJSON({minimize: true});
res.respond(201, response);
firebase.updateGroupData(savedGroup); firebase.updateGroupData(savedGroup);
firebase.addUserToGroup(savedGroup._id, user._id); firebase.addUserToGroup(savedGroup._id, user._id);
return res.respond(201, savedGroup); // TODO populate
}, },
}; };
@@ -87,7 +94,7 @@ api.getGroups = {
// TODO validate types are acceptable? probably not necessary // TODO validate types are acceptable? probably not necessary
let types = req.query.type.split(','); let types = req.query.type.split(',');
let groupFields = 'name description memberCount balance'; let groupFields = basicGroupFields.concat('description memberCount balance');
let sort = '-memberCount'; let sort = '-memberCount';
let queries = []; let queries = [];
@@ -152,7 +159,7 @@ api.getGroup = {
let validationErrors = req.validationErrors(); let validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors; if (validationErrors) throw validationErrors;
let group = await Group.getGroup({user, groupId: req.params.groupId, populateLeader: true}); let group = await Group.getGroup({user, groupId: req.params.groupId, populateLeader: false});
if (!group) throw new NotFound(res.t('groupNotFound')); if (!group) throw new NotFound(res.t('groupNotFound'));
if (!user.contributor.admin) { if (!user.contributor.admin) {
@@ -163,6 +170,8 @@ api.getGroup = {
}); });
} }
// TODO Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
group.leader = (await User.findById(group.leader).select(nameFields).exec()).toJSON({minimize: true});
res.respond(200, group); res.respond(200, group);
}, },
}; };

View File

@@ -74,12 +74,12 @@ function _getMembersForItem (type) {
let group; let group;
if (type === 'challenge-members') { if (type === 'challenge-members') {
challenge = await Challenge.findById(challengeId).select('_id type leader groupId').exec(); challenge = await Challenge.findById(challengeId).select('_id type leader group').exec();
if (!challenge) throw new NotFound(res.t('challengeNotFound')); if (!challenge) throw new NotFound(res.t('challengeNotFound'));
// optionalMembership is set to true because even if you're not member of the group you may be able to access the challenge // optionalMembership is set to true because even if you're not member of the group you may be able to access the challenge
// for example if you've been booted from it, are the leader or a site admin // for example if you've been booted from it, are the leader or a site admin
group = await Group.getGroup({user, groupId: challenge.groupId, fields: '_id type privacy', optionalMembership: true}); group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true});
if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound'));
} else { } else {
group = await Group.getGroup({user, groupId, fields: '_id type'}); group = await Group.getGroup({user, groupId, fields: '_id type'});
@@ -212,7 +212,7 @@ api.getChallengeMemberProgress = {
// optionalMembership is set to true because even if you're not member of the group you may be able to access the challenge // optionalMembership is set to true because even if you're not member of the group you may be able to access the challenge
// for example if you've been booted from it, are the leader or a site admin // for example if you've been booted from it, are the leader or a site admin
let group = await Group.getGroup({user, groupId: challenge.groupId, fields: '_id type privacy', optionalMembership: true}); let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true});
if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound'));
if (!challenge.isMember(member)) throw new NotFound(res.t('challengeMemberNotFound')); if (!challenge.isMember(member)) throw new NotFound(res.t('challengeMemberNotFound'));

View File

@@ -216,9 +216,9 @@ api.getChallengeTasks = {
let user = res.locals.user; let user = res.locals.user;
let challengeId = req.params.challengeId; let challengeId = req.params.challengeId;
let challenge = await Challenge.findOne({_id: challengeId}).select('groupId leader tasksOrder').exec(); let challenge = await Challenge.findOne({_id: challengeId}).select('group leader tasksOrder').exec();
if (!challenge) throw new NotFound(res.t('challengeNotFound')); if (!challenge) throw new NotFound(res.t('challengeNotFound'));
let group = await Group.getGroup({user, groupId: challenge.groupId, fields: '_id type privacy', optionalMembership: true}); let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true});
if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound'));
await _getTasks(req, res, res.locals.user, challenge); await _getTasks(req, res, res.locals.user, challenge);

View File

@@ -20,7 +20,7 @@ let schema = new Schema({
rewards: [{type: String, ref: 'Task'}], rewards: [{type: String, ref: 'Task'}],
}, },
leader: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.'], required: true}, leader: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.'], required: true},
groupId: {type: String, ref: 'Group', validate: [validator.isUUID, 'Invalid uuid.'], required: true}, group: {type: String, ref: 'Group', validate: [validator.isUUID, 'Invalid uuid.'], required: true},
memberCount: {type: Number, default: 1}, memberCount: {type: Number, default: 1},
prize: {type: Number, default: 0, min: 0}, // TODO no update? prize: {type: Number, default: 0, min: 0}, // TODO no update?
}); });
@@ -31,7 +31,7 @@ schema.plugin(baseModel, {
}); });
// A list of additional fields that cannot be updated (but can be set on creation) // A list of additional fields that cannot be updated (but can be set on creation)
let noUpdate = ['groupId', 'official', 'shortName', 'prize']; let noUpdate = ['group', 'official', 'shortName', 'prize'];
schema.statics.sanitizeUpdate = function sanitizeUpdate (updateObj) { schema.statics.sanitizeUpdate = function sanitizeUpdate (updateObj) {
return this.sanitize(updateObj, noUpdate); return this.sanitize(updateObj, noUpdate);
}; };
@@ -49,10 +49,7 @@ schema.methods.canModify = function canModifyChallenge (user) {
// Returns true if user has access to the challenge (can join) // Returns true if user has access to the challenge (can join)
schema.methods.hasAccess = function hasAccessToChallenge (user, group) { schema.methods.hasAccess = function hasAccessToChallenge (user, group) {
if (group.type === 'guild' && group.privacy === 'public') return true; if (group.type === 'guild' && group.privacy === 'public') return true;
let userGroups = user.guilds.slice(0); // clone user.guilds so we don't modify the original return user.getGroups().indexOf(this.group) !== -1;
if (user.party._id) userGroups.push(user.party._id);
userGroups.push('habitrpg'); // tavern
return userGroups.indexOf(this.groupId) !== -1;
}; };
// Returns true if user can view the challenge // Returns true if user can view the challenge

View File

@@ -84,6 +84,9 @@ schema.statics.sanitizeUpdate = function sanitizeUpdate (updateObj) {
return this.sanitize(updateObj, noUpdate); return this.sanitize(updateObj, noUpdate);
}; };
// Basic fields to fetch for populating a group info
export let basicFields = 'name type privacy';
// TODO migration // TODO migration
/** /**
* Derby duplicated stuff. This is a temporary solution, once we're completely off derby we'll run an mongo migration * Derby duplicated stuff. This is a temporary solution, once we're completely off derby we'll run an mongo migration
@@ -472,7 +475,7 @@ schema.methods.leave = async function leaveGroup (user, keep = 'keep-all') {
let challenges = await Challenge.find({ let challenges = await Challenge.find({
_id: {$in: user.challenges}, _id: {$in: user.challenges},
groupId: group._id, group: group._id,
}); });
let challengesToRemoveUserFrom = challenges.map(chal => { let challengesToRemoveUserFrom = challenges.map(chal => {

View File

@@ -654,6 +654,14 @@ schema.methods.isSubscribed = function isSubscribed () {
return !!this.purchased.plan.customerId; // eslint-disable-line no-implicit-coercion return !!this.purchased.plan.customerId; // eslint-disable-line no-implicit-coercion
}; };
// Get an array of groups ids the user is member of
schema.methods.getGroups = function getUserGroups () {
let userGroups = this.guilds.slice(0); // clone user.guilds so we don't modify the original
if (this.party._id) userGroups.push(this.party._id);
userGroups.push('habitrpg'); // tavern
return userGroups;
};
// Unlink challenges tasks (and the challenge itself) from user // Unlink challenges tasks (and the challenge itself) from user
schema.methods.unlinkChallengeTasks = async function unlinkChallengeTasks (challengeId, keep) { schema.methods.unlinkChallengeTasks = async function unlinkChallengeTasks (challengeId, keep) {
let user = this; let user = this;