fix linting for server (except for length of apidoc)
This commit is contained in:
@@ -12,7 +12,6 @@ import {
|
||||
} from '../../libs/errors';
|
||||
import * as passwordUtils from '../../libs/password';
|
||||
import { sendTxn as sendTxnEmail } from '../../libs/email';
|
||||
import { validatePasswordResetCodeAndFindUser, convertToBcrypt } from '../../libs/password';
|
||||
import { encrypt } from '../../libs/encryption';
|
||||
import {
|
||||
loginRes,
|
||||
@@ -125,7 +124,7 @@ api.loginLocal = {
|
||||
headers: req.headers,
|
||||
});
|
||||
|
||||
return loginRes(user, ...arguments);
|
||||
return loginRes(user, req, res);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -137,7 +136,7 @@ api.loginSocial = {
|
||||
})],
|
||||
url: '/user/auth/social',
|
||||
async handler (req, res) {
|
||||
return await loginSocial(req, res);
|
||||
await loginSocial(req, res);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -377,7 +376,7 @@ api.resetPasswordSetNewOne = {
|
||||
method: 'POST',
|
||||
url: '/user/auth/reset-password-set-new-one',
|
||||
async handler (req, res) {
|
||||
const user = await validatePasswordResetCodeAndFindUser(req.body.code);
|
||||
const user = await passwordUtils.validatePasswordResetCodeAndFindUser(req.body.code);
|
||||
const isValidCode = Boolean(user);
|
||||
|
||||
if (!isValidCode) throw new NotAuthorized(res.t('invalidPasswordResetCode'));
|
||||
@@ -395,7 +394,7 @@ api.resetPasswordSetNewOne = {
|
||||
}
|
||||
|
||||
// set new password and make sure it's using bcrypt for hashing
|
||||
await convertToBcrypt(user, String(newPassword));
|
||||
await passwordUtils.convertToBcrypt(user, String(newPassword));
|
||||
user.auth.local.passwordResetCode = undefined; // Reset saved password reset code
|
||||
await user.save();
|
||||
|
||||
@@ -418,7 +417,8 @@ api.deleteSocial = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
const { network } = req.params;
|
||||
const isSupportedNetwork = common.constants.SUPPORTED_SOCIAL_NETWORKS.find(supportedNetwork => supportedNetwork.key === network);
|
||||
const isSupportedNetwork = common.constants.SUPPORTED_SOCIAL_NETWORKS
|
||||
.find(supportedNetwork => supportedNetwork.key === network);
|
||||
if (!isSupportedNetwork) throw new BadRequest(res.t('unsupportedNetwork'));
|
||||
if (!hasBackupAuth(user, network)) throw new NotAuthorized(res.t('cantDetachSocial'));
|
||||
const unset = {
|
||||
|
||||
@@ -413,8 +413,12 @@ api.getUserChallenges = {
|
||||
User.findById(chal.leader).select(`${nameFields} backer contributor`).exec(),
|
||||
Group.findById(chal.group).select(basicGroupFields).exec(),
|
||||
]).then(populatedData => {
|
||||
resChals[index].leader = populatedData[0] ? populatedData[0].toJSON({ minimize: true }) : null;
|
||||
resChals[index].group = populatedData[1] ? populatedData[1].toJSON({ minimize: true }) : null;
|
||||
resChals[index].leader = populatedData[0]
|
||||
? populatedData[0].toJSON({ minimize: true })
|
||||
: null;
|
||||
resChals[index].group = populatedData[1]
|
||||
? populatedData[1].toJSON({ minimize: true })
|
||||
: null;
|
||||
})));
|
||||
|
||||
res.respond(200, resChals);
|
||||
@@ -460,12 +464,17 @@ api.getGroupChallenges = {
|
||||
|
||||
const challenges = await Challenge.find({ group: groupId })
|
||||
.sort('-createdAt')
|
||||
// .populate('leader', nameFields) // Only populate the leader as the group is implicit // see below why we're not using populate
|
||||
// Only populate the leader as the group is implicit // see below why we're not using populate
|
||||
// .populate('leader', nameFields)
|
||||
.exec();
|
||||
|
||||
let resChals = challenges.map(challenge => challenge.toJSON());
|
||||
|
||||
resChals = _.orderBy(resChals, [challenge => challenge.categories.map(category => category.slug).includes('habitica_official')], ['desc']);
|
||||
resChals = _.orderBy(
|
||||
resChals,
|
||||
[challenge => challenge.categories.map(category => category.slug).includes('habitica_official')],
|
||||
['desc'],
|
||||
);
|
||||
|
||||
// Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
|
||||
await Promise.all(resChals.map((chal, index) => User
|
||||
@@ -473,7 +482,9 @@ api.getGroupChallenges = {
|
||||
.select(nameFields)
|
||||
.exec()
|
||||
.then(populatedLeader => {
|
||||
resChals[index].leader = populatedLeader ? populatedLeader.toJSON({ minimize: true }) : null;
|
||||
resChals[index].leader = populatedLeader
|
||||
? populatedLeader.toJSON({ minimize: true })
|
||||
: null;
|
||||
})));
|
||||
|
||||
res.respond(200, resChals);
|
||||
@@ -559,7 +570,8 @@ api.exportChallengeCsv = {
|
||||
});
|
||||
if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound'));
|
||||
|
||||
// In v2 this used the aggregation framework to run some computation on MongoDB but then iterated through all
|
||||
// In v2 this used the aggregation framework to run some
|
||||
// computation on MongoDB but then iterated through all
|
||||
// results on the server so the perf difference isn't that big (hopefully)
|
||||
|
||||
const [members, tasks] = await Promise.all([
|
||||
@@ -578,7 +590,8 @@ api.exportChallengeCsv = {
|
||||
.exec(),
|
||||
]);
|
||||
|
||||
let resArray = members.map(member => [member._id, member.profile.name, member.auth.local.username]);
|
||||
let resArray = members
|
||||
.map(member => [member._id, member.profile.name, member.auth.local.username]);
|
||||
|
||||
let lastUserId;
|
||||
let index = -1;
|
||||
@@ -594,8 +607,8 @@ api.exportChallengeCsv = {
|
||||
return;
|
||||
}
|
||||
while (task.userId !== lastUserId) {
|
||||
index++;
|
||||
lastUserId = resArray[index][0]; // resArray[index][0] is an user id
|
||||
index += 1;
|
||||
lastUserId = [resArray[index]]; // resArray[index][0] is an user id
|
||||
}
|
||||
|
||||
const streak = task.streak || 0;
|
||||
@@ -603,8 +616,12 @@ api.exportChallengeCsv = {
|
||||
resArray[index].push(`${task.type}:${task.text}`, task.value, task.notes, streak);
|
||||
});
|
||||
|
||||
// The first row is going to be UUID name Task Value Notes repeated n times for the n challenge tasks
|
||||
const challengeTasks = _.reduce(challenge.tasksOrder.toObject(), (result, array) => result.concat(array), []).sort();
|
||||
// The first row is going to be UUID name Task Value Notes
|
||||
// repeated n times for the n challenge tasks
|
||||
const challengeTasks = _.reduce(
|
||||
challenge.tasksOrder.toObject(),
|
||||
(result, array) => result.concat(array), [],
|
||||
).sort();
|
||||
resArray.unshift(['UUID', 'Display Name', 'Username']);
|
||||
|
||||
_.times(challengeTasks.length, () => resArray[0].push('Task', 'Value', 'Notes', 'Streak'));
|
||||
|
||||
@@ -110,7 +110,6 @@ api.postChat = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
const { groupId } = req.params;
|
||||
let chatUpdated;
|
||||
|
||||
req.checkParams('groupId', apiError('groupIdRequired')).notEmpty();
|
||||
req.sanitize('message').trim();
|
||||
@@ -165,7 +164,8 @@ api.postChat = {
|
||||
throw new NotAuthorized(res.t('chatPrivilegesRevoked'));
|
||||
}
|
||||
|
||||
// prevent banned words being posted, except in private guilds/parties and in certain public guilds with specific topics
|
||||
// prevent banned words being posted, except in private guilds/parties
|
||||
// and in certain public guilds with specific topics
|
||||
if (group.privacy === 'public' && !guildsAllowingBannedWords[group._id]) {
|
||||
const matchedBadWords = getBannedWordsFromText(req.body.message);
|
||||
if (matchedBadWords.length > 0) {
|
||||
@@ -175,7 +175,9 @@ api.postChat = {
|
||||
|
||||
const chatRes = await Group.toJSONCleanChat(group, user);
|
||||
const lastClientMsg = req.query.previousMsg;
|
||||
chatUpdated = !!(lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg);
|
||||
const chatUpdated = !!(
|
||||
lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg
|
||||
);
|
||||
|
||||
if (group.checkChatSpam(user)) {
|
||||
throw new NotAuthorized(res.t('messageGroupChatSpam'));
|
||||
@@ -449,7 +451,8 @@ api.seenChat = {
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
// Do not validate group existence, it doesn't really matter and make it works if the group gets deleted
|
||||
// Do not validate group existence,
|
||||
// it doesn't really matter and make it works if the group gets deleted
|
||||
// let group = await Group.getGroup({user, groupId});
|
||||
// if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
|
||||
@@ -476,7 +479,7 @@ api.seenChat = {
|
||||
// Update the user version field manually,
|
||||
// it cannot be updated in the pre update hook
|
||||
// See https://github.com/HabitRPG/habitica/pull/9321#issuecomment-354187666 for more info
|
||||
user._v++;
|
||||
user._v += 1;
|
||||
|
||||
await User.update({ _id: user._id }, update).exec();
|
||||
res.respond(200, {});
|
||||
@@ -529,7 +532,9 @@ api.deleteChat = {
|
||||
|
||||
const chatRes = await Group.toJSONCleanChat(group, user);
|
||||
const lastClientMsg = req.query.previousMsg;
|
||||
const chatUpdated = !!(lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg);
|
||||
const chatUpdated = !!(
|
||||
lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg
|
||||
);
|
||||
|
||||
await Chat.remove({ _id: message._id }).exec();
|
||||
|
||||
|
||||
@@ -18,13 +18,17 @@ const api = {};
|
||||
|
||||
function walkContent (obj, lang) {
|
||||
_.each(obj, (item, key, source) => {
|
||||
if (_.isPlainObject(item) || _.isArray(item)) return walkContent(item, lang);
|
||||
if (_.isFunction(item) && item.i18nLangFunc) source[key] = item(lang);
|
||||
if (_.isPlainObject(item) || _.isArray(item)) {
|
||||
walkContent(item, lang);
|
||||
} else if (_.isFunction(item) && item.i18nLangFunc) {
|
||||
source[key] = item(lang);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// After the getContent route is called the first time for a certain language
|
||||
// the response is saved on disk and subsequentially served directly from there to reduce computation.
|
||||
// the response is saved on disk and subsequentially served
|
||||
// directly from there to reduce computation.
|
||||
// Example: if `cachedContentResponses.en` is true it means that the response is cached
|
||||
const cachedContentResponses = {};
|
||||
|
||||
@@ -43,18 +47,21 @@ async function saveContentToDisk (language, content) {
|
||||
try {
|
||||
cacheBeingWritten[language] = true;
|
||||
|
||||
await fs.stat(CONTENT_CACHE_PATH); // check if the directory exists, if it doesn't an error is thrown
|
||||
// check if the directory exists, if it doesn't an error is thrown
|
||||
await fs.stat(CONTENT_CACHE_PATH);
|
||||
await fs.writeFile(`${CONTENT_CACHE_PATH}${language}.json`, content, 'utf8');
|
||||
|
||||
cacheBeingWritten[language] = false;
|
||||
cachedContentResponses[language] = true;
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT' && err.syscall === 'stat') { // the directory doesn't exists, create it and retry
|
||||
// the directory doesn't exists, create it and retry
|
||||
if (err.code === 'ENOENT' && err.syscall === 'stat') {
|
||||
await fs.mkdir(CONTENT_CACHE_PATH);
|
||||
return saveContentToDisk(language, content);
|
||||
saveContentToDisk(language, content);
|
||||
} else {
|
||||
cacheBeingWritten[language] = false;
|
||||
logger.error(err);
|
||||
}
|
||||
cacheBeingWritten[language] = false;
|
||||
logger.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import common from '../../../common';
|
||||
import payments from '../../libs/payments/payments';
|
||||
import stripePayments from '../../libs/payments/stripe';
|
||||
import amzLib from '../../libs/payments/amazon';
|
||||
import shared from '../../../common';
|
||||
import apiError from '../../libs/apiError';
|
||||
|
||||
const MAX_EMAIL_INVITES_BY_USER = 200;
|
||||
@@ -122,7 +121,7 @@ api.createGroup = {
|
||||
|
||||
group.balance = 1;
|
||||
|
||||
user.balance--;
|
||||
user.balance -= 1;
|
||||
user.guilds.push(group._id);
|
||||
if (!user.achievements.joinedGuild) {
|
||||
user.achievements.joinedGuild = true;
|
||||
@@ -139,7 +138,8 @@ api.createGroup = {
|
||||
const savedGroup = results[1];
|
||||
|
||||
// Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
|
||||
// await Q.ninvoke(savedGroup, 'populate', ['leader', nameFields]); // doc.populate doesn't return a promise
|
||||
// await Q.ninvoke(savedGroup, 'populate', ['leader', nameFields]);
|
||||
// doc.populate doesn't return a promise
|
||||
const response = savedGroup.toJSON();
|
||||
// the leader is the authenticated user
|
||||
response.leader = {
|
||||
@@ -210,7 +210,7 @@ api.createGroupPlan = {
|
||||
if (req.body.paymentType === 'Stripe') {
|
||||
const token = req.body.id;
|
||||
const gift = req.query.gift ? JSON.parse(req.query.gift) : undefined;
|
||||
const sub = req.query.sub ? shared.content.subscriptionBlocks[req.query.sub] : false;
|
||||
const sub = req.query.sub ? common.content.subscriptionBlocks[req.query.sub] : false;
|
||||
const groupId = savedGroup._id;
|
||||
const { email } = req.body;
|
||||
const { headers } = req;
|
||||
@@ -228,7 +228,9 @@ api.createGroupPlan = {
|
||||
});
|
||||
} else if (req.body.paymentType === 'Amazon') {
|
||||
const { billingAgreementId } = req.body;
|
||||
const sub = req.body.subscription ? shared.content.subscriptionBlocks[req.body.subscription] : false;
|
||||
const sub = req.body.subscription
|
||||
? common.content.subscriptionBlocks[req.body.subscription]
|
||||
: false;
|
||||
const { coupon } = req.body;
|
||||
const groupId = savedGroup._id;
|
||||
const { headers } = req;
|
||||
@@ -244,7 +246,8 @@ api.createGroupPlan = {
|
||||
}
|
||||
|
||||
// Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
|
||||
// await Q.ninvoke(savedGroup, 'populate', ['leader', nameFields]); // doc.populate doesn't return a promise
|
||||
// await Q.ninvoke(savedGroup, 'populate', ['leader', nameFields]);
|
||||
// doc.populate doesn't return a promise
|
||||
const response = savedGroup.toJSON();
|
||||
// the leader is the authenticated user
|
||||
response.leader = {
|
||||
@@ -514,7 +517,9 @@ api.joinGroup = {
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
// Works even if the user is not yet a member of the group
|
||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, optionalMembership: true }); // Do not fetch chat and work even if the user is not yet a member of the group
|
||||
// Do not fetch chat and work even if the user is not yet a member of the group
|
||||
const group = await Group
|
||||
.getGroup({ user, groupId: req.params.groupId, optionalMembership: true });
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
|
||||
let isUserInvited = false;
|
||||
@@ -565,7 +570,8 @@ api.joinGroup = {
|
||||
}
|
||||
|
||||
if (isUserInvited && group.type === 'guild') {
|
||||
if (user.guilds.indexOf(group._id) !== -1) { // if user is already a member (party is checked previously)
|
||||
// if user is already a member (party is checked previously)
|
||||
if (user.guilds.indexOf(group._id) !== -1) {
|
||||
throw new NotAuthorized(res.t('youAreAlreadyInGroup'));
|
||||
}
|
||||
user.guilds.push(group._id); // Add group to user's guilds
|
||||
@@ -577,7 +583,9 @@ api.joinGroup = {
|
||||
if (!isUserInvited) throw new NotAuthorized(res.t('messageGroupRequiresInvite'));
|
||||
|
||||
// @TODO: Review the need for this and if still needed, don't base this on memberCount
|
||||
if (!group.hasNotCancelled() && group.memberCount === 0) group.leader = user._id; // If new user is only member -> set as leader
|
||||
if (!group.hasNotCancelled() && group.memberCount === 0) {
|
||||
group.leader = user._id; // If new user is only member -> set as leader
|
||||
}
|
||||
|
||||
group.memberCount += 1;
|
||||
|
||||
@@ -600,7 +608,7 @@ api.joinGroup = {
|
||||
if (!inviter.items.quests.basilist) {
|
||||
inviter.items.quests.basilist = 0;
|
||||
}
|
||||
inviter.items.quests.basilist++;
|
||||
inviter.items.quests.basilist += 1;
|
||||
inviter.markModified('items.quests');
|
||||
}
|
||||
promises.push(inviter.save());
|
||||
@@ -686,7 +694,9 @@ api.rejectGroupInvite = {
|
||||
|
||||
const hasPartyInvitation = removeFromArray(user.invitations.parties, { id: groupId });
|
||||
if (hasPartyInvitation) {
|
||||
user.invitations.party = user.invitations.parties.length > 0 ? user.invitations.parties[user.invitations.parties.length - 1] : {};
|
||||
user.invitations.party = user.invitations.parties.length > 0
|
||||
? user.invitations.parties[user.invitations.parties.length - 1]
|
||||
: {};
|
||||
user.markModified('invitations.party');
|
||||
isUserInvited = true;
|
||||
} else {
|
||||
@@ -772,7 +782,10 @@ api.leaveGroup = {
|
||||
throw new NotAuthorized(res.t('questLeaderCannotLeaveGroup'));
|
||||
}
|
||||
|
||||
if (group.quest && group.quest.active && group.quest.members && group.quest.members[user._id]) {
|
||||
if (
|
||||
group.quest && group.quest.active
|
||||
&& group.quest.members && group.quest.members[user._id]
|
||||
) {
|
||||
throw new NotAuthorized(res.t('cannotLeaveWhileActiveQuest'));
|
||||
}
|
||||
}
|
||||
@@ -909,7 +922,9 @@ api.removeGroupMember = {
|
||||
}
|
||||
if (isInvited === 'party') {
|
||||
removeFromArray(member.invitations.parties, { id: group._id });
|
||||
member.invitations.party = member.invitations.parties.length > 0 ? member.invitations.parties[member.invitations.parties.length - 1] : {};
|
||||
member.invitations.party = member.invitations.parties.length > 0
|
||||
? member.invitations.parties[member.invitations.parties.length - 1]
|
||||
: {};
|
||||
member.markModified('invitations.party');
|
||||
}
|
||||
} else {
|
||||
@@ -1064,7 +1079,8 @@ api.inviteToGroup = {
|
||||
}
|
||||
|
||||
if (usernames) {
|
||||
const usernameInvites = usernames.map(username => inviteByUserName(username, group, user, req, res));
|
||||
const usernameInvites = usernames
|
||||
.map(username => inviteByUserName(username, group, user, req, res));
|
||||
const usernameResults = await Promise.all(usernameInvites);
|
||||
results.push(...usernameResults);
|
||||
}
|
||||
|
||||
@@ -168,10 +168,9 @@ api.getHero = {
|
||||
url: '/hall/heroes/:heroId',
|
||||
middlewares: [authWithHeaders(), ensureAdmin],
|
||||
async handler (req, res) {
|
||||
let validationErrors;
|
||||
req.checkParams('heroId', res.t('heroIdRequired')).notEmpty();
|
||||
|
||||
validationErrors = req.validationErrors();
|
||||
const validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
const { heroId } = req.params;
|
||||
@@ -256,22 +255,26 @@ api.updateHero = {
|
||||
if (updateData.balance) hero.balance = updateData.balance;
|
||||
|
||||
// give them gems if they got an higher level
|
||||
let newTier = updateData.contributor && updateData.contributor.level; // tier = level in this context
|
||||
const oldTier = hero.contributor && hero.contributor.level || 0;
|
||||
// tier = level in this context
|
||||
let newTier = updateData.contributor && updateData.contributor.level;
|
||||
|
||||
const oldTier = (hero.contributor && hero.contributor.level) || 0;
|
||||
if (newTier > oldTier) {
|
||||
hero.flags.contributor = true;
|
||||
let tierDiff = newTier - oldTier; // can be 2+ tier increases at once
|
||||
while (tierDiff) {
|
||||
hero.balance += gemsPerTier[newTier] / 4; // balance is in $
|
||||
tierDiff--;
|
||||
newTier--; // give them gems for the next tier down if they weren't aready that tier
|
||||
tierDiff -= 1;
|
||||
newTier -= 1; // give them gems for the next tier down if they weren't aready that tier
|
||||
}
|
||||
|
||||
hero.addNotification('NEW_CONTRIBUTOR_LEVEL');
|
||||
}
|
||||
|
||||
if (updateData.contributor) _.assign(hero.contributor, updateData.contributor);
|
||||
if (updateData.purchased && updateData.purchased.ads) hero.purchased.ads = updateData.purchased.ads;
|
||||
if (updateData.purchased && updateData.purchased.ads) {
|
||||
hero.purchased.ads = updateData.purchased.ads;
|
||||
}
|
||||
|
||||
// give them the Dragon Hydra pet if they're above level 6
|
||||
if (hero.contributor.level >= 6) {
|
||||
@@ -279,7 +282,8 @@ api.updateHero = {
|
||||
hero.markModified('items.pets');
|
||||
}
|
||||
if (updateData.itemPath && updateData.itemVal && validateItemPath(updateData.itemPath)) {
|
||||
_.set(hero, updateData.itemPath, castItemVal(updateData.itemPath, updateData.itemVal)); // Sanitization at 5c30944 (deemed unnecessary)
|
||||
// Sanitization at 5c30944 (deemed unnecessary)
|
||||
_.set(hero, updateData.itemPath, castItemVal(updateData.itemPath, updateData.itemVal));
|
||||
}
|
||||
|
||||
if (updateData.auth && updateData.auth.blocked === true) {
|
||||
@@ -290,8 +294,12 @@ api.updateHero = {
|
||||
hero.auth.blocked = false;
|
||||
}
|
||||
|
||||
if (updateData.flags && _.isBoolean(updateData.flags.chatRevoked)) hero.flags.chatRevoked = updateData.flags.chatRevoked;
|
||||
if (updateData.flags && _.isBoolean(updateData.flags.chatShadowMuted)) hero.flags.chatShadowMuted = updateData.flags.chatShadowMuted;
|
||||
if (updateData.flags && _.isBoolean(updateData.flags.chatRevoked)) {
|
||||
hero.flags.chatRevoked = updateData.flags.chatRevoked;
|
||||
}
|
||||
if (updateData.flags && _.isBoolean(updateData.flags.chatShadowMuted)) {
|
||||
hero.flags.chatShadowMuted = updateData.flags.chatShadowMuted;
|
||||
}
|
||||
|
||||
const savedHero = await hero.save();
|
||||
const heroJSON = savedHero.toJSON();
|
||||
|
||||
@@ -259,7 +259,8 @@ api.getMemberAchievements = {
|
||||
|
||||
// Return a request handler for getMembersForGroup / getInvitesForGroup / getMembersForChallenge
|
||||
|
||||
// @TODO: This violates the Liskov substitution principle. We should create factory functions. See Webhooks for a good example
|
||||
// @TODO: This violates the Liskov substitution principle.
|
||||
// We should create factory functions. See Webhooks for a good example
|
||||
function _getMembersForItem (type) {
|
||||
// check for allowed `type`
|
||||
if (['group-members', 'group-invites', 'challenge-members'].indexOf(type) === -1) {
|
||||
@@ -288,7 +289,8 @@ function _getMembersForItem (type) {
|
||||
challenge = await Challenge.findById(challengeId).select('_id type leader group').exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
|
||||
// optionalMembership is set to true because even if you're not member of the group you may be able to access the challenge
|
||||
// optionalMembership is set to true because even
|
||||
// if you're not member of the group you may be able to access the challenge
|
||||
// for example if you've been booted from it, are the leader or a site admin
|
||||
group = await Group.getGroup({
|
||||
user,
|
||||
@@ -305,7 +307,8 @@ function _getMembersForItem (type) {
|
||||
|
||||
const query = {};
|
||||
let fields = nameFields;
|
||||
let addComputedStats = false; // add computes stats to the member info when items and stats are available
|
||||
// add computes stats to the member info when items and stats are available
|
||||
let addComputedStats = false;
|
||||
|
||||
if (type === 'challenge-members') {
|
||||
query.challenges = challenge._id;
|
||||
@@ -349,7 +352,8 @@ function _getMembersForItem (type) {
|
||||
}
|
||||
} else {
|
||||
query['invitations.party.id'] = group._id; // group._id and not groupId because groupId could be === 'party'
|
||||
// @TODO invitations are now stored like this: `'invitations.parties': []` Probably need a database index for it.
|
||||
// @TODO invitations are now stored like this: `'invitations.parties': []`
|
||||
// Probably need a database index for it.
|
||||
if (req.query.includeAllPublicFields === 'true') {
|
||||
fields = memberFields;
|
||||
addComputedStats = true;
|
||||
@@ -554,7 +558,8 @@ api.getChallengeMemberProgress = {
|
||||
const challenge = await Challenge.findById(challengeId).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
|
||||
// optionalMembership is set to true because even if you're not member of the group you may be able to access the challenge
|
||||
// optionalMembership is set to true because even if you're
|
||||
// not member of the group you may be able to access the challenge
|
||||
// for example if you've been booted from it, are the leader or a site admin
|
||||
const group = await Group.getGroup({
|
||||
user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true,
|
||||
|
||||
@@ -2,7 +2,8 @@ import { authWithHeaders } from '../../middlewares/auth';
|
||||
|
||||
const api = {};
|
||||
|
||||
// @TODO export this const, cannot export it from here because only routes are exported from controllers
|
||||
// @TODO export this const, cannot export it
|
||||
// from here because only routes are exported from controllers
|
||||
const LAST_ANNOUNCEMENT_TITLE = 'SUPERNATURAL SKINS AND HAUNTED HAIR COLORS';
|
||||
const worldDmg = { // @TODO
|
||||
bailey: false,
|
||||
|
||||
@@ -43,7 +43,7 @@ api.readNotification = {
|
||||
// Update the user version field manually,
|
||||
// it cannot be updated in the pre update hook
|
||||
// See https://github.com/HabitRPG/habitica/pull/9321#issuecomment-354187666 for more info
|
||||
user._v++;
|
||||
user._v += 1;
|
||||
|
||||
await user.update({
|
||||
$pull: { notifications: { id: req.params.notificationId } },
|
||||
@@ -90,7 +90,7 @@ api.readNotifications = {
|
||||
// Update the user version field manually,
|
||||
// it cannot be updated in the pre update hook
|
||||
// See https://github.com/HabitRPG/habitica/pull/9321#issuecomment-354187666 for more info
|
||||
user._v++;
|
||||
user._v += 1;
|
||||
|
||||
res.respond(200, UserNotification.convertNotificationsToSafeJson(user.notifications));
|
||||
},
|
||||
@@ -140,7 +140,7 @@ api.seeNotification = {
|
||||
// Update the user version field manually,
|
||||
// it cannot be updated in the pre update hook
|
||||
// See https://github.com/HabitRPG/habitica/pull/9321#issuecomment-354187666 for more info
|
||||
user._v++;
|
||||
user._v += 1;
|
||||
|
||||
res.respond(200, notification);
|
||||
},
|
||||
|
||||
@@ -305,7 +305,9 @@ api.forceStart = {
|
||||
if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported'));
|
||||
if (!group.quest.key) throw new NotFound(res.t('questNotPending'));
|
||||
if (group.quest.active) throw new NotAuthorized(res.t('questAlreadyUnderway'));
|
||||
if (!(user._id === group.quest.leader || user._id === group.leader)) throw new NotAuthorized(res.t('questOrGroupLeaderOnlyStartQuest'));
|
||||
if (!(user._id === group.quest.leader || user._id === group.leader)) {
|
||||
throw new NotAuthorized(res.t('questOrGroupLeaderOnlyStartQuest'));
|
||||
}
|
||||
|
||||
group.markModified('quest');
|
||||
|
||||
@@ -352,7 +354,8 @@ api.cancelQuest = {
|
||||
async handler (req, res) {
|
||||
// Cancel a quest BEFORE it has begun (i.e., in the invitation stage)
|
||||
// Quest scroll has not yet left quest owner's inventory so no need to return it.
|
||||
// Do not wipe quest progress for members because they'll want it to be applied to the next quest that's started.
|
||||
// Do not wipe quest progress for members because they'll
|
||||
// want it to be applied to the next quest that's started.
|
||||
const { user } = res.locals;
|
||||
const { groupId } = req.params;
|
||||
|
||||
@@ -366,7 +369,9 @@ api.cancelQuest = {
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
if (group.type !== 'party') throw new NotAuthorized(res.t('guildQuestsNotSupported'));
|
||||
if (!group.quest.key) throw new NotFound(res.t('questInvitationDoesNotExist'));
|
||||
if (user._id !== group.leader && group.quest.leader !== user._id) throw new NotAuthorized(res.t('onlyLeaderCancelQuest'));
|
||||
if (user._id !== group.leader && group.quest.leader !== user._id) {
|
||||
throw new NotAuthorized(res.t('onlyLeaderCancelQuest'));
|
||||
}
|
||||
if (group.quest.active) throw new NotAuthorized(res.t('cantCancelActiveQuest'));
|
||||
|
||||
const questName = questScrolls[group.quest.key].text('en');
|
||||
|
||||
@@ -227,7 +227,7 @@ api.deleteTag = {
|
||||
// Update the user version field manually,
|
||||
// it cannot be updated in the pre update hook
|
||||
// See https://github.com/HabitRPG/habitica/pull/9321#issuecomment-354187666 for more info
|
||||
user._v++;
|
||||
user._v += 1;
|
||||
|
||||
// Remove from all the tasks TODO test
|
||||
await Tasks.Task.update({
|
||||
|
||||
@@ -383,12 +383,23 @@ api.getTask = {
|
||||
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
} else if (task.challenge.id && !task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
|
||||
// If the task belongs to a challenge make sure the user has rights
|
||||
} else if (task.challenge.id && !task.userId) {
|
||||
const challenge = await Challenge.find({ _id: task.challenge.id }).select('leader').exec();
|
||||
if (!challenge || (user.challenges.indexOf(task.challenge.id) === -1 && challenge.leader !== user._id && !user.contributor.admin)) { // eslint-disable-line no-extra-parens
|
||||
if (
|
||||
!challenge
|
||||
|| (
|
||||
user.challenges.indexOf(task.challenge.id) === -1
|
||||
&& challenge.leader !== user._id
|
||||
&& !user.contributor.admin
|
||||
)
|
||||
) { // eslint-disable-line no-extra-parens
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
}
|
||||
} else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one
|
||||
|
||||
// If the task is owned by a user make it's the current one
|
||||
} else if (task.userId !== user._id) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
}
|
||||
|
||||
@@ -451,16 +462,21 @@ api.updateTask = {
|
||||
group = await Group.getGroup({ user, groupId: task.group.id, fields });
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
} else if (task.challenge.id && !task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
|
||||
// If the task belongs to a challenge make sure the user has rights
|
||||
} else if (task.challenge.id && !task.userId) {
|
||||
challenge = await Challenge.findOne({ _id: task.challenge.id }).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks'));
|
||||
} else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one
|
||||
|
||||
// If the task is owned by a user make it's the current one
|
||||
} else if (task.userId !== user._id) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
}
|
||||
|
||||
const oldCheckList = task.checklist;
|
||||
// we have to convert task to an object because otherwise things don't get merged correctly. Bad for performances?
|
||||
// we have to convert task to an object because otherwise things
|
||||
// don't get merged correctly. Bad for performances?
|
||||
const [updatedTaskObj] = common.ops.updateTask(task.toObject(), req);
|
||||
// Sanitize differently user tasks linked to a challenge
|
||||
let sanitizedObj;
|
||||
@@ -476,7 +492,8 @@ api.updateTask = {
|
||||
_.assign(task, sanitizedObj);
|
||||
|
||||
// console.log(task.modifiedPaths(), task.toObject().repeat === tep)
|
||||
// repeat is always among modifiedPaths because mongoose changes the other of the keys when using .toObject()
|
||||
// repeat is always among modifiedPaths because mongoose changes
|
||||
// the other of the keys when using .toObject()
|
||||
// see https://github.com/Automattic/mongoose/issues/2749
|
||||
|
||||
task.group.approval.required = false;
|
||||
@@ -585,7 +602,8 @@ api.scoreTask = {
|
||||
|
||||
const managers = await User.find({ _id: managerIds }, 'notifications preferences').exec(); // Use this method so we can get access to notifications
|
||||
|
||||
// @TODO: we can use the User.pushNotification function because we need to ensure notifications are translated
|
||||
// @TODO: we can use the User.pushNotification function because
|
||||
// we need to ensure notifications are translated
|
||||
const managerPromises = [];
|
||||
managers.forEach(manager => {
|
||||
manager.addNotification('GROUP_TASK_APPROVAL', {
|
||||
@@ -594,7 +612,8 @@ api.scoreTask = {
|
||||
taskName: task.text,
|
||||
}, manager.preferences.language),
|
||||
groupId: group._id,
|
||||
taskId: task._id, // user task id, used to match the notification when the task is approved
|
||||
// user task id, used to match the notification when the task is approved
|
||||
taskId: task._id,
|
||||
userId: user._id,
|
||||
groupTaskId: task.group.taskId, // the original task id
|
||||
direction,
|
||||
@@ -612,7 +631,8 @@ api.scoreTask = {
|
||||
const wasCompleted = task.completed;
|
||||
|
||||
const [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') common.fns.randomDrop(user, { task, delta }, req, res.analytics);
|
||||
|
||||
// If a todo was completed or uncompleted move it in or out of the user.tasksOrder.todos list
|
||||
@@ -626,7 +646,11 @@ api.scoreTask = {
|
||||
$pull: { 'tasksOrder.todos': task._id },
|
||||
}).exec();
|
||||
// user.tasksOrder.todos.pull(task._id);
|
||||
} else if (wasCompleted && !task.completed && user.tasksOrder.todos.indexOf(task._id) === -1) {
|
||||
} else if (
|
||||
wasCompleted
|
||||
&& !task.completed
|
||||
&& user.tasksOrder.todos.indexOf(task._id) === -1
|
||||
) {
|
||||
taskOrderPromise = user.update({
|
||||
$push: { 'tasksOrder.todos': task._id },
|
||||
}).exec();
|
||||
@@ -649,7 +673,9 @@ api.scoreTask = {
|
||||
}).exec();
|
||||
|
||||
if (groupTask) {
|
||||
const groupDelta = groupTask.group.assignedUsers ? delta / groupTask.group.assignedUsers.length : delta;
|
||||
const groupDelta = groupTask.group.assignedUsers
|
||||
? delta / groupTask.group.assignedUsers.length
|
||||
: delta;
|
||||
await groupTask.scoreChallengeTask(groupDelta, direction);
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -675,7 +701,8 @@ api.scoreTask = {
|
||||
});
|
||||
|
||||
if (task.challenge && task.challenge.id && task.challenge.taskId && !task.challenge.broken && task.type !== 'reward') {
|
||||
// Wrapping everything in a try/catch block because if an error occurs using `await` it MUST NOT bubble up because the request has already been handled
|
||||
// Wrapping everything in a try/catch block because if an error occurs
|
||||
// using `await` it MUST NOT bubble up because the request has already been handled
|
||||
try {
|
||||
const chalTask = await Tasks.Task.findOne({
|
||||
_id: task.challenge.taskId,
|
||||
@@ -763,7 +790,7 @@ api.moveTask = {
|
||||
// Update the user version field manually,
|
||||
// it cannot be updated in the pre update hook
|
||||
// See https://github.com/HabitRPG/habitica/pull/9321#issuecomment-354187666 for more info
|
||||
user._v++;
|
||||
user._v += 1;
|
||||
|
||||
res.respond(200, order);
|
||||
},
|
||||
@@ -811,11 +838,15 @@ api.addChecklistItem = {
|
||||
const fields = requiredGroupFields.concat(' managers');
|
||||
group = await Group.getGroup({ user, groupId: task.group.id, fields });
|
||||
if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
} else if (task.challenge.id && !task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
|
||||
// If the task belongs to a challenge make sure the user has rights
|
||||
} else if (task.challenge.id && !task.userId) {
|
||||
challenge = await Challenge.findOne({ _id: task.challenge.id }).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks'));
|
||||
} else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one
|
||||
|
||||
// If the task is owned by a user make it's the current one
|
||||
} else if (task.userId !== user._id) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
}
|
||||
|
||||
@@ -927,11 +958,15 @@ api.updateChecklistItem = {
|
||||
group = await Group.getGroup({ user, groupId: task.group.id, fields });
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
} else if (task.challenge.id && !task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
|
||||
// If the task belongs to a challenge make sure the user has rights
|
||||
} else if (task.challenge.id && !task.userId) {
|
||||
challenge = await Challenge.findOne({ _id: task.challenge.id }).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks'));
|
||||
} else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one
|
||||
|
||||
// If the task is owned by a user make it's the current one
|
||||
} else if (task.userId !== user._id) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
}
|
||||
if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo'));
|
||||
@@ -992,11 +1027,15 @@ api.removeChecklistItem = {
|
||||
group = await Group.getGroup({ user, groupId: task.group.id, fields });
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
} else if (task.challenge.id && !task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
|
||||
// If the task belongs to a challenge make sure the user has rights
|
||||
} else if (task.challenge.id && !task.userId) {
|
||||
challenge = await Challenge.findOne({ _id: task.challenge.id }).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks'));
|
||||
} else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one
|
||||
|
||||
// If the task is owned by a user make it's the current one
|
||||
} else if (task.userId !== user._id) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
}
|
||||
if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo'));
|
||||
@@ -1310,15 +1349,23 @@ api.deleteTask = {
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
await group.removeTask(task);
|
||||
} else if (task.challenge.id && !task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
|
||||
// If the task belongs to a challenge make sure the user has rights
|
||||
} else if (task.challenge.id && !task.userId) {
|
||||
challenge = await Challenge.findOne({ _id: task.challenge.id }).exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
if (!challenge.canModify(user)) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks'));
|
||||
} else if (task.userId !== user._id) { // If the task is owned by a user make it's the current one
|
||||
|
||||
// If the task is owned by a user make it's the current one
|
||||
} else if (task.userId !== user._id) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
} else if (task.userId && task.challenge.id && !task.challenge.broken) {
|
||||
throw new NotAuthorized(res.t('cantDeleteChallengeTasks'));
|
||||
} else if (task.group.id && task.group.assignedUsers.indexOf(user._id) !== -1 && !task.group.broken) {
|
||||
} else if (
|
||||
task.group.id
|
||||
&& task.group.assignedUsers.indexOf(user._id) !== -1
|
||||
&& !task.group.broken
|
||||
) {
|
||||
throw new NotAuthorized(res.t('cantDeleteAssignedGroupTasks'));
|
||||
}
|
||||
|
||||
@@ -1332,7 +1379,7 @@ api.deleteTask = {
|
||||
// Update the user version field manually,
|
||||
// it cannot be updated in the pre update hook
|
||||
// See https://github.com/HabitRPG/habitica/pull/9321#issuecomment-354187666 for more info
|
||||
if (!challenge) user._v++;
|
||||
if (!challenge) user._v += 1;
|
||||
|
||||
await Promise.all([taskOrderUpdate, task.remove()]);
|
||||
} else {
|
||||
|
||||
@@ -18,7 +18,8 @@ import apiError from '../../../libs/apiError';
|
||||
const requiredGroupFields = '_id leader tasksOrder name';
|
||||
// @TODO: abstract to task lib
|
||||
const types = Tasks.tasksTypes.map(type => `${type}s`);
|
||||
types.push('completedTodos', '_allCompletedTodos'); // _allCompletedTodos is currently in BETA and is likely to be removed in future
|
||||
// _allCompletedTodos is currently in BETA and is likely to be removed in future
|
||||
types.push('completedTodos', '_allCompletedTodos');
|
||||
|
||||
function canNotEditTasks (group, user, assignedUserId) {
|
||||
const isNotGroupLeader = group.leader !== user._id;
|
||||
@@ -96,7 +97,11 @@ api.getGroupTasks = {
|
||||
|
||||
const { user } = res.locals;
|
||||
|
||||
const group = await Group.getGroup({ user, groupId: req.params.groupId, fields: requiredGroupFields });
|
||||
const group = await Group.getGroup({
|
||||
user,
|
||||
groupId: req.params.groupId,
|
||||
fields: requiredGroupFields,
|
||||
});
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
|
||||
const tasks = await getTasks(req, res, { user, group });
|
||||
@@ -142,7 +147,11 @@ api.groupMoveTask = {
|
||||
|
||||
if (task.type === 'todo' && task.completed) throw new BadRequest(res.t('cantMoveCompletedTodo'));
|
||||
|
||||
const group = await Group.getGroup({ user, groupId: task.group.id, fields: requiredGroupFields });
|
||||
const group = await Group.getGroup({
|
||||
user,
|
||||
groupId: task.group.id,
|
||||
fields: requiredGroupFields,
|
||||
});
|
||||
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||
|
||||
if (group.leader !== user._id) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||
|
||||
@@ -123,7 +123,10 @@ api.getBuyList = {
|
||||
// return text and notes strings
|
||||
_.each(list, item => {
|
||||
_.each(item, (itemPropVal, itemPropKey) => {
|
||||
if (_.isFunction(itemPropVal) && itemPropVal.i18nLangFunc) item[itemPropKey] = itemPropVal(req.language);
|
||||
if (
|
||||
_.isFunction(itemPropVal)
|
||||
&& itemPropVal.i18nLangFunc
|
||||
) item[itemPropKey] = itemPropVal(req.language);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -166,7 +169,10 @@ api.getInAppRewardsList = {
|
||||
// return text and notes strings
|
||||
_.each(list, item => {
|
||||
_.each(item, (itemPropVal, itemPropKey) => {
|
||||
if (_.isFunction(itemPropVal) && itemPropVal.i18nLangFunc) item[itemPropKey] = itemPropVal(req.language);
|
||||
if (
|
||||
_.isFunction(itemPropVal)
|
||||
&& itemPropVal.i18nLangFunc
|
||||
) item[itemPropKey] = itemPropVal(req.language);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -455,7 +461,6 @@ api.buy = {
|
||||
async handler (req, res) {
|
||||
const { user } = res.locals;
|
||||
|
||||
let buyRes;
|
||||
// @TODO: Remove this when mobile passes type in body
|
||||
const type = req.params.key;
|
||||
if (buySpecialKeys.indexOf(type) !== -1) {
|
||||
@@ -470,7 +475,7 @@ api.buy = {
|
||||
let quantity = 1;
|
||||
if (req.body.quantity) quantity = req.body.quantity;
|
||||
req.quantity = quantity;
|
||||
buyRes = common.ops.buy(user, req, res.analytics);
|
||||
const buyRes = common.ops.buy(user, req, res.analytics);
|
||||
|
||||
await user.save();
|
||||
res.respond(200, ...buyRes);
|
||||
@@ -1003,7 +1008,12 @@ api.userPurchaseHourglass = {
|
||||
const { user } = res.locals;
|
||||
const quantity = req.body.quantity || 1;
|
||||
if (quantity < 1 || !Number.isInteger(quantity)) throw new BadRequest(res.t('invalidQuantity'), req.language);
|
||||
const purchaseHourglassRes = common.ops.buy(user, req, res.analytics, { quantity, hourglass: true });
|
||||
const purchaseHourglassRes = common.ops.buy(
|
||||
user,
|
||||
req,
|
||||
res.analytics,
|
||||
{ quantity, hourglass: true },
|
||||
);
|
||||
await user.save();
|
||||
res.respond(200, ...purchaseHourglassRes);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user