mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
shared-code-webhooks
This commit is contained in:
@@ -148,5 +148,7 @@
|
|||||||
"noSudoAccess": "You don't have sudo access.",
|
"noSudoAccess": "You don't have sudo access.",
|
||||||
"couponCodeRequired": "The coupon code is required.",
|
"couponCodeRequired": "The coupon code is required.",
|
||||||
"eventRequired": "\"req.params.event\" is required.",
|
"eventRequired": "\"req.params.event\" is required.",
|
||||||
"countRequired": "\"req.query.count\" is required."
|
"countRequired": "\"req.query.count\" is required.",
|
||||||
|
"invalidUrl": "invalid url",
|
||||||
|
"invalidEnabled": "the \"enabled\" parameter should be a boolean"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,6 +118,9 @@ import purchase from './ops/purchase';
|
|||||||
import purchaseHourglass from './ops/hourglassPurchase';
|
import purchaseHourglass from './ops/hourglassPurchase';
|
||||||
import readCard from './ops/readCard';
|
import readCard from './ops/readCard';
|
||||||
import openMysteryItem from './ops/openMysteryItem';
|
import openMysteryItem from './ops/openMysteryItem';
|
||||||
|
import addWebhook from './ops/addWebhook';
|
||||||
|
import updateWebhook from './ops/updateWebhook';
|
||||||
|
import deleteWebhook from './ops/deleteWebhook';
|
||||||
|
|
||||||
api.ops = {
|
api.ops = {
|
||||||
scoreTask,
|
scoreTask,
|
||||||
@@ -137,6 +140,9 @@ api.ops = {
|
|||||||
purchaseHourglass,
|
purchaseHourglass,
|
||||||
readCard,
|
readCard,
|
||||||
openMysteryItem,
|
openMysteryItem,
|
||||||
|
addWebhook,
|
||||||
|
updateWebhook,
|
||||||
|
deleteWebhook,
|
||||||
};
|
};
|
||||||
|
|
||||||
import handleTwoHanded from './fns/handleTwoHanded';
|
import handleTwoHanded from './fns/handleTwoHanded';
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
import refPush from '../libs/refPush';
|
import refPush from '../libs/refPush';
|
||||||
|
import validator from 'validator';
|
||||||
|
import i18n from '../i18n';
|
||||||
|
import {
|
||||||
|
NotFound,
|
||||||
|
BadRequest,
|
||||||
|
} from '../libs/errors';
|
||||||
|
|
||||||
module.exports = function(user, req, cb) {
|
module.exports = function(user, req) {
|
||||||
var wh;
|
var wh;
|
||||||
wh = user.preferences.webhooks;
|
wh = user.preferences.webhooks;
|
||||||
refPush(wh, {
|
|
||||||
|
if(!validator.isURL(req.body.url)) throw new BadRequest(i18n.t('invalidUrl', req.language));
|
||||||
|
if(!validator.isBoolean(req.body.enabled)) throw new BadRequest(i18n.t('invalidEnabled', req.language));
|
||||||
|
|
||||||
|
user.markModified('preferences.webhooks');
|
||||||
|
|
||||||
|
return refPush(wh, {
|
||||||
url: req.body.url,
|
url: req.body.url,
|
||||||
enabled: req.body.enabled || true,
|
enabled: req.body.enabled,
|
||||||
id: req.body.id
|
|
||||||
});
|
});
|
||||||
if (typeof user.markModified === "function") {
|
|
||||||
user.markModified('preferences.webhooks');
|
|
||||||
}
|
|
||||||
return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
module.exports = function(user, req, cb) {
|
|
||||||
|
module.exports = function(user, req) {
|
||||||
delete user.preferences.webhooks[req.params.id];
|
delete user.preferences.webhooks[req.params.id];
|
||||||
if (typeof user.markModified === "function") {
|
user.markModified('preferences.webhooks');
|
||||||
user.markModified('preferences.webhooks');
|
|
||||||
}
|
|
||||||
return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import validator from 'validator';
|
||||||
|
import i18n from '../i18n';
|
||||||
|
import {
|
||||||
|
NotFound,
|
||||||
|
BadRequest,
|
||||||
|
} from '../libs/errors';
|
||||||
|
|
||||||
module.exports = function(user, req, cb) {
|
module.exports = function(user, req) {
|
||||||
_.merge(user.preferences.webhooks[req.params.id], req.body);
|
if(!validator.isURL(req.body.url)) throw new BadRequest(i18n.t('invalidUrl', req.language));
|
||||||
if (typeof user.markModified === "function") {
|
if(!validator.isBoolean(req.body.enabled)) throw new BadRequest(i18n.t('invalidEnabled', req.language));
|
||||||
user.markModified('preferences.webhooks');
|
|
||||||
}
|
user.markModified('preferences.webhooks');
|
||||||
return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0;
|
user.preferences.webhooks[req.params.id].url = req.body.url;
|
||||||
|
user.preferences.webhooks[req.params.id].enabled = req.body.enabled;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -358,6 +358,10 @@ gulp.task('test:api-v3:integration', (done) => {
|
|||||||
pipe(runner);
|
pipe(runner);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
gulp.task('test:api-v3:integration:watch', () => {
|
||||||
|
gulp.watch(['website/src/controllers/api-v3/**/*', 'test/api/v3/integration/**/*', 'common/script/ops/*'], ['test:api-v3:integration']);
|
||||||
|
});
|
||||||
|
|
||||||
gulp.task('test:api-v3:integration:separate-server', (done) => {
|
gulp.task('test:api-v3:integration:separate-server', (done) => {
|
||||||
let runner = exec(
|
let runner = exec(
|
||||||
testBin('mocha test/api/v3/integration --recursive', 'LOAD_SERVER=0'),
|
testBin('mocha test/api/v3/integration --recursive', 'LOAD_SERVER=0'),
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
let user;
|
||||||
|
let endpoint = '/user/webhook';
|
||||||
|
|
||||||
|
describe('DELETE /user/webhook', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds', async () => {
|
||||||
|
let id = 'some-id';
|
||||||
|
user.preferences.webhooks[id] = { url: 'http://some-url.com', enabled: true };
|
||||||
|
await user.sync();
|
||||||
|
expect(user.preferences.webhooks).to.eql({});
|
||||||
|
let response = await user.del(`${endpoint}/${id}`);
|
||||||
|
expect(response).to.eql({});
|
||||||
|
await user.sync();
|
||||||
|
expect(user.preferences.webhooks).to.eql({});
|
||||||
|
});
|
||||||
|
});
|
||||||
29
test/api/v3/integration/user/POST-user_add_webhook.test.js
Normal file
29
test/api/v3/integration/user/POST-user_add_webhook.test.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
let user;
|
||||||
|
let endpoint = '/user/webhook';
|
||||||
|
|
||||||
|
describe('POST /user/webhook', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates', async () => {
|
||||||
|
await expect(user.post(endpoint, { enabled: true })).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('invalidUrl'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('successfully adds the webhook', async () => {
|
||||||
|
expect(user.preferences.webhooks).to.eql({});
|
||||||
|
let response = await user.post(endpoint, { enabled: true, url: 'http://some-url.com'});
|
||||||
|
expect(response.id).to.exist;
|
||||||
|
await user.sync();
|
||||||
|
expect(user.preferences.webhooks).to.not.eql({});
|
||||||
|
});
|
||||||
|
});
|
||||||
32
test/api/v3/integration/user/PUT-user_update_webhook.test.js
Normal file
32
test/api/v3/integration/user/PUT-user_update_webhook.test.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
let user;
|
||||||
|
let url = 'http://new-url.com';
|
||||||
|
let enabled = true;
|
||||||
|
|
||||||
|
describe('PUT /user/webhook/:id', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validation fails', async () => {
|
||||||
|
await expect(user.put('/user/webhook/some-id'), { enabled: true }).to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('invalidUrl'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds', async () => {
|
||||||
|
let response = await user.post('/user/webhook', { enabled: true, url: 'http://some-url.com'});
|
||||||
|
await user.sync();
|
||||||
|
expect(user.preferences.webhooks[response.id].url).to.not.eql(url);
|
||||||
|
let response2 = await user.put(`/user/webhook/${response.id}`, {url, enabled});
|
||||||
|
expect(response2).to.eql({});
|
||||||
|
await user.sync();
|
||||||
|
expect(user.preferences.webhooks[response.id].url).to.eql(url);
|
||||||
|
});
|
||||||
|
});
|
||||||
57
test/common/ops/addWebhook.test.js
Normal file
57
test/common/ops/addWebhook.test.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import addWebhook from '../../../common/script/ops/addWebhook';
|
||||||
|
import {
|
||||||
|
BadRequest,
|
||||||
|
} from '../../../common/script/libs/errors';
|
||||||
|
import i18n from '../../../common/script/i18n';
|
||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
} from '../../helpers/common.helper';
|
||||||
|
|
||||||
|
describe('shared.ops.addWebhook', () => {
|
||||||
|
let user;
|
||||||
|
let req;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
user = generateUser();
|
||||||
|
req = { body: {
|
||||||
|
enabled: true,
|
||||||
|
url: 'http://some-url.com',
|
||||||
|
} };
|
||||||
|
});
|
||||||
|
|
||||||
|
context('adds webhook', () => {
|
||||||
|
it('validates req.body.url', (done) => {
|
||||||
|
delete req.body.url;
|
||||||
|
try {
|
||||||
|
addWebhook(user, req);
|
||||||
|
} catch (err) {
|
||||||
|
expect(err).to.be.an.instanceof(BadRequest);
|
||||||
|
expect(err.message).to.equal(i18n.t('invalidUrl'));
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates req.body.enabled', (done) => {
|
||||||
|
delete req.body.enabled;
|
||||||
|
try {
|
||||||
|
addWebhook(user, req);
|
||||||
|
} catch (err) {
|
||||||
|
expect(err).to.be.an.instanceof(BadRequest);
|
||||||
|
expect(err.message).to.equal(i18n.t('invalidEnabled'));
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls marksModified()', () => {
|
||||||
|
user.markModified = sinon.spy();
|
||||||
|
addWebhook(user, req);
|
||||||
|
expect(user.markModified.called).to.eql(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds', () => {
|
||||||
|
expect(user.preferences.webhooks).to.eql({});
|
||||||
|
addWebhook(user, req);
|
||||||
|
expect(user.preferences.webhooks).to.not.eql({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
20
test/common/ops/deleteWebhook.test.js
Normal file
20
test/common/ops/deleteWebhook.test.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import deleteWebhook from '../../../common/script/ops/deleteWebhook';
|
||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
} from '../../helpers/common.helper';
|
||||||
|
|
||||||
|
describe('shared.ops.deleteWebhook', () => {
|
||||||
|
let user;
|
||||||
|
let req;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
user = generateUser();
|
||||||
|
req = { params: { id: 'some-id' } };
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds', () => {
|
||||||
|
user.preferences.webhooks = { 'some-id': {} };
|
||||||
|
deleteWebhook(user, req);
|
||||||
|
expect(user.preferences.webhooks).to.eql({});
|
||||||
|
});
|
||||||
|
});
|
||||||
42
test/common/ops/updateWebhook.test.js
Normal file
42
test/common/ops/updateWebhook.test.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import updateWebhook from '../../../common/script/ops/updateWebhook';
|
||||||
|
import {
|
||||||
|
BadRequest,
|
||||||
|
} from '../../../common/script/libs/errors';
|
||||||
|
import i18n from '../../../common/script/i18n';
|
||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
} from '../../helpers/common.helper';
|
||||||
|
|
||||||
|
describe('shared.ops.updateWebhook', () => {
|
||||||
|
let user;
|
||||||
|
let req;
|
||||||
|
let newUrl = 'http://new-url.com';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
user = generateUser();
|
||||||
|
req = { params: {
|
||||||
|
id: 'this-id',
|
||||||
|
}, body: {
|
||||||
|
url: newUrl,
|
||||||
|
enabled: true,
|
||||||
|
} };
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates body', (done) => {
|
||||||
|
delete req.body.url;
|
||||||
|
try {
|
||||||
|
updateWebhook(user, req);
|
||||||
|
} catch (err) {
|
||||||
|
expect(err).to.be.an.instanceof(BadRequest);
|
||||||
|
expect(err.message).to.equal(i18n.t('invalidUrl'));
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('succeeds', () => {
|
||||||
|
let url = 'http://existing-url.com';
|
||||||
|
user.preferences.webhooks = { 'this-id': { url } };
|
||||||
|
updateWebhook(user, req);
|
||||||
|
expect(user.preferences.webhooks['this-id'].url).to.eql(newUrl);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -764,4 +764,62 @@ api.userOpenMysteryItem = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /user/webhook
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName UserAddWebhook
|
||||||
|
* @apiGroup User
|
||||||
|
* @apiSuccess {}
|
||||||
|
**/
|
||||||
|
api.addWebhook = {
|
||||||
|
method: 'POST',
|
||||||
|
middlewares: [authWithHeaders()],
|
||||||
|
url: '/user/webhook',
|
||||||
|
async handler (req, res) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
let result = common.ops.addWebhook(user, req);
|
||||||
|
await user.save();
|
||||||
|
res.respond(200, {id: result.id});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {put} /user/webhook/:id
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName UserUpdateWebhook
|
||||||
|
* @apiGroup User
|
||||||
|
* @apiSuccess {}
|
||||||
|
**/
|
||||||
|
api.updateWebhook = {
|
||||||
|
method: 'PUT',
|
||||||
|
middlewares: [authWithHeaders()],
|
||||||
|
url: '/user/webhook/:id',
|
||||||
|
async handler (req, res) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
common.ops.updateWebhook(user, req);
|
||||||
|
await user.save();
|
||||||
|
res.respond(200, {});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {delete} /user/webhook/:id
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName UserDeleteWebhook
|
||||||
|
* @apiGroup User
|
||||||
|
* @apiSuccess {}
|
||||||
|
**/
|
||||||
|
api.deleteWebhook = {
|
||||||
|
method: 'DELETE',
|
||||||
|
middlewares: [authWithHeaders()],
|
||||||
|
url: '/user/webhook/:id',
|
||||||
|
async handler (req, res) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
common.ops.deleteWebhook(user, req);
|
||||||
|
await user.save();
|
||||||
|
res.respond(200, {});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = api;
|
module.exports = api;
|
||||||
|
|||||||
Reference in New Issue
Block a user