mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 06:37:23 +01:00
Merge branch 'group-tasks-approval' of https://github.com/TheHollidayInn/habitrpg into TheHollidayInn-group-tasks-approval2
This commit is contained in:
@@ -43,9 +43,9 @@ describe('GET /export/history.csv', () => {
|
|||||||
expect(splitRes[0]).to.equal('Task Name,Task ID,Task Type,Date,Value');
|
expect(splitRes[0]).to.equal('Task Name,Task ID,Task Type,Date,Value');
|
||||||
expect(splitRes[1]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[0].value}`);
|
expect(splitRes[1]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[0].value}`);
|
||||||
expect(splitRes[2]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[1].value}`);
|
expect(splitRes[2]).to.equal(`habit 1,${tasks[0]._id},habit,${moment(tasks[0].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[0].history[1].value}`);
|
||||||
expect(splitRes[3]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[0].value}`);
|
expect(splitRes[3]).to.equal(`daily 1,${tasks[1]._id},daily,${moment(tasks[1].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[0].value}`);
|
||||||
expect(splitRes[4]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[1].value}`);
|
expect(splitRes[4]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[0].value}`);
|
||||||
expect(splitRes[5]).to.equal(`daily 1,${tasks[1]._id},daily,${moment(tasks[1].history[0].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[1].history[0].value}`);
|
expect(splitRes[5]).to.equal(`habit 2,${tasks[2]._id},habit,${moment(tasks[2].history[1].date).format('YYYY-MM-DD HH:mm:ss')},${tasks[2].history[1].value}`);
|
||||||
expect(splitRes[6]).to.equal('');
|
expect(splitRes[6]).to.equal('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
import {
|
||||||
|
createAndPopulateGroup,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
|
import { find } from 'lodash';
|
||||||
|
|
||||||
|
describe('GET /approvals/group/:groupId', () => {
|
||||||
|
let user, guild, member, task, syncedTask;
|
||||||
|
|
||||||
|
function findAssignedTask (memberTask) {
|
||||||
|
return memberTask.group.id === guild._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
let {group, members, groupLeader} = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'Test Guild',
|
||||||
|
type: 'guild',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
guild = group;
|
||||||
|
user = groupLeader;
|
||||||
|
member = members[0];
|
||||||
|
|
||||||
|
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||||
|
text: 'test todo',
|
||||||
|
type: 'todo',
|
||||||
|
requiresApproval: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when user is not the group leader', async () => {
|
||||||
|
await expect(member.get(`/approvals/group/${guild._id}`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('onlyGroupLeaderCanEditTasks'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('gets a list of task that need approval', async () => {
|
||||||
|
let approvals = await user.get(`/approvals/group/${guild._id}`);
|
||||||
|
expect(approvals[0]._id).to.equal(syncedTask._id);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import {
|
||||||
|
createAndPopulateGroup,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
|
import { find } from 'lodash';
|
||||||
|
|
||||||
|
describe('POST /tasks/:id/approve/:userId', () => {
|
||||||
|
let user, guild, member, task;
|
||||||
|
|
||||||
|
function findAssignedTask (memberTask) {
|
||||||
|
return memberTask.group.id === guild._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
let {group, members, groupLeader} = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'Test Guild',
|
||||||
|
type: 'guild',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
guild = group;
|
||||||
|
user = groupLeader;
|
||||||
|
member = members[0];
|
||||||
|
|
||||||
|
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||||
|
text: 'test todo',
|
||||||
|
type: 'todo',
|
||||||
|
requiresApproval: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when user is not assigned', async () => {
|
||||||
|
await expect(user.post(`/tasks/${task._id}/approve/${member._id}`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 404,
|
||||||
|
error: 'NotFound',
|
||||||
|
message: t('taskNotFound'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when user is not the group leader', async () => {
|
||||||
|
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
await expect(member.post(`/tasks/${task._id}/approve/${member._id}`))
|
||||||
|
.to.eventually.be.rejected.and.to.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('onlyGroupLeaderCanEditTasks'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('approves an assigned user', async () => {
|
||||||
|
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
await user.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||||
|
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
await member.sync();
|
||||||
|
|
||||||
|
expect(member.notifications.length).to.equal(1);
|
||||||
|
expect(member.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
|
||||||
|
expect(member.notifications[0].data.message).to.equal(t('yourTaskHasBeenApproved'));
|
||||||
|
|
||||||
|
expect(syncedTask.group.approval.approved).to.be.true;
|
||||||
|
expect(syncedTask.group.approval.approvingUser).to.equal(user._id);
|
||||||
|
expect(syncedTask.group.approval.dateApproved).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import {
|
||||||
|
createAndPopulateGroup,
|
||||||
|
translate as t,
|
||||||
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
|
import { find } from 'lodash';
|
||||||
|
|
||||||
|
describe('POST /tasks/:id/score/:direction', () => {
|
||||||
|
let user, guild, member, task;
|
||||||
|
|
||||||
|
function findAssignedTask (memberTask) {
|
||||||
|
return memberTask.group.id === guild._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
let {group, members, groupLeader} = await createAndPopulateGroup({
|
||||||
|
groupDetails: {
|
||||||
|
name: 'Test Guild',
|
||||||
|
type: 'guild',
|
||||||
|
},
|
||||||
|
members: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
guild = group;
|
||||||
|
user = groupLeader;
|
||||||
|
member = members[0];
|
||||||
|
|
||||||
|
task = await user.post(`/tasks/group/${guild._id}`, {
|
||||||
|
text: 'test todo',
|
||||||
|
type: 'todo',
|
||||||
|
requiresApproval: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await user.post(`/tasks/${task._id}/assign/${member._id}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevents user from scoring a task that needs to be approved', async () => {
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
let response = await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||||
|
let updatedTask = await member.get(`/tasks/${syncedTask._id}`);
|
||||||
|
|
||||||
|
await user.sync();
|
||||||
|
|
||||||
|
expect(user.notifications.length).to.equal(1);
|
||||||
|
expect(user.notifications[0].type).to.equal('GROUP_TASK_APPROVAL');
|
||||||
|
expect(user.notifications[0].data.message).to.equal(t('userHasRequestedTaskApproval', {
|
||||||
|
user: member.auth.local.username,
|
||||||
|
taskName: updatedTask.text,
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(response.message).to.equal(t('taskApprovalHasBeenRequested'));
|
||||||
|
expect(updatedTask.group.approval.requested).to.equal(true);
|
||||||
|
expect(updatedTask.group.approval.requestedDate).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
|
||||||
|
});
|
||||||
|
|
||||||
|
it('errors when approval has already been requested', async () => {
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||||
|
|
||||||
|
await expect(member.post(`/tasks/${syncedTask._id}/score/up`))
|
||||||
|
.to.eventually.be.rejected.and.eql({
|
||||||
|
code: 401,
|
||||||
|
error: 'NotAuthorized',
|
||||||
|
message: t('taskRequiresApproval'),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows a user to score an apporoved task', async () => {
|
||||||
|
let memberTasks = await member.get('/tasks/user');
|
||||||
|
let syncedTask = find(memberTasks, findAssignedTask);
|
||||||
|
|
||||||
|
await user.post(`/tasks/${task._id}/approve/${member._id}`);
|
||||||
|
|
||||||
|
await member.post(`/tasks/${syncedTask._id}/score/up`);
|
||||||
|
let updatedTask = await member.get(`/tasks/${syncedTask._id}`);
|
||||||
|
|
||||||
|
expect(updatedTask.completed).to.equal(true);
|
||||||
|
expect(updatedTask.dateCompleted).to.be.a('string'); // date gets converted to a string as json doesn't have a Date type
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -216,5 +216,7 @@
|
|||||||
"assignTask": "Assign Task",
|
"assignTask": "Assign Task",
|
||||||
"desktopNotificationsText": "We need your permission to enable desktop notifications for new messages in party chat! Follow your browser's instructions to turn them on.<br><br>You'll receive these notifications only while you have Habitica open. If you decide you don't like them, they can be disabled in your browser's settings.<br><br>This box will close automatically when a decision is made.",
|
"desktopNotificationsText": "We need your permission to enable desktop notifications for new messages in party chat! Follow your browser's instructions to turn them on.<br><br>You'll receive these notifications only while you have Habitica open. If you decide you don't like them, they can be disabled in your browser's settings.<br><br>This box will close automatically when a decision is made.",
|
||||||
"claim": "Claim",
|
"claim": "Claim",
|
||||||
"onlyGroupLeaderCanManageSubscription": "Only the group leader can manage the group's subscription"
|
"onlyGroupLeaderCanManageSubscription": "Only the group leader can manage the group's subscription",
|
||||||
|
"yourTaskHasBeenApproved": "Your task has been approved",
|
||||||
|
"userHasRequestedTaskApproval": "<%= user %> has requested task approval for <%= taskName %>"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,5 +134,7 @@
|
|||||||
"strengthExample": "Relating to exercise and activity",
|
"strengthExample": "Relating to exercise and activity",
|
||||||
"intelligenceExample": "Relating to academic or mentally challenging pursuits",
|
"intelligenceExample": "Relating to academic or mentally challenging pursuits",
|
||||||
"perceptionExample": "Relating to work or financial tasks",
|
"perceptionExample": "Relating to work or financial tasks",
|
||||||
"constitutionExample": "Relating to health, wellness, and social interaction"
|
"constitutionExample": "Relating to health, wellness, and social interaction",
|
||||||
|
"taskRequiresApproval": "This task must be approved before you can complete it. Approval has already been requested",
|
||||||
|
"taskApprovalHasBeenRequested": "Approval has been requested"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { removeFromArray } from '../../libs/collectionManipulators';
|
|||||||
import * as Tasks from '../../models/task';
|
import * as Tasks from '../../models/task';
|
||||||
import { model as Challenge } from '../../models/challenge';
|
import { model as Challenge } from '../../models/challenge';
|
||||||
import { model as Group } from '../../models/group';
|
import { model as Group } from '../../models/group';
|
||||||
|
import { model as User } from '../../models/user';
|
||||||
import {
|
import {
|
||||||
NotFound,
|
NotFound,
|
||||||
NotAuthorized,
|
NotAuthorized,
|
||||||
@@ -315,6 +316,28 @@ api.scoreTask = {
|
|||||||
|
|
||||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||||
|
|
||||||
|
if (task.group.approval.required && !task.group.approval.approved) {
|
||||||
|
if (task.group.approval.requested) {
|
||||||
|
throw new NotAuthorized(res.t('taskRequiresApproval'));
|
||||||
|
}
|
||||||
|
|
||||||
|
task.group.approval.requested = true;
|
||||||
|
task.group.approval.requestedDate = new Date();
|
||||||
|
|
||||||
|
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
|
||||||
|
groupLeader.addNotification('GROUP_TASK_APPROVAL', {
|
||||||
|
message: res.t('userHasRequestedTaskApproval', {
|
||||||
|
user: user.profile.name,
|
||||||
|
taskName: task.text,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
await Bluebird.all([groupLeader.save(), task.save()]);
|
||||||
|
|
||||||
|
return res.respond(200, {message: res.t('taskApprovalHasBeenRequested'), task});
|
||||||
|
}
|
||||||
|
|
||||||
let wasCompleted = task.completed;
|
let wasCompleted = task.completed;
|
||||||
|
|
||||||
let [delta] = common.ops.scoreTask({task, user, direction}, req);
|
let [delta] = common.ops.scoreTask({task, user, direction}, req);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { authWithHeaders } from '../../../middlewares/auth';
|
import { authWithHeaders } from '../../../middlewares/auth';
|
||||||
import ensureDevelpmentMode from '../../../middlewares/ensureDevelpmentMode';
|
import ensureDevelpmentMode from '../../../middlewares/ensureDevelpmentMode';
|
||||||
|
import Bluebird from 'bluebird';
|
||||||
import * as Tasks from '../../../models/task';
|
import * as Tasks from '../../../models/task';
|
||||||
import { model as Group } from '../../../models/group';
|
import { model as Group } from '../../../models/group';
|
||||||
import { model as User } from '../../../models/user';
|
import { model as User } from '../../../models/user';
|
||||||
@@ -178,4 +179,97 @@ api.unassignTask = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /api/v3/tasks/:taskId/approve/:userId Approve a user's task
|
||||||
|
* @apiDescription Approves a user assigned to a group task
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName ApproveTask
|
||||||
|
* @apiGroup Task
|
||||||
|
*
|
||||||
|
* @apiParam {UUID} taskId The id of the task that is the original group task
|
||||||
|
* @apiParam {UUID} userId The id of the user that will be approved
|
||||||
|
*
|
||||||
|
* @apiSuccess task The approved task
|
||||||
|
*/
|
||||||
|
api.approveTask = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/tasks/:taskId/approve/:userId',
|
||||||
|
middlewares: [ensureDevelpmentMode, authWithHeaders()],
|
||||||
|
async handler (req, res) {
|
||||||
|
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||||
|
req.checkParams('userId', res.t('userIdRequired')).notEmpty().isUUID();
|
||||||
|
|
||||||
|
let reqValidationErrors = req.validationErrors();
|
||||||
|
if (reqValidationErrors) throw reqValidationErrors;
|
||||||
|
|
||||||
|
let user = res.locals.user;
|
||||||
|
let assignedUserId = req.params.userId;
|
||||||
|
let assignedUser = await User.findById(assignedUserId);
|
||||||
|
|
||||||
|
let taskId = req.params.taskId;
|
||||||
|
let task = await Tasks.Task.findOne({
|
||||||
|
'group.taskId': taskId,
|
||||||
|
userId: assignedUserId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
throw new NotFound(res.t('taskNotFound'));
|
||||||
|
}
|
||||||
|
|
||||||
|
let group = await Group.getGroup({user, groupId: task.group.id, fields: requiredGroupFields});
|
||||||
|
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||||
|
|
||||||
|
if (group.leader !== user._id) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||||
|
|
||||||
|
task.group.approval.dateApproved = new Date();
|
||||||
|
task.group.approval.approvingUser = user._id;
|
||||||
|
task.group.approval.approved = true;
|
||||||
|
|
||||||
|
assignedUser.addNotification('GROUP_TASK_APPROVAL', {message: res.t('yourTaskHasBeenApproved')});
|
||||||
|
|
||||||
|
await Bluebird.all([assignedUser.save(), task.save()]);
|
||||||
|
|
||||||
|
res.respond(200, task);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /api/v3/approvals/group/:groupId Get a group's approvals
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName GetGroupApprovals
|
||||||
|
* @apiGroup Task
|
||||||
|
* @apiIgnore
|
||||||
|
*
|
||||||
|
* @apiParam {UUID} groupId The id of the group from which to retrieve the approvals
|
||||||
|
*
|
||||||
|
* @apiSuccess {Array} data An array of tasks
|
||||||
|
*/
|
||||||
|
api.getGroupApprovals = {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/approvals/group/:groupId',
|
||||||
|
middlewares: [ensureDevelpmentMode, authWithHeaders()],
|
||||||
|
async handler (req, res) {
|
||||||
|
req.checkParams('groupId', res.t('groupIdRequired')).notEmpty().isUUID();
|
||||||
|
|
||||||
|
let validationErrors = req.validationErrors();
|
||||||
|
if (validationErrors) throw validationErrors;
|
||||||
|
|
||||||
|
let user = res.locals.user;
|
||||||
|
let groupId = req.params.groupId;
|
||||||
|
|
||||||
|
let group = await Group.getGroup({user, groupId, fields: requiredGroupFields});
|
||||||
|
if (!group) throw new NotFound(res.t('groupNotFound'));
|
||||||
|
|
||||||
|
if (group.leader !== user._id) throw new NotAuthorized(res.t('onlyGroupLeaderCanEditTasks'));
|
||||||
|
|
||||||
|
let approvals = await Tasks.Task.find({
|
||||||
|
'group.id': groupId,
|
||||||
|
'group.approval.approved': false,
|
||||||
|
'group.approval.requested': true,
|
||||||
|
}, 'userId group').exec();
|
||||||
|
|
||||||
|
res.respond(200, approvals);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = api;
|
module.exports = api;
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ async function _validateTaskAlias (tasks, res) {
|
|||||||
* @param options.user The user that these tasks belong to
|
* @param options.user The user that these tasks belong to
|
||||||
* @param options.challenge The challenge that these tasks belong to
|
* @param options.challenge The challenge that these tasks belong to
|
||||||
* @param options.group The group that these tasks belong to
|
* @param options.group The group that these tasks belong to
|
||||||
|
* @param options.requiresApproval A boolean stating if the task will require approval
|
||||||
* @return The created tasks
|
* @return The created tasks
|
||||||
*/
|
*/
|
||||||
export async function createTasks (req, res, options = {}) {
|
export async function createTasks (req, res, options = {}) {
|
||||||
@@ -55,6 +56,9 @@ export async function createTasks (req, res, options = {}) {
|
|||||||
newTask.challenge.id = challenge.id;
|
newTask.challenge.id = challenge.id;
|
||||||
} else if (group) {
|
} else if (group) {
|
||||||
newTask.group.id = group._id;
|
newTask.group.id = group._id;
|
||||||
|
if (taskData.requiresApproval) {
|
||||||
|
newTask.group.approval.required = true;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newTask.userId = user._id;
|
newTask.userId = user._id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -947,6 +947,8 @@ schema.methods.syncTask = async function groupSyncTask (taskToSync, user) {
|
|||||||
if (orderList.indexOf(matchingTask._id) === -1 && (matchingTask.type !== 'todo' || !matchingTask.completed)) orderList.push(matchingTask._id);
|
if (orderList.indexOf(matchingTask._id) === -1 && (matchingTask.type !== 'todo' || !matchingTask.completed)) orderList.push(matchingTask._id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
matchingTask.group.approval.required = taskToSync.group.approval.required;
|
||||||
|
|
||||||
if (!matchingTask.notes) matchingTask.notes = taskToSync.notes; // don't override the notes, but provide it if not provided
|
if (!matchingTask.notes) matchingTask.notes = taskToSync.notes; // don't override the notes, but provide it if not provided
|
||||||
if (matchingTask.tags.indexOf(group._id) === -1) matchingTask.tags.push(group._id); // add tag if missing
|
if (matchingTask.tags.indexOf(group._id) === -1) matchingTask.tags.push(group._id); // add tag if missing
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,14 @@ export let TaskSchema = new Schema({
|
|||||||
broken: {type: String, enum: ['GROUP_DELETED', 'TASK_DELETED', 'UNSUBSCRIBED']},
|
broken: {type: String, enum: ['GROUP_DELETED', 'TASK_DELETED', 'UNSUBSCRIBED']},
|
||||||
assignedUsers: [{type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.']}],
|
assignedUsers: [{type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.']}],
|
||||||
taskId: {type: String, ref: 'Task', validate: [validator.isUUID, 'Invalid uuid.']},
|
taskId: {type: String, ref: 'Task', validate: [validator.isUUID, 'Invalid uuid.']},
|
||||||
|
approval: {
|
||||||
|
required: {type: Boolean, default: false},
|
||||||
|
approved: {type: Boolean, default: false},
|
||||||
|
dateApproved: {type: Date},
|
||||||
|
approvingUser: {type: String, ref: 'User', validate: [validator.isUUID, 'Invalid uuid.']},
|
||||||
|
requested: {type: Boolean, default: false},
|
||||||
|
requestedDate: {type: Date},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
reminders: [{
|
reminders: [{
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const NOTIFICATION_TYPES = [
|
|||||||
'REBIRTH_ACHIEVEMENT',
|
'REBIRTH_ACHIEVEMENT',
|
||||||
'NEW_CONTRIBUTOR_LEVEL',
|
'NEW_CONTRIBUTOR_LEVEL',
|
||||||
'CRON',
|
'CRON',
|
||||||
|
'GROUP_TASK_APPROVAL',
|
||||||
];
|
];
|
||||||
|
|
||||||
const Schema = mongoose.Schema;
|
const Schema = mongoose.Schema;
|
||||||
|
|||||||
Reference in New Issue
Block a user