Merge branch 'develop' into greenkeeper/update-to-node-10

This commit is contained in:
Matteo Pagliazzi 2018-04-27 19:49:11 +02:00
commit b7e601be16
13 changed files with 989 additions and 1206 deletions

1686
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -9,26 +9,26 @@
"amazon-payments": "^0.2.6",
"amplitude": "^3.5.0",
"apidoc": "^0.17.5",
"autoprefixer": "^8.2.0",
"aws-sdk": "^2.224.1",
"autoprefixer": "^8.3.0",
"aws-sdk": "^2.229.1",
"axios": "^0.18.0",
"axios-progress-bar": "^1.1.8",
"babel-core": "^6.0.0",
"babel-eslint": "^8.2.2",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.3",
"babel-loader": "^7.1.4",
"babel-plugin-syntax-async-functions": "^6.13.0",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.2",
"babel-plugin-transform-object-rest-spread": "^6.16.0",
"babel-plugin-transform-regenerator": "^6.16.1",
"babel-polyfill": "^6.6.1",
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.6.0",
"babel-runtime": "^6.11.6",
"bcrypt": "^1.0.2",
"bcrypt": "^2.0.0",
"body-parser": "^1.15.0",
"bootstrap": "^4.1.0",
"bootstrap-vue": "^2.0.0-rc.6",
"bootstrap-vue": "^2.0.0-rc.9",
"compression": "^1.7.2",
"cookie-session": "^1.2.0",
"coupon-code": "^0.4.5",
@ -38,7 +38,7 @@
"cwait": "^1.1.1",
"domain-middleware": "~0.1.0",
"express": "^4.16.3",
"express-basic-auth": "^1.1.4",
"express-basic-auth": "^1.1.5",
"express-validator": "^5.1.2",
"extract-text-webpack-plugin": "^3.0.2",
"glob": "^7.1.2",
@ -52,21 +52,20 @@
"hellojs": "^1.15.1",
"html-webpack-plugin": "^3.2.0",
"image-size": "^0.6.2",
"in-app-purchase": "^1.9.0",
"intro.js": "^2.6.0",
"in-app-purchase": "^1.9.2",
"intro.js": "^2.9.3",
"jquery": ">=3.0.0",
"js2xmlparser": "^3.0.0",
"lodash": "^4.17.4",
"memwatch-next": "^0.3.0",
"lodash": "^4.17.10",
"merge-stream": "^1.0.0",
"method-override": "^2.3.5",
"moment": "^2.22.0",
"moment": "^2.22.1",
"moment-recur": "^1.0.7",
"mongoose": "^5.0.14",
"mongoose": "^5.0.16",
"morgan": "^1.7.0",
"nconf": "^0.10.0",
"node-gcm": "^0.14.4",
"node-sass": "^4.8.3",
"node-sass": "^4.9.0",
"nodemailer": "^4.6.4",
"ora": "^2.0.0",
"pageres": "^4.1.1",
@ -107,7 +106,7 @@
"vuejs-datepicker": "git://github.com/habitrpg/vuejs-datepicker.git#5d237615463a84a23dd6f3f77c6ab577d68593ec",
"webpack": "^3.11.0",
"webpack-merge": "^4.0.0",
"winston": "^2.4.1",
"winston": "^2.4.2",
"winston-loggly-bulk": "^2.0.2",
"xml2js": "^0.4.4"
},
@ -141,13 +140,13 @@
"apidoc": "gulp apidoc"
},
"devDependencies": {
"@vue/test-utils": "^1.0.0-beta.13",
"@vue/test-utils": "^1.0.0-beta.15",
"babel-plugin-istanbul": "^4.1.6",
"babel-plugin-syntax-object-rest-spread": "^6.13.0",
"chai": "^4.1.2",
"chai-as-promised": "^7.1.1",
"chalk": "^2.3.2",
"chromedriver": "^2.37.0",
"chalk": "^2.4.1",
"chromedriver": "^2.38.2",
"connect-history-api-fallback": "^1.1.0",
"coveralls": "^3.0.0",
"cross-spawn": "^6.0.5",
@ -161,7 +160,7 @@
"expect.js": "^0.3.1",
"http-proxy-middleware": "^0.18.0",
"istanbul": "^1.1.0-alpha.1",
"karma": "^2.0.0",
"karma": "^2.0.2",
"karma-babel-preprocessor": "^7.0.0",
"karma-chai-plugins": "^0.9.0",
"karma-chrome-launcher": "^2.2.0",
@ -174,9 +173,9 @@
"karma-spec-reporter": "0.0.32",
"karma-webpack": "^3.0.0",
"lcov-result-merger": "^2.0.0",
"mocha": "^5.0.5",
"mocha": "^5.1.1",
"monk": "^6.0.5",
"nightwatch": "^0.9.20",
"nightwatch": "^0.9.21",
"puppeteer": "^1.3.0",
"require-again": "^2.0.0",
"selenium-server": "^3.11.0",
@ -185,9 +184,10 @@
"sinon-stub-promise": "^4.0.0",
"webpack-bundle-analyzer": "^2.11.1",
"webpack-dev-middleware": "^2.0.5",
"webpack-hot-middleware": "^2.22.0"
"webpack-hot-middleware": "^2.22.1"
},
"optionalDependencies": {
"memwatch-next": "^0.3.0",
"node-rdkafka": "^2.3.0"
}
}

View file

@ -0,0 +1,141 @@
/* eslint-disable camelcase */
import sinon from 'sinon'; // eslint-disable-line no-shadow
import {
generateUser,
} from '../../../helpers/common.helper';
import {
BadRequest, NotAuthorized,
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import {BuyGemOperation} from '../../../../website/common/script/ops/buy/buyGem';
import planGemLimits from '../../../../website/common/script/libs/planGemLimits';
function buyGem (user, req, analytics) {
let buyOp = new BuyGemOperation(user, req, analytics);
return buyOp.purchase();
}
describe('shared.ops.buyGem', () => {
let user;
let analytics = {track () {}};
let goldPoints = 40;
let gemsBought = 40;
let userGemAmount = 10;
beforeEach(() => {
user = generateUser({
stats: { gp: goldPoints },
balance: userGemAmount,
purchased: {
plan: {
gemsBought: 0,
customerId: 'costumer-id',
},
},
});
sinon.stub(analytics, 'track');
});
afterEach(() => {
analytics.track.restore();
});
context('Gems', () => {
it('purchases gems', () => {
let [, message] = buyGem(user, {params: {type: 'gems', key: 'gem'}}, analytics);
expect(message).to.equal(i18n.t('plusGem', {count: 1}));
expect(user.balance).to.equal(userGemAmount + 0.25);
expect(user.purchased.plan.gemsBought).to.equal(1);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate);
expect(analytics.track).to.be.calledOnce;
});
it('purchases gems with a different language than the default', () => {
let [, message] = buyGem(user, {params: {type: 'gems', key: 'gem'}, language: 'de'});
expect(message).to.equal(i18n.t('plusGem', {count: 1}, 'de'));
expect(user.balance).to.equal(userGemAmount + 0.25);
expect(user.purchased.plan.gemsBought).to.equal(1);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate);
});
it('makes bulk purchases of gems', () => {
let [, message] = buyGem(user, {
params: {type: 'gems', key: 'gem'},
quantity: 2,
});
expect(message).to.equal(i18n.t('plusGem', {count: 2}));
expect(user.balance).to.equal(userGemAmount + 0.50);
expect(user.purchased.plan.gemsBought).to.equal(2);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2);
});
context('Failure conditions', () => {
it('returns an error when key is not provided', (done) => {
try {
buyGem(user, {params: {type: 'gems'}});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('missingKeyParam'));
done();
}
});
it('prevents unsubscribed user from buying gems', (done) => {
delete user.purchased.plan.customerId;
try {
buyGem(user, {params: {type: 'gems', key: 'gem'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('mustSubscribeToPurchaseGems'));
done();
}
});
it('prevents user with not enough gold from buying gems', (done) => {
user.stats.gp = 15;
try {
buyGem(user, {params: {type: 'gems', key: 'gem'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotEnoughGold'));
done();
}
});
it('prevents user that have reached the conversion cap from buying gems', (done) => {
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = gemsBought;
try {
buyGem(user, {params: {type: 'gems', key: 'gem'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('reachedGoldToGemCap', {convCap: planGemLimits.convCap}));
done();
}
});
it('prevents user from buying an invalid quantity', (done) => {
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = gemsBought;
try {
buyGem(user, {params: {type: 'gems', key: 'gem'}, quantity: 'a'});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
done();
}
});
});
});
});

View file

@ -1,6 +1,5 @@
import purchase from '../../../../website/common/script/ops/buy/purchase';
import pinnedGearUtils from '../../../../website/common/script/ops/pinnedGearUtils';
import planGemLimits from '../../../../website/common/script/libs/planGemLimits';
import {
BadRequest,
NotAuthorized,
@ -17,7 +16,6 @@ describe('shared.ops.purchase', () => {
const SEASONAL_FOOD = 'Meat';
let user;
let goldPoints = 40;
let gemsBought = 40;
let analytics = {track () {}};
before(() => {
@ -45,63 +43,6 @@ describe('shared.ops.purchase', () => {
}
});
it('returns an error when key is not provided', (done) => {
try {
purchase(user, {params: {type: 'gems'}});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('keyRequired'));
done();
}
});
it('prevents unsubscribed user from buying gems', (done) => {
try {
purchase(user, {params: {type: 'gems', key: 'gem'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('mustSubscribeToPurchaseGems'));
done();
}
});
it('prevents user with not enough gold from buying gems', (done) => {
user.purchased.plan.customerId = 'customer-id';
try {
purchase(user, {params: {type: 'gems', key: 'gem'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotEnoughGold'));
done();
}
});
it('prevents user that have reached the conversion cap from buying gems', (done) => {
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = gemsBought;
try {
purchase(user, {params: {type: 'gems', key: 'gem'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('reachedGoldToGemCap', {convCap: planGemLimits.convCap}));
done();
}
});
it('prevents user from buying an invalid quantity', (done) => {
user.stats.gp = goldPoints;
user.purchased.plan.gemsBought = gemsBought;
try {
purchase(user, {params: {type: 'gems', key: 'gem'}, quantity: 'a'});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
done();
}
});
it('returns error when unknown type is provided', (done) => {
try {
@ -185,25 +126,6 @@ describe('shared.ops.purchase', () => {
user.pinnedItems.push({type: 'bundles', key: 'featheredFriends'});
});
it('purchases gems', () => {
let [, message] = purchase(user, {params: {type: 'gems', key: 'gem'}}, analytics);
expect(message).to.equal(i18n.t('plusOneGem'));
expect(user.balance).to.equal(userGemAmount + 0.25);
expect(user.purchased.plan.gemsBought).to.equal(1);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate);
expect(analytics.track).to.be.calledOnce;
});
it('purchases gems with a different language than the default', () => {
let [, message] = purchase(user, {params: {type: 'gems', key: 'gem'}, language: 'de'});
expect(message).to.equal(i18n.t('plusOneGem', 'de'));
expect(user.balance).to.equal(userGemAmount + 0.5);
expect(user.purchased.plan.gemsBought).to.equal(2);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2);
});
it('purchases eggs', () => {
let type = 'eggs';
let key = 'Wolf';
@ -307,18 +229,6 @@ describe('shared.ops.purchase', () => {
}
});
it('makes bulk purchases of gems', () => {
let [, message] = purchase(user, {
params: {type: 'gems', key: 'gem'},
quantity: 2,
});
expect(message).to.equal(i18n.t('plusOneGem'));
expect(user.balance).to.equal(userGemAmount + 0.50);
expect(user.purchased.plan.gemsBought).to.equal(2);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2);
});
it('makes bulk purchases of eggs', () => {
let type = 'eggs';
let key = 'TigerCub';

View file

@ -36,7 +36,7 @@ describe('shared.ops.sell', () => {
sell(user, {params: { type } });
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('keyRequired'));
expect(err.message).to.equal(i18n.t('missingKeyParam'));
done();
}
});

View file

@ -120,7 +120,7 @@
.row.title-row
.col-12.col-md-6
h3(v-if='userLevel100Plus', v-once, v-html="$t('noMoreAllocate')")
h3(v-if='user.stats.points || userLevel100Plus')
h3
| {{$t('pointsAvailable')}}
.counter.badge(v-if='user.stats.points || userLevel100Plus')
| {{user.stats.points}} 
@ -131,16 +131,21 @@
v-model='user.preferences.automaticAllocation',
@change='setAutoAllocate()'
)
.row
.col-12.col-md-3(v-for='(statInfo, stat) in allocateStatsList')
.box.white.row.col-12
.col-12
.col-12.col-md-8
div(:class='stat') {{ $t(stats[stat].title) }}
.number {{ user.stats[stat] }}
.points {{$t('pts')}}
.col-12.col-md-4
.up(v-if='user.stats.points', @click='allocate(stat)')
div
.up(v-if='user.stats.points', @click='allocate(stat)')
div
.down(@click='deallocate(stat)')
.row.save-row
.col-12.col-md-6.offset-md-3.text-center
button.btn.btn-primary(@click='saveAttributes()', :disabled='loading') {{ this.loading ? $t('loading') : $t('save') }}
</template>
<script>
@ -169,6 +174,7 @@
},
data () {
return {
loading: false,
equipTypes: {
eyewear: this.$t('eyewear'),
head: this.$t('headgearCapitalized'),
@ -206,10 +212,18 @@
popover: 'perText',
},
},
statsAtStart: {
str: 0,
int: 0,
con: 0,
per: 0,
},
content: Content,
};
},
mounted () {
this.statsAtStart = Object.assign({}, this.user.stats);
},
computed: {
...mapState({
flatGear: 'content.gear.flat',
@ -271,14 +285,32 @@
return display;
},
formatOutOfTotalDisplay (stat, totalStat) {
let display = `${stat}/${totalStat}`;
return display;
},
allocate (stat) {
allocate(this.user, {query: { stat }});
axios.post(`/api/v3/user/allocate?stat=${stat}`);
},
deallocate (stat) {
if (this.user.stats[stat] === 0) return;
this.user.stats[stat] -= 1;
this.user.stats.points += 1;
},
async saveAttributes () {
this.loading = true;
const statUpdates = {};
['str', 'int', 'per', 'con'].forEach(stat => {
const diff = this.user.stats[stat] - this.statsAtStart[stat];
statUpdates[stat] = diff;
});
await axios.post('/api/v3/user/allocate-bulk', {
stats: statUpdates,
});
this.loading = false;
},
allocateNow () {
autoAllocate(this.user);
@ -384,20 +416,27 @@
margin-left: .5em;
}
.up {
.up, .down {
border: solid #a5a1ac;
border-width: 0 3px 3px 0;
display: inline-block;
padding: 3px;
}
.up:hover, .down:hover {
cursor: pointer;
}
.up {
transform: rotate(-135deg);
-webkit-transform: rotate(-135deg);
margin-top: 1em;
}
.up:hover {
cursor: pointer;
.down {
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
}
}
}
@ -457,4 +496,7 @@
margin-top: -0.2em !important;
}
.save-row {
margin-top: 1em;
}
</style>

View file

@ -93,10 +93,9 @@
"mustPurchaseToSet": "Must purchase <%= val %> to set it on <%= key %>.",
"typeRequired": "Type is required",
"positiveAmountRequired": "Positive amount is required",
"keyRequired": "Key is required",
"notAccteptedType": "Type must be in [eggs, hatchingPotions, premiumHatchingPotions, food, quests, gear]",
"contentKeyNotFound": "Key not found for Content <%= type %>",
"plusOneGem": "+1 Gem",
"plusGem": "+<%= count %> Gem",
"typeNotSellable": "Type is not sellable. Must be one of the following <%= acceptedTypes %>",
"userItemsKeyNotFound": "Key not found for user.items <%= type %>",
"userItemsNotEnough": "You do not have enough <%= type %>",

View file

@ -7,6 +7,7 @@
"buyGemsGoldText": "Alexander the Merchant will sell you Gems at a cost of 20 Gold per Gem. His monthly shipments are initially capped at 25 Gems per month, but for every 3 consecutive months that you are subscribed, this cap increases by 5 Gems, up to a maximum of 50 Gems per month!",
"mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",
"reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap resets within the first three days of each month.",
"reachedGoldToGemCapQuantity": "Your requested amount <%= quantity %> exceeds the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap resets within the first three days of each month.",
"retainHistory": "Retain additional history entries",
"retainHistoryText": "Makes completed To-Dos and task history available for longer.",
"doubleDrops": "Daily drop caps doubled",

View file

@ -74,6 +74,10 @@ export class AbstractBuyOperation {
return resultObj;
}
analyticsLabel () {
return 'acquire item';
}
sendToAnalytics (additionalData = {}) {
// spread-operator produces an "unexpected token" error
let analyticsData = _merge(additionalData, {
@ -87,7 +91,7 @@ export class AbstractBuyOperation {
analyticsData.quantityPurchased = this.quantity;
}
this.analytics.track('acquire item', analyticsData);
this.analytics.track(this.analyticsLabel(), analyticsData);
}
}
@ -100,6 +104,10 @@ export class AbstractGoldItemOperation extends AbstractBuyOperation {
return item.value;
}
getIemKey (item) {
return item.key;
}
canUserPurchase (user, item) {
this.item = item;
let itemValue = this.getItemValue(item);
@ -110,20 +118,20 @@ export class AbstractGoldItemOperation extends AbstractBuyOperation {
throw new NotAuthorized(this.i18n('messageNotEnoughGold'));
}
if (item.canOwn && !item.canOwn(user)) {
if (item && item.canOwn && !item.canOwn(user)) {
throw new NotAuthorized(this.i18n('cannotBuyItem'));
}
}
subtractCurrency (user, item, quantity = 1) {
subtractCurrency (user, item) {
let itemValue = this.getItemValue(item);
user.stats.gp -= itemValue * quantity;
user.stats.gp -= itemValue * this.quantity;
}
analyticsData () {
return {
itemKey: this.item.key,
itemKey: this.getIemKey(this.item),
itemType: 'Market',
acquireMethod: 'Gold',
goldCost: this.getItemValue(this.item),

View file

@ -11,6 +11,7 @@ import {BuyQuestWithGoldOperation} from './buyQuest';
import buySpecialSpell from './buySpecialSpell';
import purchaseOp from './purchase';
import hourglassPurchase from './hourglassPurchase';
import {BuyGemOperation} from './buyGem';
// @TODO: remove the req option style. Dependency on express structure is an anti-pattern
// We should either have more parms or a set structure validated by a Type checker
@ -45,13 +46,18 @@ module.exports = function buy (user, req = {}, analytics) {
buyRes = buyOp.purchase();
break;
}
case 'gems': {
const buyOp = new BuyGemOperation(user, req, analytics);
buyRes = buyOp.purchase();
break;
}
case 'eggs':
case 'hatchingPotions':
case 'food':
case 'quests':
case 'gear':
case 'bundles':
case 'gems':
buyRes = purchaseOp(user, req, analytics);
break;
case 'pets':

View file

@ -0,0 +1,81 @@
import pick from 'lodash/pick';
import splitWhitespace from '../../libs/splitWhitespace';
import {
BadRequest,
NotAuthorized,
} from '../../libs/errors';
import {AbstractGoldItemOperation} from './abstractBuyOperation';
import get from 'lodash/get';
import planGemLimits from '../../libs/planGemLimits';
export class BuyGemOperation extends AbstractGoldItemOperation {
constructor (user, req, analytics) {
super(user, req, analytics);
}
multiplePurchaseAllowed () {
return true;
}
getItemValue () {
return planGemLimits.convRate;
}
getIemKey () {
return 'gem';
}
extractAndValidateParams (user, req) {
let key = this.key = get(req, 'params.key');
if (!key) throw new BadRequest(this.i18n('missingKeyParam'));
let convCap = planGemLimits.convCap;
convCap += user.purchased.plan.consecutive.gemCapExtra;
// todo better name?
this.convCap = convCap;
this.canUserPurchase(user);
}
canUserPurchase (user, item) {
if (!user.purchased || !user.purchased.plan || !user.purchased.plan.customerId) {
throw new NotAuthorized(this.i18n('mustSubscribeToPurchaseGems'));
}
super.canUserPurchase(user, item);
if (user.purchased.plan.gemsBought >= this.convCap) {
throw new NotAuthorized(this.i18n('reachedGoldToGemCap', {convCap: this.convCap}));
}
if (user.purchased.plan.gemsBought + this.quantity >= this.convCap) {
throw new NotAuthorized(this.i18n('reachedGoldToGemCapQuantity', {
convCap: this.convCap,
quantity: this.quantity,
}));
}
}
executeChanges (user, item) {
user.balance += 0.25 * this.quantity;
user.purchased.plan.gemsBought += this.quantity;
this.subtractCurrency(user, item);
return [
pick(user, splitWhitespace('stats balance')),
this.i18n('plusGem', {count: this.quantity}),
];
}
analyticsLabel () {
return 'purchase gems';
}
analyticsData () {
let data = super.analyticsData();
data.itemKey = 'gem';
return data;
}
}

View file

@ -4,7 +4,6 @@ import get from 'lodash/get';
import pick from 'lodash/pick';
import forEach from 'lodash/forEach';
import splitWhitespace from '../../libs/splitWhitespace';
import planGemLimits from '../../libs/planGemLimits';
import {
NotFound,
NotAuthorized,
@ -14,48 +13,6 @@ import {
import { removeItemByPath } from '../pinnedGearUtils';
import getItemInfo from '../../libs/getItemInfo';
function buyGems (user, analytics, req, key) {
let convRate = planGemLimits.convRate;
let convCap = planGemLimits.convCap;
convCap += user.purchased.plan.consecutive.gemCapExtra;
// Some groups limit their members ability to obtain gems
// The check is async so it's done on the server (in server/controllers/api-v3/user#purchase)
// only and not on the client,
// resulting in a purchase that will seem successful until the request hit the server.
if (!user.purchased || !user.purchased.plan || !user.purchased.plan.customerId) {
throw new NotAuthorized(i18n.t('mustSubscribeToPurchaseGems', req.language));
}
if (user.stats.gp < convRate) {
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
}
if (user.purchased.plan.gemsBought >= convCap) {
throw new NotAuthorized(i18n.t('reachedGoldToGemCap', {convCap}, req.language));
}
user.balance += 0.25;
user.purchased.plan.gemsBought++;
user.stats.gp -= convRate;
if (analytics) {
analytics.track('purchase gems', {
uuid: user._id,
itemKey: key,
acquireMethod: 'Gold',
goldCost: convRate,
category: 'behavior',
headers: req.headers,
});
}
return [
pick(user, splitWhitespace('stats balance')),
i18n.t('plusOneGem', req.language),
];
}
function getItemAndPrice (user, type, key, req) {
let item;
let price;
@ -120,15 +77,7 @@ module.exports = function purchase (user, req = {}, analytics) {
}
if (!key) {
throw new BadRequest(i18n.t('keyRequired', req.language));
}
if (type === 'gems' && key === 'gem') {
let gemResponse;
for (let i = 0; i < quantity; i += 1) {
gemResponse = buyGems(user, analytics, req, key);
}
return gemResponse;
throw new BadRequest(i18n.t('missingKeyParam', req.language));
}
if (!acceptedTypes.includes(type)) {

View file

@ -26,7 +26,7 @@ module.exports = function sell (user, req = {}) {
}
if (!key) {
throw new BadRequest(i18n.t('keyRequired', req.language));
throw new BadRequest(i18n.t('missingKeyParam', req.language));
}
if (ACCEPTEDTYPES.indexOf(type) === -1) {