mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-15 21:57:22 +01:00
* Fixed more tests * Added tags into user service * Added api-v3 auth urls * v3: fix package.json * v3: fix package.json * Fixed auth tests. Updated Authctrl response * v3: remove newrelic config file in favour of env variables * v3: upgrade some deps * switch from Q to Bluebird * v3 fix tests with deferred * Removed extra consoles.log. Changed data.data to res.data * v3 fix tests and use coroutines instead of regenerator * v3: fix tests * v3: do not await a non promise * v3: q -> bluebird * Changed id param for registration response * Updated party query and create * Ensured login callback happens after user sync * Add challenges to groups. Fixed isMemberOfGuild check * Updated party and group tests * Fixed cron test * return user.id and send analytics event before changing page * fix trailing spaces * disable redirects * Api v3 party tavern fixes (#7191) * Added check if user is in party before query * Cached party query. Prevented party request when user is not in party. Updated Party create with no invites * Update tavern ctrl to use new promise * v3: misc fixes * Api v3 task fixes (#7193) * Update task view to use _id * Added try catch to user service ops calls * v3 client: saving after syncing is complete * Fixed test broken by part sync change (#7195) * v3: fix todo scoring and try to fix production testing problem * revert changes to mongoose config * mongoose: increase keepAlive * test mongoose fix * fix: Only apply captureStackTrace if it exists on the error object * v3: fix reminders with no startDate * mongoose: use options * chore(): rename website/src -> website/server and website/public -> website/client (#7199) * v3 fix GET /groups: return an error only if an invalid type is supplied not when there are 0 results (#7203) * [API v3] Fix calls to user.ops and deleting tags (#7204) * v3: fixes calls to user.ops from views and deleting tags * v3: fix tests that use user._statsComputed * Api v3 fixes continued (#7205) * Added timzeone offset back * Added APIToken back to settings page * Fixed fetch recent messages for party * Fixed returning group description * Fixed check if user is member of challenge * Fixed party members appearing in header * Updated get myGroups param to include public groups. Fixed isMemberOf group * Fixed hourglass purchase * Fixed challenge addding tasks on first creating * Updated tests to accomidate new changes * fix: Correct checklist on client Closes #7207 * fix: Pin eslint to 2.9 * minor improvements to cron code for clarity; fix inaccurate comments; add TODOs for rest-in-inn actions * fix: Add missing type param to equip call closes #7212 * rename and reword pubChalsMinPrize to reflect that it's only for Tavern challenges * allows players to send gems to each other; other minor related changes - fixes https://github.com/HabitRPG/habitrpg/issues/7227 * fix tests for /members/transfer-gems * fix: Set gems sent notification as translatable string * chore: Remove unusued variable * fix: Remove requirement on message paramter in transfer-gems * add a missing variable declaration * chore: clarify comments on cron code * fix: Correct client request from habitrpg -> tavern * update apidoc URL in package.json Closes #7222 * Fixed start party by invites * Updated spell casting to v3 * Fixed adding and removing tags on tasks * Fixed page reload on settings change * Fixed battle monsters with friends button * Loaded completed todos when done is clicked * chore: Reinstate floating version number for eslint babel-eslint regression fixed * Fixed reload tests * change "an user" to "a user" in comments and text (no code changes) (#7257) * fix: Alert user that drops were recieved * remove userServices.js from karma.conf - it's been moved to website/client/js/services * feat: Create debug update user route * fix: Correct set cron debug function * feat: Add make admin button to debug menu * lint: Add missing semicolons in test * fix: Temporarilly comment out udpate user debug route * v3: fix _tmp for crit and streakBonus * v3: execute all actions when leaving a solo party * v3 client: fix group not found when leaving party * v3 migration: fix challenge prize * v3 cron: only save modified tasks * v3: add CHALLENGE_TASK_NOT_FOUND to valid broken reasons * v3: fix tasks chart * v3 client: fix ability to leave challenge * v3 client: fix filtering by tag and correctly show tag tooltip * v3 common: fix tags tests * v3 client: support unlinking not found challenges tasks * v3: disable Bluebird warning for missing return, fixes #7269 * feat: Separate out update-user into set-cron and make-admin debug routes * chore: Disable make admin debug route for v3 prod testing * v3: misc fixes * v3: misc fixes * v3: fix adding multiple tasks * Fixed join/leave button updates * Queried only user groups to be available when creating challenges * Fixed bulk add tasks to challenge * Synced challenge tasks after leave and join. * Fixed default selected group * Fixed challenge member info. Fixed challenge winner selection * Fixed deleting challenge tasks * Fixed particiapting filter * v3 client: fix casting spells * v3: do not log sensitive data * v3: always save user when casting spell * v3: always save user when casting spell * v3: more fixes for spells * fix typos and missing information in apidocs - fixes https://github.com/HabitRPG/habitrpg/issues/7277 (#7282) * v3: add TODO for client side spells * feat: Add modify inventory debug menu * Fixed viewing user progress on challenge * Updated tests * fix: Fix quest progress button * fix incorrect Armoire test; remove unneeded param details from apidocs; disambiguate health potion * v3: fix stealth casting * v3: fix tasks saving and selection for rebirth reroll and reset (server-only) * v3: fix auto allocation * v3 client: misc fixes * rename buyPotion and buy-potion to buyHealthPotion and buy-health-potion; fix apidoc param error * Added delete for saved challenge task * Fixed member modal on front page * adjust text in apidocs for errors / clarity / consistency / standard terminology (no code changes) (#7298) * fix bug in Rebirth test, add new tests, adjust apidocs (#7293) * Updated task model to allow setting streak (#7306) * fix: Correct missing * in apidoc comments * Api v3 challenge fixes (#7287) * Fixed join/leave button updates * Queried only user groups to be available when creating challenges * Fixed bulk add tasks to challenge * Synced challenge tasks after leave and join. * Fixed default selected group * Fixed challenge member info. Fixed challenge winner selection * Fixed deleting challenge tasks * Fixed particiapting filter * Fixed viewing user progress on challenge * Updated tests * Added delete for saved challenge task * v3: fix sorting * [API v3] add CRON_SAFE_MODE (#7286) * add CRON_SAFE_MODE to example config file, fix some bugs, add an unrelated low-priority TODO * create CRON_SAFE_MODE to disable parts of cron for use after extended outage - fixes https://github.com/HabitRPG/habitrpg/issues/7161 * fix a bug with CRON_SAFE_MODE, remove duplicated code, remove completed TODO comment * fix check for CRON_SAFE_MODE * v3 client: fix typo * adjust debug menu Modify Inventory: hungrier pets, fewer Special items, "Hide" buttons * completed To-Dos: return the 30 most recent instead of 30 oldest (#7318) * v3 migration: fix createdAt date * adjust locales text, key names, and files for Rebirth, Reset, and Fortify / ReRoll for consistency with existing strings (#7321) * v3: fix unlinking multiple tasks * v3 fix releasing pets * v3: fix authenticating with apiUrl * v3: fix typo * v3 fix client tests for unlinking * v3 client: do not show start quest button when quest is active * v3 client: fix ability to send cards * v3 client: fix misc challenge issues * v3: fix notifications * v3 client: more user friendly errors * v3 client: only load completed todos once * v3 client: fix tests * v3: move TAVERN_ID to common code * fix: Provide default type and text for new task creation in score route * fix: Provide default history [] for habit in score route * fix: Add _legacyId prop to tasks to support non-uuid identifiers * chore: Change v3 migration to use _legacyId instead of legacyId * fix: check for _legacyId in tasks if id does not exist * refactor: Extract out finding task by id or _legacyId into a function * Api v3 party quest fixes (#7341) * Fix display of add challenge message when group challenges are empty * Fixed forced quest start to update quest without reload * Fixed needing to reload when accepting party invite * Fix group leave and join reload * Fixed leave current party and join another * Updated party tests * v3 client: remove console.log statement * v3: misc fixes * v3 client: fix predicatbale random * v3: info about API v3 * v3: update footer with links to developer resources * v3: support party invitation from email * v3 client: fix chat flagging * fix: Correct get tasks route to properly get todos (#7349) * move locales strings from api-v3.json to other locales files (#7347) * move locales strings from api-v3.json: authentication strings -> front.json * move locales strings from api-v3.json: authentication strings -> tasks.json * move locales strings from api-v3.json: authentication strings -> groups.json * move locales strings from api-v3.json: authentication strings -> challenge.json * move locales strings from api-v3.json: authentication strings -> groups.json (again) * move locales strings from api-v3.json: authentication strings -> quests.json * move locales strings from api-v3.json: authentication strings -> subscriber.json * move locales strings from api-v3.json: authentication strings -> spells.json * move locales strings from api-v3.json: authentication strings -> character.json * move locales strings from api-v3.json: authentication strings -> groups.json (PMs) * move locales strings from api-v3.json: authentication strings -> npc.json * move locales strings from api-v3.json: authentication strings -> pets.json * move locales strings from api-v3.json: authentication strings -> miscellaneous * move locales strings from api-v3.json: authentication strings -> contrib.json and settings.json * move locales strings from api-v3.json: delete unused string (invalidTasksOwner), delete api-v3.json, whitespace cleanup * v3 client: fix sticky header * v3: remove unused code * v3 client: correctly redirect after inviting * Removed v2 calls from views (#7351) * v3: fix tests for challenge export * v3: fallbackto authWithHeaders if wuthWithSession or authWithUrl fails * Added force cache update when fetching new messages (#7360) * v3: fetch whole user when booting from group tto avoid issues with pre save hook expecting all data * v3: misc fixes for payments * v3: limit fields of challenge tasks that can be updated * fix(tests): never connect to NODE_DB_URI for tests * Added new route for setting last cron and updated front end * v3: fix iap url * v3: fix build and ios IAP * Changed route to user set custom day start * v3: iap accessible under /api/v3, fixes to spells and groups invitations * v3: correctly use v3 routes in client * remove XP, GP when unticking a Daily with a completed checklist - fixes https://github.com/HabitRPG/habitrpg/issues/7246 * use natural language for error message about skills on challenge tasks (#7336), fix other gramatical error * Updated ui when user rejects a guild invite (#7368) * feat: complete custom day start route Closes #7363 * fix: Correct spelling of healAll skill fix: Correct sprite name of healAll skill * fix: Change all instances of spookDust -> spookySparkles * add dateCreated to all tasks; add empty challenge object to tasks that don't have one (#7386) * add plumilla to artists for Tangle Tree in Bailey message * Fixed quest drop modal (#7377) * Fixed quest drop modal * Fixed broken party test * [API v3] Maintenance Mode (#7367) * WIP(maintenance): maintenance * WIP(maintenance): working locale features * fix(maintenance): don't translate info page target * WIP(maintenance): start adding info page * fix(maintenance): linting * feat: Add container to maintenance info page * fix(maintenance): add config.json edits Also DRY variables for main vs info pages * fix(maintenance): linting * refactor(maintenance): further slim down variables * refactor: Remove unnecessary variables * fix: Correct string interpolation in maintenace view * feat: Dynamically add time to maintenance pages * maintenance mode: do not connect to mongodb * fix(maintenance): clean up timezones etc. * fix(maintenance): remove unneeded sprite * Tavern party challenges invites fix (#7394) * Added challenges and invitations to party * Loaded tavern challenges * Updated group and quest services tests * v3: implement automatic syncing if user is not up to date * Removed unnecessary fields when updating groups and challenges (#7395) * v3: do not saved populated user * v3: correctly return user subset * Chained party promises together (#7396) * v3: $w -> splitWhitespace * use bluebird * use babel polyfill * migration: fix items * update links for v3 * Updated shortname validation to support multiple browsers * Docs changes (#7401) * chore: Clarify transfer-gems documentation * chore: Clarify api status route documentation * chore: Mark webhooks as BETA * Added tags update route. Added sort to user service (#7381) * Added tags update route. Added sort to user service * Change update tasks route to reorder tasks * Fixed linting issue * Changed params for reorder tags route * Fixed not found tag and added test * Added password confirmation when deleteing account (#7402) * fix production logging * feat(commit): push * empty commit * feat(maintenance): post-downtime news & awards (#7406) * fix exporting avatar * second attempt at fixing exporting avatar * fix production logging * s3: convert moment to date instance * fix avatar sharing and caching (30 minutes) * fix: Correct missing parameter Closes #7433 * fix: Validate challenge shortname on server * adjust text strings - fixes https://github.com/HabitRPG/habitrpg/issues/5631 and also Short Name -> Tag Name
1359 lines
37 KiB
JavaScript
1359 lines
37 KiB
JavaScript
import { authWithHeaders } from '../../middlewares/api-v3/auth';
|
|
import common from '../../../../common';
|
|
import {
|
|
NotFound,
|
|
BadRequest,
|
|
NotAuthorized,
|
|
} from '../../libs/api-v3/errors';
|
|
import * as Tasks from '../../models/task';
|
|
import {
|
|
basicFields as basicGroupFields,
|
|
model as Group,
|
|
} from '../../models/group';
|
|
import { model as User } from '../../models/user';
|
|
import Bluebird from 'bluebird';
|
|
import _ from 'lodash';
|
|
import * as firebase from '../../libs/api-v3/firebase';
|
|
import * as passwordUtils from '../../libs/api-v3/password';
|
|
|
|
let api = {};
|
|
|
|
/**
|
|
* @api {get} /api/v3/user Get the authenticated user's profile
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserGet
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data The user object
|
|
*/
|
|
api.getUser = {
|
|
method: 'GET',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user',
|
|
async handler (req, res) {
|
|
let user = res.locals.user.toJSON();
|
|
|
|
// Remove apiToken from response TODO make it private at the user level? returned in signup/login
|
|
delete user.apiToken;
|
|
|
|
// TODO move to model? (maybe virtuals, maybe in toJSON)
|
|
// NOTE: if an item is manually added to user.stats common/fns/predictableRandom must be tweaked
|
|
// so it's not considered. Otherwise the client will have it while the server won't and the results will be different.
|
|
user.stats.toNextLevel = common.tnl(user.stats.lvl);
|
|
user.stats.maxHealth = common.maxHealth;
|
|
user.stats.maxMP = common.statsComputed(user).maxMP;
|
|
|
|
return res.respond(200, user);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {get} /api/v3/user/inventory/buy Get the gear items available for purchase for the current user
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserGetBuyList
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data The buy list
|
|
*/
|
|
api.getBuyList = {
|
|
method: 'GET',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/inventory/buy',
|
|
async handler (req, res) {
|
|
let list = _.cloneDeep(common.updateStore(res.locals.user));
|
|
|
|
// return text and notes strings
|
|
_.each(list, item => {
|
|
_.each(item, (itemPropVal, itemPropKey) => {
|
|
if (_.isFunction(itemPropVal) && itemPropVal.i18nLangFunc) item[itemPropKey] = itemPropVal(req.language);
|
|
});
|
|
});
|
|
|
|
res.respond(200, list);
|
|
},
|
|
};
|
|
|
|
let updatablePaths = [
|
|
'flags.customizationsNotification',
|
|
'flags.showTour',
|
|
'flags.tour',
|
|
'flags.tutorial',
|
|
'flags.communityGuidelinesAccepted',
|
|
'flags.welcomed',
|
|
'flags.cardReceived',
|
|
'flags.warnedLowHealth',
|
|
'flags.newStuff',
|
|
|
|
'achievements',
|
|
|
|
'party.order',
|
|
'party.orderAscending',
|
|
'party.quest.completed',
|
|
'party.quest.RSVPNeeded',
|
|
|
|
'preferences',
|
|
'profile',
|
|
'stats',
|
|
'inbox.optOut',
|
|
];
|
|
|
|
// This tells us for which paths users can call `PUT /user`.
|
|
// The trick here is to only accept leaf paths, not root/intermediate paths (see http://goo.gl/OEzkAs)
|
|
let acceptablePUTPaths = _.reduce(require('./../../models/user').schema.paths, (accumulator, val, leaf) => {
|
|
let found = _.find(updatablePaths, (rootPath) => {
|
|
return leaf.indexOf(rootPath) === 0;
|
|
});
|
|
|
|
if (found) accumulator[leaf] = true;
|
|
|
|
return accumulator;
|
|
}, {});
|
|
|
|
let restrictedPUTSubPaths = [
|
|
'stats.class',
|
|
|
|
'preferences.disableClasses',
|
|
'preferences.sleep',
|
|
'preferences.webhooks',
|
|
];
|
|
|
|
_.each(restrictedPUTSubPaths, (removePath) => {
|
|
delete acceptablePUTPaths[removePath];
|
|
});
|
|
|
|
let requiresPurchase = {
|
|
'preferences.background': 'background',
|
|
'preferences.shirt': 'shirt',
|
|
'preferences.size': 'size',
|
|
'preferences.skin': 'skin',
|
|
'preferences.hair.bangs': 'hair.bangs',
|
|
'preferences.hair.base': 'hair.base',
|
|
'preferences.hair.beard': 'hair.beard',
|
|
'preferences.hair.color': 'hair.color',
|
|
'preferences.hair.flower': 'hair.flower',
|
|
'preferences.hair.mustache': 'hair.mustache',
|
|
};
|
|
|
|
let checkPreferencePurchase = (user, path, item) => {
|
|
let itemPath = `${path}.${item}`;
|
|
let appearance = _.get(common.content.appearances, itemPath);
|
|
if (!appearance) return false;
|
|
if (appearance.price === 0) return true;
|
|
|
|
return _.get(user.purchased, itemPath);
|
|
};
|
|
|
|
/**
|
|
* @api {put} /api/v3/user Update the user
|
|
* @apiDescription Example body: {'stats.hp':50, 'preferences.background': 'beach'}
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserUpdate
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {object} data The updated user object
|
|
*/
|
|
api.updateUser = {
|
|
method: 'PUT',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
|
|
_.each(req.body, (val, key) => {
|
|
let purchasable = requiresPurchase[key];
|
|
|
|
if (purchasable && !checkPreferencePurchase(user, purchasable, val)) {
|
|
throw new NotAuthorized(res.t('mustPurchaseToSet', { val, key }));
|
|
}
|
|
|
|
if (acceptablePUTPaths[key]) {
|
|
_.set(user, key, val);
|
|
} else {
|
|
throw new NotAuthorized(res.t('messageUserOperationProtected', { operation: key }));
|
|
}
|
|
});
|
|
|
|
await user.save();
|
|
return res.respond(200, user);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {delete} /api/v3/user Delete an authenticated user's account
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserDelete
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} password The user's password (unless it's a Facebook account)
|
|
*
|
|
* @apiSuccess {Object} data An empty Object
|
|
*/
|
|
api.deleteUser = {
|
|
method: 'DELETE',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let plan = user.purchased.plan;
|
|
|
|
req.checkBody({
|
|
password: {
|
|
notEmpty: {errorMessage: res.t('missingPassword')},
|
|
},
|
|
});
|
|
|
|
let validationErrors = req.validationErrors();
|
|
if (validationErrors) throw validationErrors;
|
|
|
|
let oldPassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt);
|
|
if (oldPassword !== user.auth.local.hashed_password) throw new NotAuthorized(res.t('wrongPassword'));
|
|
|
|
if (plan && plan.customerId && !plan.dateTerminated) {
|
|
throw new NotAuthorized(res.t('cannotDeleteActiveAccount'));
|
|
}
|
|
|
|
let types = ['party', 'guilds'];
|
|
let groupFields = basicGroupFields.concat(' leader memberCount');
|
|
|
|
let groupsUserIsMemberOf = await Group.getGroups({user, types, groupFields});
|
|
|
|
let groupLeavePromises = groupsUserIsMemberOf.map((group) => {
|
|
return group.leave(user, 'remove-all');
|
|
});
|
|
|
|
await Bluebird.all(groupLeavePromises);
|
|
|
|
await Tasks.Task.remove({
|
|
userId: user._id,
|
|
}).exec();
|
|
|
|
await user.remove();
|
|
|
|
res.respond(200, {});
|
|
|
|
firebase.deleteUser(user._id);
|
|
},
|
|
};
|
|
|
|
function _cleanChecklist (task) {
|
|
_.forEach(task.checklist, (c, i) => {
|
|
c.text = `item ${i}`;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @api {get} /api/v3/user/anonymized Get anonymized user data
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserGetAnonymized
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data.user
|
|
* @apiSuccess {Array} data.tasks
|
|
**/
|
|
api.getUserAnonymized = {
|
|
method: 'GET',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/anonymized',
|
|
async handler (req, res) {
|
|
let user = res.locals.user.toJSON();
|
|
user.stats.toNextLevel = common.tnl(user.stats.lvl);
|
|
user.stats.maxHealth = common.maxHealth;
|
|
user.stats.maxMP = res.locals.user._statsComputed.maxMP;
|
|
|
|
delete user.apiToken;
|
|
if (user.auth) {
|
|
delete user.auth.local;
|
|
delete user.auth.facebook;
|
|
}
|
|
delete user.newMessages;
|
|
delete user.profile;
|
|
delete user.purchased.plan;
|
|
delete user.contributor;
|
|
delete user.invitations;
|
|
delete user.items.special.nyeReceived;
|
|
delete user.items.special.valentineReceived;
|
|
delete user.webhooks;
|
|
delete user.achievements.challenges;
|
|
|
|
_.forEach(user.inbox.messages, (msg) => {
|
|
msg.text = 'inbox message text';
|
|
});
|
|
_.forEach(user.tags, (tag) => {
|
|
tag.name = 'tag';
|
|
tag.challenge = 'challenge';
|
|
});
|
|
|
|
let query = {
|
|
userId: user._id,
|
|
$or: [
|
|
{ type: 'todo', completed: false },
|
|
{ type: { $in: ['habit', 'daily', 'reward'] } },
|
|
],
|
|
};
|
|
let tasks = await Tasks.Task.find(query).exec();
|
|
|
|
_.forEach(tasks, (task) => {
|
|
task.text = 'task text';
|
|
task.notes = 'task notes';
|
|
if (task.type === 'todo' || task.type === 'daily') {
|
|
_cleanChecklist(task);
|
|
}
|
|
});
|
|
|
|
return res.respond(200, { user, tasks });
|
|
},
|
|
};
|
|
|
|
const partyMembersFields = 'profile.name stats achievements items.special';
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/class/cast/:spellId Cast a skill (spell) on a target
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserCast
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} spellId The skill to cast
|
|
* @apiParam {UUID} targetId Optional query parameter, the id of the target when casting a skill on a party member or a task
|
|
*
|
|
* @apiSuccess data Will return the modified targets. For party members only the necessary fields will be populated. The user is always returned.
|
|
*/
|
|
api.castSpell = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/class/cast/:spellId',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let spellId = req.params.spellId;
|
|
let targetId = req.query.targetId;
|
|
|
|
// optional because not required by all targetTypes, presence is checked later if necessary
|
|
req.checkQuery('targetId', res.t('targetIdUUID')).optional().isUUID();
|
|
|
|
let reqValidationErrors = req.validationErrors();
|
|
if (reqValidationErrors) throw reqValidationErrors;
|
|
|
|
let klass = common.content.spells.special[spellId] ? 'special' : user.stats.class;
|
|
let spell = common.content.spells[klass][spellId];
|
|
|
|
if (!spell) throw new NotFound(res.t('spellNotFound', {spellId}));
|
|
if (spell.mana > user.stats.mp) throw new NotAuthorized(res.t('notEnoughMana'));
|
|
if (spell.value > user.stats.gp && !spell.previousPurchase) throw new NotAuthorized(res.t('messageNotEnoughGold'));
|
|
if (spell.lvl > user.stats.lvl) throw new NotAuthorized(res.t('spellLevelTooHigh', {level: spell.lvl}));
|
|
|
|
let targetType = spell.target;
|
|
|
|
if (targetType === 'task') {
|
|
if (!targetId) throw new BadRequest(res.t('targetIdUUID'));
|
|
|
|
let task = await Tasks.Task.findOne({
|
|
_id: targetId,
|
|
userId: user._id,
|
|
}).exec();
|
|
if (!task) throw new NotFound(res.t('taskNotFound'));
|
|
if (task.challenge.id) throw new BadRequest(res.t('challengeTasksNoCast'));
|
|
|
|
spell.cast(user, task, req);
|
|
|
|
let results = await Bluebird.all([
|
|
user.save(),
|
|
task.save(),
|
|
]);
|
|
|
|
res.respond(200, {
|
|
user: results[0],
|
|
task: results[1],
|
|
});
|
|
} else if (targetType === 'self') {
|
|
spell.cast(user, null, req);
|
|
await user.save();
|
|
res.respond(200, { user });
|
|
} else if (targetType === 'tasks') { // new target type in v3: when all the user's tasks are necessary
|
|
let tasks = await Tasks.Task.find({
|
|
userId: user._id,
|
|
$or: [ // exclude challenge tasks
|
|
{'challenge.id': {$exists: false}},
|
|
{'challenge.broken': {$exists: true}},
|
|
],
|
|
}).exec();
|
|
|
|
spell.cast(user, tasks, req);
|
|
|
|
let toSave = tasks
|
|
.filter(t => t.isModified())
|
|
.map(t => t.save());
|
|
|
|
toSave.unshift(user.save());
|
|
let saved = await Bluebird.all(toSave);
|
|
|
|
let response = {
|
|
tasks: saved,
|
|
user,
|
|
};
|
|
|
|
res.respond(200, response);
|
|
} else if (targetType === 'party' || targetType === 'user') {
|
|
let party = await Group.getGroup({groupId: 'party', user});
|
|
// arrays of users when targetType is 'party' otherwise single users
|
|
let partyMembers;
|
|
|
|
if (targetType === 'party') {
|
|
if (!party) {
|
|
partyMembers = [user]; // Act as solo party
|
|
} else {
|
|
partyMembers = await User
|
|
.find({
|
|
'party._id': party._id,
|
|
_id: { $ne: user._id }, // add separately
|
|
})
|
|
// .select(partyMembersFields) Selecting the entire user because otherwise when saving it'll save
|
|
// default values for non-selected fields and pre('save') will mess up thinking some values are missing
|
|
.exec();
|
|
|
|
partyMembers.unshift(user);
|
|
}
|
|
|
|
spell.cast(user, partyMembers, req);
|
|
await Bluebird.all(partyMembers.map(m => m.save()));
|
|
} else {
|
|
if (!party && (!targetId || user._id === targetId)) {
|
|
partyMembers = user;
|
|
} else {
|
|
if (!targetId) throw new BadRequest(res.t('targetIdUUID'));
|
|
if (!party) throw new NotFound(res.t('partyNotFound'));
|
|
partyMembers = await User
|
|
.findOne({_id: targetId, 'party._id': party._id})
|
|
// .select(partyMembersFields) Selecting the entire user because otherwise when saving it'll save
|
|
// default values for non-selected fields and pre('save') will mess up thinking some values are missing
|
|
.exec();
|
|
}
|
|
|
|
if (!partyMembers) throw new NotFound(res.t('userWithIDNotFound', {userId: targetId}));
|
|
|
|
spell.cast(user, partyMembers, req);
|
|
|
|
if (partyMembers !== user) {
|
|
await Bluebird.all([
|
|
user.save(),
|
|
partyMembers.save(),
|
|
]);
|
|
} else {
|
|
await partyMembers.save(); // partyMembers is user
|
|
}
|
|
}
|
|
|
|
let partyMembersRes = Array.isArray(partyMembers) ? partyMembers : [partyMembers];
|
|
// Only return some fields.
|
|
// See comment above on why we can't just select the necessary fields when querying
|
|
partyMembersRes = partyMembersRes.map(partyMember => {
|
|
return common.pickDeep(partyMember.toJSON(), common.$w(partyMembersFields));
|
|
});
|
|
|
|
res.respond(200, {
|
|
partyMembers: partyMembersRes,
|
|
user,
|
|
});
|
|
|
|
if (party && !spell.silent) {
|
|
let message = `\`${user.profile.name} casts ${spell.text()}${targetType === 'user' ? ` on ${partyMembers.profile.name}` : ' for the party'}.\``;
|
|
party.sendChat(message);
|
|
await party.save();
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/sleep Make the user start / stop sleeping (resting in the Inn)
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserSleep
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {boolean} data user.preferences.sleep
|
|
*/
|
|
api.sleep = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/sleep',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let sleepRes = common.ops.sleep(user);
|
|
await user.save();
|
|
res.respond(200, ...sleepRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/allocate Allocate an attribute point
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserAllocate
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} stat Query parameter - Defaults to 'str', mast be one of be of str, con, int or per
|
|
*
|
|
* @apiSuccess {Object} data user.stats
|
|
*/
|
|
api.allocate = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
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);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/allocate-now Allocate all attribute points
|
|
* @apiDescription Uses the user's chosen automatic allocation method, or if none, assigns all to STR.
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserAllocateNow
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data user.stats
|
|
*/
|
|
api.allocateNow = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/allocate-now',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let allocateNowRes = common.ops.allocateNow(user, req);
|
|
await user.save();
|
|
res.respond(200, ...allocateNowRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /user/buy/:key Buy gear, armoire or potion
|
|
* @apiDescription Under the hood uses UserBuyGear, UserBuyPotion and UserBuyArmoire
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserBuy
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} key The item to buy
|
|
*/
|
|
api.buy = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/buy/:key',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let buyRes = common.ops.buy(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...buyRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /user/buy-gear/:key Buy a piece of gear
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserBuyGear
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} key The item to buy
|
|
*
|
|
* @apiSuccess {object} data.items user.items
|
|
* @apiSuccess {object} data.flags user.flags
|
|
* @apiSuccess {object} data.achievements user.achievements
|
|
* @apiSuccess {object} data.stats user.stats
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.buyGear = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/buy-gear/:key',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let buyGearRes = common.ops.buyGear(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...buyGearRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /user/buy-armoire Buy an armoire item
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserBuyArmoire
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {object} data.items user.items
|
|
* @apiSuccess {object} data.flags user.flags
|
|
* @apiSuccess {object} data.armoire Extra item given by the armoire
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.buyArmoire = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/buy-armoire',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let buyArmoireResponse = common.ops.buyArmoire(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...buyArmoireResponse);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /user/buy-health-potion Buy a health potion
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserBuyPotion
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data user.stats
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.buyHealthPotion = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/buy-health-potion',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let buyHealthPotionResponse = common.ops.buyHealthPotion(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...buyHealthPotionResponse);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /user/buy-mystery-set/:key Buy a mystery set
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserBuyMysterySet
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} key The mystery set to buy
|
|
*
|
|
* @apiSuccess {Object} data.items user.items
|
|
* @apiSuccess {Object} data.purchasedPlanConsecutive user.purchased.plan.consecutive
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.buyMysterySet = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/buy-mystery-set/:key',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let buyMysterySetRes = common.ops.buyMysterySet(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...buyMysterySetRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/buy-quest/:key Buy a quest with gold
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserBuyQuest
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} key The quest scroll to buy
|
|
*
|
|
* @apiSuccess {Object} data `user.items.quests`
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.buyQuest = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/buy-quest/:key',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let buyQuestRes = common.ops.buyQuest(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...buyQuestRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/buy-special-spell/:key Buy special "spell" item
|
|
* @apiDescription Includes gift cards (e.g., birthday card), and avatar Transformation Items and their antidotes (e.g., Snowball item and Salt reward).
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserBuySpecialSpell
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} key The special item to buy. Must be one of the keys from "content.special", such as birthday, snowball, salt.
|
|
*
|
|
* @apiSuccess {Object} data.stats user.stats
|
|
* @apiSuccess {Object} data.items user.items
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.buySpecialSpell = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/buy-special-spell/:key',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let buySpecialSpellRes = common.ops.buySpecialSpell(user, req);
|
|
await user.save();
|
|
res.respond(200, ...buySpecialSpellRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/hatch/:egg/:hatchingPotion Hatch a pet
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserHatch
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} egg The egg to use
|
|
* @apiParam {string} hatchingPotion The hatching potion to use
|
|
*
|
|
* @apiSuccess {Object} data user.items
|
|
* @apiSuccess {string} message
|
|
*/
|
|
api.hatch = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/hatch/:egg/:hatchingPotion',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let hatchRes = common.ops.hatch(user, req);
|
|
await user.save();
|
|
res.respond(200, ...hatchRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/equip/:type/:key Equip an item
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserEquip
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} type The type of item to equip (mount, pet, costume or equipped)
|
|
* @apiParam {string} key The item to equip
|
|
*
|
|
* @apiSuccess {Object} data user.items
|
|
* @apiSuccess {string} message Optional success message
|
|
*/
|
|
api.equip = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/equip/:type/:key',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let equipRes = common.ops.equip(user, req);
|
|
await user.save();
|
|
res.respond(200, ...equipRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/equip/:pet/:food Feed a pet
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserFeed
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} pet
|
|
* @apiParam {string} food
|
|
*
|
|
* @apiSuccess {number} data The pet value
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.feed = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/feed/:pet/:food',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let feedRes = common.ops.feed(user, req);
|
|
await user.save();
|
|
res.respond(200, ...feedRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/change-class Change class
|
|
* @apiDescription User must be at least level 10. If ?class is defined and user.flags.classSelected is false it'll change the class. If user.preferences.disableClasses it'll enable classes, otherwise it sets user.flags.classSelected to false (costs 3 gems)
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserChangeClass
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} class Query parameter - ?class={warrior|rogue|wizard|healer}
|
|
*
|
|
* @apiSuccess {object} data.flags user.flags
|
|
* @apiSuccess {object} data.stats user.stats
|
|
* @apiSuccess {object} data.preferences user.preferences
|
|
* @apiSuccess {object} data.items user.items
|
|
*/
|
|
api.changeClass = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/change-class',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let changeClassRes = common.ops.changeClass(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...changeClassRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/disable-classes Disable classes
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserDisableClasses
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {object} data.flags user.flags
|
|
* @apiSuccess {object} data.stats user.stats
|
|
* @apiSuccess {object} data.preferences user.preferences
|
|
*/
|
|
api.disableClasses = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/disable-classes',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let disableClassesRes = common.ops.disableClasses(user, req);
|
|
await user.save();
|
|
res.respond(200, ...disableClassesRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/purchase/:type/:key Purchase Gem or Gem-purchasable item
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserPurchase
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} type Type of item to purchase. Must be one of: gems, eggs, hatchingPotions, food, quests, or gear
|
|
* @apiParam {string} key Item's key (use "gem" for purchasing gems)
|
|
*
|
|
* @apiSuccess {object} data.items user.items
|
|
* @apiSuccess {number} data.balance user.balance
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.purchase = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/purchase/:type/:key',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let purchaseRes = common.ops.purchase(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...purchaseRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/purchase-hourglass/:type/:key Purchase Hourglass-purchasable item
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserPurchaseHourglass
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} type The type of item to purchase (pets or mounts)
|
|
* @apiParam {string} key Ex: {MantisShrimp-Base}. The key for the mount/pet
|
|
*
|
|
* @apiSuccess {object} data.items user.items
|
|
* @apiSuccess {object} data.purchasedPlanConsecutive user.purchased.plan.consecutive
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.userPurchaseHourglass = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/purchase-hourglass/:type/:key',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let purchaseHourglassRes = common.ops.purchaseHourglass(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...purchaseHourglassRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/read-card/:cardType Reads a card
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserReadCard
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} cardType Type of card to read
|
|
*
|
|
* @apiSuccess {object} data.specialItems user.items.special
|
|
* @apiSuccess {boolean} data.cardReceived user.flags.cardReceived
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.readCard = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/read-card/:cardType',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let readCardRes = common.ops.readCard(user, req);
|
|
await user.save();
|
|
res.respond(200, ...readCardRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/open-mystery-item Open the Mystery Item box
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserOpenMysteryItem
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data user.items.gear.owned
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.userOpenMysteryItem = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/open-mystery-item',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let openMysteryItemRes = common.ops.openMysteryItem(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...openMysteryItemRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/webhook Create a new webhook - BETA
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserAddWebhook
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} url Body parameter - The webhook's URL
|
|
* @apiParam {boolean} enabled Body parameter - If the webhook should be enabled
|
|
*
|
|
* @apiSuccess {Object} data The created webhook
|
|
*/
|
|
api.addWebhook = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/webhook',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let addWebhookRes = common.ops.addWebhook(user, req);
|
|
await user.save();
|
|
res.respond(200, ...addWebhookRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {put} /api/v3/user/webhook/:id Edit a webhook - BETA
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserUpdateWebhook
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {UUID} id The id of the webhook to update
|
|
* @apiParam {string} url Body parameter - The webhook's URL
|
|
* @apiParam {boolean} enabled Body parameter - If the webhook should be enabled
|
|
*
|
|
* @apiSuccess {Object} data The updated webhook
|
|
*/
|
|
api.updateWebhook = {
|
|
method: 'PUT',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/webhook/:id',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let updateWebhookRes = common.ops.updateWebhook(user, req);
|
|
await user.save();
|
|
res.respond(200, ...updateWebhookRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {delete} /api/v3/user/webhook/:id Delete a webhook - BETA
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserDeleteWebhook
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {UUID} id The id of the webhook to delete
|
|
*
|
|
* @apiSuccess {Object} data The user webhooks
|
|
*/
|
|
api.deleteWebhook = {
|
|
method: 'DELETE',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/webhook/:id',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let deleteWebhookRes = common.ops.deleteWebhook(user, req);
|
|
await user.save();
|
|
res.respond(200, ...deleteWebhookRes);
|
|
},
|
|
};
|
|
|
|
|
|
/* @api {post} /api/v3/user/release-pets Release pets
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserReleasePets
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data.items `user.items.pets`
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.userReleasePets = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/release-pets',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let releasePetsRes = common.ops.releasePets(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...releasePetsRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/release-both Release pets and mounts and grants Triad Bingo
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserReleaseBoth
|
|
* @apiGroup User
|
|
|
|
* @apiSuccess {Object} data.achievements
|
|
* @apiSuccess {Object} data.items
|
|
* @apiSuccess {number} data.balance
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.userReleaseBoth = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/release-both',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let releaseBothRes = common.ops.releaseBoth(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...releaseBothRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/release-mounts Release mounts
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserReleaseMounts
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data user.items.mounts
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.userReleaseMounts = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/release-mounts',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let releaseMountsRes = common.ops.releaseMounts(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...releaseMountsRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/sell/:type/:key Sell a gold-sellable item owned by the user
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserSell
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} type The type of item to sell. Must be one of: eggs, hatchingPotions, or food
|
|
* @apiParam {string} key The key of the item
|
|
*
|
|
* @apiSuccess {Object} data.stats
|
|
* @apiSuccess {Object} data.items
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.userSell = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/sell/:type/:key',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let sellRes = common.ops.sell(user, req);
|
|
await user.save();
|
|
res.respond(200, ...sellRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/unlock Unlock item or set of items by purchase
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserUnlock
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} path Query parameter. The path to unlock
|
|
*
|
|
* @apiSuccess {Object} data.purchased
|
|
* @apiSuccess {Object} data.items
|
|
* @apiSuccess {Object} data.preferences
|
|
* @apiSuccess {string} message
|
|
*/
|
|
api.userUnlock = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/unlock',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let unlockRes = common.ops.unlock(user, req);
|
|
await user.save();
|
|
res.respond(200, ...unlockRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/revive Revive user from death
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserRevive
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data user.items
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.userRevive = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/revive',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let reviveRes = common.ops.revive(user, req, res.analytics);
|
|
await user.save();
|
|
res.respond(200, ...reviveRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/rebirth Use Orb of Rebirth on user
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserRebirth
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data.user
|
|
* @apiSuccess {array} data.tasks User's modified tasks (no rewards)
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.userRebirth = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/rebirth',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let tasks = await Tasks.Task.find({
|
|
userId: user._id,
|
|
type: {$in: ['daily', 'habit', 'todo']},
|
|
$or: [ // exclude challenge tasks
|
|
{'challenge.id': {$exists: false}},
|
|
{'challenge.broken': {$exists: true}},
|
|
],
|
|
}).exec();
|
|
|
|
let rebirthRes = common.ops.rebirth(user, tasks, req, res.analytics);
|
|
|
|
let toSave = tasks.map(task => task.save());
|
|
|
|
toSave.push(user.save());
|
|
|
|
await Bluebird.all(toSave);
|
|
|
|
res.respond(200, ...rebirthRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/block/:uuid Block and unblock a user
|
|
* @apiDescription Must be an admin to make this request.
|
|
* @apiVersion 3.0.0
|
|
* @apiName BlockUser
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {UUID} uuid The uuid of the user to block / unblock
|
|
*
|
|
* @apiSuccess {array} data user.inbox.blocks
|
|
**/
|
|
api.blockUser = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/block/:uuid',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let blockUserRes = common.ops.blockUser(user, req);
|
|
await user.save();
|
|
res.respond(200, ...blockUserRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {delete} /api/v3/user/messages/:id Delete a message
|
|
* @apiVersion 3.0.0
|
|
* @apiName deleteMessage
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {UUID} id The id of the message to delete
|
|
*
|
|
* @apiSuccess {object} data user.inbox.messages
|
|
**/
|
|
api.deleteMessage = {
|
|
method: 'DELETE',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/messages/:id',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let deletePMRes = common.ops.deletePM(user, req);
|
|
await user.save();
|
|
res.respond(200, ...deletePMRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {delete} /api/v3/user/messages Delete all messages
|
|
* @apiVersion 3.0.0
|
|
* @apiName clearMessages
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {object} data user.inbox.messages
|
|
**/
|
|
api.clearMessages = {
|
|
method: 'DELETE',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/messages',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let clearPMsRes = common.ops.clearPMs(user, req);
|
|
await user.save();
|
|
res.respond(200, ...clearPMsRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/mark-pms-read Marks Private Messages as read
|
|
* @apiVersion 3.0.0
|
|
* @apiName markPmsRead
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {object} data user.inbox.messages
|
|
**/
|
|
api.markPmsRead = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/mark-pms-read',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let markPmsResponse = common.ops.markPmsRead(user, req);
|
|
await user.save();
|
|
res.respond(200, markPmsResponse);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/reroll Reroll a user using the Fortify Potion
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserReroll
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data.user
|
|
* @apiSuccess {Object} data.tasks User's modified tasks (no rewards)
|
|
* @apiSuccess {Object} message Success message
|
|
*/
|
|
api.userReroll = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/reroll',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let query = {
|
|
userId: user._id,
|
|
type: {$in: ['daily', 'habit', 'todo']},
|
|
$or: [ // exclude challenge tasks
|
|
{'challenge.id': {$exists: false}},
|
|
{'challenge.broken': {$exists: true}},
|
|
],
|
|
};
|
|
let tasks = await Tasks.Task.find(query).exec();
|
|
let rerollRes = common.ops.reroll(user, tasks, req, res.analytics);
|
|
|
|
let promises = tasks.map(task => task.save());
|
|
promises.push(user.save());
|
|
|
|
await Bluebird.all(promises);
|
|
|
|
res.respond(200, ...rerollRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/addPushDevice Add a push device to a user
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserAddPushDevice
|
|
* @apiGroup User
|
|
*
|
|
* @apiParam {string} regId The id of the push device
|
|
* @apiParam {string} uuid The type of push device
|
|
*
|
|
* @apiSuccess {Object} data List of push devices
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.userAddPushDevice = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/addPushDevice',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
|
|
let addPushDeviceRes = common.ops.addPushDevice(user, req);
|
|
await user.save();
|
|
|
|
res.respond(200, ...addPushDeviceRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/reset Reset user
|
|
* @apiVersion 3.0.0
|
|
* @apiName UserReset
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data.user
|
|
* @apiSuccess {Object} data.tasksToRemove IDs of removed tasks
|
|
* @apiSuccess {string} message Success message
|
|
*/
|
|
api.userReset = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/reset',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
|
|
let tasks = await Tasks.Task.find({
|
|
userId: user._id,
|
|
$or: [ // exclude challenge tasks
|
|
{'challenge.id': {$exists: false}},
|
|
{'challenge.broken': {$exists: true}},
|
|
],
|
|
}).select('_id type challenge').exec();
|
|
|
|
let resetRes = common.ops.reset(user, tasks, req);
|
|
|
|
await Bluebird.all([
|
|
Tasks.Task.remove({_id: {$in: resetRes[0].tasksToRemove}, userId: user._id}),
|
|
user.save(),
|
|
]);
|
|
|
|
res.respond(200, ...resetRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {post} /api/v3/user/custom-day-start Sets preferences.dayStart for user
|
|
* @apiVersion 3.0.0
|
|
* @apiName setCustomDayStart
|
|
* @apiGroup User
|
|
*
|
|
* @apiSuccess {Object} data An empty Object
|
|
*/
|
|
api.setCustomDayStart = {
|
|
method: 'POST',
|
|
middlewares: [authWithHeaders()],
|
|
url: '/user/custom-day-start',
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
let dayStart = req.body.dayStart;
|
|
|
|
user.preferences.dayStart = dayStart;
|
|
user.lastCron = new Date();
|
|
|
|
await user.save();
|
|
|
|
res.respond(200, {
|
|
message: res.t('customDayStartHasChanged'),
|
|
});
|
|
},
|
|
};
|
|
|
|
module.exports = api;
|