Always use .exec() for .find*() and .update() (#8361)

* add exec where missing in /models

* ix taskManager query

* fix top-level controllers

* fix api-v3 controllers
This commit is contained in:
Matteo Pagliazzi
2017-01-04 16:49:43 +01:00
committed by GitHub
parent 6cc359a935
commit 518b874f64
13 changed files with 93 additions and 51 deletions

View File

@@ -525,7 +525,7 @@ api.resetPassword = {
let newPassword = passwordUtils.makeSalt(); // use a salt as the new password too (they'll change it later) let newPassword = passwordUtils.makeSalt(); // use a salt as the new password too (they'll change it later)
let hashedPassword = passwordUtils.encrypt(newPassword, salt); let hashedPassword = passwordUtils.encrypt(newPassword, salt);
let user = await User.findOne({ 'auth.local.email': email }); let user = await User.findOne({ 'auth.local.email': email }).exec();
if (user) { if (user) {
user.auth.local.salt = salt; user.auth.local.salt = salt;
@@ -578,7 +578,10 @@ api.updateEmail = {
let validationErrors = req.validationErrors(); let validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors; if (validationErrors) throw validationErrors;
let emailAlreadyInUse = await User.findOne({'auth.local.email': req.body.newEmail}).select({_id: 1}).lean().exec(); let emailAlreadyInUse = await User.findOne({
'auth.local.email': req.body.newEmail,
}).select({_id: 1}).lean().exec();
if (emailAlreadyInUse) throw new NotAuthorized(res.t('cannotFulfillReq')); if (emailAlreadyInUse) throw new NotAuthorized(res.t('cannotFulfillReq'));
let candidatePassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt); let candidatePassword = passwordUtils.encrypt(req.body.password, user.auth.local.salt);

View File

@@ -138,7 +138,7 @@ api.joinChallenge = {
let validationErrors = req.validationErrors(); let validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors; if (validationErrors) throw validationErrors;
let challenge = await Challenge.findOne({ _id: req.params.challengeId }); let challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
if (!challenge) throw new NotFound(res.t('challengeNotFound')); if (!challenge) throw new NotFound(res.t('challengeNotFound'));
if (challenge.isMember(user)) throw new NotAuthorized(res.t('userAlreadyInChallenge')); if (challenge.isMember(user)) throw new NotAuthorized(res.t('userAlreadyInChallenge'));
@@ -187,7 +187,7 @@ api.leaveChallenge = {
let validationErrors = req.validationErrors(); let validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors; if (validationErrors) throw validationErrors;
let challenge = await Challenge.findOne({ _id: req.params.challengeId }); let challenge = await Challenge.findOne({ _id: req.params.challengeId }).exec();
if (!challenge) throw new NotFound(res.t('challengeNotFound')); if (!challenge) throw new NotFound(res.t('challengeNotFound'));
let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy'}); let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy'});
@@ -282,7 +282,11 @@ api.getGroupChallenges = {
let resChals = challenges.map(challenge => challenge.toJSON()); let resChals = challenges.map(challenge => challenge.toJSON());
// Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833 // Instead of populate we make a find call manually because of https://github.com/Automattic/mongoose/issues/3833
await Bluebird.all(resChals.map((chal, index) => { await Bluebird.all(resChals.map((chal, index) => {
return User.findById(chal.leader).select(nameFields).exec().then(populatedLeader => { return User
.findById(chal.leader)
.select(nameFields)
.exec()
.then(populatedLeader => {
resChals[index].leader = populatedLeader ? populatedLeader.toJSON({minimize: true}) : null; resChals[index].leader = populatedLeader ? populatedLeader.toJSON({minimize: true}) : null;
}); });
})); }));
@@ -315,10 +319,10 @@ api.getChallenge = {
let user = res.locals.user; let user = res.locals.user;
let challengeId = req.params.challengeId; let challengeId = req.params.challengeId;
let challenge = await Challenge.findById(challengeId)
// Don't populate the group as we'll fetch it manually later // Don't populate the group as we'll fetch it manually later
// .populate('leader', nameFields) // .populate('leader', nameFields)
.exec(); let challenge = await Challenge.findById(challengeId).exec();
if (!challenge) throw new NotFound(res.t('challengeNotFound')); if (!challenge) throw new NotFound(res.t('challengeNotFound'));
// Fetching basic group data // Fetching basic group data
@@ -374,8 +378,12 @@ api.exportChallengeCsv = {
.lean() // so we don't involve mongoose .lean() // so we don't involve mongoose
.exec(), .exec(),
Tasks.Task.find({'challenge.id': challengeId, userId: {$exists: true}}) Tasks.Task.find({
.sort({userId: 1, text: 1}).select('userId type text value notes').lean().exec(), 'challenge.id': challengeId,
userId: {$exists: true},
}).sort({userId: 1, text: 1})
.select('userId type text value notes')
.lean().exec(),
]); ]);
let resArray = members.map(member => [member._id, member.profile.name]); let resArray = members.map(member => [member._id, member.profile.name]);

View File

@@ -31,7 +31,7 @@ async function getAuthorEmailFromMessage (message) {
return 'system'; return 'system';
} }
let author = await User.findOne({_id: authorId}, {auth: 1}); let author = await User.findOne({_id: authorId}, {auth: 1}).exec();
if (author) { if (author) {
return getUserInfo(author, ['email']).email; return getUserInfo(author, ['email']).email;
@@ -182,7 +182,7 @@ api.likeChat = {
await Group.update( await Group.update(
{_id: group._id, 'chat.id': message.id}, {_id: group._id, 'chat.id': message.id},
update update
); ).exec();
res.respond(200, message); // TODO what if the message is flagged and shouldn't be returned? res.respond(200, message); // TODO what if the message is flagged and shouldn't be returned?
}, },
}; };
@@ -257,7 +257,7 @@ api.flagChat = {
await Group.update( await Group.update(
{_id: group._id, 'chat.id': message.id}, {_id: group._id, 'chat.id': message.id},
update update
); ).exec();
let reporterEmailContent = getUserInfo(user, ['email']).email; let reporterEmailContent = getUserInfo(user, ['email']).email;
let authorEmail = await getAuthorEmailFromMessage(message); let authorEmail = await getAuthorEmailFromMessage(message);
@@ -344,7 +344,7 @@ api.clearChatFlags = {
await Group.update( await Group.update(
{_id: group._id, 'chat.id': message.id}, {_id: group._id, 'chat.id': message.id},
{$set: {'chat.$.flagCount': message.flagCount}} {$set: {'chat.$.flagCount': message.flagCount}}
); ).exec();
let adminEmailContent = getUserInfo(user, ['email']).email; let adminEmailContent = getUserInfo(user, ['email']).email;
let authorEmail = getAuthorEmailFromMessage(message); let authorEmail = getAuthorEmailFromMessage(message);
@@ -454,7 +454,7 @@ api.deleteChat = {
await Group.update( await Group.update(
{_id: group._id}, {_id: group._id},
{$pull: {chat: {id: chatId}}} {$pull: {chat: {id: chatId}}}
); ).exec();
if (chatUpdated) { if (chatUpdated) {
let chatRes = Group.toJSONCleanChat(group, user).chat; let chatRes = Group.toJSONCleanChat(group, user).chat;

View File

@@ -308,7 +308,8 @@ api.updateGroup = {
let response = Group.toJSONCleanChat(savedGroup, user); let response = Group.toJSONCleanChat(savedGroup, user);
// If the leader changed fetch new data, otherwise use authenticated user // If the leader changed fetch new data, otherwise use authenticated user
if (response.leader !== user._id) { if (response.leader !== user._id) {
response.leader = (await User.findById(response.leader).select(nameFields).exec()).toJSON({minimize: true}); let rawLeader = await User.findById(response.leader).select(nameFields).exec();
response.leader = rawLeader.toJSON({minimize: true});
} else { } else {
response.leader = { response.leader = {
_id: user._id, _id: user._id,
@@ -423,10 +424,16 @@ api.joinGroup = {
if (group.type === 'party' && inviter) { if (group.type === 'party' && inviter) {
if (group.memberCount > 1) { if (group.memberCount > 1) {
promises.push(User.update({$or: [{'party._id': group._id}, {_id: user._id}], 'achievements.partyUp': {$ne: true}}, {$set: {'achievements.partyUp': true}}, {multi: true}).exec()); promises.push(User.update({
$or: [{'party._id': group._id}, {_id: user._id}],
'achievements.partyUp': {$ne: true},
}, {$set: {'achievements.partyUp': true}}, {multi: true}).exec());
} }
if (group.memberCount > 3) { if (group.memberCount > 3) {
promises.push(User.update({$or: [{'party._id': group._id}, {_id: user._id}], 'achievements.partyOn': {$ne: true}}, {$set: {'achievements.partyOn': true}}, {multi: true}).exec()); promises.push(User.update({
$or: [{'party._id': group._id}, {_id: user._id}],
'achievements.partyOn': {$ne: true},
}, {$set: {'achievements.partyOn': true}}, {multi: true}).exec());
} }
} }

View File

@@ -77,7 +77,8 @@ api.inviteToQuest = {
let members = await User.find({ let members = await User.find({
'party._id': group._id, 'party._id': group._id,
_id: {$ne: user._id}, _id: {$ne: user._id},
}).select('auth.facebook auth.local preferences.emailNotifications profile.name pushDevices') })
.select('auth.facebook auth.local preferences.emailNotifications profile.name pushDevices')
.exec(); .exec();
group.markModified('quest'); group.markModified('quest');
@@ -376,7 +377,7 @@ api.cancelQuest = {
{'party._id': groupId}, {'party._id': groupId},
{$set: {'party.quest': Group.cleanQuestProgress()}}, {$set: {'party.quest': Group.cleanQuestProgress()}},
{multi: true} {multi: true}
), ).exec(),
]); ]);
res.respond(200, savedGroup.quest); res.respond(200, savedGroup.quest);

View File

@@ -157,9 +157,17 @@ api.getChallengeTasks = {
let user = res.locals.user; let user = res.locals.user;
let challengeId = req.params.challengeId; let challengeId = req.params.challengeId;
let challenge = await Challenge.findOne({_id: challengeId}).select('group leader tasksOrder').exec(); let challenge = await Challenge.findOne({
_id: challengeId,
}).select('group leader tasksOrder').exec();
if (!challenge) throw new NotFound(res.t('challengeNotFound')); if (!challenge) throw new NotFound(res.t('challengeNotFound'));
let group = await Group.getGroup({user, groupId: challenge.group, fields: '_id type privacy', optionalMembership: true});
let group = await Group.getGroup({
user,
groupId: challenge.group,
fields: '_id type privacy',
optionalMembership: true,
});
if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound')); if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound'));
let tasks = await getTasks(req, res, {user, challenge}); let tasks = await getTasks(req, res, {user, challenge});
@@ -339,7 +347,8 @@ api.scoreTask = {
task.group.approval.requestedDate = new Date(); task.group.approval.requestedDate = new Date();
let group = await Group.getGroup({user, groupId: task.group.id, fields: requiredGroupFields}); let group = await Group.getGroup({user, groupId: task.group.id, fields: requiredGroupFields});
let groupLeader = await User.findById(group.leader); // Use this method so we can get access to notifications let groupLeader = await User.findById(group.leader).exec(); // Use this method so we can get access to notifications
groupLeader.addNotification('GROUP_TASK_APPROVAL', { groupLeader.addNotification('GROUP_TASK_APPROVAL', {
message: res.t('userHasRequestedTaskApproval', { message: res.t('userHasRequestedTaskApproval', {
user: user.profile.name, user: user.profile.name,

View File

@@ -104,7 +104,7 @@ api.assignTask = {
let user = res.locals.user; let user = res.locals.user;
let assignedUserId = req.params.assignedUserId; let assignedUserId = req.params.assignedUserId;
let assignedUser = await User.findById(assignedUserId); let assignedUser = await User.findById(assignedUserId).exec();
let taskId = req.params.taskId; let taskId = req.params.taskId;
let task = await Tasks.Task.findByIdOrAlias(taskId, user._id); let task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
@@ -162,7 +162,7 @@ api.unassignTask = {
let user = res.locals.user; let user = res.locals.user;
let assignedUserId = req.params.assignedUserId; let assignedUserId = req.params.assignedUserId;
let assignedUser = await User.findById(assignedUserId); let assignedUser = await User.findById(assignedUserId).exec();
let taskId = req.params.taskId; let taskId = req.params.taskId;
let task = await Tasks.Task.findByIdOrAlias(taskId, user._id); let task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
@@ -211,13 +211,13 @@ api.approveTask = {
let user = res.locals.user; let user = res.locals.user;
let assignedUserId = req.params.userId; let assignedUserId = req.params.userId;
let assignedUser = await User.findById(assignedUserId); let assignedUser = await User.findById(assignedUserId).exec();
let taskId = req.params.taskId; let taskId = req.params.taskId;
let task = await Tasks.Task.findOne({ let task = await Tasks.Task.findOne({
'group.taskId': taskId, 'group.taskId': taskId,
userId: assignedUserId, userId: assignedUserId,
}); }).exec();
if (!task) { if (!task) {
throw new NotFound(res.t('taskNotFound')); throw new NotFound(res.t('taskNotFound'));

View File

@@ -39,15 +39,16 @@ api.unsubscribe = {
let userUpdated = await User.update( let userUpdated = await User.update(
{_id: data._id}, {_id: data._id},
{ $set: {'preferences.emailNotifications.unsubscribeFromAll': true}} { $set: {'preferences.emailNotifications.unsubscribeFromAll': true}}
); ).exec();
if (userUpdated.nModified !== 1) throw new NotFound(res.t('userNotFound')); if (userUpdated.nModified !== 1) throw new NotFound(res.t('userNotFound'));
res.send(`<h1>${res.t('unsubscribedSuccessfully')}</h1> ${res.t('unsubscribedTextUsers')}`); res.send(`<h1>${res.t('unsubscribedSuccessfully')}</h1> ${res.t('unsubscribedTextUsers')}`);
} else { } else {
let unsubscribedEmail = await EmailUnsubscription.findOne({email: data.email.toLowerCase()}); let unsubscribedEmail = await EmailUnsubscription.findOne({email: data.email.toLowerCase()}).exec();
let okResponse = `<h1>${res.t('unsubscribedSuccessfully')}</h1> ${res.t('unsubscribedTextOthers')}`;
if (!unsubscribedEmail) await EmailUnsubscription.create({email: data.email.toLowerCase()}); if (!unsubscribedEmail) await EmailUnsubscription.create({email: data.email.toLowerCase()});
let okResponse = `<h1>${res.t('unsubscribedSuccessfully')}</h1> ${res.t('unsubscribedTextOthers')}`;
res.send(okResponse); res.send(okResponse);
} }
}, },

View File

@@ -144,7 +144,7 @@ api.checkout = {
if (gift) { if (gift) {
if (gift.type === 'subscription') method = 'createSubscription'; if (gift.type === 'subscription') method = 'createSubscription';
gift.member = await User.findById(gift ? gift.uuid : undefined); gift.member = await User.findById(gift ? gift.uuid : undefined).exec();
data.gift = gift; data.gift = gift;
data.paymentMethod = 'Amazon Payments (Gift)'; data.paymentMethod = 'Amazon Payments (Gift)';
} }
@@ -184,7 +184,7 @@ api.subscribe = {
if (sub.discount) { // apply discount if (sub.discount) { // apply discount
if (!coupon) throw new BadRequest(res.t('couponCodeRequired')); if (!coupon) throw new BadRequest(res.t('couponCodeRequired'));
let result = await Coupon.findOne({_id: cc.validate(coupon), event: sub.key}); let result = await Coupon.findOne({_id: cc.validate(coupon), event: sub.key}).exec();
if (!result) throw new NotAuthorized(res.t('invalidCoupon')); if (!result) throw new NotAuthorized(res.t('invalidCoupon'));
} }

View File

@@ -134,7 +134,7 @@ api.checkoutSuccess = {
delete req.session.gift; delete req.session.gift;
if (gift) { if (gift) {
gift.member = await User.findById(gift.uuid); gift.member = await User.findById(gift.uuid).exec();
if (gift.type === 'subscription') { if (gift.type === 'subscription') {
method = 'createSubscription'; method = 'createSubscription';
} }
@@ -168,7 +168,7 @@ api.subscribe = {
if (sub.discount) { if (sub.discount) {
if (!req.query.coupon) throw new BadRequest(res.t('couponCodeRequired')); if (!req.query.coupon) throw new BadRequest(res.t('couponCodeRequired'));
let coupon = await Coupon.findOne({_id: cc.validate(req.query.coupon), event: sub.key}); let coupon = await Coupon.findOne({_id: cc.validate(req.query.coupon), event: sub.key}).exec();
if (!coupon) throw new NotAuthorized(res.t('invalidCoupon')); if (!coupon) throw new NotAuthorized(res.t('invalidCoupon'));
} }
@@ -295,14 +295,18 @@ api.ipn = {
await ipnVerifyAsync(req.body); await ipnVerifyAsync(req.body);
if (req.body.txn_type === 'recurring_payment_profile_cancel' || req.body.txn_type === 'subscr_cancel') { if (req.body.txn_type === 'recurring_payment_profile_cancel' || req.body.txn_type === 'subscr_cancel') {
let user = await User.findOne({ 'purchased.plan.customerId': req.body.recurring_payment_id }); let user = await User.findOne({ 'purchased.plan.customerId': req.body.recurring_payment_id }).exec();
if (user) { if (user) {
await payments.cancelSubscription({ user, paymentMethod: 'Paypal' }); await payments.cancelSubscription({ user, paymentMethod: 'Paypal' });
return; return;
} }
let groupFields = basicGroupFields.concat(' purchased'); let groupFields = basicGroupFields.concat(' purchased');
let group = await Group.findOne({ 'purchased.plan.customerId': req.body.recurring_payment_id }).select(groupFields).exec(); let group = await Group
.findOne({ 'purchased.plan.customerId': req.body.recurring_payment_id })
.select(groupFields)
.exec();
if (group) { if (group) {
await payments.cancelSubscription({ groupId: group._id, paymentMethod: 'Paypal' }); await payments.cancelSubscription({ groupId: group._id, paymentMethod: 'Paypal' });
} }

View File

@@ -58,7 +58,7 @@ api.checkout = {
if (sub) { if (sub) {
if (sub.discount) { if (sub.discount) {
if (!req.query.coupon) throw new BadRequest(res.t('couponCodeRequired')); if (!req.query.coupon) throw new BadRequest(res.t('couponCodeRequired'));
coupon = await Coupon.findOne({_id: cc.validate(req.query.coupon), event: sub.key}); coupon = await Coupon.findOne({_id: cc.validate(req.query.coupon), event: sub.key}).exec();
if (!coupon) throw new BadRequest(res.t('invalidCoupon')); if (!coupon) throw new BadRequest(res.t('invalidCoupon'));
} }
@@ -114,7 +114,7 @@ api.checkout = {
}; };
if (gift) { if (gift) {
let member = await User.findById(gift.uuid); let member = await User.findById(gift.uuid).exec();
gift.member = member; gift.member = member;
if (gift.type === 'subscription') method = 'createSubscription'; if (gift.type === 'subscription') method = 'createSubscription';
data.paymentMethod = 'Stripe (Gift)'; data.paymentMethod = 'Stripe (Gift)';

View File

@@ -107,6 +107,8 @@ export async function getTasks (req, res, options = {}) {
} = options; } = options;
let query = {userId: user._id}; let query = {userId: user._id};
let limit;
let sort;
let owner = group || challenge || user; let owner = group || challenge || user;
if (challenge) { if (challenge) {
@@ -122,18 +124,21 @@ export async function getTasks (req, res, options = {}) {
query.completed = false; // Exclude completed todos query.completed = false; // Exclude completed todos
query.type = 'todo'; query.type = 'todo';
} else if (type === 'completedTodos' || type === '_allCompletedTodos') { // _allCompletedTodos is currently in BETA and is likely to be removed in future } else if (type === 'completedTodos' || type === '_allCompletedTodos') { // _allCompletedTodos is currently in BETA and is likely to be removed in future
let limit = 30; limit = 30;
if (type === '_allCompletedTodos') { if (type === '_allCompletedTodos') {
limit = 0; // no limit limit = 0; // no limit
} }
query = Tasks.Task.find({
query = {
userId: user._id, userId: user._id,
type: 'todo', type: 'todo',
completed: true, completed: true,
}).limit(limit).sort({ };
sort = {
dateCompleted: -1, dateCompleted: -1,
}); };
} else { } else {
query.type = type.slice(0, -1); // removing the final "s" query.type = type.slice(0, -1); // removing the final "s"
} }
@@ -144,7 +149,11 @@ export async function getTasks (req, res, options = {}) {
]; ];
} }
let tasks = await Tasks.Task.find(query).exec(); let mQuery = Tasks.Task.find(query);
if (limit) mQuery.limit(limit);
if (sort) mQuery.sort(sort);
let tasks = await mQuery.exec();
// Order tasks based on tasksOrder // Order tasks based on tasksOrder
if (type && type !== 'completedTodos' && type !== '_allCompletedTodos') { if (type && type !== 'completedTodos' && type !== '_allCompletedTodos') {

View File

@@ -461,7 +461,7 @@ schema.methods.startQuest = async function startQuest (user) {
let partyId = this._id; let partyId = this._id;
let questMembers = this.quest.members; let questMembers = this.quest.members;
await Bluebird.map(Object.keys(this.quest.members), async (memberId) => { await Bluebird.map(Object.keys(this.quest.members), async (memberId) => {
let member = await User.findOne({_id: memberId, 'party._id': partyId}).select('_id').lean(); let member = await User.findOne({_id: memberId, 'party._id': partyId}).select('_id').lean().exec();
if (!member) { if (!member) {
delete questMembers[memberId]; delete questMembers[memberId];
@@ -553,7 +553,7 @@ schema.methods.sendGroupChatReceivedWebhooks = function sendGroupChatReceivedWeb
query.guilds = this._id; query.guilds = this._id;
} }
User.find(query).select({webhooks: 1}).lean().then((users) => { User.find(query).select({webhooks: 1}).lean().exec().then((users) => {
users.forEach((user) => { users.forEach((user) => {
let { webhooks } = user; let { webhooks } = user;
groupChatReceivedWebhook.send(webhooks, { groupChatReceivedWebhook.send(webhooks, {
@@ -797,7 +797,7 @@ schema.statics.processQuestProgress = async function processQuestProgress (user,
}); });
}; };
// to set a boss: `db.groups.update({_id:TAVERN_ID},{$set:{quest:{key:'dilatory',active:true,progress:{hp:1000,rage:1500}}}})` // to set a boss: `db.groups.update({_id:TAVERN_ID},{$set:{quest:{key:'dilatory',active:true,progress:{hp:1000,rage:1500}}}}).exec()`
// we export an empty object that is then populated with the query-returned data // we export an empty object that is then populated with the query-returned data
export let tavernQuest = {}; export let tavernQuest = {};
let tavernQ = {_id: TAVERN_ID, 'quest.key': {$ne: null}}; let tavernQ = {_id: TAVERN_ID, 'quest.key': {$ne: null}};
@@ -891,7 +891,7 @@ schema.methods.leave = async function leaveGroup (user, keep = 'keep-all') {
let challenges = await Challenge.find({ let challenges = await Challenge.find({
_id: {$in: user.challenges}, _id: {$in: user.challenges},
group: group._id, group: group._id,
}); }).exec();
let challengesToRemoveUserFrom = challenges.map(chal => { let challengesToRemoveUserFrom = challenges.map(chal => {
return chal.unlinkTasks(user, keep); return chal.unlinkTasks(user, keep);
@@ -903,7 +903,7 @@ schema.methods.leave = async function leaveGroup (user, keep = 'keep-all') {
'group.id': group._id, 'group.id': group._id,
userId: {$exists: false}, userId: {$exists: false},
'group.assignedUsers': user._id, 'group.assignedUsers': user._id,
}); }).exec();
let assignedTasksToRemoveUserFrom = assignedTasks.map(task => { let assignedTasksToRemoveUserFrom = assignedTasks.map(task => {
return this.unlinkTask(task, user, keep); return this.unlinkTask(task, user, keep);
}); });