From e6edaca11d3ef8855505f8f06e43d58ec838f04b Mon Sep 17 00:00:00 2001 From: Carl Vuorinen Date: Fri, 10 Jan 2020 20:01:15 +0200 Subject: [PATCH 1/4] Add questInvited option to webhook model --- test/api/unit/libs/webhooks.test.js | 3 ++- test/api/unit/models/webhook.test.js | 4 ++++ website/server/models/webhook.js | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/api/unit/libs/webhooks.test.js b/test/api/unit/libs/webhooks.test.js index 761eda713b..b3cf687c21 100644 --- a/test/api/unit/libs/webhooks.test.js +++ b/test/api/unit/libs/webhooks.test.js @@ -43,6 +43,7 @@ describe('webhooks', () => { options: { questStarted: true, questFinised: true, + questInvited: true, }, }, { id: 'userActivity', @@ -576,7 +577,7 @@ describe('webhooks', () => { }; }); - ['questStarted', 'questFinised'].forEach(type => { + ['questStarted', 'questFinised', 'questInvited'].forEach(type => { it(`sends ${type} webhooks`, () => { data.type = type; diff --git a/test/api/unit/models/webhook.test.js b/test/api/unit/models/webhook.test.js index 4a49b6213f..5527e686f0 100644 --- a/test/api/unit/models/webhook.test.js +++ b/test/api/unit/models/webhook.test.js @@ -183,6 +183,7 @@ describe('Webhook Model', () => { options: { questStarted: true, questFinished: true, + questInvited: true, }, }; }); @@ -197,6 +198,7 @@ describe('Webhook Model', () => { expect(wh.options).to.eql({ questStarted: false, questFinished: false, + questInvited: false, }); }); @@ -210,6 +212,7 @@ describe('Webhook Model', () => { expect(wh.options).to.eql({ questStarted: false, questFinished: true, + questInvited: true, }); }); @@ -224,6 +227,7 @@ describe('Webhook Model', () => { expect(wh.options).to.eql({ questStarted: true, questFinished: true, + questInvited: true, }); }); diff --git a/website/server/models/webhook.js b/website/server/models/webhook.js index aaa4560a56..21b79e6791 100644 --- a/website/server/models/webhook.js +++ b/website/server/models/webhook.js @@ -28,6 +28,7 @@ const USER_ACTIVITY_DEFAULT_OPTIONS = Object.freeze({ const QUEST_ACTIVITY_DEFAULT_OPTIONS = Object.freeze({ questStarted: false, questFinished: false, + questInvited: false, }); export const schema = new Schema({ From 04180fe9742e6cd253c817a1309018a09f9e0e8c Mon Sep 17 00:00:00 2001 From: Carl Vuorinen Date: Fri, 10 Jan 2020 20:58:21 +0200 Subject: [PATCH 2/4] Add integration tests for questActivity webhook --- .../webhook/POST-user_add_webhook.test.js | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/api/v3/integration/webhook/POST-user_add_webhook.test.js b/test/api/v3/integration/webhook/POST-user_add_webhook.test.js index 306ba771c0..1bf8399a8d 100644 --- a/test/api/v3/integration/webhook/POST-user_add_webhook.test.js +++ b/test/api/v3/integration/webhook/POST-user_add_webhook.test.js @@ -226,6 +226,69 @@ describe('POST /user/webhook', () => { }); }); + it('defaults questActivity options', async () => { + body.type = 'questActivity'; + + const webhook = await user.post('/user/webhook', body); + + expect(webhook.options).to.eql({ + questStarted: false, + questFinished: false, + questInvited: false, + }); + }); + + it('can set questActivity options', async () => { + body.type = 'questActivity'; + body.options = { + questStarted: true, + questFinished: true, + questInvited: true, + }; + + const webhook = await user.post('/user/webhook', body); + + expect(webhook.options).to.eql({ + questStarted: true, + questFinished: true, + questInvited: true, + }); + }); + + it('discards extra properties in questActivity options', async () => { + body.type = 'questActivity'; + body.options = { + questStarted: false, + questFinished: true, + questInvited: true, + foo: 'bar', + }; + + const webhook = await user.post('/user/webhook', body); + + expect(webhook.options.foo).to.not.exist; + expect(webhook.options).to.eql({ + questStarted: false, + questFinished: true, + questInvited: true, + }); + }); + + ['questStarted', 'questFinished', 'questInvited'].forEach(option => { + it(`requires questActivity option ${option} to be a boolean`, async () => { + body.type = 'questActivity'; + body.options = { + [option]: 'not a boolean', + }; + + await expect(user.post('/user/webhook', body)).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('webhookBooleanOption', { option }), + }); + }); + }); + it('discards extra properties in globalActivity options', async () => { body.type = 'globalActivity'; body.options = { From 9258f8ad26778acb43a76c89ef89d693cf8cab25 Mon Sep 17 00:00:00 2001 From: Carl Vuorinen Date: Sat, 11 Jan 2020 00:32:10 +0200 Subject: [PATCH 3/4] Send questInvited webhooks --- .../POST-groups_groupId_quests_invite.test.js | 35 +++++++++++++++++++ website/server/controllers/api-v3/quests.js | 10 +++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js b/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js index 3581663c6e..81a0d1327c 100644 --- a/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js +++ b/test/api/v3/integration/quests/POST-groups_groupId_quests_invite.test.js @@ -2,6 +2,7 @@ import { v4 as generateUUID } from 'uuid'; import { createAndPopulateGroup, translate as t, + server, sleep, } from '../../../../helpers/api-integration/v3'; import { quests as questScrolls } from '../../../../../website/common/script/content/quests'; @@ -210,5 +211,39 @@ describe('POST /groups/:groupId/quests/invite/:questKey', () => { const returnedGroup = await groupLeader.get(`/groups/${group._id}`); expect(returnedGroup.chat[0]._meta).to.be.undefined; }); + + context('sending quest activity webhooks', () => { + before(async () => { + await server.start(); + }); + + after(async () => { + await server.close(); + }); + + it('sends quest invited webhook', async () => { + const uuid = generateUUID(); + + await member.post('/user/webhook', { + url: `http://localhost:${server.port}/webhooks/${uuid}`, + type: 'questActivity', + enabled: true, + options: { + questInvited: true, + }, + }); + + await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`); + + await sleep(); + + const body = server.getWebhookData(uuid); + + expect(body.type).to.eql('questInvited'); + expect(body.group.id).to.eql(questingGroup.id); + expect(body.group.name).to.eql(questingGroup.name); + expect(body.quest.key).to.eql(PET_QUEST); + }); + }); }); }); diff --git a/website/server/controllers/api-v3/quests.js b/website/server/controllers/api-v3/quests.js index 5b4c0322cb..2ac5f81485 100644 --- a/website/server/controllers/api-v3/quests.js +++ b/website/server/controllers/api-v3/quests.js @@ -18,6 +18,7 @@ import { import common from '../../../common'; import { sendNotification as sendPushNotification } from '../../libs/pushNotifications'; import apiError from '../../libs/apiError'; +import { questActivityWebhook } from '../../libs/webhook'; const questScrolls = common.content.quests; @@ -79,7 +80,7 @@ api.inviteToQuest = { 'party._id': group._id, _id: { $ne: user._id }, }) - .select('auth.facebook auth.google auth.local preferences.emailNotifications preferences.pushNotifications preferences.language profile.name pushDevices') + .select('auth.facebook auth.google auth.local preferences.emailNotifications preferences.pushNotifications preferences.language profile.name pushDevices webhooks') .exec(); group.markModified('quest'); @@ -132,6 +133,13 @@ api.inviteToQuest = { ); } + // Send webhooks + questActivityWebhook.send(member, { + type: 'questInvited', + group, + quest, + }); + return member.preferences.emailNotifications.invitedQuest !== false; }); sendTxnEmail(membersToEmail, `invite-${quest.boss ? 'boss' : 'collection'}-quest`, [ From a487c708bed97c34917b4eb8e651510112396c79 Mon Sep 17 00:00:00 2001 From: Carl Vuorinen Date: Sat, 11 Jan 2020 00:36:45 +0200 Subject: [PATCH 4/4] Add documentation about questActivity and userActivity webhooks --- website/server/controllers/api-v3/webhook.js | 33 +++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/website/server/controllers/api-v3/webhook.js b/website/server/controllers/api-v3/webhook.js index b7c037eeff..2007099f49 100644 --- a/website/server/controllers/api-v3/webhook.js +++ b/website/server/controllers/api-v3/webhook.js @@ -35,8 +35,8 @@ const api = {}; * @apiParam (Body) {String} [label] A label to remind you what this webhook does * @apiParam (Body) {Boolean} [enabled=true] If the webhook should be enabled * @apiParam (Body) {String="taskActivity","groupChatReceived", - "userActivity"} [type="taskActivity"] The webhook's type. - * @apiParam (Body) {Object} [options] The webhook's options. Wil differ depending on type. + "userActivity","questActivity"} [type="taskActivity"] The webhook's type. + * @apiParam (Body) {Object} [options] The webhook's options. Will differ depending on type. * Required for `groupChatReceived` type. * If a webhook supports options, the default values * are displayed in the examples below @@ -63,6 +63,30 @@ const api = {}; * "groupId": "required-uuid-of-group" * } * } + * @apiParamExample {json} User Activity Example + * { + * "enabled": true, + * "url": "http://some-webhook-url.com", + * "label": "My Activity Webhook", + * "type": "userActivity", + * "options": { // set at least one to true + * "petHatched": false, // default + * "mountRaised": false, // default + * "leveledUp": false, // default + * } + * } + * @apiParamExample {json} Quest Activity Example + * { + * "enabled": true, + * "url": "http://some-webhook-url.com", + * "label": "My Quest Webhook", + * "type": "questActivity", + * "options": { // set at least one to true + * "questStarted": false, // default + * "questFinished": false, // default + * "questInvited": false, // default + * } + * } * @apiParamExample {json} Minimal Example * { * "url": "http://some-webhook-url.com" @@ -131,8 +155,9 @@ api.getWebhook = { * @apiParam (Body) {String} [url] The webhook's URL * @apiParam (Body) {String} [label] A label to remind you what this webhook does * @apiParam (Body) {Boolean} [enabled] If the webhook should be enabled - * @apiParam (Body) {String="taskActivity","groupChatReceived"} [type] The webhook's type. - * @apiParam (Body) {Object} [options] The webhook's options. Wil differ depending on type. + * @apiParam (Body) {String="taskActivity","groupChatReceived", + * "userActivity","questActivity"} [type] The webhook's type. + * @apiParam (Body) {Object} [options] The webhook's options. Will differ depending on type. * The options are enumerated in the * [add webhook examples](#api-Webhook-UserAddWebhook). * @apiParamExample {json} Update Enabled and Type Properties