diff --git a/website/client/src/components/tasks/task.vue b/website/client/src/components/tasks/task.vue index 76565c720d..b28016e545 100644 --- a/website/client/src/components/tasks/task.vue +++ b/website/client/src/components/tasks/task.vue @@ -1078,7 +1078,9 @@ export default { } if (this.task.group.completedBy.userId === this.user._id) return false; if (this.teamManagerAccess) { - if (!this.task.group.assignedUsers) return false; + if (!this.task.group.assignedUsers || this.task.group.assignedUsers.length === 0) { + return false; + } if (this.task.group.assignedUsers.length === 1) return false; } return true; @@ -1162,11 +1164,12 @@ export default { if ( this.isGroupTask && direction === 'down' && ['todo', 'daily'].indexOf(this.task.type) !== -1 - && !this.task.group.assignedUsersDetail[this.user._id] + && (!this.task.group.assignedUsersDetail + || !this.task.group.assignedUsersDetail[this.user._id]) ) { this.$store.dispatch('tasks:needsWork', { taskId: this.task._id, - userId: this.task.group.assignedUsers[0], + userId: this.task.group.assignedUsers[0] || this.task.group.completedBy.userId, }); this.task.completed = false; return; diff --git a/website/common/script/ops/scoreTask.js b/website/common/script/ops/scoreTask.js index 8d08ebf482..e85b4509e0 100644 --- a/website/common/script/ops/scoreTask.js +++ b/website/common/script/ops/scoreTask.js @@ -294,7 +294,7 @@ export default function scoreTask (options = {}, req = {}, analytics) { if (direction === 'up') { if (task.group.id) { - if (!task.group.assignedUsers) { + if (!task.group.assignedUsers || task.group.assignedUsers.length === 0) { task.group.completedBy = { userId: user._id, date: new Date(), @@ -365,7 +365,7 @@ export default function scoreTask (options = {}, req = {}, analytics) { } else { if (direction === 'up') { if (task.group.id) { - if (!task.group.assignedUsers) { + if (!task.group.assignedUsers || task.group.assignedUsers.length === 0) { task.group.completedBy = { userId: user._id, date: new Date(), diff --git a/website/server/controllers/api-v3/tasks.js b/website/server/controllers/api-v3/tasks.js index b3facd60a2..c919937d85 100644 --- a/website/server/controllers/api-v3/tasks.js +++ b/website/server/controllers/api-v3/tasks.js @@ -385,7 +385,7 @@ api.getUserTasks = { url: '/tasks/user', middlewares: [authWithHeaders({ // Some fields (including _id, preferences) are always loaded (see middlewares/auth) - userFieldsToInclude: ['tasksOrder'], + userFieldsToInclude: ['guilds', 'party', 'tasksOrder'], })], async handler (req, res) { const types = Tasks.tasksTypes.map(type => `${type}s`); diff --git a/website/server/controllers/api-v3/tasks/groups.js b/website/server/controllers/api-v3/tasks/groups.js index 49f998cd1a..1ded11ac1a 100644 --- a/website/server/controllers/api-v3/tasks/groups.js +++ b/website/server/controllers/api-v3/tasks/groups.js @@ -355,10 +355,10 @@ api.taskNeedsWork = { if (['daily', 'todo'].indexOf(task.type) === -1) { throw new BadRequest('Cannot roll back use of Habits or Rewards.'); } - if (!task.group.assignedUsersDetail || !task.group.assignedUsersDetail[assignedUserId]) { - throw new BadRequest('User not assigned to this task.'); - } - if (!task.group.assignedUsersDetail[assignedUserId].completed) { + if ( + (task.group.completedBy.userId && task.group.completedBy.userId !== assignedUserId) + || (task.group.assignedUsersDetail && !(task.group.assignedUsersDetail[assignedUserId] + && task.group.assignedUsersDetail[assignedUserId].completed))) { throw new BadRequest('Task not completed by this user.'); } diff --git a/website/server/libs/tasks/index.js b/website/server/libs/tasks/index.js index 3c1d652dbc..26df0831e5 100644 --- a/website/server/libs/tasks/index.js +++ b/website/server/libs/tasks/index.js @@ -1,5 +1,7 @@ import moment from 'moment'; -import _ from 'lodash'; +import cloneDeep from 'lodash/cloneDeep'; +import compact from 'lodash/compact'; +import keys from 'lodash/keys'; import validator from 'validator'; import { setNextDue, @@ -150,23 +152,52 @@ async function getTasks (req, res, options = {}) { dueDate, } = options; - let query = { $or: [{ userId: user._id }, { 'group.assignedUsers': user._id }] }; + let query; let limit; let sort; + let upgradedGroups; const owner = group || challenge || user; if (challenge) { query = { 'challenge.id': challenge.id, userId: { $exists: false } }; } else if (group) { - query = { 'group.id': group._id, userId: { $exists: false } }; + query = { 'group.id': group._id }; + } else { + upgradedGroups = await Group.find( + { + _id: { $in: user.guilds.concat(user.party._id) }, + 'purchased.plan.customerId': { $exists: true }, + $or: [ + { 'purchased.plan.dateTerminated': { $exists: false } }, + { 'purchased.plan.dateTerminated': null }, + { 'purchased.plan.dateTerminated': { $gt: new Date() } }, + ], + }, + { _id: 1 }, + ).exec(); + if (upgradedGroups.length > 0) { + const upgradedGroupIds = []; + for (const upgradedGroup of upgradedGroups) { + upgradedGroupIds.push(upgradedGroup._id); + } + query = { + $or: [ + { userId: user._id }, + { 'group.assignedUsers': user._id }, + { 'group.id': { $in: upgradedGroupIds }, 'group.assignedUsers.0': { $exists: false } }, + ], + }; + } else { + query = { userId: user._id }; + } } const { type } = req.query; if (type) { if (type === 'todos') { - query.completed = false; // Exclude completed todos query.type = 'todo'; + query.completed = false; // Exclude completed todos } else if (type === 'completedTodos' || type === '_allCompletedTodos') { // _allCompletedTodos is currently in BETA and is likely to be removed in future limit = 30; @@ -177,7 +208,13 @@ async function getTasks (req, res, options = {}) { query.type = 'todo'; query.completed = true; - if (owner._id === user._id) { + if (upgradedGroups && upgradedGroups.length > 0) { + query.$or = [ + { userId: user._id }, + { 'group.assignedUsers': user._id }, + { 'group.completedBy.userId': user._id }, + ]; + } else if (owner._id === user._id) { query.userId = user._id; } @@ -234,7 +271,7 @@ async function getTasks (req, res, options = {}) { }); // Remove empty values from the array and add any unordered task - orderedTasks = _.compact(orderedTasks).concat(unorderedTasks); + orderedTasks = compact(orderedTasks).concat(unorderedTasks); return orderedTasks; } @@ -307,7 +344,7 @@ async function handleTeamTask (task, delta, direction) { if (teamTask) { const groupDelta = teamTask.group.assignedUsers - ? delta / _.keys(teamTask.group.assignedUsers).length + ? delta / keys(teamTask.group.assignedUsers).length : delta; await teamTask.scoreChallengeTask(groupDelta, direction); if (task.type === 'daily' || task.type === 'todo') { @@ -330,10 +367,12 @@ async function handleTeamTask (task, delta, direction) { */ async function scoreTask (user, task, direction, req, res) { if (task.type === 'daily' || task.type === 'todo') { - if (task.group.id && task.group.assignedUsers && task.group.assignedUsers[user._id]) { - if (task.group.assignedUsers[user._id].completed && direction === 'up') { + if (task.group.id && task.group.assignedUsersDetail + && task.group.assignedUsersDetail[user._id] + ) { + if (task.group.assignedUsersDetail[user._id].completed && direction === 'up') { throw new NotAuthorized(res.t('sessionOutdated')); - } else if (!task.group.assignedUsers[user._id].completed && direction === 'down') { + } else if (!task.group.assignedUsersDetail[user._id].completed && direction === 'down') { throw new NotAuthorized(res.t('sessionOutdated')); } } else if (task.completed && direction === 'up') { @@ -361,12 +400,12 @@ async function scoreTask (user, task, direction, req, res) { const userIsManagement = group.leader === user._id || Boolean(group.managers[user._id]); if (!userIsManagement && !(task.group.completedBy && task.group.completedBy.userId === user._id) - && !(task.group.assignedUsers && task.group.assignedUsers[user._id]) + && !(task.group.assignedUsersDetail && task.group.assignedUsersDetail[user._id]) ) { throw new BadRequest('Cannot uncheck task you did not complete if not a manager.'); } - if (task.group.assignedUsers && _.keys(task.group.assignedUsers).length === 1) { - const rollbackUserId = _.keys(task.group.assignedUsers)[0]; + if (task.group.assignedUsers && keys(task.group.assignedUsers).length === 1) { + const rollbackUserId = keys(task.group.assignedUsers)[0]; rollbackUser = await User.findOne({ _id: rollbackUserId }); } else { rollbackUser = await User.findOne({ _id: task.group.completedBy.userId }); @@ -471,7 +510,7 @@ async function scoreTask (user, task, direction, req, res) { pushTask, // clone user._tmp so that it's not overwritten by other score operations // when using the bulk scoring API - _tmp: _.cloneDeep(user._tmp), + _tmp: cloneDeep(user._tmp), }; }