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:
Keith Holliday
2017-02-27 13:58:30 -07:00
committed by GitHub
parent 30954fe7c5
commit 68a042cdb9
10 changed files with 324 additions and 279 deletions

View File

@@ -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');

View File

@@ -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,

View File

@@ -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];

View File

@@ -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

View File

@@ -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,
} }
}); });
}; };

View File

@@ -1,257 +1,258 @@
{ {
"tavern": "Tavern Chat", "tavern": "Tavern Chat",
"innCheckOut": "Check Out of Inn", "innCheckOut": "Check Out of Inn",
"innCheckIn": "Rest in the Inn", "innCheckIn": "Rest in the Inn",
"innText": "You're resting in the Inn! While checked-in, your Dailies won't hurt you at the day's end, but they will still refresh every day. Be warned: If you are participating in a Boss Quest, the Boss will still damage you for your party mates' missed Dailies unless they are also in the Inn! Also, your own damage to the Boss (or items collected) will not be applied until you check out of the Inn.", "innText": "You're resting in the Inn! While checked-in, your Dailies won't hurt you at the day's end, but they will still refresh every day. Be warned: If you are participating in a Boss Quest, the Boss will still damage you for your party mates' missed Dailies unless they are also in the Inn! Also, your own damage to the Boss (or items collected) will not be applied until you check out of the Inn.",
"innTextBroken": "You're resting in the Inn, I guess... While checked-in, your Dailies won't hurt you at the day's end, but they will still refresh every day... If you are participating in a Boss Quest, the Boss will still damage you for your party mates' missed Dailies... unless they are also in the Inn... Also, your own damage to the Boss (or items collected) will not be applied until you check out of the Inn... so tired...", "innTextBroken": "You're resting in the Inn, I guess... While checked-in, your Dailies won't hurt you at the day's end, but they will still refresh every day... If you are participating in a Boss Quest, the Boss will still damage you for your party mates' missed Dailies... unless they are also in the Inn... Also, your own damage to the Boss (or items collected) will not be applied until you check out of the Inn... so tired...",
"lfgPosts": "Looking for Group (Party Wanted) Posts", "lfgPosts": "Looking for Group (Party Wanted) Posts",
"tutorial": "Tutorial", "tutorial": "Tutorial",
"glossary": "<a target='_blank' href='http://habitica.wikia.com/wiki/Glossary'>Glossary</a>", "glossary": "<a target='_blank' href='http://habitica.wikia.com/wiki/Glossary'>Glossary</a>",
"wiki": "Wiki", "wiki": "Wiki",
"wikiLink": "<a target='_blank' href='http://habitica.wikia.com/'>Wiki</a>", "wikiLink": "<a target='_blank' href='http://habitica.wikia.com/'>Wiki</a>",
"reportAP": "Report a Problem", "reportAP": "Report a Problem",
"requestAF": "Request a Feature", "requestAF": "Request a Feature",
"community": "<a target='_blank' href='http://habitica.wikia.com/wiki/Special:Forum'>Community Forum</a>", "community": "<a target='_blank' href='http://habitica.wikia.com/wiki/Special:Forum'>Community Forum</a>",
"dataTool": "Data Display Tool", "dataTool": "Data Display Tool",
"resources": "Resources", "resources": "Resources",
"askQuestionNewbiesGuild": "Ask a Question (Habitica Help guild)", "askQuestionNewbiesGuild": "Ask a Question (Habitica Help guild)",
"tavernAlert1": "To report a bug, visit", "tavernAlert1": "To report a bug, visit",
"tavernAlert2": "the Report a Bug Guild", "tavernAlert2": "the Report a Bug Guild",
"moderatorIntro1": "Tavern and guild moderators are: ", "moderatorIntro1": "Tavern and guild moderators are: ",
"communityGuidelines": "Community Guidelines", "communityGuidelines": "Community Guidelines",
"communityGuidelinesRead1": "Please read our", "communityGuidelinesRead1": "Please read our",
"communityGuidelinesRead2": "before chatting.", "communityGuidelinesRead2": "before chatting.",
"party": "Party", "party": "Party",
"createAParty": "Create A Party", "createAParty": "Create A Party",
"updatedParty": "Party settings updated.", "updatedParty": "Party settings updated.",
"noPartyText": "You are either not in a party or your party is taking a while to load. You can either create one and invite friends, or if you want to join an existing party, have them enter your Unique User ID below and then come back here to look for the invitation:", "noPartyText": "You are either not in a party or your party is taking a while to load. You can either create one and invite friends, or if you want to join an existing party, have them enter your Unique User ID below and then come back here to look for the invitation:",
"LFG": "To advertise your new party or find one to join, go to the <%= linkStart %>Party Wanted (Looking for Group)<%= linkEnd %> Guild.", "LFG": "To advertise your new party or find one to join, go to the <%= linkStart %>Party Wanted (Looking for Group)<%= linkEnd %> Guild.",
"wantExistingParty": "Want to join an existing party? Go to the <%= linkStart %>Party Wanted Guild<%= linkEnd %> and post this User ID:", "wantExistingParty": "Want to join an existing party? Go to the <%= linkStart %>Party Wanted Guild<%= linkEnd %> and post this User ID:",
"joinExistingParty": "Join Someone Else's Party", "joinExistingParty": "Join Someone Else's Party",
"needPartyToStartQuest": "Whoops! You need to <a href='http://habitica.wikia.com/wiki/Party' target='_blank'>create or join a party</a> before you can start a quest!", "needPartyToStartQuest": "Whoops! You need to <a href='http://habitica.wikia.com/wiki/Party' target='_blank'>create or join a party</a> before you can start a quest!",
"create": "Create", "create": "Create",
"userId": "User ID", "userId": "User ID",
"invite": "Invite", "invite": "Invite",
"leave": "Leave", "leave": "Leave",
"invitedTo": "Invited to <%= name %>", "invitedTo": "Invited to <%= name %>",
"invitedToNewParty": "You were invited to join a party! Do you want to leave this party and join <%= partyName %>?", "invitedToNewParty": "You were invited to join a party! Do you want to leave this party and join <%= partyName %>?",
"invitationAcceptedHeader": "Your Invitation has been Accepted", "invitationAcceptedHeader": "Your Invitation has been Accepted",
"invitationAcceptedBody": "<%= username %> accepted your invitation to <%= groupName %>!", "invitationAcceptedBody": "<%= username %> accepted your invitation to <%= groupName %>!",
"joinNewParty": "Join New Party", "joinNewParty": "Join New Party",
"declineInvitation": "Decline Invitation", "declineInvitation": "Decline Invitation",
"partyLoading1": "Your party is being summoned. Please wait...", "partyLoading1": "Your party is being summoned. Please wait...",
"partyLoading2": "Your party is coming in from battle. Please wait...", "partyLoading2": "Your party is coming in from battle. Please wait...",
"partyLoading3": "Your party is gathering. Please wait...", "partyLoading3": "Your party is gathering. Please wait...",
"partyLoading4": "Your party is materializing. Please wait...", "partyLoading4": "Your party is materializing. Please wait...",
"systemMessage": "System Message", "systemMessage": "System Message",
"newMsg": "New message in \"<%= name %>\"", "newMsg": "New message in \"<%= name %>\"",
"chat": "Chat", "chat": "Chat",
"sendChat": "Send Chat", "sendChat": "Send Chat",
"toolTipMsg": "Fetch Recent Messages", "toolTipMsg": "Fetch Recent Messages",
"sendChatToolTip": "You can send a chat from the keyboard by tabbing to the 'Send Chat' button and pressing Enter or by pressing Control (Command on a Mac) + Enter.", "sendChatToolTip": "You can send a chat from the keyboard by tabbing to the 'Send Chat' button and pressing Enter or by pressing Control (Command on a Mac) + Enter.",
"syncPartyAndChat": "Sync Party and Chat", "syncPartyAndChat": "Sync Party and Chat",
"guildBankPop1": "Guild Bank", "guildBankPop1": "Guild Bank",
"guildBankPop2": "Gems which your guild leader can use for challenge prizes.", "guildBankPop2": "Gems which your guild leader can use for challenge prizes.",
"guildGems": "Guild Gems", "guildGems": "Guild Gems",
"editGroup": "Edit Group", "editGroup": "Edit Group",
"newGroupName": "<%= groupType %> Name", "newGroupName": "<%= groupType %> Name",
"groupName": "Group Name", "groupName": "Group Name",
"groupLeader": "Group Leader", "groupLeader": "Group Leader",
"groupID": "Group ID", "groupID": "Group ID",
"groupDescr": "Description shown in public Guilds list (Markdown OK)", "groupDescr": "Description shown in public Guilds list (Markdown OK)",
"logoUrl": "Logo URL", "logoUrl": "Logo URL",
"assignLeader": "Assign Group Leader", "assignLeader": "Assign Group Leader",
"members": "Members", "members": "Members",
"partyList": "Order for party members in header", "partyList": "Order for party members in header",
"banTip": "Boot Member", "banTip": "Boot Member",
"moreMembers": "more members", "moreMembers": "more members",
"invited": "Invited", "invited": "Invited",
"leaderMsg": "Message from group leader (Markdown OK)", "leaderMsg": "Message from group leader (Markdown OK)",
"name": "Name", "name": "Name",
"description": "Description", "description": "Description",
"public": "Public", "public": "Public",
"inviteOnly": "Invite Only", "inviteOnly": "Invite Only",
"gemCost": "The Gem cost promotes high quality Guilds, and is transferred into your Guild's bank for use as prizes in Guild Challenges!", "gemCost": "The Gem cost promotes high quality Guilds, and is transferred into your Guild's bank for use as prizes in Guild Challenges!",
"search": "Search", "search": "Search",
"publicGuilds": "Public Guilds", "publicGuilds": "Public Guilds",
"createGuild": "Create Guild", "createGuild": "Create Guild",
"guild": "Guild", "guild": "Guild",
"guilds": "Guilds", "guilds": "Guilds",
"guildsLink": "<a href='http://habitica.wikia.com/wiki/Guilds' target='_blank'>Guilds</a>", "guildsLink": "<a href='http://habitica.wikia.com/wiki/Guilds' target='_blank'>Guilds</a>",
"sureKick": "Do you really want to remove this member from the party/guild?", "sureKick": "Do you really want to remove this member from the party/guild?",
"optionalMessage": "Optional message", "optionalMessage": "Optional message",
"yesRemove": "Yes, remove them", "yesRemove": "Yes, remove them",
"foreverAlone": "Can't like your own message. Don't be that person.", "foreverAlone": "Can't like your own message. Don't be that person.",
"sortLevel": "Sort by level", "sortLevel": "Sort by level",
"sortRandom": "Sort randomly", "sortRandom": "Sort randomly",
"sortPets": "Sort by number of pets", "sortPets": "Sort by number of pets",
"sortName": "Sort by avatar name", "sortName": "Sort by avatar name",
"sortBackgrounds": "Sort by background", "sortBackgrounds": "Sort by background",
"sortHabitrpgJoined": "Sort by Habitica date joined", "sortHabitrpgJoined": "Sort by Habitica date joined",
"sortHabitrpgLastLoggedIn": "Sort by last time user logged in", "sortHabitrpgLastLoggedIn": "Sort by last time user logged in",
"ascendingSort": "Sort Ascending", "ascendingSort": "Sort Ascending",
"descendingSort": "Sort Descending", "descendingSort": "Sort Descending",
"confirmGuild": "Create Guild for 4 Gems?", "confirmGuild": "Create Guild for 4 Gems?",
"leaveGroupCha": "Leave Guild challenges and...", "leaveGroupCha": "Leave Guild challenges and...",
"confirm": "Confirm", "confirm": "Confirm",
"leaveGroup": "Leave Guild?", "leaveGroup": "Leave Guild?",
"leavePartyCha": "Leave party challenges and...", "leavePartyCha": "Leave party challenges and...",
"leaveParty": "Leave party?", "leaveParty": "Leave party?",
"sendPM": "Send private message", "sendPM": "Send private message",
"send": "Send", "send": "Send",
"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",
"clearAll": "Delete All Messages", "possessiveParty": "<%= name %>'s Party",
"confirmDeleteAllMessages": "Are you sure you want to delete all messages in your inbox? Other users will still see messages you have sent to them.", "clearAll": "Delete All Messages",
"optOutPopover": "Don't like private messages? Click to completely opt out", "confirmDeleteAllMessages": "Are you sure you want to delete all messages in your inbox? Other users will still see messages you have sent to them.",
"block": "Block", "optOutPopover": "Don't like private messages? Click to completely opt out",
"unblock": "Un-block", "block": "Block",
"pm-reply": "Send a reply", "unblock": "Un-block",
"inbox": "Inbox", "pm-reply": "Send a reply",
"messageRequired": "A message is required.", "inbox": "Inbox",
"toUserIDRequired": "A User ID is required", "messageRequired": "A message is required.",
"gemAmountRequired": "A number of gems is required", "toUserIDRequired": "A User ID is required",
"notAuthorizedToSendMessageToThisUser": "Can't send message to this user.", "gemAmountRequired": "A number of gems is required",
"privateMessageGiftGemsMessage": "Hello <%= receiverName %>, <%= senderName %> has sent you <%= gemAmount %> gems!", "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
"privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription! ", "privateMessageGiftGemsMessage": "Hello <%= receiverName %>, <%= senderName %> has sent you <%= gemAmount %> gems!",
"cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.", "privateMessageGiftSubscriptionMessage": "<%= numberOfMonths %> months of subscription! ",
"badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.", "cannotSendGemsToYourself": "Cannot send gems to yourself. Try a subscription instead.",
"abuseFlag": "Report violation of Community Guidelines", "badAmountOfGemsToSend": "Amount must be within 1 and your current number of gems.",
"abuseFlagModalHeading": "Report <%= name %> for violation?", "abuseFlag": "Report violation of Community Guidelines",
"abuseFlagModalBody": "Are you sure you want to report this post? You should ONLY report a post that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reporting a post is a violation of the Community Guidelines and may give you an infraction. Appropriate reasons to flag a post include but are not limited to:<br><br><ul style='margin-left: 10px;'><li>swearing, religous oaths</li><li>bigotry, slurs</li><li>adult topics</li><li>violence, including as a joke</li><li>spam, nonsensical messages</li></ul>", "abuseFlagModalHeading": "Report <%= name %> for violation?",
"abuseFlagModalButton": "Report Violation", "abuseFlagModalBody": "Are you sure you want to report this post? You should ONLY report a post that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Inappropriately reporting a post is a violation of the Community Guidelines and may give you an infraction. Appropriate reasons to flag a post include but are not limited to:<br><br><ul style='margin-left: 10px;'><li>swearing, religous oaths</li><li>bigotry, slurs</li><li>adult topics</li><li>violence, including as a joke</li><li>spam, nonsensical messages</li></ul>",
"abuseReported": "Thank you for reporting this violation. The moderators have been notified.", "abuseFlagModalButton": "Report Violation",
"abuseAlreadyReported": "You have already reported this message.", "abuseReported": "Thank you for reporting this violation. The moderators have been notified.",
"needsText": "Please type a message.", "abuseAlreadyReported": "You have already reported this message.",
"needsTextPlaceholder": "Type your message here.", "needsText": "Please type a message.",
"copyMessageAsToDo": "Copy message as To-Do", "needsTextPlaceholder": "Type your message here.",
"messageAddedAsToDo": "Message copied as To-Do.", "copyMessageAsToDo": "Copy message as To-Do",
"messageWroteIn": "<%= user %> wrote in <%= group %>", "messageAddedAsToDo": "Message copied as To-Do.",
"taskFromInbox": "<%= from %> wrote '<%= message %>'", "messageWroteIn": "<%= user %> wrote in <%= group %>",
"taskTextFromInbox": "Message from <%= from %>", "taskFromInbox": "<%= from %> wrote '<%= message %>'",
"msgPreviewHeading": "Message Preview", "taskTextFromInbox": "Message from <%= from %>",
"leaderOnlyChallenges": "Only group leader can create challenges", "msgPreviewHeading": "Message Preview",
"sendGift": "Send Gift", "leaderOnlyChallenges": "Only group leader can create challenges",
"inviteFriends": "Invite Friends", "sendGift": "Send Gift",
"inviteByEmail": "Invite by Email", "inviteFriends": "Invite Friends",
"inviteByEmailExplanation": "If a friend joins Habitica via your email, they'll automatically be invited to your party!", "inviteByEmail": "Invite by Email",
"inviteFriendsNow": "Invite Friends Now", "inviteByEmailExplanation": "If a friend joins Habitica via your email, they'll automatically be invited to your party!",
"inviteFriendsLater": "Invite Friends Later", "inviteFriendsNow": "Invite Friends Now",
"inviteAlertInfo": "If you have friends already using Habitica, invite them by <a href='http://habitica.wikia.com/wiki/API_Options' target='_blank'>User ID</a> here.", "inviteFriendsLater": "Invite Friends Later",
"inviteExistUser": "Invite Existing Users", "inviteAlertInfo": "If you have friends already using Habitica, invite them by <a href='http://habitica.wikia.com/wiki/API_Options' target='_blank'>User ID</a> here.",
"byColon": "By:", "inviteExistUser": "Invite Existing Users",
"inviteNewUsers": "Invite New Users", "byColon": "By:",
"sendInvitations": "Send Invitations", "inviteNewUsers": "Invite New Users",
"invitationsSent": "Invitations sent!", "sendInvitations": "Send Invitations",
"invitationSent": "Invitation sent!", "invitationsSent": "Invitations sent!",
"inviteAlertInfo2": "Or share this link (copy/paste):", "invitationSent": "Invitation sent!",
"sendGiftHeading": "Send Gift to <%= name %>", "inviteAlertInfo2": "Or share this link (copy/paste):",
"sendGiftGemsBalance": "From <%= number %> Gems", "sendGiftHeading": "Send Gift to <%= name %>",
"sendGiftCost": "Total: $<%= cost %> USD", "sendGiftGemsBalance": "From <%= number %> Gems",
"sendGiftFromBalance": "From Balance", "sendGiftCost": "Total: $<%= cost %> USD",
"sendGiftPurchase": "Purchase", "sendGiftFromBalance": "From Balance",
"sendGiftMessagePlaceholder": "Personal message (optional)", "sendGiftPurchase": "Purchase",
"sendGiftSubscription": "<%= months %> Month(s): $<%= price %> USD", "sendGiftMessagePlaceholder": "Personal message (optional)",
"battleWithFriends": "Battle Monsters With Friends", "sendGiftSubscription": "<%= months %> Month(s): $<%= price %> USD",
"startPartyWithFriends": "Start a Party with your friends!", "battleWithFriends": "Battle Monsters With Friends",
"startAParty": "Start a Party", "startPartyWithFriends": "Start a Party with your friends!",
"addToParty": "Add someone to your party", "startAParty": "Start a Party",
"likePost": "Click if you like this post!", "addToParty": "Add someone to your party",
"partyExplanation1": "Play Habitica with friends to stay accountable!", "likePost": "Click if you like this post!",
"partyExplanation2": "Battle monsters and create Challenges!", "partyExplanation1": "Play Habitica with friends to stay accountable!",
"partyExplanation3": "Invite friends now to earn a Quest Scroll!", "partyExplanation2": "Battle monsters and create Challenges!",
"wantToStartParty": "Do you want to start a party?", "partyExplanation3": "Invite friends now to earn a Quest Scroll!",
"exclusiveQuestScroll": "Inviting a friend to your party will grant you an exclusive Quest Scroll to battle the Basi-List together!", "wantToStartParty": "Do you want to start a party?",
"nameYourParty": "Name your new party!", "exclusiveQuestScroll": "Inviting a friend to your party will grant you an exclusive Quest Scroll to battle the Basi-List together!",
"partyEmpty": "You're the only one in your party. Invite your friends!", "nameYourParty": "Name your new party!",
"partyChatEmpty": "Your party chat is empty! Type a message in the box above to start chatting.", "partyEmpty": "You're the only one in your party. Invite your friends!",
"guildChatEmpty": "This guild's chat is empty! Type a message in the box above to start chatting.", "partyChatEmpty": "Your party chat is empty! Type a message in the box above to start chatting.",
"possessiveParty": "<%= name %>'s Party", "guildChatEmpty": "This guild's chat is empty! Type a message in the box above to start chatting.",
"requestAcceptGuidelines": "If you would like to post messages in the Tavern or any party or guild chat, please first read our <%= linkStart %>Community Guidelines<%= linkEnd %> and then click the button below to indicate that you accept them.", "possessiveParty": "<%= name %>'s Party",
"partyUpName": "Party Up", "requestAcceptGuidelines": "If you would like to post messages in the Tavern or any party or guild chat, please first read our <%= linkStart %>Community Guidelines<%= linkEnd %> and then click the button below to indicate that you accept them.",
"partyOnName": "Party On", "partyUpName": "Party Up",
"partyUpText": "Joined a Party with another person! Have fun battling monsters and supporting each other.", "partyOnName": "Party On",
"partyOnText": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!", "partyUpText": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
"largeGroupNote": "Note: This Guild is now too large to support notifications! Be sure to check back every day to see new messages.", "partyOnText": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
"groupIdRequired": "\"groupId\" must be a valid UUID", "largeGroupNote": "Note: This Guild is now too large to support notifications! Be sure to check back every day to see new messages.",
"groupNotFound": "Group not found or you don't have access.", "groupIdRequired": "\"groupId\" must be a valid UUID",
"groupTypesRequired": "You must supply a valid \"type\" query string.", "groupNotFound": "Group not found or you don't have access.",
"questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.", "groupTypesRequired": "You must supply a valid \"type\" query string.",
"cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.", "questLeaderCannotLeaveGroup": "You cannot leave your party when you have started a quest. Abort the quest first.",
"onlyLeaderCanRemoveMember": "Only group leader can remove a member!", "cannotLeaveWhileActiveQuest": "You cannot leave party during an active quest. Please leave the quest first.",
"memberCannotRemoveYourself": "You cannot remove yourself!", "onlyLeaderCanRemoveMember": "Only group leader can remove a member!",
"groupMemberNotFound": "User not found among group's members", "memberCannotRemoveYourself": "You cannot remove yourself!",
"mustBeGroupMember": "Must be member of the group.", "groupMemberNotFound": "User not found among group's members",
"keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"", "mustBeGroupMember": "Must be member of the group.",
"keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"", "keepOrRemoveAll": "req.query.keep must be either \"keep-all\" or \"remove-all\"",
"canOnlyInviteEmailUuid": "Can only invite using uuids or emails.", "keepOrRemove": "req.query.keep must be either \"keep\" or \"remove\"",
"inviteMissingEmail": "Missing email address in invite.", "canOnlyInviteEmailUuid": "Can only invite using uuids or emails.",
"inviteMissingUuid": "Missing user id in invite", "inviteMissingEmail": "Missing email address in invite.",
"inviteMustNotBeEmpty": "Invite must not be empty.", "inviteMissingUuid": "Missing user id in invite",
"partyMustbePrivate": "Parties must be private", "inviteMustNotBeEmpty": "Invite must not be empty.",
"userAlreadyInGroup": "User already in that group.", "partyMustbePrivate": "Parties must be private",
"cannotInviteSelfToGroup": "You cannot invite yourself to a group.", "userAlreadyInGroup": "User already in that group.",
"userAlreadyInvitedToGroup": "User already invited to that group.", "cannotInviteSelfToGroup": "You cannot invite yourself to a group.",
"userAlreadyPendingInvitation": "User already pending invitation.", "userAlreadyInvitedToGroup": "User already invited to that group.",
"userAlreadyInAParty": "User already in a party.", "userAlreadyPendingInvitation": "User already pending invitation.",
"userWithIDNotFound": "User with id \"<%= userId %>\" not found.", "userAlreadyInAParty": "User already in a party.",
"userHasNoLocalRegistration": "User does not have a local registration (username, email, password).", "userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
"uuidsMustBeAnArray": "User ID invites must be an array.", "userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
"emailsMustBeAnArray": "Email address invites must be an array.", "uuidsMustBeAnArray": "User ID invites must be an array.",
"canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time", "emailsMustBeAnArray": "Email address invites must be an array.",
"onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!", "canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
"onlyGroupLeaderCanEditTasks": "Not authorized to manage tasks!", "onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!",
"onlyGroupTasksCanBeAssigned": "Only group tasks can be assigned", "onlyGroupLeaderCanEditTasks": "Not authorized to manage tasks!",
"newChatMessagePlainNotification": "New message in <%= groupName %> by <%= authorName %>. Click here to open the chat page!", "onlyGroupTasksCanBeAssigned": "Only group tasks can be assigned",
"newChatMessageTitle": "New message in <%= groupName %>", "newChatMessagePlainNotification": "New message in <%= groupName %> by <%= authorName %>. Click here to open the chat page!",
"exportInbox": "Export Messages", "newChatMessageTitle": "New message in <%= groupName %>",
"exportInboxPopoverTitle": "Export your messages as HTML", "exportInbox": "Export Messages",
"exportInboxPopoverBody": "HTML allows easy reading of messages in a browser. For a machine-readable format, use Data > Export Data", "exportInboxPopoverTitle": "Export your messages as HTML",
"to": "To:", "exportInboxPopoverBody": "HTML allows easy reading of messages in a browser. For a machine-readable format, use Data > Export Data",
"from": "From:", "to": "To:",
"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.", "from": "From:",
"confirmAddTag": "Do you want to assign this task to \"<%= tag %>\"?", "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.",
"confirmRemoveTag": "Do you really want to remove \"<%= tag %>\"?", "confirmAddTag": "Do you want to assign this task to \"<%= 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.",
"claim": "Claim", "claim": "Claim",
"onlyGroupLeaderCanManageSubscription": "Only the group leader can manage the group's subscription", "onlyGroupLeaderCanManageSubscription": "Only the group leader can manage the group's subscription",
"yourTaskHasBeenApproved": "Your task \"<%= taskText %>\" has been approved", "yourTaskHasBeenApproved": "Your task \"<%= taskText %>\" has been approved",
"userHasRequestedTaskApproval": "<%= user %> has requested task approval for <%= taskName %>", "userHasRequestedTaskApproval": "<%= user %> has requested task approval for <%= taskName %>",
"approve": "Approve", "approve": "Approve",
"approvalTitle": "<%= text %> for user: <%= userName %>", "approvalTitle": "<%= text %> for user: <%= userName %>",
"confirmTaskApproval": "Do you want to reward <%= username %> for completing this task?", "confirmTaskApproval": "Do you want to reward <%= username %> for completing this task?",
"groupSubscriptionPrice": "$9 every month + $3 a month for every additional group member", "groupSubscriptionPrice": "$9 every month + $3 a month for every additional group member",
"groupAdditionalUserCost": " +$3.00/month/user", "groupAdditionalUserCost": " +$3.00/month/user",
"groupBenefitsTitle": "How a group plan can help you", "groupBenefitsTitle": "How a group plan can help you",
"groupBenefitsDescription": "We've just launched the beta version of our group plans! Upgrading to a group plan unlocks some unique features to optimize the social side of Habitica.", "groupBenefitsDescription": "We've just launched the beta version of our group plans! Upgrading to a group plan unlocks some unique features to optimize the social side of Habitica.",
"groupBenefitOneTitle": "Create a shared task list", "groupBenefitOneTitle": "Create a shared task list",
"groupBenefitOneDescription": "Set up a shared task list for the group that everyone can easily view and edit.", "groupBenefitOneDescription": "Set up a shared task list for the group that everyone can easily view and edit.",
"groupBenefitTwoTitle": "Assign tasks to group members", "groupBenefitTwoTitle": "Assign tasks to group members",
"groupBenefitTwoDescription": "Want a coworker to answer a critical email? Need your roommate to pick up the groceries? Just assign them the tasks you create, and they'll automatically appear in that person's task dashboard.", "groupBenefitTwoDescription": "Want a coworker to answer a critical email? Need your roommate to pick up the groceries? Just assign them the tasks you create, and they'll automatically appear in that person's task dashboard.",
"groupBenefitThreeTitle": "Claim a task that you are working on", "groupBenefitThreeTitle": "Claim a task that you are working on",
"groupBenefitThreeDescription": "Stake your claim on any group task with a simple click. Make it clear what everybody is working on!", "groupBenefitThreeDescription": "Stake your claim on any group task with a simple click. Make it clear what everybody is working on!",
"groupBenefitFourTitle": "Mark tasks that require special approval", "groupBenefitFourTitle": "Mark tasks that require special approval",
"groupBenefitFourDescription": "Need to verify that a task really did get done before that user gets their rewards? Just adjust the approval settings for added control.", "groupBenefitFourDescription": "Need to verify that a task really did get done before that user gets their rewards? Just adjust the approval settings for added control.",
"groupBenefitFiveTitle": "Chat privately with your group", "groupBenefitFiveTitle": "Chat privately with your group",
"groupBenefitFiveDescription": "Stay in the loop about important decisions in our easy-to-use chatroom!", "groupBenefitFiveDescription": "Stay in the loop about important decisions in our easy-to-use chatroom!",
"createAGroup": "Create a Group", "createAGroup": "Create a Group",
"assignFieldPlaceholder": "Type a group member's profile name", "assignFieldPlaceholder": "Type a group member's profile name",
"cannotDeleteActiveGroup": "You cannot remove a group with an active subscription", "cannotDeleteActiveGroup": "You cannot remove a group with an active subscription",
"groupTasksTitle": "Group Tasks List", "groupTasksTitle": "Group Tasks List",
"approvalsTitle": "Tasks Awaiting Approval", "approvalsTitle": "Tasks Awaiting Approval",
"upgradeTitle": "Upgrade", "upgradeTitle": "Upgrade",
"blankApprovalsDescription": "When your group completes tasks that need your approval, they'll appear here! Adjust approval requirement settings under task editing.", "blankApprovalsDescription": "When your group completes tasks that need your approval, they'll appear here! Adjust approval requirement settings under task editing.",
"userIsClamingTask": "`<%= username %> has claimed \"<%= task %>\"`", "userIsClamingTask": "`<%= username %> has claimed \"<%= task %>\"`",
"approvalRequested": "Approval Requested", "approvalRequested": "Approval Requested",
"refreshApprovals": "Refresh Approvals", "refreshApprovals": "Refresh Approvals",
"refreshGroupTasks": "Refresh Group Tasks", "refreshGroupTasks": "Refresh Group Tasks",
"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'"
}

View File

@@ -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, {});

View File

@@ -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);

View File

@@ -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);
} }
}; };

View File

@@ -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({