mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
port shared.ops.allocate
This commit is contained in:
@@ -100,5 +100,7 @@
|
|||||||
"targetIdUUID": "\"targetId\" must be a valid UUID.",
|
"targetIdUUID": "\"targetId\" must be a valid UUID.",
|
||||||
"challengeTasksNoCast": "Casting a spell on challenge tasks is not supported.",
|
"challengeTasksNoCast": "Casting a spell on challenge tasks is not supported.",
|
||||||
"spellNotOwned": "You don't own this spell.",
|
"spellNotOwned": "You don't own this spell.",
|
||||||
"spellLevelTooHigh": "You must be level <%= level %> to use this spell."
|
"spellLevelTooHigh": "You must be level <%= level %> to use this spell.",
|
||||||
|
"invalidAttribute": "\"<%= attr %>\" is not a valid attribute.",
|
||||||
|
"notEnoughAttrPoints": "You don't have enough attribute points."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
export const MAX_HEALTH = 50;
|
export const MAX_HEALTH = 50;
|
||||||
export const MAX_LEVEL = 100;
|
export const MAX_LEVEL = 100;
|
||||||
export const MAX_STAT_POINTS = MAX_LEVEL;
|
export const MAX_STAT_POINTS = MAX_LEVEL;
|
||||||
|
export const ATTRIBUTES = ['str', 'int', 'per', 'con'];
|
||||||
|
|||||||
@@ -100,10 +100,12 @@ api.count = count;
|
|||||||
// TODO As ops and fns are ported, exported them through the api object
|
// TODO As ops and fns are ported, exported them through the api object
|
||||||
import scoreTask from './ops/scoreTask';
|
import scoreTask from './ops/scoreTask';
|
||||||
import sleep from './ops/sleep';
|
import sleep from './ops/sleep';
|
||||||
|
import allocate from './ops/allocate';
|
||||||
|
|
||||||
api.ops = {
|
api.ops = {
|
||||||
scoreTask,
|
scoreTask,
|
||||||
sleep,
|
sleep,
|
||||||
|
allocate,
|
||||||
};
|
};
|
||||||
api.fns = {};
|
api.fns = {};
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,30 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import splitWhitespace from '../libs/splitWhitespace';
|
import splitWhitespace from '../libs/splitWhitespace';
|
||||||
|
import {
|
||||||
|
ATTRIBUTES,
|
||||||
|
} from '../constants';
|
||||||
|
import {
|
||||||
|
BadRequest,
|
||||||
|
NotAuthorized,
|
||||||
|
} from '../libs/errors';
|
||||||
|
import i18n from '../i18n';
|
||||||
|
|
||||||
|
module.exports = function allocate (user, req = {}) {
|
||||||
|
let stat = _.get(req, 'query.stat', 'str');
|
||||||
|
|
||||||
|
if (ATTRIBUTES.indexOf(stat) === -1) {
|
||||||
|
throw new BadRequest(i18n.t('invalidAttribute', {attr: stat}, req.language));
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = function(user, req, cb) {
|
|
||||||
var stat;
|
|
||||||
stat = req.query.stat || 'str';
|
|
||||||
if (user.stats.points > 0) {
|
if (user.stats.points > 0) {
|
||||||
user.stats[stat]++;
|
user.stats[stat]++;
|
||||||
user.stats.points--;
|
user.stats.points--;
|
||||||
if (stat === 'int') {
|
if (stat === 'int') {
|
||||||
user.stats.mp++;
|
user.stats.mp++;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
throw new NotAuthorized(i18n.t('notEnoughAttrPoints', req.language));
|
||||||
}
|
}
|
||||||
return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats'))) : void 0;
|
|
||||||
|
return _.pick(user, splitWhitespace('stats'));
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ const COMMON_FILES = [
|
|||||||
'!./common/script/ops/addTag.js',
|
'!./common/script/ops/addTag.js',
|
||||||
'!./common/script/ops/addTask.js',
|
'!./common/script/ops/addTask.js',
|
||||||
'!./common/script/ops/addWebhook.js',
|
'!./common/script/ops/addWebhook.js',
|
||||||
'!./common/script/ops/allocate.js',
|
|
||||||
'!./common/script/ops/allocateNow.js',
|
'!./common/script/ops/allocateNow.js',
|
||||||
'!./common/script/ops/blockUser.js',
|
'!./common/script/ops/blockUser.js',
|
||||||
'!./common/script/ops/buy.js',
|
'!./common/script/ops/buy.js',
|
||||||
|
|||||||
41
test/api/v3/integration/user/POST-user_allocate.test.js
Normal file
41
test/api/v3/integration/user/POST-user_allocate.test.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
|
describe('POST /user/allocate', () => {
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
user = await generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
// More tests in common code unit tests
|
||||||
|
|
||||||
|
it('returns an error if an invalid attribute is supplied', async () => {
|
||||||
|
await expect(user.post(`/user/allocate?stat=invalid`))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 400,
|
||||||
|
error: 'BadRequest',
|
||||||
|
message: t('invalidAttribute', {attr: 'invalid'}),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns an error if the user doesn\'t have attribute points', async () => {
|
||||||
|
await expect(user.post(`/user/allocate`))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('notEnoughAttrPoints'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allocates attribute points', async () => {
|
||||||
|
await user.update({'stats.points': 1});
|
||||||
|
let res = await user.post(`/user/allocate?stat=con`);
|
||||||
|
await user.sync();
|
||||||
|
expect(user.stats.con).to.equal(1);
|
||||||
|
expect(user.stats.points).to.equal(0);
|
||||||
|
expect(res.stats.con).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -9,6 +9,8 @@ describe('POST /user/sleep', () => {
|
|||||||
user = await generateUser();
|
user = await generateUser();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// More tests in common code unit tests
|
||||||
|
|
||||||
it('toggles sleep status', async () => {
|
it('toggles sleep status', async () => {
|
||||||
let res = await user.post(`/user/sleep`);
|
let res = await user.post(`/user/sleep`);
|
||||||
expect(res).to.eql({
|
expect(res).to.eql({
|
||||||
11
test/common/constants.js
Normal file
11
test/common/constants.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import {
|
||||||
|
ATTRIBUTES,
|
||||||
|
} from '../../common/script/constants';
|
||||||
|
|
||||||
|
describe('constants', () => {
|
||||||
|
describe('ATTRIBUTES', () => {
|
||||||
|
it('provides a list of attributes', () => {
|
||||||
|
expect(ATTRIBUTES).to.eql(['str', 'int', 'per', 'con']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
59
test/common/ops/allocate.js
Normal file
59
test/common/ops/allocate.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import allocate from '../../../common/script/ops/allocate';
|
||||||
|
import {
|
||||||
|
BadRequest,
|
||||||
|
NotAuthorized,
|
||||||
|
} from '../../../common/script/libs/errors';
|
||||||
|
import i18n from '../../../common/script/i18n';
|
||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
} from '../../helpers/common.helper';
|
||||||
|
|
||||||
|
describe('shared.ops.allocate', () => {
|
||||||
|
let user;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
user = generateUser();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if an invalid attribute is supplied', () => {
|
||||||
|
try {
|
||||||
|
expect(allocate(user, {
|
||||||
|
query: {stat: 'notValid'},
|
||||||
|
})).to.throw(BadRequest);
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).to.equal(i18n.t('invalidAttribute', {attr: 'notValid'}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('throws an error if the user doesn\'t have attribute points', () => {
|
||||||
|
try {
|
||||||
|
expect(allocate(user)).to.throw(NotAuthorized);
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).to.equal(i18n.t('notEnoughAttrPoints'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults to the "str" attribute', () => {
|
||||||
|
expect(user.stats.str).to.equal(0);
|
||||||
|
user.stats.points = 1;
|
||||||
|
allocate(user);
|
||||||
|
expect(user.stats.str).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allocates attribute points', () => {
|
||||||
|
expect(user.stats.con).to.equal(0);
|
||||||
|
user.stats.points = 1;
|
||||||
|
allocate(user, {query: {stat: 'con'}});
|
||||||
|
expect(user.stats.con).to.equal(1);
|
||||||
|
expect(user.stats.points).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('increases mana when allocating to "int"', () => {
|
||||||
|
expect(user.stats.int).to.equal(0);
|
||||||
|
expect(user.stats.mp).to.equal(10);
|
||||||
|
user.stats.points = 1;
|
||||||
|
allocate(user, {query: {stat: 'int'}});
|
||||||
|
expect(user.stats.int).to.equal(1);
|
||||||
|
expect(user.stats.mp).to.equal(11);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -16,8 +16,6 @@ import _ from 'lodash';
|
|||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { preenHistory } from '../../libs/api-v3/preening';
|
import { preenHistory } from '../../libs/api-v3/preening';
|
||||||
|
|
||||||
const scoreTask = common.ops.scoreTask;
|
|
||||||
|
|
||||||
let api = {};
|
let api = {};
|
||||||
|
|
||||||
// challenge must be passed only when a challenge task is being created
|
// challenge must be passed only when a challenge task is being created
|
||||||
@@ -401,7 +399,7 @@ api.scoreTask = {
|
|||||||
task.completed = direction === 'up'; // TODO move into scoreTask
|
task.completed = direction === 'up'; // TODO move into scoreTask
|
||||||
}
|
}
|
||||||
|
|
||||||
let delta = scoreTask({task, user, direction}, req);
|
let delta = common.ops.scoreTask({task, user, direction}, req);
|
||||||
// Drop system (don't run on the client, as it would only be discarded since ops are sent to the API, not the results)
|
// Drop system (don't run on the client, as it would only be discarded since ops are sent to the API, not the results)
|
||||||
if (direction === 'up') user.fns.randomDrop({task, delta}, req);
|
if (direction === 'up') user.fns.randomDrop({task, delta}, req);
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import Q from 'q';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import * as passwordUtils from '../../libs/api-v3/password';
|
import * as passwordUtils from '../../libs/api-v3/password';
|
||||||
|
|
||||||
const sleep = common.ops.sleep;
|
|
||||||
|
|
||||||
let api = {};
|
let api = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -293,10 +291,30 @@ api.sleep = {
|
|||||||
url: '/user/sleep',
|
url: '/user/sleep',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
let user = res.locals.user;
|
||||||
let sleepRes = sleep(user);
|
let sleepRes = common.ops.sleep(user);
|
||||||
await user.save();
|
await user.save();
|
||||||
res.respond(200, sleepRes);
|
res.respond(200, sleepRes);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /user/allocate Allocate an attribute point.
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName UserAllocate
|
||||||
|
* @apiGroup User
|
||||||
|
*
|
||||||
|
* @apiSuccess {Object} Returs `user.stats`
|
||||||
|
*/
|
||||||
|
api.allocate = {
|
||||||
|
method: 'POST',
|
||||||
|
middlewares: [authWithHeaders(), cron],
|
||||||
|
url: '/user/allocate',
|
||||||
|
async handler (req, res) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
let allocateRes = common.ops.allocate(user, req);
|
||||||
|
await user.save();
|
||||||
|
res.respond(200, allocateRes);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = api;
|
module.exports = api;
|
||||||
|
|||||||
Reference in New Issue
Block a user