mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-15 21:57:22 +01:00
Allow Multiple Invites to Party (#8683)
* (server) Add parties array to store invites * (server) Lint files * Update joinGroup, rejectGroupInvite, _inviteByUUID, and remove clearPartyInvitation.js * Update user schema: detailed 'invitations.parties' attributes * Code improvement and do not let invite twice * Check if the user is already invited earlier in the code * Added message to invitation page, and show all invitations * Added join party confirmation alert * Small fixes * Created test: allow inviting a user to 2 different parties * Updated tests * Update invitations.parties on more places * Small adjustments * Updates on invitations.party references * Show all invitations when user is already in a party * Fixed notifications counter * Update both 'party' and 'parties' at _handleGroupInvitation * Updated a test * Fixed small mistake at _handleGroupInvitation * More test update * Update invitation.party when removing single invite and small adjust at view
This commit is contained in:
@@ -220,7 +220,7 @@ describe('POST /group/:groupId/join', () => {
|
||||
it('clears invitation from user when joining party', async () => {
|
||||
await invitedUser.post(`/groups/${party._id}/join`);
|
||||
|
||||
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id');
|
||||
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.parties[0].id');
|
||||
});
|
||||
|
||||
it('increments memberCount when joining party', async () => {
|
||||
|
||||
@@ -247,7 +247,7 @@ describe('POST /groups/:groupId/leave', () => {
|
||||
|
||||
let userWithoutInvitation = await invitedUser.get('/user');
|
||||
|
||||
expect(userWithoutInvitation.invitations.party).to.be.empty;
|
||||
expect(userWithoutInvitation.invitations.parties[0]).to.be.empty;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('POST /group/:groupId/reject-invite', () => {
|
||||
it('clears invitation from user', async () => {
|
||||
await invitedUser.post(`/groups/${party._id}/reject-invite`);
|
||||
|
||||
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.party.id');
|
||||
await expect(invitedUser.get('/user')).to.eventually.not.have.deep.property('invitations.parties[0].id');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -177,13 +177,13 @@ describe('POST /groups/:groupId/removeMember/:memberId', () => {
|
||||
});
|
||||
|
||||
it('can remove other invites', async () => {
|
||||
expect(partyInvitedUser.invitations.party).to.not.be.empty;
|
||||
expect(partyInvitedUser.invitations.parties[0]).to.not.be.empty;
|
||||
|
||||
await partyLeader.post(`/groups/${party._id}/removeMember/${partyInvitedUser._id}`);
|
||||
|
||||
let invitedUserWithoutInvite = await partyInvitedUser.get('/user');
|
||||
|
||||
expect(invitedUserWithoutInvite.invitations.party).to.be.empty;
|
||||
expect(invitedUserWithoutInvite.invitations.parties[0]).to.be.empty;
|
||||
});
|
||||
|
||||
it('removes new messages from a member who is removed', async () => {
|
||||
|
||||
@@ -440,7 +440,38 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id);
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
});
|
||||
|
||||
it('allow inviting a user to 2 different parties', async () => {
|
||||
// Create another inviter
|
||||
let inviter2 = await generateUser();
|
||||
|
||||
// Create user to invite
|
||||
let userToInvite = await generateUser();
|
||||
|
||||
// Create second group
|
||||
let party2 = await inviter2.post('/groups', {
|
||||
name: 'Test Party 2',
|
||||
type: 'party',
|
||||
});
|
||||
|
||||
// Invite to first party
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
// Invite to second party
|
||||
await inviter2.post(`/groups/${party2._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
|
||||
// Get updated user
|
||||
let invitedUser = await userToInvite.get('/user');
|
||||
|
||||
expect(invitedUser.invitations.parties.length).to.equal(2);
|
||||
expect(invitedUser.invitations.parties[0].id).to.equal(party._id);
|
||||
expect(invitedUser.invitations.parties[1].id).to.equal(party2._id);
|
||||
});
|
||||
|
||||
it('allow inviting a user if party id is not associated with a real party', async () => {
|
||||
@@ -451,7 +482,7 @@ describe('Post /groups/:groupId/invite', () => {
|
||||
await inviter.post(`/groups/${party._id}/invite`, {
|
||||
uuids: [userToInvite._id],
|
||||
});
|
||||
expect((await userToInvite.get('/user')).invitations.party.id).to.equal(party._id);
|
||||
expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id);
|
||||
});
|
||||
|
||||
it('allows 30 members in a party', async () => {
|
||||
|
||||
@@ -509,11 +509,9 @@ describe('POST /user/auth/local/register', () => {
|
||||
confirmPassword: password,
|
||||
});
|
||||
|
||||
expect(user.invitations.party).to.eql({
|
||||
id: group._id,
|
||||
name: group.name,
|
||||
inviter: groupLeader._id,
|
||||
});
|
||||
expect(user.invitations.parties[0].id).to.eql(group._id);
|
||||
expect(user.invitations.parties[0].name).to.eql(group.name);
|
||||
expect(user.invitations.parties[0].inviter).to.eql(groupLeader._id);
|
||||
});
|
||||
|
||||
it('awards achievement to inviter', async () => {
|
||||
|
||||
@@ -14,7 +14,7 @@ angular.module('habitrpg')
|
||||
|
||||
if (user.purchased && user.purchased.plan && user.purchased.plan.mysteryItems && user.purchased.plan.mysteryItems.length) {
|
||||
return mysteryValue;
|
||||
} else if ((user.invitations.party && user.invitations.party.id) || (user.invitations.guilds && user.invitations.guilds.length > 0)) {
|
||||
} else if ((user.invitations.parties && user.invitations.parties.length > 0) || (user.invitations.guilds && user.invitations.guilds.length > 0)) {
|
||||
return invitationValue;
|
||||
} else if (user.flags.cardReceived) {
|
||||
return cardValue;
|
||||
@@ -76,8 +76,8 @@ angular.module('habitrpg')
|
||||
$scope.getNotificationsCount = function() {
|
||||
var count = 0;
|
||||
|
||||
if($scope.user.invitations.party && $scope.user.invitations.party.id){
|
||||
count++;
|
||||
if($scope.user.invitations.parties){
|
||||
count += $scope.user.invitations.parties.length;
|
||||
}
|
||||
|
||||
if($scope.user.purchased.plan && $scope.user.purchased.plan.mysteryItems.length){
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','Challenges','$state','$compile','Analytics','Quests','Social', 'Achievement', 'Members', 'Tasks',
|
||||
function($rootScope, $scope, Groups, Chat, User, Challenges, $state, $compile, Analytics, Quests, Social, Achievement, Members, Tasks) {
|
||||
habitrpg.controller("PartyCtrl", ['$rootScope','$scope','$window','Groups','Chat','User','Challenges','$state','$compile','Analytics','Quests','Social', 'Achievement', 'Members', 'Tasks',
|
||||
function($rootScope, $scope, $window, Groups, Chat, User, Challenges, $state, $compile, Analytics, Quests, Social, Achievement, Members, Tasks) {
|
||||
|
||||
var PARTY_LOADING_MESSAGES = 4;
|
||||
|
||||
@@ -129,6 +129,10 @@ habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$window.confirm(window.env.t('joinPartyConfirmationText', {partyName: party.name}))){
|
||||
return;
|
||||
}
|
||||
|
||||
Groups.Group.join(party.id)
|
||||
.then(function (response) {
|
||||
$rootScope.party = $scope.group = response.data.data;
|
||||
|
||||
@@ -36,7 +36,9 @@
|
||||
"invite": "Invite",
|
||||
"leave": "Leave",
|
||||
"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, reject all other party invitations and join <%= partyName %>?",
|
||||
"partyInvitationsText": "You have <%= numberInvites %> party invitations! Choose wisely, because you can only be in one party at a time.",
|
||||
"joinPartyConfirmationText": "Are you sure you want to join the party \"<%= partyName %>\"? You can only be in one party at a time. If you join, all other party invitations will be rejected.",
|
||||
"invitationAcceptedHeader": "Your Invitation has been Accepted",
|
||||
"invitationAcceptedBody": "<%= username %> accepted your invitation to <%= groupName %>!",
|
||||
"joinNewParty": "Join New Party",
|
||||
|
||||
@@ -46,6 +46,7 @@ async function _handleGroupInvitation (user, invite) {
|
||||
|
||||
if (group.type === 'party') {
|
||||
user.invitations.party = {id: group._id, name: group.name, inviter};
|
||||
user.invitations.parties.push(user.invitations.party);
|
||||
} else {
|
||||
user.invitations.guilds.push({id: group._id, name: group.name, inviter});
|
||||
}
|
||||
|
||||
@@ -480,29 +480,35 @@ api.joinGroup = {
|
||||
|
||||
let isUserInvited = false;
|
||||
|
||||
if (group.type === 'party' && group._id === user.invitations.party.id) {
|
||||
inviter = user.invitations.party.inviter;
|
||||
user.invitations.party = {}; // Clear invite
|
||||
user.markModified('invitations.party');
|
||||
if (group.type === 'party') {
|
||||
// Check if was invited to party
|
||||
let inviterParty = _.find(user.invitations.parties, {id: group._id});
|
||||
if (inviterParty) {
|
||||
inviter = inviterParty.inviter;
|
||||
|
||||
// invite new user to pending quest
|
||||
if (group.quest.key && !group.quest.active) {
|
||||
user.party.quest.RSVPNeeded = true;
|
||||
user.party.quest.key = group.quest.key;
|
||||
group.quest.members[user._id] = null;
|
||||
group.markModified('quest.members');
|
||||
// Clear all invitations of new user
|
||||
user.invitations.parties = [];
|
||||
user.invitations.party = {};
|
||||
|
||||
// invite new user to pending quest
|
||||
if (group.quest.key && !group.quest.active) {
|
||||
user.party.quest.RSVPNeeded = true;
|
||||
user.party.quest.key = group.quest.key;
|
||||
group.quest.members[user._id] = null;
|
||||
group.markModified('quest.members');
|
||||
}
|
||||
|
||||
// If user was in a different party (when partying solo you can be invited to a new party)
|
||||
// make them leave that party before doing anything
|
||||
if (user.party._id) {
|
||||
let userPreviousParty = await Group.getGroup({user, groupId: user.party._id});
|
||||
if (userPreviousParty) await userPreviousParty.leave(user);
|
||||
}
|
||||
|
||||
user.party._id = group._id; // Set group as user's party
|
||||
|
||||
isUserInvited = true;
|
||||
}
|
||||
|
||||
// If user was in a different party (when partying solo you can be invited to a new party)
|
||||
// make him leave that party before doing anything
|
||||
if (user.party._id) {
|
||||
let userPreviousParty = await Group.getGroup({user, groupId: user.party._id});
|
||||
if (userPreviousParty) await userPreviousParty.leave(user);
|
||||
}
|
||||
|
||||
user.party._id = group._id; // Set group as user's party
|
||||
|
||||
isUserInvited = true;
|
||||
} else if (group.type === 'guild') {
|
||||
let hasInvitation = removeFromArray(user.invitations.guilds, { id: group._id });
|
||||
|
||||
@@ -636,8 +642,9 @@ api.rejectGroupInvite = {
|
||||
let groupId = req.params.groupId;
|
||||
let isUserInvited = false;
|
||||
|
||||
if (groupId === user.invitations.party.id) {
|
||||
user.invitations.party = {};
|
||||
let hasPartyInvitation = removeFromArray(user.invitations.parties, { id: groupId });
|
||||
if (hasPartyInvitation) {
|
||||
user.invitations.party = user.invitations.parties.length > 0 ? user.invitations.parties[user.invitations.parties.length - 1] : {};
|
||||
user.markModified('invitations.party');
|
||||
isUserInvited = true;
|
||||
} else {
|
||||
@@ -804,7 +811,7 @@ api.removeGroupMember = {
|
||||
}
|
||||
|
||||
let isInvited;
|
||||
if (member.invitations.party && member.invitations.party.id === group._id) {
|
||||
if (_.find(member.invitations.parties, {id: group._id})) {
|
||||
isInvited = 'party';
|
||||
} else if (_.findIndex(member.invitations.guilds, {id: group._id}) !== -1) {
|
||||
isInvited = 'guild';
|
||||
@@ -849,7 +856,8 @@ api.removeGroupMember = {
|
||||
removeFromArray(member.invitations.guilds, { id: group._id });
|
||||
}
|
||||
if (isInvited === 'party') {
|
||||
member.invitations.party = {};
|
||||
removeFromArray(member.invitations.parties, { id: group._id });
|
||||
member.invitations.party = member.invitations.parties.length > 0 ? member.invitations.parties[member.invitations.parties.length - 1] : {};
|
||||
member.markModified('invitations.party');
|
||||
}
|
||||
} else {
|
||||
@@ -888,7 +896,8 @@ async function _inviteByUUID (uuid, group, inviter, req, res) {
|
||||
if (group.isSubscribed() && !group.hasNotCancelled()) guildInvite.cancelledPlan = true;
|
||||
userToInvite.invitations.guilds.push(guildInvite);
|
||||
} else if (group.type === 'party') {
|
||||
if (userToInvite.invitations.party.id) {
|
||||
// Do not add to invitations.parties array if the user is already invited to that party
|
||||
if (_.find(userToInvite.invitations.parties, {id: group._id})) {
|
||||
throw new NotAuthorized(res.t('userAlreadyPendingInvitation'));
|
||||
}
|
||||
|
||||
@@ -901,6 +910,8 @@ async function _inviteByUUID (uuid, group, inviter, req, res) {
|
||||
|
||||
let partyInvite = {id: group._id, name: group.name, inviter: inviter._id};
|
||||
if (group.isSubscribed() && !group.hasNotCancelled()) partyInvite.cancelledPlan = true;
|
||||
|
||||
userToInvite.invitations.parties.push(partyInvite);
|
||||
userToInvite.invitations.party = partyInvite;
|
||||
}
|
||||
|
||||
@@ -943,7 +954,7 @@ async function _inviteByUUID (uuid, group, inviter, req, res) {
|
||||
if (group.type === 'guild') {
|
||||
return userInvited.invitations.guilds[userToInvite.invitations.guilds.length - 1];
|
||||
} else if (group.type === 'party') {
|
||||
return userInvited.invitations.party;
|
||||
return userInvited.invitations.parties[userToInvite.invitations.parties.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -390,7 +390,8 @@ schema.methods.removeGroupInvitations = async function removeGroupInvitations ()
|
||||
|
||||
let userUpdates = usersToRemoveInvitationsFrom.map(user => {
|
||||
if (group.type === 'party') {
|
||||
user.invitations.party = {};
|
||||
removeFromArray(user.invitations.parties, { id: group._id });
|
||||
user.invitations.party = user.invitations.parties.length > 0 ? user.invitations.parties[user.invitations.parties.length - 1] : {};
|
||||
this.markModified('invitations.party');
|
||||
} else {
|
||||
removeFromArray(user.invitations.guilds, { id: group._id });
|
||||
|
||||
@@ -384,6 +384,24 @@ let schema = new Schema({
|
||||
party: {type: Schema.Types.Mixed, default: () => {
|
||||
return {};
|
||||
}},
|
||||
parties: [{
|
||||
id: {
|
||||
type: String,
|
||||
ref: 'Group',
|
||||
required: true,
|
||||
validate: [validator.isUUID, 'Invalid uuid.'],
|
||||
},
|
||||
name: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
inviter: {
|
||||
type: String,
|
||||
ref: 'User',
|
||||
required: true,
|
||||
validate: [validator.isUUID, 'Invalid uuid.'],
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
||||
guilds: [{type: String, ref: 'Group', validate: [validator.isUUID, 'Invalid uuid.']}],
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
- var newParty = 'User.user.invitations.party'
|
||||
.containter-fluid(ng-if='#{newParty}.id && group._id')
|
||||
.row.text-center
|
||||
.col-sm-6.col-sm-offset-3.alert.alert-warning
|
||||
p {{::env.t('invitedToNewParty', { partyName: #{newParty}.name })}}
|
||||
p
|
||||
button.btn.btn-success(ng-click='leaveOldPartyAndJoinNewParty(#{newParty}.id, #{newParty}.name)')=env.t('joinNewParty')
|
||||
button.btn.btn-default(ng-click='reject(#{newParty})')=env.t('declineInvitation')
|
||||
- var newParties = 'User.user.invitations.parties'
|
||||
.containter-fluid(ng-if='#{newParties}.length > 0 && group._id')
|
||||
div(ng-repeat='partyInvite in #{newParties}')
|
||||
.row.text-center
|
||||
.col-sm-6.col-sm-offset-3.alert.alert-warning
|
||||
p {{::env.t('invitedToNewParty', { partyName: partyInvite.name })}}
|
||||
p
|
||||
button.btn.btn-success(ng-click='leaveOldPartyAndJoinNewParty(partyInvite.id, partyInvite.name)')=env.t('joinNewParty')
|
||||
button.btn.btn-default(ng-click='reject(partyInvite)')=env.t('declineInvitation')
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
.container-fluid(ng-show='user.invitations.party.id')
|
||||
h2=env.t('invitedTo', {name: '{{user.invitations.party.name}}'})
|
||||
.container-fluid(ng-show='user.invitations.parties.length > 0')
|
||||
h3=env.t('partyInvitationsText', {numberInvites: '{{user.invitations.parties.length}}'})
|
||||
|
||||
a.btn.btn-success(
|
||||
data-type='party',
|
||||
ng-click='join(user.invitations.party)'
|
||||
)=env.t('accept')
|
||||
a.btn.btn-danger(ng-click='reject(user.invitations.party)')=env.t('reject')
|
||||
div(ng-repeat='partyInvite in user.invitations.parties')
|
||||
h2=env.t('invitedTo', {name: '{{partyInvite.name}}'})
|
||||
a.btn.btn-success(
|
||||
data-type='party',
|
||||
ng-click='join(partyInvite)'
|
||||
)=env.t('accept')
|
||||
a.btn.btn-danger(ng-click='reject(partyInvite)')=env.t('reject')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.container.text-center(ng-hide='user.invitations.party.id')
|
||||
.container.text-center(ng-hide='user.invitations.parties.length > 0')
|
||||
.row.row-margin: .col-sm-6.col-sm-offset-3
|
||||
a.btn.btn-primary.btn-lg.btn-block(ng-click="inviteOrStartParty(group)")=env.t("startPartyWithFriends")
|
||||
|
||||
|
||||
@@ -198,10 +198,10 @@ nav.toolbar(ng-controller='MenuCtrl')
|
||||
a(ng-click='$state.go("options.inventory.drops"); ')
|
||||
span.glyphicon.glyphicon-gift
|
||||
span=env.t('newSubscriberItem')
|
||||
li(ng-if='user.invitations.party.id')
|
||||
li(ng-repeat='party in user.invitations.parties')
|
||||
a(ui-sref='options.social.party')
|
||||
span.glyphicon.glyphicon-user
|
||||
span=env.t('invitedTo', {name: '{{user.invitations.party.name}}'})
|
||||
span=env.t('invitedTo', {name: '{{party.name}}'})
|
||||
li(ng-if='user.flags.cardReceived')
|
||||
a(ng-click='$state.go("options.inventory.drops"); ')
|
||||
span.glyphicon.glyphicon-envelope
|
||||
|
||||
Reference in New Issue
Block a user