diff --git a/common/script/index.js b/common/script/index.js index 9468dcbbf3..01a9aee671 100644 --- a/common/script/index.js +++ b/common/script/index.js @@ -94,6 +94,9 @@ api.noTags = noTags; import appliedTags from './libs/appliedTags'; api.appliedTags = appliedTags; +import pickDeep from './libs/pickDeep'; +api.pickDeep = pickDeep; + import count from './count'; api.count = count; @@ -101,11 +104,19 @@ api.count = count; import scoreTask from './ops/scoreTask'; import sleep from './ops/sleep'; import allocate from './ops/allocate'; +import buy from './ops/buy'; +import buyMysterySet from './ops/buyMysterySet'; +import buyQuest from './ops/buyQuest'; +import buySpecialSpell from './ops/buySpecialSpell'; api.ops = { scoreTask, sleep, allocate, + buy, + buyMysterySet, + buySpecialSpell, + buyQuest, }; import handleTwoHanded from './fns/handleTwoHanded'; diff --git a/common/script/ops/buy.js b/common/script/ops/buy.js index b880de4b58..4a5b5b3b7f 100644 --- a/common/script/ops/buy.js +++ b/common/script/ops/buy.js @@ -106,15 +106,17 @@ module.exports = function buy (user, req = {}, analytics) { } user.items.gear.owned[item.key] = true; - if (!message) { - message = i18n.t('messageBought', { - itemText: item.text(req.language), - }, req.language); - } if (item.last) ultimateGear(user); } user.stats.gp -= item.value; + + if (!message) { + message = i18n.t('messageBought', { + itemText: item.text(req.language), + }, req.language); + } + if (analytics) { analytics.track('acquire item', { uuid: user._id, @@ -125,11 +127,12 @@ module.exports = function buy (user, req = {}, analytics) { }); } - let buyResp = _.pick(user, splitWhitespace('items achievements stats flags')); - if (armoireResp) buyResp.armoire = armoireResp; - - return { - data: buyResp, + let res = { + data: _.pick(user, splitWhitespace('items achievements stats flags')), message, }; + + if (armoireResp) res.armoire = armoireResp; + + return res; }; diff --git a/common/script/ops/buyQuest.js b/common/script/ops/buyQuest.js index 3f9596293e..b8efd8eee7 100644 --- a/common/script/ops/buyQuest.js +++ b/common/script/ops/buyQuest.js @@ -6,6 +6,8 @@ import { NotFound, } from '../libs/errors'; import _ from 'lodash'; + +// buy a quest with gold module.exports = function buyQuest (user, req = {}, analytics) { let key = _.get(req, 'params.key'); if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); diff --git a/test/api/v3/integration/user/POST-user_buy.test.js b/test/api/v3/integration/user/POST-user_buy.test.js new file mode 100644 index 0000000000..af730a2c98 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_buy.test.js @@ -0,0 +1,42 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import shared from '../../../../../common/script'; + +let content = shared.content; + +describe('POST /user/buy/:key', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ + 'stats.gp': 400, + }); + }); + + // More tests in common code unit tests + + it('returns an error if the item is not found', async () => { + await expect(user.post(`/user/buy/notExisting`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('itemNotFound', {key: 'notExisting'}), + }); + }); + + it('buys an item', async () => { + let potion = content.potion; + let res = await user.post(`/user/buy/potion`); + await user.sync(); + + expect(res.data).to.eql({ + items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared + achievements: user.achievements, + stats: user.stats, + flags: JSON.parse(JSON.stringify(user.flags)), // otherwise dates can't be compared + }); + expect(res.message).to.equal(t('messageBought', {itemText: potion.text()})); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_buy_mystery_set.test.js b/test/api/v3/integration/user/POST-user_buy_mystery_set.test.js new file mode 100644 index 0000000000..0a6035a954 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_buy_mystery_set.test.js @@ -0,0 +1,42 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /user/buy-mystery-set/:key', () => { + let user; + + beforeEach(async () => { + user = await generateUser({ + 'purchased.plan.consecutive.trinkets': 1, + }); + }); + + // More tests in common code unit tests + + it('returns an error if the mystery set is not found', async () => { + await expect(user.post(`/user/buy-mystery-set/notExisting`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('mysterySetNotFound'), + }); + }); + + it('buys a mystery set', async () => { + let key = 301404; + + let res = await user.post(`/user/buy-mystery-set/${key}`); + await user.sync(); + + expect(res.data).to.eql({ + items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared + purchased: { + plan: { + consecutive: user.purchased.plan.consecutive, + }, + }, + }); + expect(res.message).to.equal(t('hourglassPurchaseSet')); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_buy_quest.test.js b/test/api/v3/integration/user/POST-user_buy_quest.test.js new file mode 100644 index 0000000000..068574d977 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_buy_quest.test.js @@ -0,0 +1,40 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import shared from '../../../../../common/script'; + +let content = shared.content; + +describe('POST /user/buy-quest/:key', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('returns an error if the quest is not found', async () => { + await expect(user.post(`/user/buy-quest/notExisting`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('questNotFound', {key: 'notExisting'}), + }); + }); + + it('buys a quest', async () => { + let key = 'dilatoryDistress1'; + let item = content.quests[key]; + + await user.update({'stats.gp': 250}); + let res = await user.post(`/user/buy-quest/${key}`); + await user.sync(); + + expect(res.data).to.eql(user.items.quests); + expect(res.message).to.equal(t('messageBought', { + itemText: item.text(), + })); + }); +}); diff --git a/test/api/v3/integration/user/POST-user_buy_special_spell.test.js b/test/api/v3/integration/user/POST-user_buy_special_spell.test.js new file mode 100644 index 0000000000..366a7f93c6 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_buy_special_spell.test.js @@ -0,0 +1,43 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import shared from '../../../../../common/script'; + +let content = shared.content; + +describe('POST /user/buy-special-spell/:key', () => { + let user; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('returns an error if the special spell is not found', async () => { + await expect(user.post(`/user/buy-special-spell/notExisting`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('spellNotFound', {spellId: 'notExisting'}), + }); + }); + + it('buys a special spell', async () => { + let key = 'thankyou'; + let item = content.special[key]; + + await user.update({'stats.gp': 250}); + let res = await user.post(`/user/buy-special-spell/${key}`); + await user.sync(); + + expect(res.data).to.eql({ + items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared + stats: user.stats, + }); + expect(res.message).to.equal(t('messageBought', { + itemText: item.text(), + })); + }); +}); diff --git a/website/src/controllers/api-v3/user.js b/website/src/controllers/api-v3/user.js index cb5b0524ff..be191596e3 100644 --- a/website/src/controllers/api-v3/user.js +++ b/website/src/controllers/api-v3/user.js @@ -196,4 +196,97 @@ api.allocate = { }, }; +/** + * @api {post} /user/buy/:key Buy a content item. + * @apiVersion 3.0.0 + * @apiName UserBuy + * @apiGroup User + * + * @apiParam {string} key The item to buy. + * + * @apiSuccess {Object} data `items, achievements, stats, flags` + * @apiSuccess {object} armoireResp Optional extra item given by the armoire + * @apiSuccess {string} message + */ +api.buy = { + method: 'POST', + middlewares: [authWithHeaders(), cron], + url: '/user/buy/:key', + async handler (req, res) { + let user = res.locals.user; + let buyRes = common.ops.buy(user, req, res.analytics); + await user.save(); + res.respond(200, buyRes); + }, +}; + +/** + * @api {post} /user/buy-mystery-set/:key Buy a mystery set. + * @apiVersion 3.0.0 + * @apiName UserBuyMysterySet + * @apiGroup User + * + * @apiParam {string} key The mystery set to buy. + * + * @apiSuccess {Object} data `items, purchased.plan.consecutive` + * @apiSuccess {string} message + */ +api.buyMysterySet = { + method: 'POST', + middlewares: [authWithHeaders(), cron], + url: '/user/buy-mystery-set/:key', + async handler (req, res) { + let user = res.locals.user; + let buyMysterySetRes = common.ops.buyMysterySet(user, req, res.analytics); + await user.save(); + res.respond(200, buyMysterySetRes); + }, +}; + +/** + * @api {post} /user/buy-quest/:key Buy a quest with gold. + * @apiVersion 3.0.0 + * @apiName UserBuyQuest + * @apiGroup User + * + * @apiParam {string} key The quest spell to buy. + * + * @apiSuccess {Object} data `items.quests` + * @apiSuccess {string} message + */ +api.buyQuest = { + method: 'POST', + middlewares: [authWithHeaders(), cron], + url: '/user/buy-quest/:key', + async handler (req, res) { + let user = res.locals.user; + let buyQuestRes = common.ops.buyQuest(user, req, res.analytics); + await user.save(); + res.respond(200, buyQuestRes); + }, +}; + +/** + * @api {post} /user/buy-special-spell/:key Buy special spell. + * @apiVersion 3.0.0 + * @apiName UserBuySpecialSpell + * @apiGroup User + * + * @apiParam {string} key The special spell to buy. + * + * @apiSuccess {Object} data `items, stats` + * @apiSuccess {string} message + */ +api.buySpecialSpell = { + method: 'POST', + middlewares: [authWithHeaders(), cron], + url: '/user/buy-special-spell/:key', + async handler (req, res) { + let user = res.locals.user; + let buySpecialSpellRes = common.ops.buySpecialSpell(user, req); + await user.save(); + res.respond(200, buySpecialSpellRes); + }, +}; + module.exports = api;