diff --git a/website/src/controllers/api-v3/auth.js b/website/src/controllers/api-v3/auth.js index a1d2c1b82a..c66b254e9b 100644 --- a/website/src/controllers/api-v3/auth.js +++ b/website/src/controllers/api-v3/auth.js @@ -56,7 +56,7 @@ api.registerLocal = { let lowerCaseUsername = username.toLowerCase(); // Search for duplicates using lowercase version of username - let user = User.findOne({$or: [ + let user = await User.findOne({$or: [ {'auth.local.email': email}, {'auth.local.lowerCaseUsername': lowerCaseUsername}, ]}, {'auth.local': 1}).exec(); diff --git a/website/src/controllers/api-v3/challenges.js b/website/src/controllers/api-v3/challenges.js index 1d950446fa..5a19663f74 100644 --- a/website/src/controllers/api-v3/challenges.js +++ b/website/src/controllers/api-v3/challenges.js @@ -27,75 +27,72 @@ api.createChallenge = { method: 'POST', url: '/challenges', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkBody('group', res.t('groupIdRequired')).notEmpty(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; let groupId = req.body.group; let prize = req.body.prize; - Group.getGroup(user, groupId, '-chat') - .then(group => { - if (!group) throw new NotFound(res.t('groupNotFound')); + let group = await Group.getGroup(user, groupId, '-chat'); + if (!group) throw new NotFound(res.t('groupNotFound')); - if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) { - throw new NotAuthorized(res.t('onlyGroupLeaderChal')); + if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) { + throw new NotAuthorized(res.t('onlyGroupLeaderChal')); + } + + if (groupId === 'habitrpg' && prize < 1) { + throw new NotAuthorized(res.t('pubChalsMinPrize')); + } + + if (prize > 0) { + let groupBalance = group.balance && group.leader === user._id ? group.balance : 0; + let prizeCost = prize / 4; + + if (prizeCost > user.balance + groupBalance) { + throw new NotAuthorized(res.t('cantAfford')); } - if (groupId === 'habitrpg' && prize < 1) { - throw new NotAuthorized(res.t('pubChalsMinPrize')); + if (groupBalance >= prizeCost) { + // Group pays for all of prize + group.balance -= prizeCost; + } else if (groupBalance > 0) { + // User pays remainder of prize cost after group + let remainder = prizeCost - group.balance; + group.balance = 0; + user.balance -= remainder; + } else { + // User pays for all of prize + user.balance -= prizeCost; } + } - if (prize > 0) { - let groupBalance = group.balance && group.leader === user._id ? group.balance : 0; - let prizeCost = prize / 4; + group.challengeCount += 1; - if (prizeCost > user.balance + groupBalance) { - throw new NotAuthorized(res.t('cantAfford')); - } + let tasks = req.body.tasks || []; // TODO validate + req.body.leader = user._id; + req.body.official = user.contributor.admin && req.body.official; + let challenge = new Challenge(Challenge.sanitize(req.body)); - if (groupBalance >= prizeCost) { - // Group pays for all of prize - group.balance -= prizeCost; - } else if (groupBalance > 0) { - // User pays remainder of prize cost after group - let remainder = prizeCost - group.balance; - group.balance = 0; - user.balance -= remainder; - } else { - // User pays for all of prize - user.balance -= prizeCost; - } - } + let toSave = tasks.map(tasks, taskToCreate => { + // TODO validate type + let task = new Tasks[taskToCreate.type](Tasks.Task.sanitizeCreate(taskToCreate)); + task.challenge.id = challenge._id; + challenge.tasksOrder[`${task.type}s`].push(task._id); + return task.save(); + }); - group.challengeCount += 1; + toSave.unshift(challenge, group); - let tasks = req.body.tasks || []; // TODO validate - req.body.leader = user._id; - req.body.official = user.contributor.admin && req.body.official; - let challenge = new Challenge(Challenge.sanitize(req.body)); + let results = await Q.all(toSave); + let savedChal = results[0]; - let toSave = tasks.map(tasks, taskToCreate => { - // TODO validate type - let task = new Tasks[taskToCreate.type](Tasks.Task.sanitizeCreate(taskToCreate)); - task.challenge.id = challenge._id; - challenge.tasksOrder[`${task.type}s`].push(task._id); - return task.save(); - }); - - toSave.unshift(challenge, group); - return Q.all(toSave); - }) - .then(results => { - let savedChal = results[0]; - return savedChal.syncToUser(user) // (it also saves the user) - .then(() => res.respond(201, savedChal)); - }) - .catch(next); + await savedChal.syncToUser(user); // (it also saves the user) + res.respond(201, savedChal); }, }; @@ -111,14 +108,14 @@ api.getChallenges = { method: 'GET', url: '/challenges', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; let groups = user.guilds || []; if (user.party._id) groups.push(user.party._id); groups.push('habitrpg'); // Public challenges - Challenge.find({ + let challenges = await Challenge.find({ $or: [ {_id: {$in: user.challenges}}, // Challenges where the user is participating {group: {$in: groups}}, // Challenges in groups where I'm a member @@ -130,11 +127,9 @@ api.getChallenges = { // TODO populate // .populate('group', '_id name type') // .populate('leader', 'profile.name') - .exec() - .then(challenges => { - res.respond(200, challenges); - }) - .catch(next); + .exec(); + + res.respond(200, challenges); }, }; @@ -190,7 +185,7 @@ function _closeChal (challenge, broken = {}) { })); } - return Q.allSettled(tasks); // TODO look if allSettle could be useful somewhere else + return Q.allSettled(tasks); // TODO look if allSettled could be useful somewhere else // TODO catch and handle } @@ -206,25 +201,21 @@ api.deleteChallenge = { method: 'DELETE', url: '/challenges/:challengeId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('challenge', res.t('challengeIdRequired')).notEmpty().isUUID(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Challenge.findOne({_id: req.params.challengeId}) - .exec() - .then(challenge => { - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - if (challenge.leader !== user._id && !user.contributor.admin) throw new NotAuthorized(res.t('onlyLeaderDeleteChal')); + let challenge = await Challenge.findOne({_id: req.params.challengeId}).exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + if (challenge.leader !== user._id && !user.contributor.admin) throw new NotAuthorized(res.t('onlyLeaderDeleteChal')); - res.respond(200, {}); - // Close channel in background - _closeChal(challenge, {broken: 'CHALLENGE_DELETED'}); - }) - .catch(next); + res.respond(200, {}); + // Close channel in background + _closeChal(challenge, {broken: 'CHALLENGE_DELETED'}); }, }; @@ -240,34 +231,25 @@ api.selectChallengeWinner = { method: 'POST', url: '/challenges/:challengeId/selectWinner/:winnerId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; - let challenge; req.checkParams('challenge', res.t('challengeIdRequired')).notEmpty().isUUID(); req.checkParams('winnerId', res.t('winnerIdRequired')).notEmpty().isUUID(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Challenge.findOne({_id: req.params.challengeId}) - .exec() - .then(challengeFound => { - if (!challenge) throw new NotFound(res.t('challengeNotFound')); - if (challenge.leader !== user._id && !user.contributor.admin) throw new NotAuthorized(res.t('onlyLeaderDeleteChal')); + let challenge = await Challenge.findOne({_id: req.params.challengeId}).exec(); + if (!challenge) throw new NotFound(res.t('challengeNotFound')); + if (challenge.leader !== user._id && !user.contributor.admin) throw new NotAuthorized(res.t('onlyLeaderDeleteChal')); - challenge = challengeFound; + let winner = await User.findOne({_id: req.params.winnerId}).exec(); + if (!winner || winner.challenges.indexOf(challenge._id) === -1) throw new NotFound(res.t('winnerNotFound', {userId: req.parama.winnerId})); - return User.findOne({_id: req.params.winnerId}).exec(); - }) - .then(winner => { - if (!winner || winner.challenges.indexOf(challenge._id) === -1) throw new NotFound(res.t('winnerNotFound', {userId: req.parama.winnerId})); - - res.respond(200, {}); - // Close channel in background - _closeChal(challenge, {broken: 'CHALLENGE_DELETED', winner}); - }) - .catch(next); + res.respond(200, {}); + // Close channel in background + _closeChal(challenge, {broken: 'CHALLENGE_DELETED', winner}); }, }; diff --git a/website/src/controllers/api-v3/chat.js b/website/src/controllers/api-v3/chat.js index 2bfb4c6847..48dc2e440d 100644 --- a/website/src/controllers/api-v3/chat.js +++ b/website/src/controllers/api-v3/chat.js @@ -25,21 +25,18 @@ api.getChat = { method: 'GET', url: '/groups/:groupId/chat', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Group.getGroup(user, req.params.groupId, 'chat') - .then(group => { - if (!group) throw new NotFound(res.t('groupNotFound')); + let group = await Group.getGroup(user, req.params.groupId, 'chat'); + if (!group) throw new NotFound(res.t('groupNotFound')); - res.respond(200, group.chat); - }) - .catch(next); + res.respond(200, group.chat); }, }; @@ -59,7 +56,7 @@ api.postChat = { method: 'POST', url: '/groups/:groupId/chat', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; let groupId = req.params.groupId; let chatUpdated; @@ -68,34 +65,31 @@ api.postChat = { req.checkBody('message', res.t('messageGroupChatBlankMessage')).notEmpty(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Group.getGroup(user, groupId) - .then((group) => { - if (!group) throw new NotFound(res.t('groupNotFound')); - if (group.type !== 'party' && user.flags.chatRevoked) { - throw new NotFound('Your chat privileges have been revoked.'); - } + let group = await Group.getGroup(user, groupId); - let lastClientMsg = req.query.previousMsg; - chatUpdated = lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg ? true : false; + if (!group) throw new NotFound(res.t('groupNotFound')); + if (group.type !== 'party' && user.flags.chatRevoked) { + throw new NotFound('Your chat privileges have been revoked.'); + } - group.sendChat(req.body.message, user); + let lastClientMsg = req.query.previousMsg; + chatUpdated = lastClientMsg && group.chat && group.chat[0] && group.chat[0].id !== lastClientMsg ? true : false; - if (group.type === 'party') { - user.party.lastMessageSeen = group.chat[0].id; - user.save(); - } - return group.save(); - }) - .then((group) => { - if (chatUpdated) { - res.respond(200, {chat: group.chat}); - } else { - res.respond(200, {message: group.chat[0]}); - } - }) - .catch(next); + group.sendChat(req.body.message, user); + + if (group.type === 'party') { + user.party.lastMessageSeen = group.chat[0].id; + user.save(); // TODO why this is non-blocking? must catch? + } + + let savedGroup = await group.save(); + if (chatUpdated) { + res.respond(200, {chat: savedGroup.chat}); + } else { + res.respond(200, {message: savedGroup.chat[0]}); + } }, }; @@ -114,42 +108,35 @@ api.likeChat = { method: 'Post', url: '/groups/:groupId/chat/:chatId/like', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; let groupId = req.params.groupId; - let message; req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); req.checkParams('chatId', res.t('chatIdRequired')).notEmpty(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Group.getGroup(user, groupId) - .then((group) => { - if (!group) throw new NotFound(res.t('groupNotFound')); - message = _.find(group.chat, {id: req.params.chatId}); - if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); + let group = await Group.getGroup(user, groupId); + if (!group) throw new NotFound(res.t('groupNotFound')); - if (message.uuid === user._id) throw new NotFound(res.t('messageGroupChatLikeOwnMessage')); + let message = _.find(group.chat, {id: req.params.chatId}); + if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); + if (message.uuid === user._id) throw new NotFound(res.t('messageGroupChatLikeOwnMessage')); - let update = {$set: {}}; + let update = {$set: {}}; - if (!message.likes) message.likes = {}; + if (!message.likes) message.likes = {}; - message.likes[user._id] = !message.likes[user._id]; - update.$set[`chat.$.likes.${user._id}`] = message.likes[user._id]; + message.likes[user._id] = !message.likes[user._id]; + update.$set[`chat.$.likes.${user._id}`] = message.likes[user._id]; - return Group.update( - {_id: group._id, 'chat.id': message.id}, - update - ); - }) - .then((groupSaved) => { - if (!groupSaved) throw new NotFound(res.t('groupNotFound')); - res.respond(200, message); - }) - .catch(next); + await Group.update( + {_id: group._id, 'chat.id': message.id}, + update + ); + res.respond(200, message); }, }; @@ -168,116 +155,104 @@ api.flagChat = { method: 'Post', url: '/groups/:groupId/chat/:chatId/flag', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; let groupId = req.params.groupId; - let message; - let group; - let author; req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); req.checkParams('chatId', res.t('chatIdRequired')).notEmpty(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Group.getGroup(user, groupId) - .then((groupFound) => { - if (!groupFound) throw new NotFound(res.t('groupNotFound')); - group = groupFound; - message = _.find(group.chat, {id: req.params.chatId}); + let group = await Group.getGroup(user, groupId); + if (!group) throw new NotFound(res.t('groupNotFound')); + let message = _.find(group.chat, {id: req.params.chatId}); - if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); + if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); - if (message.uuid === user._id) throw new NotFound(res.t('messageGroupChatFlagOwnMessage')); + if (message.uuid === user._id) throw new NotFound(res.t('messageGroupChatFlagOwnMessage')); - return User.findOne({_id: message.uuid}, {auth: 1}); - }) - .then((foundAuthor) => { - author = foundAuthor; + let author = await User.findOne({_id: message.uuid}, {auth: 1}); - let update = {$set: {}}; + let update = {$set: {}}; - // Log user ids that have flagged the message - if (!message.flags) message.flags = {}; - if (message.flags[user._id] && !user.contributor.admin) throw new NotFound(res.t('messageGroupChatFlagAlreadyReported')); - message.flags[user._id] = true; - update.$set[`chat.$.flags.${user._id}`] = true; + // Log user ids that have flagged the message + if (!message.flags) message.flags = {}; + if (message.flags[user._id] && !user.contributor.admin) throw new NotFound(res.t('messageGroupChatFlagAlreadyReported')); + message.flags[user._id] = true; + update.$set[`chat.$.flags.${user._id}`] = true; - // Log total number of flags (publicly viewable) - if (!message.flagCount) message.flagCount = 0; - if (user.contributor.admin) { - // Arbitraty amount, higher than 2 - message.flagCount = 5; - } else { - message.flagCount++; - } - update.$set['chat.$.flagCount'] = message.flagCount; + // Log total number of flags (publicly viewable) + if (!message.flagCount) message.flagCount = 0; + if (user.contributor.admin) { + // Arbitraty amount, higher than 2 + message.flagCount = 5; + } else { + message.flagCount++; + } + update.$set['chat.$.flagCount'] = message.flagCount; - return Group.update( - {_id: group._id, 'chat.id': message.id}, - update - ); - }) - .then((group2) => { - if (!group2) throw new NotFound(res.t('groupNotFound')); + await Group.update( + {_id: group._id, 'chat.id': message.id}, + update + ); - let addressesToSendTo = nconf.get('FLAG_REPORT_EMAIL'); - addressesToSendTo = typeof addressesToSendTo === 'string' ? JSON.parse(addressesToSendTo) : addressesToSendTo; + let addressesToSendTo = nconf.get('FLAG_REPORT_EMAIL'); + addressesToSendTo = typeof addressesToSendTo === 'string' ? JSON.parse(addressesToSendTo) : addressesToSendTo; - if (Array.isArray(addressesToSendTo)) { - addressesToSendTo = addressesToSendTo.map((email) => { - return {email, canSend: true}; - }); - } else { - addressesToSendTo = {email: addressesToSendTo}; - } + if (Array.isArray(addressesToSendTo)) { + addressesToSendTo = addressesToSendTo.map((email) => { + return {email, canSend: true}; + }); + } else { + addressesToSendTo = {email: addressesToSendTo}; + } - let reporterEmailContent; - if (user.auth.local) { - reporterEmailContent = user.auth.local.email; - } else if (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0]) { - reporterEmailContent = user.auth.facebook.emails[0].value; - } + let reporterEmailContent; + if (user.auth.local) { + reporterEmailContent = user.auth.local.email; + } else if (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0]) { + reporterEmailContent = user.auth.facebook.emails[0].value; + } - let authorEmailContent; - if (author.auth.local) { - authorEmailContent = author.auth.local.email; - } else if (author.auth.facebook && author.auth.facebook.emails && author.auth.facebook.emails[0]) { - authorEmailContent = author.auth.facebook.emails[0].value; - } + let authorEmailContent; + if (author.auth.local) { + authorEmailContent = author.auth.local.email; + } else if (author.auth.facebook && author.auth.facebook.emails && author.auth.facebook.emails[0]) { + authorEmailContent = author.auth.facebook.emails[0].value; + } - let groupUrl; - if (group._id === 'habitrpg') { - groupUrl = '/#/options/groups/tavern'; - } else if (group.type === 'guild') { - groupUrl = `/#/options/groups/guilds/{$group._id}`; - } else { - groupUrl = 'party'; - } + let groupUrl; + if (group._id === 'habitrpg') { + groupUrl = '/#/options/groups/tavern'; + } else if (group.type === 'guild') { + groupUrl = `/#/options/groups/guilds/{$group._id}`; + } else { + groupUrl = 'party'; + } - sendTxn(addressesToSendTo, 'flag-report-to-mods', [ - {name: 'MESSAGE_TIME', content: (new Date(message.timestamp)).toString()}, - {name: 'MESSAGE_TEXT', content: message.text}, + sendTxn(addressesToSendTo, 'flag-report-to-mods', [ + {name: 'MESSAGE_TIME', content: (new Date(message.timestamp)).toString()}, + {name: 'MESSAGE_TEXT', content: message.text}, - {name: 'REPORTER_USERNAME', content: user.profile.name}, - {name: 'REPORTER_UUID', content: user._id}, - {name: 'REPORTER_EMAIL', content: reporterEmailContent}, - {name: 'REPORTER_MODAL_URL', content: `/static/front/#?memberId={$user._id}`}, + {name: 'REPORTER_USERNAME', content: user.profile.name}, + {name: 'REPORTER_UUID', content: user._id}, + {name: 'REPORTER_EMAIL', content: reporterEmailContent}, + {name: 'REPORTER_MODAL_URL', content: `/static/front/#?memberId={$user._id}`}, - {name: 'AUTHOR_USERNAME', content: message.user}, - {name: 'AUTHOR_UUID', content: message.uuid}, - {name: 'AUTHOR_EMAIL', content: authorEmailContent}, - {name: 'AUTHOR_MODAL_URL', content: `/static/front/#?memberId={$message.uuid}`}, + {name: 'AUTHOR_USERNAME', content: message.user}, + {name: 'AUTHOR_UUID', content: message.uuid}, + {name: 'AUTHOR_EMAIL', content: authorEmailContent}, + {name: 'AUTHOR_MODAL_URL', content: `/static/front/#?memberId={$message.uuid}`}, - {name: 'GROUP_NAME', content: group.name}, - {name: 'GROUP_TYPE', content: group.type}, - {name: 'GROUP_ID', content: group._id}, - {name: 'GROUP_URL', content: groupUrl}, - ]); - res.respond(200, message); - }) - .catch(next); + {name: 'GROUP_NAME', content: group.name}, + {name: 'GROUP_TYPE', content: group.type}, + {name: 'GROUP_ID', content: group._id}, + {name: 'GROUP_URL', content: groupUrl}, + ]); + + res.respond(200, message); }, }; diff --git a/website/src/controllers/api-v3/groups.js b/website/src/controllers/api-v3/groups.js index 8a3ce9630b..1edf18b026 100644 --- a/website/src/controllers/api-v3/groups.js +++ b/website/src/controllers/api-v3/groups.js @@ -29,36 +29,31 @@ api.createGroup = { method: 'POST', url: '/groups', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; let group = new Group(Group.sanitize(req.body)); // TODO validate empty req.body group.leader = user._id; if (group.type === 'guild') { - if (user.balance < 1) return next(new NotAuthorized(res.t('messageInsufficientGems'))); + if (user.balance < 1) throw new NotAuthorized(res.t('messageInsufficientGems')); group.balance = 1; user.balance--; user.guilds.push(group._id); } else { - if (user.party._id) return next(new NotAuthorized(res.t('messageGroupAlreadyInParty'))); + if (user.party._id) throw new NotAuthorized(res.t('messageGroupAlreadyInParty')); user.party._id = group._id; } - Q.all([ - user.save(), - group.save(), - ]).then(results => { - let savedGroup = results[1]; + let results = await Q.all([user.save(), group.save()]); + let savedGroup = results[1]; - firebase.updateGroupData(savedGroup); - firebase.addUserToGroup(savedGroup._id, user._id); - return res.respond(201, savedGroup); // TODO populate - }) - .catch(next); + firebase.updateGroupData(savedGroup); + firebase.addUserToGroup(savedGroup._id, user._id); + return res.respond(201, savedGroup); // TODO populate }, }; @@ -76,13 +71,13 @@ api.getGroups = { method: 'GET', url: '/groups', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkQuery('type', res.t('groupTypesRequired')).notEmpty(); // TODO better validation let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; // TODO validate types are acceptable? probably not necessary let types = req.query.type.split(','); @@ -115,16 +110,14 @@ api.getGroups = { }); // If no valid value for type was supplied, return an error - if (queries.length === 0) return next(new BadRequest(res.t('groupTypesRequired'))); + if (queries.length === 0) throw new BadRequest(res.t('groupTypesRequired')); - Q.all(queries) // TODO we would like not to return a single big array but Q doesn't support the funtionality https://github.com/kriskowal/q/issues/328 - .then(results => { - res.respond(200, _.reduce(results, (m, v) => { - if (_.isEmpty(v)) return m; - return m.concat(Array.isArray(v) ? v : [v]); - }, [])); - }) - .catch(next); + let results = await Q.all(queries); // TODO we would like not to return a single big array but Q doesn't support the funtionality https://github.com/kriskowal/q/issues/328 + + res.respond(200, _.reduce(results, (m, v) => { + if (_.isEmpty(v)) return m; + return m.concat(Array.isArray(v) ? v : [v]); + }, [])); }, }; @@ -142,21 +135,18 @@ api.getGroup = { method: 'GET', url: '/groups/:groupId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Group.getGroup(user, req.params.groupId) - .then(group => { - if (!group) throw new NotFound(res.t('groupNotFound')); + let group = await Group.getGroup(user, req.params.groupId); + if (!group) throw new NotFound(res.t('groupNotFound')); - res.respond(200, group); - }) - .catch(next); + res.respond(200, group); }, }; @@ -174,28 +164,24 @@ api.updateGroup = { method: 'PUT', url: '/groups/:groupId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Group.getGroup(user, req.params.groupId) - .then(group => { - if (!group) throw new NotFound(res.t('groupNotFound')); + let group = await Group.getGroup(user, req.params.groupId); + if (!group) throw new NotFound(res.t('groupNotFound')); - if (group.leader !== user._id) throw new NotAuthorized(res.t('messageGroupOnlyLeaderCanUpdate')); + if (group.leader !== user._id) throw new NotAuthorized(res.t('messageGroupOnlyLeaderCanUpdate')); - _.assign(group, _.merge(group.toObject(), Group.sanitizeUpdate(req.body))); + _.assign(group, _.merge(group.toObject(), Group.sanitizeUpdate(req.body))); - return group.save(); - }).then(savedGroup => { - res.respond(200, savedGroup); - firebase.updateGroupData(savedGroup); - }) - .catch(next); + let savedGroup = await group.save(); + res.respond(200, savedGroup); + firebase.updateGroupData(savedGroup); }, }; @@ -213,60 +199,57 @@ api.joinGroup = { method: 'POST', url: '/groups/:groupId/join', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('groupId', res.t('groupIdRequired')).notEmpty().isUUID(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Group.getGroup(user, req.params.groupId, '-chat', true) // Do not fetch chat and work even if the user is not yet a member of the group - .then(group => { - if (!group) throw new NotFound(res.t('groupNotFound')); + let group = await Group.getGroup(user, req.params.groupId, '-chat', true); // Do not fetch chat and work even if the user is not yet a member of the group + if (!group) throw new NotFound(res.t('groupNotFound')); - let isUserInvited = false; + let isUserInvited = false; - if (group.type === 'party' && group._id === (user.invitations.party && user.invitations.party.id)) { - user.invitations.party = {}; // Clear invite TODO mark modified? + if (group.type === 'party' && group._id === (user.invitations.party && user.invitations.party.id)) { + user.invitations.party = {}; // Clear invite TODO mark modified? - // invite new user to pending quest - if (group.quest.key && !group.quest.active) { - user.party.quest.RSVPNeeded = true; - user.party.quest.key = group.quest.key; - group.quest.members[user._id] = undefined; - group.markModified('quest.members'); - } - - user.party._id = group._id; // Set group as user's party - - isUserInvited = true; - } else if (group.type === 'guild' && user.invitations.guilds) { - let i = _.findIndex(user.invitations.guilds, {id: group._id}); - - if (i !== -1) { - isUserInvited = true; - user.invitations.guilds.splice(i, 1); // Remove invitation - } else { - isUserInvited = group.privacy === 'private' ? false : true; - } + // invite new user to pending quest + if (group.quest.key && !group.quest.active) { + user.party.quest.RSVPNeeded = true; + user.party.quest.key = group.quest.key; + group.quest.members[user._id] = undefined; + group.markModified('quest.members'); } - if (isUserInvited && group.type === 'guild') user.guilds.push(group._id); // Add group to user's guilds - if (!isUserInvited) throw new NotAuthorized(res.t('messageGroupRequiresInvite')); + user.party._id = group._id; // Set group as user's party - if (group.memberCount === 0) group.leader = user._id; // If new user is only member -> set as leader + isUserInvited = true; + } else if (group.type === 'guild' && user.invitations.guilds) { + let i = _.findIndex(user.invitations.guilds, {id: group._id}); - Q.all([ - group.save(), - user.save(), - User.update({_id: user.invitations.party.inviter}, {$inc: {'items.quests.basilist': 1}}).exec(), // Reward inviter - ]).then(() => { - firebase.addUserToGroup(group._id, user._id); - res.respond(200, {}); // TODO what to return? - }); - }) - .catch(next); + if (i !== -1) { + isUserInvited = true; + user.invitations.guilds.splice(i, 1); // Remove invitation + } else { + isUserInvited = group.privacy === 'private' ? false : true; + } + } + + if (isUserInvited && group.type === 'guild') user.guilds.push(group._id); // Add group to user's guilds + if (!isUserInvited) throw new NotAuthorized(res.t('messageGroupRequiresInvite')); + + if (group.memberCount === 0) group.leader = user._id; // If new user is only member -> set as leader + + await Q.all([ + group.save(), + user.save(), + User.update({_id: user.invitations.party.inviter}, {$inc: {'items.quests.basilist': 1}}).exec(), // Reward inviter + ]); + + firebase.addUserToGroup(group._id, user._id); + res.respond(200, {}); // TODO what to return? }, }; @@ -285,7 +268,7 @@ api.leaveGroup = { method: 'POST', url: '/groups/:groupId/leave', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); @@ -293,27 +276,24 @@ api.leaveGroup = { req.checkQuery('keep', res.t('keepOrRemoveAll')).optional().isIn(['keep-all', 'remove-all']); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Group.getGroup(user, req.params.groupId, '-chat') // Do not fetch chat - .then(group => { - if (!group) throw new NotFound(res.t('groupNotFound')); + let group = await Group.getGroup(user, req.params.groupId, '-chat'); // Do not fetch chat + if (!group) throw new NotFound(res.t('groupNotFound')); - // During quests, checke wheter user can leave - if (group.type === 'party') { - if (group.quest && group.quest.leader === user._id) { - throw new NotAuthorized(res.t('questLeaderCannotLeaveGroup')); - } - - if (group.quest && group.quest.active && group.quest.members && group.quest.members[user._id]) { - throw new NotAuthorized(res.t('cannotLeaveWhileActiveQuest')); - } + // During quests, checke wheter user can leave + if (group.type === 'party') { + if (group.quest && group.quest.leader === user._id) { + throw new NotAuthorized(res.t('questLeaderCannotLeaveGroup')); } - return group.leave(user, req.query.keep); - }) - .then(() => res.respond(200, {})) - .catch(next); + if (group.quest && group.quest.active && group.quest.members && group.quest.members[user._id]) { + throw new NotAuthorized(res.t('cannotLeaveWhileActiveQuest')); + } + } + + await group.leave(user, req.query.keep); + res.respond(200, {}); }, }; @@ -345,71 +325,65 @@ api.removeGroupMember = { method: 'POST', url: '/groups/:groupId/removeMember/:memberId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; - let group; req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); req.checkParams('memberId', res.t('userIdRequired')).notEmpty().isUUID(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Group.getGroup(user, req.params.groupId, '-chat') // Do not fetch chat - .then(foundGroup => { - group = foundGroup; - if (!group) throw new NotFound(res.t('groupNotFound')); + let group = await Group.getGroup(user, req.params.groupId, '-chat'); // Do not fetch chat + if (!group) throw new NotFound(res.t('groupNotFound')); - let uuid = req.query.memberId; + let uuid = req.query.memberId; - if (group.leader !== user._id) throw new NotAuthorized(res.t('onlyLeaderCanRemoveMember')); - if (user._id === uuid) throw new NotAuthorized(res.t('memberCannotRemoveYourself')); + if (group.leader !== user._id) throw new NotAuthorized(res.t('onlyLeaderCanRemoveMember')); + if (user._id === uuid) throw new NotAuthorized(res.t('memberCannotRemoveYourself')); - return User.findOne({_id: uuid}).select('party guilds invitations newMessages').exec(); - }).then(member => { - // We're removing the user from a guild or a party? is the user invited only? - let isInGroup = member.party._id === group._id ? 'party' : member.guilds.indexOf(group._id) !== 1 ? 'guild' : undefined; // eslint-disable-line no-nested-ternary - let isInvited = member.invitations.party.id === group._id ? 'party' : _.findIndex(member.invitations.guilds, {id: group._id}) !== 1 ? 'guild' : undefined; // eslint-disable-line no-nested-ternary + let member = await User.findOne({_id: uuid}).select('party guilds invitations newMessages').exec(); + // We're removing the user from a guild or a party? is the user invited only? + let isInGroup = member.party._id === group._id ? 'party' : member.guilds.indexOf(group._id) !== 1 ? 'guild' : undefined; // eslint-disable-line no-nested-ternary + let isInvited = member.invitations.party.id === group._id ? 'party' : _.findIndex(member.invitations.guilds, {id: group._id}) !== 1 ? 'guild' : undefined; // eslint-disable-line no-nested-ternary - if (isInGroup) { - group.memberCount -= 1; + if (isInGroup) { + group.memberCount -= 1; - if (group.quest && group.quest.leader === member._id) { - group.quest.key = null; - group.quest.leader = null; // TODO markmodified? - } else if (group.quest && group.quest.members) { - // remove member from quest - group.quest.members[member._id] = undefined; - } - - if (isInGroup === 'guild') _.pull(member.guilds, group._id); - if (isInGroup === 'party') member.party._id = undefined; // TODO remove quest information too? - - member.newMessages.group._id = undefined; - - if (group.quest && group.quest.active && group.quest.leader === member._id) { - member.items.quests[group.quest.key] += 1; // TODO why this? - } - } else if (isInvited) { - if (isInvited === 'guild') { - let i = _.findIndex(member.invitations.guilds, {id: group._id}); - if (i !== -1) member.invitations.guilds.splice(i, 1); - } - if (isInvited === 'party') user.invitations.party = {}; // TODO mark modified? - } else { - throw new NotFound(res.t('groupMemberNotFound')); + if (group.quest && group.quest.leader === member._id) { + group.quest.key = null; + group.quest.leader = null; // TODO markmodified? + } else if (group.quest && group.quest.members) { + // remove member from quest + group.quest.members[member._id] = undefined; } - let message = req.query.message; - if (message) _sendMessageToRemoved(group, member, message); + if (isInGroup === 'guild') _.pull(member.guilds, group._id); + if (isInGroup === 'party') member.party._id = undefined; // TODO remove quest information too? - return Q.all([ - member.save(), - group.save(), - ]); - }) - .then(() => res.respond(200, {})) - .catch(next); + member.newMessages.group._id = undefined; + + if (group.quest && group.quest.active && group.quest.leader === member._id) { + member.items.quests[group.quest.key] += 1; // TODO why this? + } + } else if (isInvited) { + if (isInvited === 'guild') { + let i = _.findIndex(member.invitations.guilds, {id: group._id}); + if (i !== -1) member.invitations.guilds.splice(i, 1); + } + if (isInvited === 'party') user.invitations.party = {}; // TODO mark modified? + } else { + throw new NotFound(res.t('groupMemberNotFound')); + } + + let message = req.query.message; + if (message) _sendMessageToRemoved(group, member, message); + + await Q.all([ + member.save(), + group.save(), + ]); + res.respond(200, {}); }, }; @@ -571,32 +545,29 @@ api.inviteToGroup = { method: 'POST', url: '/groups/:groupId/invite', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Group.getGroup(user, req.params.groupId, '-chat') // Do not fetch chat TODO other fields too? - .then(group => { - if (!group) throw new NotFound(res.t('groupNotFound')); + let group = await Group.getGroup(user, req.params.groupId, '-chat'); // Do not fetch chat TODO other fields too? + if (!group) throw new NotFound(res.t('groupNotFound')); - let uuids = req.body.uuids; - let emails = req.body.emails; + let uuids = req.body.uuids; + let emails = req.body.emails; - if (uuids && emails) { // TODO fix this, low priority, allow for inviting by both at the same time - throw new BadRequest(res.t('canOnlyInviteEmailUuid')); - } else if (Array.isArray(uuids)) { - // return _inviteByUUIDs(uuids, group, user, req, res, next); - } else if (Array.isArray(emails)) { - // return _inviteByEmails(emails, group, user, req, res, next); - } else { - throw new BadRequest(res.t('canOnlyInviteEmailUuid')); - } - }) - .catch(next); + if (uuids && emails) { // TODO fix this, low priority, allow for inviting by both at the same time + throw new BadRequest(res.t('canOnlyInviteEmailUuid')); + } else if (Array.isArray(uuids)) { + // return _inviteByUUIDs(uuids, group, user, req, res, next); + } else if (Array.isArray(emails)) { + // return _inviteByEmails(emails, group, user, req, res, next); + } else { + throw new BadRequest(res.t('canOnlyInviteEmailUuid')); + } }, }; diff --git a/website/src/controllers/api-v3/tags.js b/website/src/controllers/api-v3/tags.js index 20d3f2f5ed..27191bf0ee 100644 --- a/website/src/controllers/api-v3/tags.js +++ b/website/src/controllers/api-v3/tags.js @@ -20,18 +20,15 @@ api.createTag = { method: 'POST', url: '/tags', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; user.tags.push(Tag.sanitize(req.body)); + let savedUser = await user.save(); - user.save() - .then((savedUser) => { - let l = savedUser.tags.length; - let tag = savedUser.tags[l - 1]; - res.respond(201, tag); - }) - .catch(next); + let l = savedUser.tags.length; + let tag = savedUser.tags[l - 1]; + res.respond(201, tag); }, }; @@ -47,7 +44,7 @@ api.getTags = { method: 'GET', url: '/tags', middlewares: [authWithHeaders(), cron], - handler (req, res) { + async handler (req, res) { let user = res.locals.user; res.respond(200, user.tags); }, @@ -67,16 +64,16 @@ api.getTag = { method: 'GET', url: '/tags/:tagId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; let tag = user.tags.id(req.params.tagId); - if (!tag) return next(new NotFound(res.t('tagNotFound'))); + if (!tag) throw new NotFound(res.t('tagNotFound')); res.respond(200, tag); }, }; @@ -95,7 +92,7 @@ api.updateTag = { method: 'PUT', url: '/tags/:tagId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID(); @@ -104,16 +101,15 @@ api.updateTag = { let tagId = req.params.tagId; let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; let tag = user.tags.id(tagId); - if (!tag) return next(new NotFound(res.t('tagNotFound'))); + if (!tag) throw new NotFound(res.t('tagNotFound')); _.merge(tag, Tag.sanitize(req.body)); - user.save() - .then((savedUser) => res.respond(200, savedUser.tags.id(tagId))) - .catch(next); + let savedUser = await user.save(); + res.respond(200, savedUser.tags.id(tagId)); }, }; @@ -131,21 +127,20 @@ api.deleteTag = { method: 'DELETE', url: '/tags/:tagId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; let tag = user.tags.id(req.params.tagId); - if (!tag) return next(new NotFound(res.t('tagNotFound'))); + if (!tag) throw new NotFound(res.t('tagNotFound')); tag.remove(); - user.save() - .then(() => res.respond(200, {})) - .catch(next); + await user.save(); + res.respond(200, {}); }, }; diff --git a/website/src/controllers/api-v3/tasks.js b/website/src/controllers/api-v3/tasks.js index 99e217e401..f67a25b10f 100644 --- a/website/src/controllers/api-v3/tasks.js +++ b/website/src/controllers/api-v3/tasks.js @@ -29,11 +29,11 @@ api.createTask = { method: 'POST', url: '/tasks', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { req.checkBody('type', res.t('invalidTaskType')).notEmpty().isIn(Tasks.tasksTypes); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; let user = res.locals.user; let taskType = req.body.type; @@ -43,12 +43,12 @@ api.createTask = { user.tasksOrder[`${taskType}s`].unshift(newTask._id); - Q.all([ + let results = await Q.all([ newTask.save(), user.save(), - ]) - .then((results) => res.respond(201, results[0])) - .catch(next); + ]); + + res.respond(201, results[0]); }, }; @@ -67,11 +67,11 @@ api.getTasks = { method: 'GET', url: '/tasks', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { req.checkQuery('type', res.t('invalidTaskType')).optional().isIn(Tasks.tasksTypes); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; let user = res.locals.user; let query = {userId: user._id}; @@ -95,16 +95,15 @@ api.getTasks = { dateCompleted: 1, }); - Q.all([ + let results = await Q.all([ queryCompleted.exec(), Tasks.Task.find(query).exec(), - ]) - .then((results) => res.respond(200, results[1].concat(results[0]))) - .catch(next); + ]); + + res.respond(200, results[1].concat(results[0])); } else { - Tasks.Task.find(query).exec() - .then((tasks) => res.respond(200, tasks)) - .catch(next); + let tasks = await Tasks.Task.find(query).exec(); + res.respond(200, tasks); } }, }; @@ -123,23 +122,21 @@ api.getTask = { method: 'GET', url: '/tasks/:taskId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Tasks.Task.findOne({ + let task = await Tasks.Task.findOne({ _id: req.params.taskId, userId: user._id, - }).exec() - .then((task) => { - if (!task) throw new NotFound(res.t('taskNotFound')); - res.respond(200, task); - }) - .catch(next); + }).exec(); + + if (!task) throw new NotFound(res.t('taskNotFound')); + res.respond(200, task); }, }; @@ -157,7 +154,7 @@ api.updateTask = { method: 'PUT', url: '/tasks/:taskId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); @@ -165,37 +162,36 @@ api.updateTask = { // TODO make sure tags are updated correctly (they aren't set as modified!) maybe use specific routes let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Tasks.Task.findOne({ + let task = await Tasks.Task.findOne({ _id: req.params.taskId, userId: user._id, - }).exec() - .then((task) => { - if (!task) throw new NotFound(res.t('taskNotFound')); + }).exec(); - // If checklist is updated -> replace the original one - if (req.body.checklist) { - task.checklist = req.body.checklist; - delete req.body.checklist; - } + if (!task) throw new NotFound(res.t('taskNotFound')); - // If tags are updated -> replace the original ones - if (req.body.tags) { - task.tags = req.body.tags; - delete req.body.tags; - } + // If checklist is updated -> replace the original one + if (req.body.checklist) { + task.checklist = req.body.checklist; + delete req.body.checklist; + } - // TODO we have to convert task to an object because otherwise thigns doesn't get merged correctly, very bad for performances - // TODO regarding comment above make sure other models with nested fields are using this trick too - _.assign(task, _.merge(task.toObject(), Tasks.Task.sanitizeUpdate(req.body))); - // TODO console.log(task.modifiedPaths(), task.toObject().repeat === tep) - // repeat is always among modifiedPaths because mongoose changes the other of the keys when using .toObject() - // see https://github.com/Automattic/mongoose/issues/2749 - return task.save(); - }) - .then((savedTask) => res.respond(200, savedTask)) - .catch(next); + // If tags are updated -> replace the original ones + if (req.body.tags) { + task.tags = req.body.tags; + delete req.body.tags; + } + + // TODO we have to convert task to an object because otherwise thigns doesn't get merged correctly, very bad for performances + // TODO regarding comment above make sure other models with nested fields are using this trick too + _.assign(task, _.merge(task.toObject(), Tasks.Task.sanitizeUpdate(req.body))); + // TODO console.log(task.modifiedPaths(), task.toObject().repeat === tep) + // repeat is always among modifiedPaths because mongoose changes the other of the keys when using .toObject() + // see https://github.com/Automattic/mongoose/issues/2749 + + let savedTask = await task.save(); + res.respond(200, savedTask); }, }; @@ -239,81 +235,82 @@ api.scoreTask = { method: 'POST', url: '/tasks/:taskId/score/:direction', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); req.checkParams('direction', res.t('directionUpDown')).notEmpty().isIn(['up', 'down']); // TODO what about rewards? maybe separate route? let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; let user = res.locals.user; let direction = req.params.direction; - Tasks.Task.findOne({ + let task = await Tasks.Task.findOne({ _id: req.params.taskId, userId: user._id, - }).exec() - .then((task) => { - if (!task) throw new NotFound(res.t('taskNotFound')); + }).exec(); - let wasCompleted = task.completed; - if (task.type === 'daily' || task.type === 'todo') { - task.completed = direction === 'up'; // TODO move into scoreTask - } + if (!task) throw new NotFound(res.t('taskNotFound')); - let delta = 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) - if (direction === 'up') user.fns.randomDrop({task, delta}, req); + let wasCompleted = task.completed; + if (task.type === 'daily' || task.type === 'todo') { + task.completed = direction === 'up'; // TODO move into scoreTask + } - // If a todo was completed or uncompleted move it in or out of the user.tasksOrder.todos list - if (task.type === 'todo') { - if (!wasCompleted && task.completed) { - let i = user.tasksOrder.todos.indexOf(task._id); - if (i !== -1) user.tasksOrder.todos.splice(i, 1); - } else if (wasCompleted && !task.completed) { - let i = user.tasksOrder.todos.indexOf(task._id); - if (i === -1) { - user.tasksOrder.todos.push(task._id); // TODO push at the top? - } else { // If for some reason it hadn't been removed TODO ok? - user.tasksOrder.todos.splice(i, 1); - user.tasksOrder.push(task._id); - } + let delta = 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) + if (direction === 'up') user.fns.randomDrop({task, delta}, req); + + // If a todo was completed or uncompleted move it in or out of the user.tasksOrder.todos list + if (task.type === 'todo') { + if (!wasCompleted && task.completed) { + let i = user.tasksOrder.todos.indexOf(task._id); + if (i !== -1) user.tasksOrder.todos.splice(i, 1); + } else if (wasCompleted && !task.completed) { + let i = user.tasksOrder.todos.indexOf(task._id); + if (i === -1) { + user.tasksOrder.todos.push(task._id); // TODO push at the top? + } else { // If for some reason it hadn't been removed TODO ok? + user.tasksOrder.todos.splice(i, 1); + user.tasksOrder.push(task._id); } } + } - return Q.all([ - user.save(), - task.save(), - ]).then((results) => { - let savedUser = results[0]; + let results = await Q.all([ + user.save(), + task.save(), + ]); - let userStats = savedUser.stats.toJSON(); - let resJsonData = _.extend({delta, _tmp: user._tmp}, userStats); - res.respond(200, resJsonData); + let savedUser = results[0]; - sendTaskWebhook(user.preferences.webhooks, _generateWebhookTaskData(task, direction, delta, userStats, user)); + let userStats = savedUser.stats.toJSON(); + let resJsonData = _.extend({delta, _tmp: user._tmp}, userStats); + res.respond(200, resJsonData); - // TODO test? - if (task.challenge.id && task.challenge.taskId && !task.challenge.broken && task.type !== 'reward') { - Tasks.Task.findOne({ - _id: task.challenge.taskId, - }).exec() - .then(chalTask => { - chalTask.value += delta; - if (chalTask.type === 'habit' || chalTask.type === 'daily') { - chalTask.history.push({value: chalTask.value, date: Number(new Date())}); - // TODO 1. treat challenges as subscribed users for preening 2. it's expensive to do it at every score - how to have it happen once like for cron? - chalTask.history = preenHistory(user, chalTask.history); - chalTask.markModified('history'); - } + sendTaskWebhook(user.preferences.webhooks, _generateWebhookTaskData(task, direction, delta, userStats, user)); - return chalTask.save(); - }); - // .catch(next) TODO what to do here + // TODO test? + if (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 + try { + let chalTask = await Tasks.Task.findOne({ + _id: task.challenge.taskId, + }).exec(); + + chalTask.value += delta; + if (chalTask.type === 'habit' || chalTask.type === 'daily') { + chalTask.history.push({value: chalTask.value, date: Number(new Date())}); + // TODO 1. treat challenges as subscribed users for preening 2. it's expensive to do it at every score - how to have it happen once like for cron? + chalTask.history = preenHistory(user, chalTask.history); + chalTask.markModified('history'); } - }); - }) - .catch(next); + + await chalTask.save(); + } catch (e) { + // TODO handle + } + } }, }; @@ -334,41 +331,39 @@ api.moveTask = { method: 'POST', url: '/tasks/move/:taskId/to/:position', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); req.checkParams('position', res.t('positionRequired')).notEmpty().isNumeric(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; let user = res.locals.user; let to = Number(req.params.position); - Tasks.Task.findOne({ + let task = await Tasks.Task.findOne({ _id: req.params.taskId, userId: user._id, - }).exec() - .then((task) => { - if (!task) throw new NotFound(res.t('taskNotFound')); - if (task.type === 'todo' && task.completed) throw new NotFound(res.t('cantMoveCompletedTodo')); - let order = user.tasksOrder[`${task.type}s`]; - let currentIndex = order.indexOf(task._id); + }).exec(); - // If for some reason the task isn't ordered (should never happen) - // or if the task is moved to a non existing position - // or if the task is moved to postion -1 (push to bottom) - // -> push task at end of list - if (currentIndex === -1 || !order[to] || to === -1) { - order.push(task._id); - } else { - let taskToMove = order.splice(currentIndex, 1)[0]; - order.splice(to, 0, taskToMove); - } + if (!task) throw new NotFound(res.t('taskNotFound')); + if (task.type === 'todo' && task.completed) throw new NotFound(res.t('cantMoveCompletedTodo')); + let order = user.tasksOrder[`${task.type}s`]; + let currentIndex = order.indexOf(task._id); - return user.save(); - }) - .then(() => res.respond(200, {})) // TODO what to return - .catch(next); + // If for some reason the task isn't ordered (should never happen) + // or if the task is moved to a non existing position + // or if the task is moved to postion -1 (push to bottom) + // -> push task at end of list + if (currentIndex === -1 || !order[to] || to === -1) { + order.push(task._id); + } else { + let taskToMove = order.splice(currentIndex, 1)[0]; + order.splice(to, 0, taskToMove); + } + + await user.save(); + res.respond(200, {}); // TODO what to return }, }; @@ -386,28 +381,27 @@ api.addChecklistItem = { method: 'POST', url: '/tasks/:taskId/checklist', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); // TODO check that req.body isn't empty and is an array let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Tasks.Task.findOne({ + let task = await Tasks.Task.findOne({ _id: req.params.taskId, userId: user._id, - }).exec() - .then((task) => { - if (!task) throw new NotFound(res.t('taskNotFound')); - if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); + }).exec(); - task.checklist.push(Tasks.Task.sanitizeChecklist(req.body)); - return task.save(); - }) - .then((savedTask) => res.respond(200, savedTask)) // TODO what to return - .catch(next); + if (!task) throw new NotFound(res.t('taskNotFound')); + if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); + + task.checklist.push(Tasks.Task.sanitizeChecklist(req.body)); + let savedTask = await task.save(); + + res.respond(200, savedTask); // TODO what to return }, }; @@ -426,31 +420,30 @@ api.scoreCheckListItem = { method: 'POST', url: '/tasks/:taskId/checklist/:itemId/score', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Tasks.Task.findOne({ + let task = await Tasks.Task.findOne({ _id: req.params.taskId, userId: user._id, - }).exec() - .then((task) => { - if (!task) throw new NotFound(res.t('taskNotFound')); - if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); + }).exec(); - let item = _.find(task.checklist, {_id: req.params.itemId}); + if (!task) throw new NotFound(res.t('taskNotFound')); + if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); - if (!item) throw new NotFound(res.t('checklistItemNotFound')); - item.completed = !item.completed; - return task.save(); - }) - .then((savedTask) => res.respond(200, savedTask)) // TODO what to return - .catch(next); + let item = _.find(task.checklist, {_id: req.params.itemId}); + + if (!item) throw new NotFound(res.t('checklistItemNotFound')); + item.completed = !item.completed; + let savedTask = await task.save(); + + res.respond(200, savedTask); // TODO what to return }, }; @@ -469,31 +462,30 @@ api.updateChecklistItem = { method: 'PUT', url: '/tasks/:taskId/checklist/:itemId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Tasks.Task.findOne({ + let task = await Tasks.Task.findOne({ _id: req.params.taskId, userId: user._id, - }).exec() - .then((task) => { - if (!task) throw new NotFound(res.t('taskNotFound')); - if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); + }).exec(); - let item = _.find(task.checklist, {_id: req.params.itemId}); - if (!item) throw new NotFound(res.t('checklistItemNotFound')); + if (!task) throw new NotFound(res.t('taskNotFound')); + if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); - _.merge(item, Tasks.Task.sanitizeChecklist(req.body)); - return task.save(); - }) - .then((savedTask) => res.respond(200, savedTask)) // TODO what to return - .catch(next); + let item = _.find(task.checklist, {_id: req.params.itemId}); + if (!item) throw new NotFound(res.t('checklistItemNotFound')); + + _.merge(item, Tasks.Task.sanitizeChecklist(req.body)); + let savedTask = await task.save(); + + res.respond(200, savedTask); // TODO what to return }, }; @@ -512,31 +504,30 @@ api.removeChecklistItem = { method: 'DELETE', url: '/tasks/:taskId/checklist/:itemId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Tasks.Task.findOne({ + let task = await Tasks.Task.findOne({ _id: req.params.taskId, userId: user._id, - }).exec() - .then((task) => { - if (!task) throw new NotFound(res.t('taskNotFound')); - if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); + }).exec(); - let itemI = _.findIndex(task.checklist, {_id: req.params.itemId}); - if (itemI === -1) throw new NotFound(res.t('checklistItemNotFound')); + if (!task) throw new NotFound(res.t('taskNotFound')); + if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo')); - task.checklist.splice(itemI, 1); - return task.save(); - }) - .then(() => res.respond(200, {})) // TODO what to return - .catch(next); + let itemI = _.findIndex(task.checklist, {_id: req.params.itemId}); + if (itemI === -1) throw new NotFound(res.t('checklistItemNotFound')); + + task.checklist.splice(itemI, 1); + + await task.save(); + res.respond(200, {}); // TODO what to return }, }; @@ -555,7 +546,7 @@ api.addTagToTask = { method: 'POST', url: '/tasks/:taskId/tags/:tagId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); @@ -563,24 +554,23 @@ api.addTagToTask = { req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID().isIn(userTags); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Tasks.Task.findOne({ + let task = await Tasks.Task.findOne({ _id: req.params.taskId, userId: user._id, - }).exec() - .then((task) => { - if (!task) throw new NotFound(res.t('taskNotFound')); - let tagId = req.params.tagId; + }).exec(); - let alreadyTagged = task.tags.indexOf(tagId) !== -1; - if (alreadyTagged) throw new BadRequest(res.t('alreadyTagged')); + if (!task) throw new NotFound(res.t('taskNotFound')); + let tagId = req.params.tagId; - task.tags.push(tagId); - return task.save(); - }) - .then((savedTask) => res.respond(200, savedTask)) // TODO what to return - .catch(next); + let alreadyTagged = task.tags.indexOf(tagId) !== -1; + if (alreadyTagged) throw new BadRequest(res.t('alreadyTagged')); + + task.tags.push(tagId); + + let savedTask = await task.save(); + res.respond(200, savedTask); // TODO what to return }, }; @@ -599,30 +589,29 @@ api.removeTagFromTask = { method: 'DELETE', url: '/tasks/:taskId/tags/:tagId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Tasks.Task.findOne({ + let task = await Tasks.Task.findOne({ _id: req.params.taskId, userId: user._id, - }).exec() - .then((task) => { - if (!task) throw new NotFound(res.t('taskNotFound')); + }).exec(); - let tagI = task.tags.indexOf(req.params.tagId); - if (tagI === -1) throw new NotFound(res.t('tagNotFound')); + if (!task) throw new NotFound(res.t('taskNotFound')); - task.tags.splice(tagI, 1); - return task.save(); - }) - .then(() => res.respond(200, {})) // TODO what to return - .catch(next); + let tagI = task.tags.indexOf(req.params.tagId); + if (tagI === -1) throw new NotFound(res.t('tagNotFound')); + + task.tags.splice(tagI, 1); + + await task.save(); + res.respond(200, {}); // TODO what to return }, }; @@ -656,30 +645,26 @@ api.deleteTask = { method: 'DELETE', url: '/tasks/:taskId', middlewares: [authWithHeaders(), cron], - handler (req, res, next) { + async handler (req, res) { let user = res.locals.user; req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID(); let validationErrors = req.validationErrors(); - if (validationErrors) return next(validationErrors); + if (validationErrors) throw validationErrors; - Tasks.Task.findOne({ + let task = await Tasks.Task.findOne({ _id: req.params.taskId, userId: user._id, - }).exec() - .then((task) => { - if (!task) throw new NotFound(res.t('taskNotFound')); - if (task.challenge.id) throw new NotAuthorized(res.t('cantDeleteChallengeTasks')); + }).exec(); - _removeTaskTasksOrder(user, req.params.taskId); - return Q.all([ - user.save(), - task.remove(), - ]); - }) - .then(() => res.respond(200, {})) - .catch(next); + if (!task) throw new NotFound(res.t('taskNotFound')); + if (task.challenge.id) throw new NotAuthorized(res.t('cantDeleteChallengeTasks')); + + _removeTaskTasksOrder(user, req.params.taskId); + await Q.all([user.save(), task.remove()]); + + res.respond(200, {}); }, }; diff --git a/website/src/controllers/api-v3/user.js b/website/src/controllers/api-v3/user.js index ef6749209c..39efc002f9 100644 --- a/website/src/controllers/api-v3/user.js +++ b/website/src/controllers/api-v3/user.js @@ -16,7 +16,7 @@ api.getUser = { method: 'GET', middlewares: [authWithHeaders(), cron], url: '/user', - handler (req, res) { + async handler (req, res) { let user = res.locals.user.toJSON(); // Remove apiToken from resonse TODO make it priavte at the user level? returned in signup/login