diff --git a/common/locales/en/api-v3.json b/common/locales/en/api-v3.json index 5ab3b3399e..a4fd69f0a3 100644 --- a/common/locales/en/api-v3.json +++ b/common/locales/en/api-v3.json @@ -151,5 +151,8 @@ "countRequired": "\"req.query.count\" is required.", "petsReleased": "Pets released.", "mountsAndPetsReleased": "Mounts and pets released", - "mountsReleased": "Mounts released" + "mountsReleased": "Mounts released", + "typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>", + "userItemsKeyNotFound": "Key not found for user.items <%= type %>", + "sold": "You sold a <%= key %> <%= type %>" } diff --git a/common/script/index.js b/common/script/index.js index 7482a09fd7..c55ac24c9f 100644 --- a/common/script/index.js +++ b/common/script/index.js @@ -121,6 +121,7 @@ import openMysteryItem from './ops/openMysteryItem'; import releasePets from './ops/releasePets'; import releaseBoth from './ops/releaseBoth'; import releaseMounts from './ops/releaseMounts'; +import sell from './ops/sell'; api.ops = { scoreTask, @@ -143,6 +144,7 @@ api.ops = { releasePets, releaseBoth, releaseMounts, + sell, }; import handleTwoHanded from './fns/handleTwoHanded'; diff --git a/common/script/ops/sell.js b/common/script/ops/sell.js index 33e5f7d673..d5dfa9fbbb 100644 --- a/common/script/ops/sell.js +++ b/common/script/ops/sell.js @@ -1,23 +1,42 @@ import content from '../content/index'; +import i18n from '../../../common/script/i18n'; import _ from 'lodash'; import splitWhitespace from '../libs/splitWhitespace'; +import { + NotFound, + NotAuthorized, + BadRequest, +} from '../libs/errors'; -module.exports = function(user, req, cb) { - var key, ref, type; - ref = req.params, key = ref.key, type = ref.type; - if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food') { - return typeof cb === "function" ? cb({ - code: 404, - message: ":type not found. Must bes in [eggs, hatchingPotions, food]" - }) : void 0; +const ACCEPTEDTYPES = ['eggs', 'hatchingPotions', 'food']; + +module.exports = function sell (user, req = {}) { + let key = _.get(req.params, 'key'); + let type = _.get(req.params, 'type'); + + if (!type) { + throw new BadRequest(i18n.t('typeRequired', req.language)); } + + if (!key) { + throw new BadRequest(i18n.t('keyRequired', req.language)); + } + + if (ACCEPTEDTYPES.indexOf(type) === -1) { + throw new NotAuthorized(i18n.t('typeNotSellable', {acceptedTypes: ACCEPTEDTYPES.join(', ')}, req.language)); + } + if (!user.items[type][key]) { - return typeof cb === "function" ? cb({ - code: 404, - message: ":key not found for user.items." + type - }) : void 0; + throw new NotFound(i18n.t('userItemsKeyNotFound', {type}, req.language)); } + user.items[type][key]--; user.stats.gp += content[type][key].value; - return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats items'))) : void 0; + + let response = { + data: _.pick(user, splitWhitespace('stats items')), + message: i18n.t('sold', {type, key}), + }; + + return response; }; diff --git a/tasks/gulp-eslint.js b/tasks/gulp-eslint.js index e71cde8258..6ed2b567a9 100644 --- a/tasks/gulp-eslint.js +++ b/tasks/gulp-eslint.js @@ -30,7 +30,6 @@ const COMMON_FILES = [ '!./common/script/ops/reroll.js', '!./common/script/ops/reset.js', '!./common/script/ops/revive.js', - '!./common/script/ops/sell.js', '!./common/script/ops/sortTag.js', '!./common/script/ops/sortTask.js', '!./common/script/ops/unlock.js', diff --git a/test/api/v3/integration/user/POST-user_sell.test.js b/test/api/v3/integration/user/POST-user_sell.test.js new file mode 100644 index 0000000000..be8d55c932 --- /dev/null +++ b/test/api/v3/integration/user/POST-user_sell.test.js @@ -0,0 +1,42 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-integration/v3'; +import content from '../../../../../common/script/content'; + +describe('POST /user/sell/:type/:key', () => { + let user; + let type = 'eggs'; + let key = 'Wolf'; + + beforeEach(async () => { + user = await generateUser(); + }); + + // More tests in common code unit tests + + it('returns an error when user does not have item', async () => { + await expect(user.post(`/user/sell/${type}/${key}`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userItemsKeyNotFound', {type}), + }); + }); + + it('sells an item', async () => { + await user.update({ + items: { + eggs: { + Wolf: 1, + }, + }, + }); + + let response = await user.post(`/user/sell/${type}/${key}`); + await user.sync(); + + expect(response.message).to.equal(t('sold', {type, key})); + expect(user.stats.gp).to.equal(content[type][key].value); + }); +}); diff --git a/test/common/ops/sell.js b/test/common/ops/sell.js new file mode 100644 index 0000000000..c5499b00d2 --- /dev/null +++ b/test/common/ops/sell.js @@ -0,0 +1,81 @@ +import sell from '../../../common/script/ops/sell'; +import i18n from '../../../common/script/i18n'; +import { + generateUser, +} from '../../helpers/common.helper'; +import { + NotAuthorized, + BadRequest, + NotFound, +} from '../../../common/script/libs/errors'; +import content from '../../../common/script/content/index'; + +describe('shared.ops.sell', () => { + let user; + let type = 'eggs'; + let key = 'Wolf'; + let acceptedTypes = ['eggs', 'hatchingPotions', 'food']; + + beforeEach(() => { + user = generateUser(); + user.items[type][key] = 1; + }); + + it('returns an error when type is not provided', (done) => { + try { + sell(user); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('typeRequired')); + done(); + } + }); + + it('returns an error when key is not provided', (done) => { + try { + sell(user, {params: { type } }); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('keyRequired')); + done(); + } + }); + + it('returns an error when non-sellable type is provided', (done) => { + let nonSellableType = 'nonSellableType'; + + try { + sell(user, {params: { type: nonSellableType, key } }); + } catch (err) { + expect(err).to.be.an.instanceof(NotAuthorized); + expect(err.message).to.equal(i18n.t('typeNotSellable', {acceptedTypes: acceptedTypes.join(', ')})); + done(); + } + }); + + it('returns an error when key is not found with type provided', (done) => { + let fakeKey = 'fakeKey'; + + try { + sell(user, {params: { type, key: fakeKey } }); + } catch (err) { + expect(err).to.be.an.instanceof(NotFound); + expect(err.message).to.equal(i18n.t('userItemsKeyNotFound', {type})); + done(); + } + }); + + it('reduces item count from user', () => { + let response = sell(user, {params: { type, key } }); + + expect(response.message).to.equal(i18n.t('sold', {type, key})); + expect(user.items[type][key]).to.equal(0); + }); + + it('increases user\'s gold', () => { + let response = sell(user, {params: { type, key } }); + + expect(response.message).to.equal(i18n.t('sold', {type, key})); + expect(user.stats.gp).to.equal(content[type][key].value); + }); +}); diff --git a/website/src/controllers/api-v3/user.js b/website/src/controllers/api-v3/user.js index 0267e9511b..c60b2a3eef 100644 --- a/website/src/controllers/api-v3/user.js +++ b/website/src/controllers/api-v3/user.js @@ -828,4 +828,24 @@ api.userReleaseMounts = { }, }; +/* +* @api {post} /user/sell/:type/:key Sells user's items. +* @apiVersion 3.0.0 +* @apiName UserSell +* @apiGroup User +* +* @apiSuccess {Object} data `stats items` +*/ +api.userSell = { + method: 'POST', + middlewares: [authWithHeaders(), cron], + url: '/user/sell/:type/:key', + async handler (req, res) { + let user = res.locals.user; + let sellResponse = common.ops.sell(user, req); + await user.save(); + res.respond(200, sellResponse); + }, +}; + module.exports = api;