Merge branch 'api-v3-hourglass-purchase' of https://github.com/TheHollidayInn/habitrpg into TheHollidayInn-api-v3-hourglass-purchase2

This commit is contained in:
Matteo Pagliazzi
2016-04-01 16:28:20 +02:00
8 changed files with 236 additions and 36 deletions

View File

@@ -136,5 +136,6 @@
"notAccteptedType": "Type must be in [eggs, hatchingPotions, food, quests, gear]",
"contentKeyNotFound": "Key not found for Content <%= type %>",
"plusOneGem": "+1 Gem",
"purchased": "You purchsed a <%= key %> <%= type %>"
"purchased": "You purchsed a <%= key %> <%= type %>",
"notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass."
}

View File

@@ -113,7 +113,7 @@
"hourglassBuyItemConfirm": "Buy this item for 1 Mystic Hourglass?",
"petsAlreadyOwned": "Pet already owned.",
"mountsAlreadyOwned": "Mount already owned.",
"typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: ",
"typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: <%= allowedTypes %>",
"petsNotAllowedHourglass": "Pet not available for purchase with Mystic Hourglass.",
"mountsNotAllowedHourglass": "Mount not available for purchase with Mystic Hourglass.",
"hourglassPurchase": "Purchased an item using a Mystic Hourglass!",

View File

@@ -115,6 +115,7 @@ import equip from './ops/equip';
import changeClass from './ops/changeClass';
import disableClasses from './ops/disableClasses';
import purchase from './ops/purchase';
import purchaseHourglass from './ops/hourglassPurchase';
api.ops = {
scoreTask,
@@ -131,6 +132,7 @@ api.ops = {
changeClass,
disableClasses,
purchase,
purchaseHourglass,
};
import handleTwoHanded from './fns/handleTwoHanded';

View File

@@ -3,53 +3,58 @@ import i18n from '../i18n';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
import pickDeep from '../libs/pickDeep';
import {
BadRequest,
NotAuthorized,
} from '../libs/errors';
module.exports = function purchaseHourglass (user, req = {}, analytics) {
let key = _.get(req, 'params.key');
if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
let type = _.get(req, 'params.type');
if (!type) throw new BadRequest(i18n.t('missingTypeParam', req.language));
module.exports = function(user, req, cb, analytics) {
var analyticsData, key, ref, type;
ref = req.params, type = ref.type, key = ref.key;
if (!content.timeTravelStable[type]) {
return typeof cb === "function" ? cb({
code: 403,
message: i18n.t('typeNotAllowedHourglass', req.language) + JSON.stringify(_.keys(content.timeTravelStable))
}) : void 0;
throw new NotAuthorized(i18n.t('typeNotAllowedHourglass', {allowedTypes: _.keys(content.timeTravelStable).toString()}, req.language));
}
if (!_.contains(_.keys(content.timeTravelStable[type]), key)) {
return typeof cb === "function" ? cb({
code: 403,
message: i18n.t(type + 'NotAllowedHourglass', req.language)
}) : void 0;
throw new NotAuthorized(i18n.t('notAllowedHourglass', req.language));
}
if (user.items[type][key]) {
return typeof cb === "function" ? cb({
code: 403,
message: i18n.t(type + 'AlreadyOwned', req.language)
}) : void 0;
throw new NotAuthorized(i18n.t(`${type}AlreadyOwned`, req.language));
}
if (!(user.purchased.plan.consecutive.trinkets > 0)) {
return typeof cb === "function" ? cb({
code: 403,
message: i18n.t('notEnoughHourglasses', req.language)
}) : void 0;
if (user.purchased.plan.consecutive.trinkets <= 0) {
throw new NotAuthorized(i18n.t('notEnoughHourglasses', req.language));
}
user.purchased.plan.consecutive.trinkets--;
if (type === 'pets') {
user.items.pets[key] = 5;
}
if (type === 'mounts') {
user.items.mounts[key] = true;
}
analyticsData = {
if (analytics) {
analytics.track('acquire item', {
uuid: user._id,
itemKey: key,
itemType: type,
acquireMethod: 'Hourglass',
category: 'behavior'
};
if (analytics != null) {
analytics.track('acquire item', analyticsData);
category: 'behavior',
});
}
return typeof cb === "function" ? cb({
code: 200,
message: i18n.t('hourglassPurchase', req.language)
}, pickDeep(user, splitWhitespace('items purchased.plan.consecutive'))) : void 0;
let res = {
data: pickDeep(user, splitWhitespace('items purchased.plan.consecutive')),
message: i18n.t('hourglassPurchase', req.language),
};
return res;
};

View File

@@ -28,7 +28,6 @@ const COMMON_FILES = [
'!./common/script/ops/deleteWebhook.js',
'!./common/script/ops/getTag.js',
'!./common/script/ops/getTags.js',
'!./common/script/ops/hourglassPurchase.js',
'!./common/script/ops/openMysteryItem.js',
'!./common/script/ops/readCard.js',
'!./common/script/ops/rebirth.js',

View File

@@ -0,0 +1,25 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('POST /user/purchase-hourglass/:type/:key', () => {
let user;
beforeEach(async () => {
user = await generateUser({
'purchased.plan.consecutive.trinkets': 2,
});
});
// More tests in common code unit tests
it('buys a hourglass pet', async () => {
let response = await user.post('/user/purchase-hourglass/pets/MantisShrimp-Base');
await user.sync();
expect(response.message).to.eql(t('hourglassPurchase'));
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
expect(user.items.pets).to.eql({'MantisShrimp-Base': 5});
});
});

View File

@@ -0,0 +1,145 @@
import hourglassPurchase from '../../../common/script/ops/hourglassPurchase';
import {
BadRequest,
NotAuthorized,
} from '../../../common/script/libs/errors';
import i18n from '../../../common/script/i18n';
import content from '../../../common/script/content/index';
import {
generateUser,
} from '../../helpers/common.helper';
describe('user.ops.hourglassPurchase', () => {
let user;
beforeEach(() => {
user = generateUser();
});
context('failure conditions', () => {
it('return error when key is not provided', (done) => {
try {
hourglassPurchase(user, {params: {}});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.eql(i18n.t('missingKeyParam'));
done();
}
});
it('returns error when type is not provided', (done) => {
try {
hourglassPurchase(user, {params: {key: 'Base'}});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.eql(i18n.t('missingTypeParam'));
done();
}
});
it('returns error when inccorect type is provided', (done) => {
try {
hourglassPurchase(user, {params: {type: 'notAType', key: 'MantisShrimp-Base'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.eql(i18n.t('typeNotAllowedHourglass', {allowedTypes: _.keys(content.timeTravelStable).toString()}));
done();
}
});
it('does not grant to pets without Mystic Hourglasses', (done) => {
try {
hourglassPurchase(user, {params: {type: 'pets', key: 'MantisShrimp-Base'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.eql(i18n.t('notEnoughHourglasses'));
done();
}
});
it('does not grant to mounts without Mystic Hourglasses', (done) => {
try {
hourglassPurchase(user, {params: {type: 'mounts', key: 'MantisShrimp-Base'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.eql(i18n.t('notEnoughHourglasses'));
done();
}
});
it('does not grant pet that is not part of the Time Travel Stable', (done) => {
user.purchased.plan.consecutive.trinkets = 1;
try {
hourglassPurchase(user, {params: {type: 'pets', key: 'Wolf-Veteran'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.eql(i18n.t('notAllowedHourglass'));
done();
}
});
it('does not grant mount that is not part of the Time Travel Stable', (done) => {
user.purchased.plan.consecutive.trinkets = 1;
try {
hourglassPurchase(user, {params: {type: 'mounts', key: 'Orca-Base'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.eql(i18n.t('notAllowedHourglass'));
done();
}
});
it('does not grant pet that has already been purchased', (done) => {
user.purchased.plan.consecutive.trinkets = 1;
user.items.pets = {
'MantisShrimp-Base': true,
};
try {
hourglassPurchase(user, {params: {type: 'pets', key: 'MantisShrimp-Base'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.eql(i18n.t('petsAlreadyOwned'));
done();
}
});
it('does not grant mount that has already been purchased', (done) => {
user.purchased.plan.consecutive.trinkets = 1;
user.items.mounts = {
'MantisShrimp-Base': true,
};
try {
hourglassPurchase(user, {params: {type: 'mounts', key: 'MantisShrimp-Base'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.eql(i18n.t('mountsAlreadyOwned'));
done();
}
});
});
context('successful purchases', () => {
it('buys a pet', () => {
user.purchased.plan.consecutive.trinkets = 2;
let response = hourglassPurchase(user, {params: {type: 'pets', key: 'MantisShrimp-Base'}});
expect(response.message).to.eql(i18n.t('hourglassPurchase'));
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
expect(user.items.pets).to.eql({'MantisShrimp-Base': 5});
});
it('buys a mount', () => {
user.purchased.plan.consecutive.trinkets = 2;
let response = hourglassPurchase(user, {params: {type: 'mounts', key: 'MantisShrimp-Base'}});
expect(response.message).to.eql(i18n.t('hourglassPurchase'));
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
expect(user.items.mounts).to.eql({'MantisShrimp-Base': true});
});
});
});

View File

@@ -699,4 +699,27 @@ api.purchase = {
},
};
/**
* @api {post} /user/purchase-hourglass/:type/:key Purchase Hourglass.
* @apiVersion 3.0.0
* @apiName UserPurchaseHourglass
* @apiGroup User
*
* @apiParam {string} type {pets|mounts}. The type of item to purchase
* @apiParam {string} key Ex: {MantisShrimp-Base}. The key for the mount/pet
*
* @apiSuccess {Object} data `items purchased.plan.consecutive`
*/
api.userPurchaseHourglass = {
method: 'POST',
middlewares: [authWithHeaders(), cron],
url: '/user/purchase-hourglass/:type/:key',
async handler (req, res) {
let user = res.locals.user;
let purchaseHourglassResponse = common.ops.purchaseHourglass(user, req, res.analytics);
await user.save();
res.respond(200, purchaseHourglassResponse);
},
};
module.exports = api;