mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 06:37:23 +01:00
Leaving a group (#8517)
* Leaving a group or a guild no longer removes the user from the challenges of that group or guild. * Updating api docs for leaving group to take into account the default path no longer leaving challenges when leaving a group. * Updating api docs for leaving group to take into account the default path no longer leaving challenges when leaving a group. * refactored according to blade's comments to not be a breaking change. The api now accepts a body parameter to specify wether the user should remain in the groups challenges or leave them. The change also adds more tests around this behavior to confirm that it works as expected.
This commit is contained in:
@@ -78,7 +78,7 @@ describe('POST /groups/:groupId/leave', () => {
|
|||||||
expect(leader.newMessages[groupToLeave._id]).to.be.empty;
|
expect(leader.newMessages[groupToLeave._id]).to.be.empty;
|
||||||
});
|
});
|
||||||
|
|
||||||
context('With challenges', () => {
|
context('with challenges', () => {
|
||||||
let challenge;
|
let challenge;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -106,10 +106,25 @@ describe('POST /groups/:groupId/leave', () => {
|
|||||||
|
|
||||||
let userWithChallengeTasks = await leader.get('/user');
|
let userWithChallengeTasks = await leader.get('/user');
|
||||||
|
|
||||||
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
|
||||||
// @TODO find elegant way to assert against the task existing
|
// @TODO find elegant way to assert against the task existing
|
||||||
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
expect(userWithChallengeTasks.tasksOrder.habits).to.not.be.empty;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('keeps the user in the challenge when the keepChallenges parameter is set to remain-in-challenges', async () => {
|
||||||
|
await leader.post(`/groups/${groupToLeave._id}/leave`, {keepChallenges: 'remain-in-challenges'});
|
||||||
|
|
||||||
|
let userWithChallengeTasks = await leader.get('/user');
|
||||||
|
|
||||||
|
expect(userWithChallengeTasks.challenges).to.include(challenge._id);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drops the user in the challenge when the keepChallenges parameter isn\'t set', async () => {
|
||||||
|
await leader.post(`/groups/${groupToLeave._id}/leave`);
|
||||||
|
|
||||||
|
let userWithChallengeTasks = await leader.get('/user');
|
||||||
|
|
||||||
|
expect(userWithChallengeTasks.challenges).to.not.include(challenge._id);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prevents quest leader from leaving a groupToLeave');
|
it('prevents quest leader from leaving a groupToLeave');
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import {
|
|||||||
createAndPopulateGroup,
|
createAndPopulateGroup,
|
||||||
generateGroup,
|
generateGroup,
|
||||||
generateUser,
|
generateUser,
|
||||||
|
generateChallenge,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../helpers/api-integration/v3';
|
||||||
import {
|
import {
|
||||||
@@ -64,6 +65,30 @@ describe('DELETE /user', () => {
|
|||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('reduces memberCount in challenges user is linked to', async () => {
|
||||||
|
let populatedGroup = await createAndPopulateGroup({
|
||||||
|
members: 2,
|
||||||
|
});
|
||||||
|
|
||||||
|
let group = populatedGroup.group;
|
||||||
|
let authorizedUser = populatedGroup.members[1];
|
||||||
|
|
||||||
|
let challenge = await generateChallenge(populatedGroup.groupLeader, group);
|
||||||
|
await authorizedUser.post(`/challenges/${challenge._id}/join`);
|
||||||
|
|
||||||
|
await challenge.sync();
|
||||||
|
|
||||||
|
expect(challenge.memberCount).to.eql(2);
|
||||||
|
|
||||||
|
await authorizedUser.del('/user', {
|
||||||
|
password,
|
||||||
|
});
|
||||||
|
|
||||||
|
await challenge.sync();
|
||||||
|
|
||||||
|
expect(challenge.memberCount).to.eql(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('deletes the user', async () => {
|
it('deletes the user', async () => {
|
||||||
await user.del('/user', {
|
await user.del('/user', {
|
||||||
password,
|
password,
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ habitrpg.controller("GuildsCtrl", ['$scope', 'Groups', 'User', 'Challenges', '$r
|
|||||||
$scope.selectedGroup = undefined;
|
$scope.selectedGroup = undefined;
|
||||||
$scope.popoverEl.popover('destroy');
|
$scope.popoverEl.popover('destroy');
|
||||||
} else {
|
} else {
|
||||||
Groups.Group.leave($scope.selectedGroup._id, keep)
|
Groups.Group.leave($scope.selectedGroup._id, keep, 'remain-in-challenges')
|
||||||
.success(function (data) {
|
.success(function (data) {
|
||||||
var index = User.user.guilds.indexOf($scope.selectedGroup._id);
|
var index = User.user.guilds.indexOf($scope.selectedGroup._id);
|
||||||
delete User.user.guilds[index];
|
delete User.user.guilds[index];
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','
|
|||||||
$scope.selectedGroup = undefined;
|
$scope.selectedGroup = undefined;
|
||||||
$scope.popoverEl.popover('destroy');
|
$scope.popoverEl.popover('destroy');
|
||||||
} else {
|
} else {
|
||||||
Groups.Group.leave($scope.selectedGroup._id, keep)
|
Groups.Group.leave($scope.selectedGroup._id, keep, 'remain-in-challenges')
|
||||||
.then(function (response) {
|
.then(function (response) {
|
||||||
Analytics.updateUser({'partySize':null,'partyID':null});
|
Analytics.updateUser({'partySize':null,'partyID':null});
|
||||||
User.sync().then(function () {
|
User.sync().then(function () {
|
||||||
@@ -197,7 +197,7 @@ habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','
|
|||||||
|
|
||||||
$scope.leaveOldPartyAndJoinNewParty = function(newPartyId, newPartyName) {
|
$scope.leaveOldPartyAndJoinNewParty = function(newPartyId, newPartyName) {
|
||||||
if (confirm('Are you sure you want to delete your party and join ' + newPartyName + '?')) {
|
if (confirm('Are you sure you want to delete your party and join ' + newPartyName + '?')) {
|
||||||
Groups.Group.leave(Groups.data.party._id, false)
|
Groups.Group.leave(Groups.data.party._id, false, 'remain-in-challenges')
|
||||||
.then(function() {
|
.then(function() {
|
||||||
$rootScope.party = $scope.group = {
|
$rootScope.party = $scope.group = {
|
||||||
loadingParty: true
|
loadingParty: true
|
||||||
|
|||||||
@@ -69,12 +69,13 @@ angular.module('habitrpg')
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Group.leave = function(gid, keep) {
|
Group.leave = function(gid, keep, keepChallenges) {
|
||||||
return $http({
|
return $http({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: groupApiURLPrefix + '/' + gid + '/leave',
|
url: groupApiURLPrefix + '/' + gid + '/leave',
|
||||||
data: {
|
data: {
|
||||||
keep: keep,
|
keep: keep,
|
||||||
|
keepChallenges: keepChallenges,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -102,6 +102,7 @@
|
|||||||
"messageSentAlert": "Message sent",
|
"messageSentAlert": "Message sent",
|
||||||
"pmHeading": "Private message to <%= name %>",
|
"pmHeading": "Private message to <%= name %>",
|
||||||
"pmsMarkedRead": "Your private messages have been marked as read",
|
"pmsMarkedRead": "Your private messages have been marked as read",
|
||||||
|
"possessiveParty": "<%= name %>'s Party",
|
||||||
"clearAll": "Delete All Messages",
|
"clearAll": "Delete All Messages",
|
||||||
"confirmDeleteAllMessages": "Are you sure you want to delete all messages in your inbox? Other users will still see messages you have sent to them.",
|
"confirmDeleteAllMessages": "Are you sure you want to delete all messages in your inbox? Other users will still see messages you have sent to them.",
|
||||||
"optOutPopover": "Don't like private messages? Click to completely opt out",
|
"optOutPopover": "Don't like private messages? Click to completely opt out",
|
||||||
@@ -213,7 +214,6 @@
|
|||||||
"desktopNotificationsText": "We need your permission to enable desktop notifications for new messages in party chat! Follow your browser's instructions to turn them on.<br><br>You'll receive these notifications only while you have Habitica open. If you decide you don't like them, they can be disabled in your browser's settings.<br><br>This box will close automatically when a decision is made.",
|
"desktopNotificationsText": "We need your permission to enable desktop notifications for new messages in party chat! Follow your browser's instructions to turn them on.<br><br>You'll receive these notifications only while you have Habitica open. If you decide you don't like them, they can be disabled in your browser's settings.<br><br>This box will close automatically when a decision is made.",
|
||||||
"confirmAddTag": "Do you want to assign this task to \"<%= tag %>\"?",
|
"confirmAddTag": "Do you want to assign this task to \"<%= tag %>\"?",
|
||||||
"confirmRemoveTag": "Do you really want to remove \"<%= tag %>\"?",
|
"confirmRemoveTag": "Do you really want to remove \"<%= tag %>\"?",
|
||||||
|
|
||||||
"groupHomeTitle": "Home",
|
"groupHomeTitle": "Home",
|
||||||
"assignTask": "Assign Task",
|
"assignTask": "Assign Task",
|
||||||
"desktopNotificationsText": "We need your permission to enable desktop notifications for new messages in party chat! Follow your browser's instructions to turn them on.<br><br>You'll receive these notifications only while you have Habitica open. If you decide you don't like them, they can be disabled in your browser's settings.<br><br>This box will close automatically when a decision is made.",
|
"desktopNotificationsText": "We need your permission to enable desktop notifications for new messages in party chat! Follow your browser's instructions to turn them on.<br><br>You'll receive these notifications only while you have Habitica open. If you decide you don't like them, they can be disabled in your browser's settings.<br><br>This box will close automatically when a decision is made.",
|
||||||
@@ -253,5 +253,6 @@
|
|||||||
"claimedBy": "\n\nClaimed by: <%= claimingUsers %>",
|
"claimedBy": "\n\nClaimed by: <%= claimingUsers %>",
|
||||||
"cantDeleteAssignedGroupTasks": "Can't delete group tasks that are assigned to you.",
|
"cantDeleteAssignedGroupTasks": "Can't delete group tasks that are assigned to you.",
|
||||||
"confirmGuildPlanCreation": "Create this group?",
|
"confirmGuildPlanCreation": "Create this group?",
|
||||||
"onlyGroupLeaderCanInviteToGroupPlan": "Only the group leader can invite users to a group with a subscription."
|
"onlyGroupLeaderCanInviteToGroupPlan": "Only the group leader can invite users to a group with a subscription.",
|
||||||
|
"remainOrLeaveChallenges": "req.query.keep must be either 'remain-in-challenges' or 'leave-challenges'"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,8 +195,6 @@ api.leaveChallenge = {
|
|||||||
|
|
||||||
if (!challenge.isMember(user)) throw new NotAuthorized(res.t('challengeMemberNotFound'));
|
if (!challenge.isMember(user)) throw new NotAuthorized(res.t('challengeMemberNotFound'));
|
||||||
|
|
||||||
challenge.memberCount -= 1;
|
|
||||||
|
|
||||||
// Unlink challenge's tasks from user's tasks and save the challenge
|
// Unlink challenge's tasks from user's tasks and save the challenge
|
||||||
await Bluebird.all([challenge.unlinkTasks(user, keep), challenge.save()]);
|
await Bluebird.all([challenge.unlinkTasks(user, keep), challenge.save()]);
|
||||||
res.respond(200, {});
|
res.respond(200, {});
|
||||||
|
|||||||
@@ -524,7 +524,8 @@ function _removeMessagesFromMember (member, groupId) {
|
|||||||
* @apiGroup Group
|
* @apiGroup Group
|
||||||
*
|
*
|
||||||
* @apiParam {String} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
|
* @apiParam {String} groupId The group _id ('party' for the user party and 'habitrpg' for tavern are accepted)
|
||||||
* @apiParam {String="remove-all","keep-all"} keep Query parameter - Whether to keep or not challenges' tasks. Defaults to keep-all
|
* @apiParam (Query) {String="remove-all","keep-all"} keep=keep-all Whether or not to keep challenge tasks belonging to the group being left.
|
||||||
|
* @apiParam (Body) {String="remain-in-challenges","leave-challenges"} [keepChallenges=leave-challenges] Whether or not to remain in the challenges of the group being left.
|
||||||
*
|
*
|
||||||
* @apiSuccess {Object} data An empty object
|
* @apiSuccess {Object} data An empty object
|
||||||
*
|
*
|
||||||
@@ -539,6 +540,7 @@ api.leaveGroup = {
|
|||||||
req.checkParams('groupId', res.t('groupIdRequired')).notEmpty();
|
req.checkParams('groupId', res.t('groupIdRequired')).notEmpty();
|
||||||
// When removing the user from challenges, should we keep the tasks?
|
// When removing the user from challenges, should we keep the tasks?
|
||||||
req.checkQuery('keep', res.t('keepOrRemoveAll')).optional().isIn(['keep-all', 'remove-all']);
|
req.checkQuery('keep', res.t('keepOrRemoveAll')).optional().isIn(['keep-all', 'remove-all']);
|
||||||
|
req.checkBody('keepChallenges', res.t('remainOrLeaveChallenges')).optional().isIn(['remain-in-challenges', 'leave-challenges']);
|
||||||
|
|
||||||
let validationErrors = req.validationErrors();
|
let validationErrors = req.validationErrors();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors) throw validationErrors;
|
||||||
@@ -560,7 +562,7 @@ api.leaveGroup = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await group.leave(user, req.query.keep);
|
await group.leave(user, req.query.keep, req.body.keepChallenges);
|
||||||
|
|
||||||
if (group.purchased.plan && group.purchased.plan.customerId) await payments.updateStripeGroupPlan(group);
|
if (group.purchased.plan && group.purchased.plan.customerId) await payments.updateStripeGroupPlan(group);
|
||||||
|
|
||||||
|
|||||||
@@ -237,13 +237,14 @@ schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
removeFromArray(user.challenges, challengeId);
|
removeFromArray(user.challenges, challengeId);
|
||||||
|
this.memberCount--;
|
||||||
|
|
||||||
if (keep === 'keep-all') {
|
if (keep === 'keep-all') {
|
||||||
await Tasks.Task.update(findQuery, {
|
await Tasks.Task.update(findQuery, {
|
||||||
$set: {challenge: {}},
|
$set: {challenge: {}},
|
||||||
}, {multi: true}).exec();
|
}, {multi: true}).exec();
|
||||||
|
|
||||||
await user.save();
|
return Bluebird.all([user.save(), this.save()]);
|
||||||
} else { // keep = 'remove-all'
|
} else { // keep = 'remove-all'
|
||||||
let tasks = await Tasks.Task.find(findQuery).select('_id type completed').exec();
|
let tasks = await Tasks.Task.find(findQuery).select('_id type completed').exec();
|
||||||
let taskPromises = tasks.map(task => {
|
let taskPromises = tasks.map(task => {
|
||||||
@@ -255,7 +256,7 @@ schema.methods.unlinkTasks = async function challengeUnlinkTasks (user, keep) {
|
|||||||
return task.remove();
|
return task.remove();
|
||||||
});
|
});
|
||||||
user.markModified('tasksOrder');
|
user.markModified('tasksOrder');
|
||||||
taskPromises.push(user.save());
|
taskPromises.push(user.save(), this.save());
|
||||||
return Bluebird.all(taskPromises);
|
return Bluebird.all(taskPromises);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -900,7 +900,7 @@ schema.statics.tavernBoss = async function tavernBoss (user, progress) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
schema.methods.leave = async function leaveGroup (user, keep = 'keep-all') {
|
schema.methods.leave = async function leaveGroup (user, keep = 'keep-all', keepChallenges = 'leave-challenges') {
|
||||||
let group = this;
|
let group = this;
|
||||||
let update = {};
|
let update = {};
|
||||||
|
|
||||||
@@ -908,16 +908,18 @@ schema.methods.leave = async function leaveGroup (user, keep = 'keep-all') {
|
|||||||
throw new NotAuthorized(shared.i18n.t('cannotDeleteActiveGroup'));
|
throw new NotAuthorized(shared.i18n.t('cannotDeleteActiveGroup'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlink user challenge tasks
|
// only remove user from challenges if it's set to leave-challenges
|
||||||
let challenges = await Challenge.find({
|
if (keepChallenges === 'leave-challenges') {
|
||||||
_id: {$in: user.challenges},
|
let challenges = await Challenge.find({
|
||||||
group: group._id,
|
_id: {$in: user.challenges},
|
||||||
}).exec();
|
group: group._id,
|
||||||
|
}).exec();
|
||||||
|
|
||||||
let challengesToRemoveUserFrom = challenges.map(chal => {
|
let challengesToRemoveUserFrom = challenges.map(chal => {
|
||||||
return chal.unlinkTasks(user, keep);
|
return chal.unlinkTasks(user, keep);
|
||||||
});
|
});
|
||||||
await Bluebird.all(challengesToRemoveUserFrom);
|
await Bluebird.all(challengesToRemoveUserFrom);
|
||||||
|
}
|
||||||
|
|
||||||
// Unlink group tasks)
|
// Unlink group tasks)
|
||||||
let assignedTasks = await Tasks.Task.find({
|
let assignedTasks = await Tasks.Task.find({
|
||||||
|
|||||||
Reference in New Issue
Block a user