From eaa5f821a4b1fc8171088a6fe179d597490e48ee Mon Sep 17 00:00:00 2001 From: SabreCat Date: Fri, 21 Jan 2022 16:26:30 -0600 Subject: [PATCH] WIP(multi-assign): functioning multi --- .../client/src/components/tasks/taskModal.vue | 9 +- website/client/src/store/actions/tasks.js | 2 +- .../server/controllers/api-v3/tasks/groups.js | 31 ++-- website/server/models/group.js | 144 +++++++++--------- website/server/models/task.js | 5 +- 5 files changed, 100 insertions(+), 91 deletions(-) diff --git a/website/client/src/components/tasks/taskModal.vue b/website/client/src/components/tasks/taskModal.vue index b01b04566b..218c4e3d53 100644 --- a/website/client/src/components/tasks/taskModal.vue +++ b/website/client/src/components/tasks/taskModal.vue @@ -1469,11 +1469,10 @@ export default { tasks: [this.task], }); Object.assign(this.task, response); - const promises = this.assignedMembers.map(memberId => this.$store.dispatch('tasks:assignTask', { + await this.$store.dispatch('tasks:assignTask', { taskId: this.task._id, - userId: memberId, - })); - Promise.all(promises); + assignedUserIds: this.assignedMembers, + }); this.assignedMembers.forEach(memberId => { if (!this.task.assignedUsers) this.task.assignedUsers = {}; this.task.assignedUsers[memberId] = { @@ -1520,7 +1519,7 @@ export default { } else { await this.$store.dispatch('tasks:assignTask', { taskId: this.task._id, - userId: memberId, + assignedUserIds: [memberId], }); } }, diff --git a/website/client/src/store/actions/tasks.js b/website/client/src/store/actions/tasks.js index 863f137578..e6fe10e488 100644 --- a/website/client/src/store/actions/tasks.js +++ b/website/client/src/store/actions/tasks.js @@ -204,7 +204,7 @@ export async function createGroupTasks (store, payload) { } export async function assignTask (store, payload) { - const response = await axios.post(`/api/v4/tasks/${payload.taskId}/assign/${payload.userId}`); + const response = await axios.post(`/api/v4/tasks/${payload.taskId}/assign`, payload.assignedUserIds); return response.data.data; } diff --git a/website/server/controllers/api-v3/tasks/groups.js b/website/server/controllers/api-v3/tasks/groups.js index 30bf179649..fd7755b914 100644 --- a/website/server/controllers/api-v3/tasks/groups.js +++ b/website/server/controllers/api-v3/tasks/groups.js @@ -1,3 +1,4 @@ +import isUUID from 'validator/lib/isUUID'; import { authWithHeaders } from '../../../middlewares/auth'; import * as Tasks from '../../../models/task'; import { model as Group } from '../../../models/group'; @@ -169,30 +170,31 @@ api.groupMoveTask = { }; /** - * @api {post} /api/v3/tasks/:taskId/assign/:assignedUserId Assign a group task to a user - * @apiDescription Assigns a user to a group task + * @api {post} /api/v3/tasks/:taskId/assign Assign a group task to a user or users + * @apiDescription Assign users to a group task * @apiName AssignTask * @apiGroup Task * * @apiParam (Path) {UUID} taskId The id of the task that will be assigned - * @apiParam (Path) {UUID} assignedUserId The id of the user that will be assigned to the task + * @apiParam (Body) {UUID[]} [assignedUserIds] Array of user IDs to be assigned to the task * * @apiSuccess data The assigned task */ api.assignTask = { method: 'POST', - url: '/tasks/:taskId/assign/:assignedUserId', + url: '/tasks/:taskId/assign', middlewares: [authWithHeaders()], async handler (req, res) { req.checkParams('taskId', apiError('taskIdRequired')).notEmpty().isUUID(); - req.checkParams('assignedUserId', res.t('userIdRequired')).notEmpty().isUUID(); const reqValidationErrors = req.validationErrors(); if (reqValidationErrors) throw reqValidationErrors; const { user } = res.locals; - const { assignedUserId } = req.params; - const assignedUser = await User.findById(assignedUserId).exec(); + const assignedUserIds = req.body; + for (const userId of assignedUserIds) { + if (!isUUID(userId)) throw new BadRequest('Assigned users must be UUIDs'); + } const { taskId } = req.params; const task = await Tasks.Task.findByIdOrAlias(taskId, user._id); @@ -211,18 +213,21 @@ api.assignTask = { if (canNotEditTasks(group, user)) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks')); + const assignedUsers = await User.find({ _id: { $in: assignedUserIds } }).exec(); const promises = []; const taskText = task.text; const userName = `@${user.auth.local.username}`; - if (user._id !== assignedUserId) { - assignedUser.addNotification('GROUP_TASK_ASSIGNED', { - message: res.t('youHaveBeenAssignedTask', { managerName: userName, taskText }), - taskId: task._id, - }); + for (const userToAssign of assignedUsers) { + if (user._id !== userToAssign._id) { + userToAssign.addNotification('GROUP_TASK_ASSIGNED', { + message: res.t('youHaveBeenAssignedTask', { managerName: userName, taskText }), + taskId: task._id, + }); + } } - promises.push(group.syncTask(task, assignedUser, user)); + promises.push(group.syncTask(task, assignedUsers, user)); promises.push(group.save()); await Promise.all(promises); diff --git a/website/server/models/group.js b/website/server/models/group.js index 812450c4b6..88adac0bae 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -1499,82 +1499,84 @@ schema.methods.updateTask = async function updateTask (taskToSync, options = {}) await taskSchema.update(updateQuery, updateCmd, { multi: true }).exec(); }; -schema.methods.syncTask = async function groupSyncTask (taskToSync, user, assigningUser) { +schema.methods.syncTask = async function groupSyncTask (taskToSync, users, assigningUser) { const group = this; const toSave = []; - const assignmentData = { - assignedDate: new Date(), - assigningUsername: assigningUser && assigningUser._id !== user._id - ? assigningUser.auth.local.username : null, - completed: false, - }; + for (const user of users) { + const assignmentData = { + assignedDate: new Date(), + assigningUsername: assigningUser && assigningUser._id !== user._id + ? assigningUser.auth.local.username : null, + completed: false, + }; - if (!taskToSync.group.assignedUsers) { - taskToSync.group.assignedUsers = {}; - } - if (!taskToSync.group.assignedUsers[user._id]) { - taskToSync.group.assignedUsers[user._id] = assignmentData; - } - - // Sync tags - const userTags = user.tags; - const i = _.findIndex(userTags, { id: group._id }); - - if (i !== -1) { - if (userTags[i].name !== group.name) { - // update the name - it's been changed since - userTags[i].name = group.name; - userTags[i].group = group._id; + if (!taskToSync.group.assignedUsers) { + taskToSync.group.assignedUsers = {}; } - } else { - userTags.push({ - id: group._id, - name: group.name, - group: group._id, - }); + if (!taskToSync.group.assignedUsers[user._id]) { + taskToSync.group.assignedUsers[user._id] = assignmentData; + } + + // Sync tags + const userTags = user.tags; + const i = _.findIndex(userTags, { id: group._id }); + + if (i !== -1) { + if (userTags[i].name !== group.name) { + // update the name - it's been changed since + userTags[i].name = group.name; + userTags[i].group = group._id; + } + } else { + userTags.push({ + id: group._id, + name: group.name, + group: group._id, + }); + } + + const findQuery = { + 'group.taskId': taskToSync._id, + userId: user._id, + 'group.id': group._id, + }; + + let matchingTask = await Tasks.Task.findOne(findQuery).exec(); // eslint-disable-line + + if (!matchingTask) { // If the task is new, create it + matchingTask = new Tasks[taskToSync.type](Tasks.Task.sanitize(syncableAttrs(taskToSync))); + matchingTask.group.id = taskToSync.group.id; + matchingTask.userId = user._id; + matchingTask.group.taskId = taskToSync._id; + user.tasksOrder[`${taskToSync.type}s`].unshift(matchingTask._id); + } else { + _.merge(matchingTask, syncableAttrs(taskToSync)); + // Make sure the task is in user.tasksOrder + const orderList = user.tasksOrder[`${taskToSync.type}s`]; + if (orderList.indexOf(matchingTask._id) === -1 && (matchingTask.type !== 'todo' || !matchingTask.completed)) orderList.push(matchingTask._id); + } + matchingTask.group.assignedUsers = taskToSync.group.assignedUsers; + matchingTask.group.sharedCompletion = taskToSync.group.sharedCompletion; + matchingTask.group.managerNotes = taskToSync.group.managerNotes; + + // sync checklist + if (taskToSync.checklist) { + taskToSync.checklist.forEach(element => { + const newCheckList = { completed: false }; + newCheckList.linkId = element.id; + newCheckList.text = element.text; + matchingTask.checklist.push(newCheckList); + }); + } + + // don't override the notes, but provide it if not provided + if (!matchingTask.notes) matchingTask.notes = taskToSync.notes; + // add tag if missing + if (matchingTask.tags.indexOf(group._id) === -1) matchingTask.tags.push(group._id); + + toSave.push(matchingTask.save(), user.save()); } - - const findQuery = { - 'group.taskId': taskToSync._id, - userId: user._id, - 'group.id': group._id, - }; - - let matchingTask = await Tasks.Task.findOne(findQuery).exec(); - - if (!matchingTask) { // If the task is new, create it - matchingTask = new Tasks[taskToSync.type](Tasks.Task.sanitize(syncableAttrs(taskToSync))); - matchingTask.group.id = taskToSync.group.id; - matchingTask.userId = user._id; - matchingTask.group.taskId = taskToSync._id; - user.tasksOrder[`${taskToSync.type}s`].unshift(matchingTask._id); - } else { - _.merge(matchingTask, syncableAttrs(taskToSync)); - // Make sure the task is in user.tasksOrder - const orderList = user.tasksOrder[`${taskToSync.type}s`]; - if (orderList.indexOf(matchingTask._id) === -1 && (matchingTask.type !== 'todo' || !matchingTask.completed)) orderList.push(matchingTask._id); - } - - matchingTask.group.assignedUsers = taskToSync.group.assignedUsers; - matchingTask.group.sharedCompletion = taskToSync.group.sharedCompletion; - matchingTask.group.managerNotes = taskToSync.group.managerNotes; - - // sync checklist - if (taskToSync.checklist) { - taskToSync.checklist.forEach(element => { - const newCheckList = { completed: false }; - newCheckList.linkId = element.id; - newCheckList.text = element.text; - matchingTask.checklist.push(newCheckList); - }); - } - - // don't override the notes, but provide it if not provided - if (!matchingTask.notes) matchingTask.notes = taskToSync.notes; - // add tag if missing - if (matchingTask.tags.indexOf(group._id) === -1) matchingTask.tags.push(group._id); - - toSave.push(matchingTask.save(), taskToSync.save(), user.save()); + toSave.push(taskToSync.save()); return Promise.all(toSave); }; diff --git a/website/server/models/task.js b/website/server/models/task.js index b85025b6d3..42b376e0e3 100644 --- a/website/server/models/task.js +++ b/website/server/models/task.js @@ -140,7 +140,10 @@ export const TaskSchema = new Schema({ $type: String, default: 'singleCompletion', // legacy data }, managerNotes: { $type: String }, - completedBy: { $type: String, ref: 'User', validate: [v => validator.isUUID(v), 'Invalid uuid for group completing user.'] }, + completedBy: { + $type: Schema.Types.Mixed, + default: () => ({}), // { 'UUID': Date } + }, }, reminders: [reminderSchema],