Merge branch 'api-v3-groups' into api-v3-members
This commit is contained in:
@@ -117,12 +117,13 @@ api.getGroups = {
|
||||
// If no valid value for type was supplied, return an error
|
||||
if (queries.length === 0) throw new BadRequest(res.t('groupTypesRequired'));
|
||||
|
||||
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
|
||||
// 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
|
||||
let results = _.reduce(await Q.all(queries), (previousValue, currentValue) => {
|
||||
if (_.isEmpty(currentValue)) return previousValue; // don't add anything to the results if the query returned null or an empty array
|
||||
return previousValue.concat(Array.isArray(currentValue) ? currentValue : [currentValue]); // otherwise concat the new results to the previousValue
|
||||
}, []);
|
||||
|
||||
res.respond(200, _.reduce(results, (m, v) => {
|
||||
if (_.isEmpty(v)) return m;
|
||||
return m.concat(Array.isArray(v) ? v : [v]);
|
||||
}, []));
|
||||
res.respond(200, results);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import { authWithHeaders } from '../../middlewares/api-v3/auth';
|
||||
import cron from '../../middlewares/api-v3/cron';
|
||||
import { sendTaskWebhook } from '../../libs/api-v3/webhook';
|
||||
import * as Tasks from '../../models/task';
|
||||
import { model as Challenge } from '../../models/challenge';
|
||||
import {
|
||||
NotFound,
|
||||
NotAuthorized,
|
||||
@@ -10,70 +11,213 @@ import {
|
||||
import shared from '../../../../common';
|
||||
import Q from 'q';
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
import scoreTask from '../../../../common/script/api-v3/scoreTask';
|
||||
import { preenHistory } from '../../../../common/script/api-v3/preenHistory';
|
||||
import { preenHistory } from '../../../../common/script/api-v3/preening';
|
||||
|
||||
let api = {};
|
||||
|
||||
// challenge must be passed only when a challenge task is being created
|
||||
async function _createTasks (req, res, user, challenge) {
|
||||
let toSave = Array.isArray(req.body) ? req.body : [req.body];
|
||||
|
||||
toSave = toSave.map(taskData => {
|
||||
// Validate that task.type is valid
|
||||
if (!taskData || Tasks.tasksTypes.indexOf(taskData.type) === -1) throw new BadRequest(res.t('invalidTaskType'));
|
||||
|
||||
let taskType = taskData.type;
|
||||
let newTask = new Tasks[taskType](Tasks.Task.sanitizeCreate(taskData));
|
||||
|
||||
if (challenge) {
|
||||
newTask.challenge.id = challenge.id;
|
||||
} else {
|
||||
newTask.userId = user._id;
|
||||
}
|
||||
|
||||
// Validate that the task is valid and throw if it isn't
|
||||
// otherwise since we're saving user/challenge and task in parallel it could save the user/challenge with a tasksOrder that doens't match reality
|
||||
let validationErrors = newTask.validateSync();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
// Otherwise update the user/challenge
|
||||
(challenge || user).tasksOrder[`${taskType}s`].unshift(newTask._id);
|
||||
|
||||
return newTask;
|
||||
}).map(task => task.save({ // If all tasks are valid (this is why it's not in the previous .map()), save everything, withough running validation again
|
||||
validateBeforeSave: false,
|
||||
}));
|
||||
|
||||
toSave.unshift((challenge || user).save());
|
||||
|
||||
let tasks = await Q.all(toSave);
|
||||
tasks.splice(0, 1); // Remove user or challenge
|
||||
return tasks;
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /tasks Create a new task. Can be passed an object to create a single task or an array of objects to create multiple tasks.
|
||||
* @api {post} /tasks/user Create a new task belonging to the autheticated user. Can be passed an object to create a single task or an array of objects to create multiple tasks.
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName CreateTask
|
||||
* @apiName CreateUserTasks
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiSuccess {Object|Array} task The newly created task(s)
|
||||
*/
|
||||
api.createTask = {
|
||||
api.createUserTasks = {
|
||||
method: 'POST',
|
||||
url: '/tasks',
|
||||
url: '/tasks/user',
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
async handler (req, res) {
|
||||
let tasksData = Array.isArray(req.body) ? req.body : [req.body];
|
||||
let user = res.locals.user;
|
||||
|
||||
let toSave = tasksData.map(taskData => {
|
||||
// Validate that task.type is valid
|
||||
if (!taskData || Tasks.tasksTypes.indexOf(taskData.type) === -1) throw new BadRequest(res.t('invalidTaskType'));
|
||||
|
||||
let taskType = taskData.type;
|
||||
let newTask = new Tasks[taskType](Tasks.Task.sanitizeCreate(taskData));
|
||||
newTask.userId = user._id;
|
||||
|
||||
// Validate that the task is valid and throw if it isn't
|
||||
// otherwise since we're saving user and task in parallel it could save the user with a tasksOrder that doens't match reality
|
||||
let validationErrors = newTask.validateSync();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
// Otherwise update the user
|
||||
user.tasksOrder[`${taskType}s`].unshift(newTask._id);
|
||||
|
||||
return newTask;
|
||||
});
|
||||
|
||||
// If all tasks are valid, save everything, withough running validation again
|
||||
toSave = toSave.map(task => task.save({
|
||||
validateBeforeSave: false,
|
||||
}));
|
||||
toSave.unshift(user.save());
|
||||
let results = await Q.all(toSave);
|
||||
|
||||
if (results.length === 2) { // Just one task created
|
||||
res.respond(201, results[1]);
|
||||
} else {
|
||||
results.splice(0, 1); // remove the user
|
||||
res.respond(201, results);
|
||||
}
|
||||
let tasks = await _createTasks(req, res, res.locals.user);
|
||||
res.respond(201, tasks.length === 1 ? tasks[0] : tasks);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {get} /tasks Get an user's tasks
|
||||
* @api {post} /tasks/challenge/:challengeId Create a new task belonging to the challenge. Can be passed an object to create a single task or an array of objects to create multiple tasks.
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName CreateChallengeTasks
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} challengeId The id of the challenge the new task(s) will belong to.
|
||||
*
|
||||
* @apiSuccess {Object|Array} task The newly created task(s)
|
||||
*/
|
||||
api.createChallengeTasks = {
|
||||
method: 'POST',
|
||||
url: '/tasks/challenge/:challengeId',
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
async handler (req, res) {
|
||||
req.checkQuery('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
||||
|
||||
let reqValidationErrors = req.validationErrors();
|
||||
if (reqValidationErrors) throw reqValidationErrors;
|
||||
|
||||
let user = res.local.user;
|
||||
let challengeId = req.params.challengeId;
|
||||
|
||||
let challenge = await Challenge.findOne({_id: challengeId}).exec();
|
||||
|
||||
// If the challenge does not exist, or if it exists but user is not the leader -> throw error
|
||||
if (!challenge || user.challenges.indexOf(challengeId) === -1) throw new NotFound(res.t('challengeNotFound'));
|
||||
if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks'));
|
||||
|
||||
let tasks = await _createTasks(req, res, user, challenge);
|
||||
res.respond(201, tasks.length === 1 ? tasks[0] : tasks);
|
||||
|
||||
// If adding tasks to a challenge -> sync users
|
||||
if (challenge) challenge.addTasks(tasks); // TODO catch/log
|
||||
},
|
||||
};
|
||||
|
||||
// challenge must be passed only when a challenge task is being created
|
||||
async function _getTasks (req, res, user, challenge) {
|
||||
let query = challenge ? {'challenge.id': challenge.id, userId: {$exists: false}} : {userId: user._id};
|
||||
let type = req.query.type;
|
||||
|
||||
if (type) {
|
||||
query.type = type;
|
||||
if (type === 'todo') query.completed = false; // Exclude completed todos
|
||||
} else {
|
||||
query.$or = [ // Exclude completed todos
|
||||
{type: 'todo', completed: false},
|
||||
{type: {$in: ['habit', 'daily', 'reward']}},
|
||||
];
|
||||
}
|
||||
|
||||
if (req.query.includeCompletedTodos === 'true' && (!type || type === 'todo')) {
|
||||
if (challenge) throw new BadRequest(res.t('noCompletedTodosChallenge')); // no completed todos for challenges
|
||||
|
||||
let queryCompleted = Tasks.Task.find({
|
||||
type: 'todo',
|
||||
completed: true,
|
||||
}).limit(30).sort({ // TODO add ability to pick more than 30 completed todos
|
||||
dateCompleted: 1,
|
||||
});
|
||||
|
||||
let results = await Q.all([
|
||||
queryCompleted.exec(),
|
||||
Tasks.Task.find(query).exec(),
|
||||
]);
|
||||
|
||||
res.respond(200, results[1].concat(results[0]));
|
||||
} else {
|
||||
let tasks = await Tasks.Task.find(query).exec();
|
||||
res.respond(200, tasks);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /tasks/user Get an user's tasks
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName GetUserTasks
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {string="habit","daily","todo","reward"} type Optional query parameter to return just a type of tasks
|
||||
* @apiParam {boolean} includeCompletedTodos Optional query parameter to include completed todos when "type" is "todo". Only valid whe "tasksOwner" is "user".
|
||||
*
|
||||
* @apiSuccess {Array} tasks An array of task objects
|
||||
*/
|
||||
api.getUserTasks = {
|
||||
method: 'GET',
|
||||
url: '/tasks/user',
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
async handler (req, res) {
|
||||
req.checkQuery('type', res.t('invalidTaskType')).optional().isIn(Tasks.tasksTypes);
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
await _getTasks(req, res, res.locals.user);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {get} /tasks/challenge/:challengeId Get a challenge's tasks
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName GetChallengeTasks
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} challengeId The id of the challenge from which to retrieve the tasks.
|
||||
*
|
||||
* @apiParam {string="habit","daily","todo","reward"} type Optional query parameter to return just a type of tasks
|
||||
*
|
||||
* @apiSuccess {Array} tasks An array of task objects
|
||||
*/
|
||||
api.getChallengeTasks = {
|
||||
method: 'GET',
|
||||
url: '/tasks/challenge/:challengeId',
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
async handler (req, res) {
|
||||
req.checkQuery('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
||||
req.checkQuery('type', res.t('invalidTaskType')).optional().isIn(Tasks.tasksTypes);
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let user = res.local.user;
|
||||
let challengeId = req.params.challengeId;
|
||||
|
||||
let challenge = await Challenge.findOne({_id: challengeId}).select('leader').exec();
|
||||
|
||||
// If the challenge does not exist, or if it exists but user is not a member, not the leader and not an admin -> throw error
|
||||
if (!challenge || (user.challenges.indexOf(challengeId) === -1 && challenge.leader !== user._id && !user.contributor.admin)) { // eslint-disable-line no-extra-parens
|
||||
throw new NotFound(res.t('challengeNotFound'));
|
||||
}
|
||||
|
||||
await _getTasks(req, res, res.locals.user, challenge);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {get} /tasks/:tasksOwner/:challengeId Get an user's tasks
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName GetTasks
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {string="user","challenge"} tasksOwner Query parameter to return tasks belonging to a challenge (specifying the "challengeId" parameter) or to the autheticated user.
|
||||
* @apiParam {UUID} challengeId Optional query parameter. If "tasksOwner" is "challenge" then required to specify the challenge id.
|
||||
* @apiParam {string="habit","daily","todo","reward"} type Optional query parameter to return just a type of tasks
|
||||
* @apiParam {boolean} includeCompletedTodos Optional query parameter to include completed todos when "type" is "todo"
|
||||
* @apiParam {boolean} includeCompletedTodos Optional query parameter to include completed todos when "type" is "todo". Only valid whe "tasksOwner" is "user".
|
||||
*
|
||||
* @apiSuccess {Array} tasks An array of task objects
|
||||
*/
|
||||
@@ -82,13 +226,11 @@ api.getTasks = {
|
||||
url: '/tasks',
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
async handler (req, res) {
|
||||
req.checkQuery('type', res.t('invalidTaskType')).optional().isIn(Tasks.tasksTypes);
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let user = res.locals.user;
|
||||
let query = {userId: user._id};
|
||||
let challengeId = req.query.challengeId;
|
||||
let challenge;
|
||||
|
||||
let query = challenge ? {'challenge.id': challengeId, userId: {$exists: false}} : {userId: user._id};
|
||||
let type = req.query.type;
|
||||
|
||||
if (type) {
|
||||
@@ -102,6 +244,8 @@ api.getTasks = {
|
||||
}
|
||||
|
||||
if (req.query.includeCompletedTodos === 'true' && (!type || type === 'todo')) {
|
||||
if (challengeId) throw new BadRequest(res.t('noCompletedTodosChallenge'));
|
||||
|
||||
let queryCompleted = Tasks.Task.find({
|
||||
type: 'todo',
|
||||
completed: true,
|
||||
@@ -146,10 +290,19 @@ api.getTask = {
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
userId: user._id,
|
||||
}).exec();
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
} else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
let challenge = await Challenge.find().selec({_id: task.challenge.id}).select('leader').exec();
|
||||
if (!challenge || (user.challenges.indexOf(task.challenge.id) === -1 && challenge.leader !== user._id && !user.contributor.admin)) { // eslint-disable-line no-extra-parens
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
}
|
||||
} else if (task.userId !== user._id) { // If the task is owned by an user make it's the current one
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
}
|
||||
|
||||
res.respond(200, task);
|
||||
},
|
||||
};
|
||||
@@ -170,6 +323,7 @@ api.updateTask = {
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
// TODO check that req.body isn't empty
|
||||
@@ -180,10 +334,17 @@ api.updateTask = {
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
userId: user._id,
|
||||
}).exec();
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
} else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
challenge = await Challenge.find().selec({_id: task.challenge.id}).select('leader').exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks'));
|
||||
} else if (task.userId !== user._id) { // If the task is owned by an user make it's the current one
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
}
|
||||
|
||||
// If checklist is updated -> replace the original one
|
||||
if (req.body.checklist) {
|
||||
@@ -206,6 +367,7 @@ api.updateTask = {
|
||||
|
||||
let savedTask = await task.save();
|
||||
res.respond(200, savedTask);
|
||||
if (challenge) challenge.updateTask(savedTask); // TODO catch/log
|
||||
},
|
||||
};
|
||||
|
||||
@@ -313,11 +475,27 @@ api.scoreTask = {
|
||||
}).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');
|
||||
// Add only one history entry per day
|
||||
if (moment(chalTask.history[chalTask.history.length - 1].date).isSame(new Date(), 'day')) {
|
||||
chalTask.history[chalTask.history.length - 1] = {
|
||||
date: Number(new Date()),
|
||||
value: chalTask.value,
|
||||
};
|
||||
chalTask.markModified(`history.${chalTask.history.length - 1}`);
|
||||
} else {
|
||||
chalTask.history.push({
|
||||
date: Number(new Date()),
|
||||
value: chalTask.value,
|
||||
});
|
||||
|
||||
// Only preen task history once a day when the task is scored first
|
||||
if (chalTask.history.length > 365) {
|
||||
chalTask.history = preenHistory(chalTask.history, true); // true means the challenge will retain as much entries as a subscribed user
|
||||
chalTask.markModified(`history.${chalTask.history.length - 1}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await chalTask.save();
|
||||
@@ -330,6 +508,7 @@ api.scoreTask = {
|
||||
|
||||
// completed todos cannot be moved, they'll be returned ordered by date of completion
|
||||
// TODO check that it works when a tag is selected or todos are split between dated and due
|
||||
// TODO support challenges?
|
||||
/**
|
||||
* @api {post} /tasks/move/:taskId/to/:position Move a task to a new position
|
||||
* @apiVersion 3.0.0
|
||||
@@ -361,7 +540,7 @@ api.moveTask = {
|
||||
}).exec();
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
if (task.type === 'todo' && task.completed) throw new NotFound(res.t('cantMoveCompletedTodo'));
|
||||
if (task.type === 'todo' && task.completed) throw new BadRequest(res.t('cantMoveCompletedTodo'));
|
||||
let order = user.tasksOrder[`${task.type}s`];
|
||||
let currentIndex = order.indexOf(task._id);
|
||||
|
||||
@@ -397,6 +576,7 @@ api.addChecklistItem = {
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
// TODO check that req.body isn't empty and is an array
|
||||
@@ -406,16 +586,25 @@ api.addChecklistItem = {
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
userId: user._id,
|
||||
}).exec();
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
} else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
challenge = await Challenge.find().selec({_id: task.challenge.id}).select('leader').exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks'));
|
||||
} else if (task.userId !== user._id) { // If the task is owned by an user make it's the current one
|
||||
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
|
||||
if (challenge) challenge.updateTask(savedTask);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -478,6 +667,7 @@ api.updateChecklistItem = {
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID();
|
||||
@@ -487,10 +677,17 @@ api.updateChecklistItem = {
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
userId: user._id,
|
||||
}).exec();
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
} else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
challenge = await Challenge.find().selec({_id: task.challenge.id}).select('leader').exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks'));
|
||||
} else if (task.userId !== user._id) { // If the task is owned by an user make it's the current one
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
}
|
||||
if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo'));
|
||||
|
||||
let item = _.find(task.checklist, {_id: req.params.itemId});
|
||||
@@ -500,6 +697,7 @@ api.updateChecklistItem = {
|
||||
let savedTask = await task.save();
|
||||
|
||||
res.respond(200, savedTask); // TODO what to return
|
||||
if (challenge) challenge.updateTask(savedTask);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -520,6 +718,7 @@ api.removeChecklistItem = {
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID();
|
||||
@@ -529,10 +728,17 @@ api.removeChecklistItem = {
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
userId: user._id,
|
||||
}).exec();
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
} else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
challenge = await Challenge.find().selec({_id: task.challenge.id}).select('leader').exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks'));
|
||||
} else if (task.userId !== user._id) { // If the task is owned by an user make it's the current one
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
}
|
||||
if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo'));
|
||||
|
||||
let itemI = _.findIndex(task.checklist, {_id: req.params.itemId});
|
||||
@@ -540,8 +746,9 @@ api.removeChecklistItem = {
|
||||
|
||||
task.checklist.splice(itemI, 1);
|
||||
|
||||
await task.save();
|
||||
let savedTask = await task.save();
|
||||
res.respond(200, {}); // TODO what to return
|
||||
if (challenge) challenge.updateTask(savedTask);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -629,22 +836,64 @@ api.removeTagFromTask = {
|
||||
},
|
||||
};
|
||||
|
||||
// Remove a task from user.tasksOrder
|
||||
function _removeTaskTasksOrder (user, taskId) {
|
||||
// Loop through all lists and when the task is found, remove it and return
|
||||
for (let i = 0; i < Tasks.tasksTypes.length; i++) {
|
||||
let list = user.tasksOrder[`${Tasks.tasksTypes[i]}s`];
|
||||
let index = list.indexOf(taskId);
|
||||
// Remove a task from (user|challenge).tasksOrder
|
||||
function _removeTaskTasksOrder (userOrChallenge, taskId, taskType) {
|
||||
let list = userOrChallenge.tasksOrder[`${taskType}s`];
|
||||
let index = list.indexOf(taskId);
|
||||
|
||||
if (index !== -1) {
|
||||
list.splice(index, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
if (index !== -1) list.splice(index, 1);
|
||||
}
|
||||
|
||||
// TODO this method needs some limitation, like to check if the challenge is really broken?
|
||||
/**
|
||||
* @api {post} /tasks/unlink/:taskId Unlink a challenge task
|
||||
* @apiVersion 3.0.0
|
||||
* @apiName UnlinkTask
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
*
|
||||
* @apiSuccess {object} empty An empty object
|
||||
*/
|
||||
api.unlinkTask = {
|
||||
method: 'POST',
|
||||
url: '/tasks/unlink/:taskId',
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
async handler (req, res) {
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkQuery('keep', res.t('keepOrRemove')).notEmpty().isIn(['keep', 'remove']);
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let user = res.locals.user;
|
||||
let keep = req.query.keep;
|
||||
let taskId = req.params.taskId;
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: taskId,
|
||||
userId: user._id,
|
||||
}).exec();
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
if (!task.challenge.id) throw new BadRequest(res.t('cantOnlyUnlinkChalTask'));
|
||||
|
||||
if (keep === 'keep') {
|
||||
task.challenge = {};
|
||||
await task.save();
|
||||
} else { // remove
|
||||
if (task.type !== 'todo' || !task.completed) { // eslint-disable-line no-lonely-if
|
||||
_removeTaskTasksOrder(user, taskId, task.type);
|
||||
await Q.all([user.save(), task.remove()]);
|
||||
} else {
|
||||
await task.remove();
|
||||
}
|
||||
}
|
||||
|
||||
res.respond(200, {}); // TODO what to return
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* @api {delete} /task/:taskId Delete a user task given its id
|
||||
* @apiVersion 3.0.0
|
||||
@@ -661,24 +910,37 @@ api.deleteTask = {
|
||||
middlewares: [authWithHeaders(), cron],
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
userId: user._id,
|
||||
}).exec();
|
||||
let taskId = req.params.taskId;
|
||||
let task = await Tasks.Task.findById(taskId).exec();
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
if (task.challenge.id) throw new NotAuthorized(res.t('cantDeleteChallengeTasks'));
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
} else if (!task.userId) { // If the task belongs to a challenge make sure the user has rights
|
||||
challenge = await Challenge.find().selec({_id: task.challenge.id}).select('leader').exec();
|
||||
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||
if (challenge.leader !== user._id) throw new NotAuthorized(res.t('onlyChalLeaderEditTasks'));
|
||||
} else if (task.userId !== user._id) { // If the task is owned by an user make it's the current one
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
} else if (task.userId && task.challenge.id) {
|
||||
throw new NotAuthorized(res.t('cantDeleteChallengeTasks'));
|
||||
}
|
||||
|
||||
_removeTaskTasksOrder(user, req.params.taskId);
|
||||
await Q.all([user.save(), task.remove()]);
|
||||
if (task.type !== 'todo' || !task.completed) {
|
||||
_removeTaskTasksOrder(challenge || user, taskId, task.type);
|
||||
await Q.all([(challenge || user).save(), task.remove()]);
|
||||
} else {
|
||||
await task.remove();
|
||||
}
|
||||
|
||||
res.respond(200, {});
|
||||
if (challenge) challenge.removeTask(task);
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user