diff --git a/test/common/fns/updateStats.test.js b/test/common/fns/updateStats.test.js index e10ad63c98..60bd489db2 100644 --- a/test/common/fns/updateStats.test.js +++ b/test/common/fns/updateStats.test.js @@ -8,7 +8,7 @@ describe('common.fns.updateStats', () => { beforeEach(() => { user = generateUser(); - // user.addNotification = sinon.spy(); + user.addNotification = sinon.spy(); }); context('No Hp', () => { @@ -110,14 +110,14 @@ describe('common.fns.updateStats', () => { expect(user.stats.points).to.eql(10); }); - xit('add user notification when drops are enabled', () => { + it('add user notification when drops are enabled', () => { user.stats.lvl = 3; updateStats(user, { }); expect(user.addNotification).to.be.calledOnce; expect(user.addNotification).to.be.calledWith('DROPS_ENABLED'); }); - xit('add user notification when the user levels up', () => { + it('add user notification when the user levels up', () => { const initialLvl = user.stats.lvl; updateStats(user, { exp: 3000, @@ -129,7 +129,7 @@ describe('common.fns.updateStats', () => { }); }); - xit('add user notification when rebirth is enabled', () => { + it('add user notification when rebirth is enabled', () => { user.stats.lvl = 51; updateStats(user, { }); expect(user.addNotification).to.be.calledTwice; // once is for drops enabled diff --git a/website/common/script/fns/updateStats.js b/website/common/script/fns/updateStats.js index cfe3310153..0538ec3805 100644 --- a/website/common/script/fns/updateStats.js +++ b/website/common/script/fns/updateStats.js @@ -20,7 +20,7 @@ module.exports = function updateStats (user, stats, req = {}, analytics) { if (stats.exp >= experienceToNextLevel) { user.stats.exp = stats.exp; - // const initialLvl = user.stats.lvl; + const initialLvl = user.stats.lvl; while (stats.exp >= experienceToNextLevel) { stats.exp -= experienceToNextLevel; @@ -50,13 +50,12 @@ module.exports = function updateStats (user, stats, req = {}, analytics) { } } - // @TODO: Tmp disable to see if this is causing concurrency - // const newLvl = user.stats.lvl; - // - // if (user.addNotification) user.addNotification('LEVELED_UP', { - // initialLvl, - // newLvl, - // }); + const newLvl = user.stats.lvl; + + if (user.addNotification) user.addNotification('LEVELED_UP', { + initialLvl, + newLvl, + }); } user.stats.exp = stats.exp; diff --git a/website/server/models/group.js b/website/server/models/group.js index a2383b15f6..8d98a1b1e7 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -12,7 +12,10 @@ import * as Tasks from './task'; import validator from 'validator'; import { removeFromArray } from '../libs/collectionManipulators'; import payments from '../libs/payments/payments'; -import { groupChatReceivedWebhook } from '../libs/webhook'; +import { + groupChatReceivedWebhook, + questActivityWebhook, +} from '../libs/webhook'; import { InternalServerError, BadRequest, @@ -648,20 +651,24 @@ schema.methods.startQuest = async function startQuest (user) { removeFromArray(nonUserQuestMembers, user._id); // remove any users from quest.members who aren't in the party - let partyId = this._id; - let questMembers = this.quest.members; - await Promise.all(Object.keys(this.quest.members).map(memberId => { - return User.findOne({_id: memberId, 'party._id': partyId}) - .select('_id') - .lean() - .exec() - .then((member) => { - if (!member) { - delete questMembers[memberId]; + // and get the data necessary to send webhooks + const members = []; + + await User.find({ + _id: {$in: Object.keys(this.quest.members)}, + }) + .select('party.quest party._id items.quests auth preferences.emailNotifications preferences.pushNotifications pushDevices profile.name webhooks') + .lean() + .exec() + .then(partyMembers => { + partyMembers.forEach(member => { + if (!member.party || member.party._id !== this._id) { + delete this.quest.members[member._id]; + } else { + members.push(member); } - return; }); - })); + }); if (userIsParticipating) { user.party.quest.key = this.quest.key; @@ -670,20 +677,23 @@ schema.methods.startQuest = async function startQuest (user) { user.markModified('party.quest'); } + const promises = []; + // Remove the quest from the quest leader items (if they are the current user) if (this.quest.leader === user._id) { user.items.quests[this.quest.key] -= 1; user.markModified('items.quests'); + promises.push(user.save()); } else { // another user is starting the quest, update the leader separately - await User.update({_id: this.quest.leader}, { + promises.push(User.update({_id: this.quest.leader}, { $inc: { [`items.quests.${this.quest.key}`]: -1, }, - }).exec(); + }).exec()); } // update the remaining users - await User.update({ + promises.push(User.update({ _id: { $in: nonUserQuestMembers }, }, { $set: { @@ -691,7 +701,9 @@ schema.methods.startQuest = async function startQuest (user) { 'party.quest.progress.down': 0, 'party.quest.completed': null, }, - }, { multi: true }).exec(); + }, { multi: true }).exec()); + + await Promise.all(promises); // update the users who are not participating // Do not block updates @@ -703,38 +715,45 @@ schema.methods.startQuest = async function startQuest (user) { }, }, { multi: true }).exec(); - // send notifications in the background without blocking - User.find( - { _id: { $in: nonUserQuestMembers } }, - 'party.quest items.quests auth.facebook auth.local preferences.emailNotifications preferences.pushNotifications pushDevices profile.name' - ).exec().then((membersToNotify) => { - let membersToEmail = _.filter(membersToNotify, (member) => { - // send push notifications and filter users that disabled emails - return member.preferences.emailNotifications.questStarted !== false && - member._id !== user._id; - }); - sendTxnEmail(membersToEmail, 'quest-started', [ - { name: 'PARTY_URL', content: '/party' }, - ]); - let membersToPush = _.filter(membersToNotify, (member) => { - // send push notifications and filter users that disabled emails - return member.preferences.pushNotifications.questStarted !== false && - member._id !== user._id; - }); - _.each(membersToPush, (member) => { - sendPushNotification(member, - { - title: quest.text(), - message: `${shared.i18n.t('questStarted')}: ${quest.text()}`, - identifier: 'questStarted', - }); - }); - }); const newMessage = this.sendChat(`\`Your quest, ${quest.text('en')}, has started.\``, null, { participatingMembers: this.getParticipatingQuestMembers().join(', '), }); - await newMessage.save(); + + const membersToEmail = []; + const pushTitle = quest.text(); + const pushMessage = `${shared.i18n.t('questStarted')}: ${quest.text()}`; + + // send notifications and webhooks in the background without blocking + members.forEach(member => { + if (member._id !== user._id) { + // send push notifications and filter users that disabled emails + if (member.preferences.emailNotifications.questStarted !== false) { + membersToEmail.push(member); + } + + // send push notifications and filter users that disabled emails + if (member.preferences.pushNotifications.questStarted !== false) { + sendPushNotification(member, { + title: pushTitle, + message: pushMessage, + identifier: 'questStarted', + }); + } + } + + // Send webhooks + questActivityWebhook.send(member, { + type: 'questStarted', + group: this, + quest, + }); + }); + + // Send emails in bulk + sendTxnEmail(membersToEmail, 'quest-started', [ + { name: 'PARTY_URL', content: '/party' }, + ]); }; schema.methods.sendGroupChatReceivedWebhooks = function sendGroupChatReceivedWebhooks (chat) { @@ -753,15 +772,14 @@ schema.methods.sendGroupChatReceivedWebhooks = function sendGroupChatReceivedWeb query.guilds = this._id; } - /* User.find(query).select({webhooks: 1}).lean().exec().then((users) => { + User.find(query).select({webhooks: 1}).lean().exec().then((users) => { users.forEach((user) => { - let { webhooks } = user; - groupChatReceivedWebhook.send(webhooks, { + groupChatReceivedWebhook.send(user, { group: this, chat, }); }); - }); */ + }); }; schema.statics.cleanQuestProgress = _cleanQuestProgress; @@ -907,6 +925,31 @@ schema.methods.finishQuest = async function finishQuest (quest) { })); } + // Send webhooks in background + // @TODO move the find users part to a worker as well, not just the http request + User.find({ + _id: {$in: participants}, + webhooks: { + $elemMatch: { + type: 'questActivity', + 'options.questFinished': true, + }, + }, + }) + .select('_id webhooks') + .lean() + .exec() + .then(participantsWithWebhook => { + participantsWithWebhook.forEach(participantWithWebhook => { + // Send webhooks + questActivityWebhook.send(participantWithWebhook, { + type: 'questFinished', + group: this, + quest, + }); + }); + }); + return await Promise.all(promises); };