diff --git a/migrations/archive/2024/2024_purge_invite_accepted.js b/migrations/archive/2024/2024_purge_invite_accepted.js new file mode 100644 index 0000000000..8d9d9b8acd --- /dev/null +++ b/migrations/archive/2024/2024_purge_invite_accepted.js @@ -0,0 +1,47 @@ +/* eslint-disable no-console */ +import { model as User } from '../../../website/server/models/user'; + +const MIGRATION_NAME = '2024_purge_invite_accepted'; +const progressCount = 1000; +let count = 0; + +async function updateUsers (userIds) { + count += userIds.length; + if (count % progressCount === 0) console.warn(`${count} ${userIds[0]}`); + + return await User.updateMany( + { _id: { $in: userIds } }, + { $pull: { notifications: { type: 'GROUP_INVITE_ACCEPTED' } } }, + ).exec(); +} + +export default async function processUsers () { + let query = { + migration: { $ne: MIGRATION_NAME }, + 'notifications.type': 'GROUP_INVITE_ACCEPTED', + 'auth.timestamps.loggedin': { $gt: new Date('2024-06-25') }, + }; + + while (true) { // eslint-disable-line no-constant-condition + const users = await User // eslint-disable-line no-await-in-loop + .find(query) + .limit(250) + .sort({ _id: 1 }) + .select({ _id: 1 }) + .exec(); + + if (users.length === 0) { + console.warn('All appropriate users found and modified.'); + console.warn(`\n${count} users processed\n`); + break; + } else { + query._id = { + $gt: users[users.length - 1], + }; + } + + const userIds = users.map(user => user._id); + + await updateUsers(userIds); // eslint-disable-line no-await-in-loop + } +}; diff --git a/test/api/v3/integration/groups/POST-groups_groupId_join.test.js b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js index 61dfaee833..f279177aed 100644 --- a/test/api/v3/integration/groups/POST-groups_groupId_join.test.js +++ b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js @@ -85,22 +85,6 @@ describe('POST /group/:groupId/join', () => { await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 1); }); - it('notifies inviting user that their invitation was accepted', async () => { - await invitedUser.post(`/groups/${guild._id}/join`); - - const inviter = await user.get('/user'); - const expectedData = { - headerText: t('invitationAcceptedHeader'), - bodyText: t('invitationAcceptedBody', { - username: invitedUser.auth.local.username, - groupName: guild.name, - }), - }; - - expect(inviter.notifications[1].type).to.eql('GROUP_INVITE_ACCEPTED'); - expect(inviter.notifications[1].data).to.eql(expectedData); - }); - it('awards Joined Guild achievement', async () => { await invitedUser.post(`/groups/${guild._id}/join`); @@ -155,23 +139,6 @@ describe('POST /group/:groupId/join', () => { await expect(invitedUser.get('/user')).to.eventually.have.nested.property('party._id', party._id); }); - it('notifies inviting user that their invitation was accepted', async () => { - await invitedUser.post(`/groups/${party._id}/join`); - - const inviter = await user.get('/user'); - - const expectedData = { - headerText: t('invitationAcceptedHeader'), - bodyText: t('invitationAcceptedBody', { - username: invitedUser.auth.local.username, - groupName: party.name, - }), - }; - - expect(inviter.notifications[0].type).to.eql('GROUP_INVITE_ACCEPTED'); - expect(inviter.notifications[0].data).to.eql(expectedData); - }); - it('clears invitation from user when joining party', async () => { await invitedUser.post(`/groups/${party._id}/join`); diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js index a2b46e718e..1c7f3eca23 100644 --- a/website/server/controllers/api-v3/groups.js +++ b/website/server/controllers/api-v3/groups.js @@ -610,7 +610,6 @@ api.joinGroup = { if (hasInvitation) { isUserInvited = true; - inviter = hasInvitation.inviter; } else { isUserInvited = group.privacy !== 'private'; } @@ -634,42 +633,28 @@ api.joinGroup = { group.leader = user._id; // If new user is only member -> set as leader } + let promises = [user.save()]; + if (group.type === 'party') { // For parties we count the number of members from the database to get the correct value. // See #12275 on why this is necessary and only done for parties. const currentMembers = await group.getMemberCount(); - group.memberCount = currentMembers + 1; - } else { - group.memberCount += 1; - } + // Load the inviter + if (inviter) inviter = await User.findById(inviter).exec(); - let promises = [group.save(), user.save()]; - - // Load the inviter - if (inviter) inviter = await User.findById(inviter).exec(); - - // Check the inviter again, could be a deleted account - if (inviter) { - const data = { - headerText: common.i18n.t('invitationAcceptedHeader', inviter.preferences.language), - bodyText: common.i18n.t('invitationAcceptedBody', { - groupName: group.name, - username: user.profile.name, - }, inviter.preferences.language), - }; - inviter.addNotification('GROUP_INVITE_ACCEPTED', data); - - // Reward Inviter - if (group.type === 'party') { + // Check the inviter again, could be a deleted account + if (inviter) { + // Reward Inviter if (!inviter.items.quests.basilist) { inviter.items.quests.basilist = 0; } inviter.items.quests.basilist += 1; inviter.markModified('items.quests'); + promises.push(inviter.save()); } - } + group.memberCount = currentMembers + 1; - if (group.type === 'party' && inviter) { + // Handle awarding party-related achievements if (group.memberCount > 1) { const notification = new UserNotification({ type: 'ACHIEVEMENT_PARTY_UP' }); @@ -677,20 +662,12 @@ api.joinGroup = { { $or: [{ 'party._id': group._id }, { _id: user._id }], 'achievements.partyUp': { $ne: true }, - _id: { $ne: inviter._id }, }, { $set: { 'achievements.partyUp': true }, $push: { notifications: notification.toObject() }, }, ).exec()); - - if (inviter) { - if (inviter.achievements.partyUp !== true) { - inviter.achievements.partyUp = true; - inviter.addNotification('ACHIEVEMENT_PARTY_UP'); - } - } } if (group.memberCount > 3) { @@ -700,23 +677,19 @@ api.joinGroup = { { $or: [{ 'party._id': group._id }, { _id: user._id }], 'achievements.partyOn': { $ne: true }, - _id: { $ne: inviter._id }, }, { $set: { 'achievements.partyOn': true }, $push: { notifications: notification.toObject() }, }, ).exec()); - - if (inviter) { - if (inviter.achievements.partyOn !== true) { - inviter.achievements.partyOn = true; - inviter.addNotification('ACHIEVEMENT_PARTY_ON'); - } - } } + } else { + group.memberCount += 1; } + promises.push(group.save()); + const analyticsObject = { uuid: user._id, hitType: 'event', @@ -727,15 +700,9 @@ api.joinGroup = { privacy: group.privacy, headers: req.headers, invited: isUserInvited, + seekingParty: group.type === 'party' ? seekingParty : null, }; - if (group.type === 'party') { - analyticsObject.seekingParty = seekingParty; - } - if (group.privacy === 'public') { - analyticsObject.groupName = group.name; - } - if (inviter) promises.push(inviter.save()); promises = await Promise.all(promises); if (group.hasNotCancelled()) { @@ -743,7 +710,7 @@ api.joinGroup = { await group.updateGroupPlan(); } - const response = await Group.toJSONCleanChat(promises[0], user); + const response = await Group.toJSONCleanChat(group, user); const leader = await User.findById(response.leader).select(nameFields).exec(); if (leader) { response.leader = leader.toJSON({ minimize: true });