diff --git a/test/common/ops/buy/hourglassPurchase.js b/test/common/ops/buy/hourglassPurchase.js index d1631fb277..6f9bf50808 100644 --- a/test/common/ops/buy/hourglassPurchase.js +++ b/test/common/ops/buy/hourglassPurchase.js @@ -9,11 +9,18 @@ import { generateUser, } from '../../../helpers/common.helper'; import errorMessage from '../../../../website/common/script/libs/errorMessage'; +import {BuyHourglassMountOperation} from '../../../../website/common/script/ops/buy/buyMount'; describe('common.ops.hourglassPurchase', () => { let user; let analytics = {track () {}}; + function buyMount (_user, _req, _analytics) { + const buyOp = new BuyHourglassMountOperation(_user, _req, _analytics); + + return buyOp.purchase(); + } + beforeEach(() => { user = generateUser(); sinon.stub(analytics, 'track'); @@ -66,7 +73,7 @@ describe('common.ops.hourglassPurchase', () => { it('does not grant to mounts without Mystic Hourglasses', (done) => { try { - hourglassPurchase(user, {params: {type: 'mounts', key: 'MantisShrimp-Base'}}); + buyMount(user, {params: {key: 'MantisShrimp-Base'}}); } catch (err) { expect(err).to.be.an.instanceof(NotAuthorized); expect(err.message).to.eql(i18n.t('notEnoughHourglasses')); @@ -90,7 +97,7 @@ describe('common.ops.hourglassPurchase', () => { user.purchased.plan.consecutive.trinkets = 1; try { - hourglassPurchase(user, {params: {type: 'mounts', key: 'Orca-Base'}}); + buyMount(user, {params: {key: 'Orca-Base'}}); } catch (err) { expect(err).to.be.an.instanceof(NotAuthorized); expect(err.message).to.eql(i18n.t('notAllowedHourglass')); @@ -120,7 +127,7 @@ describe('common.ops.hourglassPurchase', () => { }; try { - hourglassPurchase(user, {params: {type: 'mounts', key: 'MantisShrimp-Base'}}); + buyMount(user, {params: {key: 'MantisShrimp-Base'}}); } catch (err) { expect(err).to.be.an.instanceof(NotAuthorized); expect(err.message).to.eql(i18n.t('mountsAlreadyOwned')); @@ -144,7 +151,7 @@ describe('common.ops.hourglassPurchase', () => { it('buys a mount', () => { user.purchased.plan.consecutive.trinkets = 2; - let [, message] = hourglassPurchase(user, {params: {type: 'mounts', key: 'MantisShrimp-Base'}}); + let [, message] = buyMount(user, {params: {key: 'MantisShrimp-Base'}}); expect(message).to.eql(i18n.t('hourglassPurchase')); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); expect(user.items.mounts).to.eql({'MantisShrimp-Base': true}); diff --git a/website/common/script/ops/buy/abstractBuyOperation.js b/website/common/script/ops/buy/abstractBuyOperation.js index 0a6f7735dd..3f87f08694 100644 --- a/website/common/script/ops/buy/abstractBuyOperation.js +++ b/website/common/script/ops/buy/abstractBuyOperation.js @@ -194,3 +194,28 @@ export class AbstractGemItemOperation extends AbstractBuyOperation { }; } } + +export class AbstractHourglassItemOperation extends AbstractBuyOperation { + constructor (user, req, analytics) { + super(user, req, analytics); + } + + canUserPurchase (user, item) { + this.item = item; + + if (user.purchased.plan.consecutive.trinkets <= 0) { + throw new NotAuthorized(this.i18n('notEnoughHourglasses')); + } + } + + subtractCurrency (user) { + user.purchased.plan.consecutive.trinkets--; + } + + analyticsData () { + return { + itemKey: this.item.key, + acquireMethod: 'Hourglass', + }; + } +} diff --git a/website/common/script/ops/buy/buy.js b/website/common/script/ops/buy/buy.js index a8241e9a59..76bec34b6e 100644 --- a/website/common/script/ops/buy/buy.js +++ b/website/common/script/ops/buy/buy.js @@ -13,6 +13,7 @@ import hourglassPurchase from './hourglassPurchase'; import errorMessage from '../../libs/errorMessage'; import {BuyGemOperation} from './buyGem'; import {BuyQuestWithGemOperation} from './buyQuestGem'; +import {BuyHourglassMountOperation} from './buyMount'; // @TODO: remove the req option style. Dependency on express structure is an anti-pattern // We should either have more params or a set structure validated by a Type checker @@ -72,8 +73,13 @@ module.exports = function buy (user, req = {}, analytics, options = {quantity: 1 case 'bundles': buyRes = purchaseOp(user, req, analytics); break; + case 'mounts': { + const buyOp = new BuyHourglassMountOperation(user, req, analytics); + + buyRes = buyOp.purchase(); + break; + } case 'pets': - case 'mounts': buyRes = hourglassPurchase(user, req, analytics); break; case 'quest': { diff --git a/website/common/script/ops/buy/buyMount.js b/website/common/script/ops/buy/buyMount.js new file mode 100644 index 0000000000..ced5cddfc8 --- /dev/null +++ b/website/common/script/ops/buy/buyMount.js @@ -0,0 +1,59 @@ +import content from '../../content/index'; +import { + BadRequest, + NotAuthorized, +} from '../../libs/errors'; + +import {AbstractHourglassItemOperation} from './abstractBuyOperation'; +import get from 'lodash/get'; +import includes from 'lodash/includes'; +import keys from 'lodash/keys'; + +export class BuyHourglassMountOperation extends AbstractHourglassItemOperation { + constructor (user, req, analytics) { + super(user, req, analytics); + } + + multiplePurchaseAllowed () { + return false; + } + + extractAndValidateParams (user, req) { + let key = this.key = get(req, 'params.key'); + if (!key) throw new BadRequest(this.i18n('missingKeyParam')); + + + if (!includes(keys(content.timeTravelStable.mounts), key)) { + throw new NotAuthorized(this.i18n('notAllowedHourglass')); + } + + if (user.items.mounts[key]) { + throw new NotAuthorized(this.i18n('mountsAlreadyOwned')); + } + + this.canUserPurchase(user, { + key, + }); + } + + executeChanges (user) { + user.items.mounts[this.key] = true; + + if (user.markModified) user.markModified('items.mounts'); + + this.subtractCurrency(user); + + let message = this.i18n('hourglassPurchase'); + + return [ + { items: user.items, purchasedPlanConsecutive: user.purchased.plan.consecutive }, + message, + ]; + } + + analyticsData () { + let data = super.analyticsData(); + data.itemType = 'mounts'; + return data; + } +}