diff --git a/test/api/groups.coffee b/test/api/groups.coffee index 2daa783645..2c2674bbdf 100644 --- a/test/api/groups.coffee +++ b/test/api/groups.coffee @@ -11,7 +11,7 @@ describe "Guilds", -> registerNewUser -> User.findByIdAndUpdate user._id, $set: - "balance": 10 + "balance": 40 , (err, _user) -> done() , true @@ -150,6 +150,28 @@ describe "Guilds", -> expectCode res, 204 done() + it "deletes a group when the last member leaves", (done) -> + groupToDeleteAfterLeave = undefined + request.post(baseURL + "/groups").send( + name: "TestGroupToDeleteAfteLeave" + type: "guild" + privacy: "private" + ).end (res) -> + groupToDeleteAfterLeave = res.body + async.waterfall [ + (cb) -> + request.post(baseURL + "/groups/" + groupToDeleteAfterLeave._id + "/leave") + .end (res) -> + expectCode res, 204 + cb() + + (cb) -> + request.post(baseURL + "/groups/" + groupToDeleteAfterLeave._id) + .end (res) -> + expectCode res, 404 + cb() + ], done + context "removing users groups", -> it "allows guild leaders to remove a member (but not themselves)", (done) -> guildToRemoveMember = undefined diff --git a/website/src/controllers/groups.js b/website/src/controllers/groups.js index 64b45da0e8..774037b8c0 100644 --- a/website/src/controllers/groups.js +++ b/website/src/controllers/groups.js @@ -511,8 +511,77 @@ api.leave = function(req, res, next) { // When removing the user from challenges, should we keep the tasks? var keep = (/^remove-all/i).test(req.query.keep) ? 'remove-all' : 'keep-all'; - - group.leave(user, keep, function(err){ + async.parallel([ + // Remove active quest from user if they're leaving the party + function(cb){ + if (group.type != 'party') return cb(null,{},1); + user.party.quest = Group.cleanQuestProgress(); + user.save(cb); + }, + // Remove user from group challenges + function(cb){ + async.waterfall([ + // Find relevant challenges + function(cb2) { + Challenge.find({ + _id: {$in: user.challenges}, // Challenges I am in + group: group._id // that belong to the group I am leaving + }, cb2); + }, + // Update each challenge + function(challenges, cb2) { + Challenge.update( + {_id:{$in: _.pluck(challenges, '_id')}}, + {$pull:{members:user._id}}, + {multi: true}, + function(err) { + cb2(err, challenges); // pass `challenges` above to cb + } + ); + }, + // Unlink the challenge tasks from user + function(challenges, cb2) { + async.waterfall(challenges.map(function(chal) { + return function(cb3) { + var i = user.challenges.indexOf(chal._id) + if (~i) user.challenges.splice(i,1); + user.unlink({cid:chal._id, keep:keep}, cb3); + } + }), cb2); + } + ], cb); + }, + // Update the group + function(cb){ + var update = {$pull:{members:user._id}}; + if (group.type == 'party' && group.quest.key){ + update['$unset'] = {}; + update['$unset']['quest.members.' + user._id] = 1; + } + // FIXME do we want to remove the group `if group.members.length == 0` ? (well, 1 since the update hasn't gone through yet) + if (group.members.length > 1) { + var seniorMember = _.find(group.members, function (m) {return m != user._id}); + // If the leader is leaving (or if the leader previously left, and this wasn't accounted for) + var leader = group.leader; + if (leader == user._id || !~group.members.indexOf(leader)) { + update['$set'] = update['$set'] || {}; + update['$set'].leader = seniorMember; + } + leader = group.quest && group.quest.leader; + if (leader && (leader == user._id || !~group.members.indexOf(leader))) { + update['$set'] = update['$set'] || {}; + update['$set']['quest.leader'] = seniorMember; + } + update['$inc'] = {memberCount: -1}; + Group.update({_id:group._id}, update, cb); + } else if (group.members.length === 1) { + //We don't delete public groups when they are empty + if (group.privacy === 'private' || group.type === 'party') { + Group.remove({_id:group._id}, cb); + } + } + } + ],function(err){ if (err) return next(err); user = group = keep = null; return res.send(204);