mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
@@ -16,6 +16,22 @@ import logger from '../../libs/api-v3/logger';
|
||||
|
||||
let api = {};
|
||||
|
||||
async function _validateTaskAlias (tasks, res) {
|
||||
let tasksWithAliases = tasks.filter(task => task.alias);
|
||||
let aliases = tasksWithAliases.map(task => task.alias);
|
||||
|
||||
// Compares the short names in tasks against
|
||||
// a Set, where values cannot repeat. If the
|
||||
// lengths are different, some name was duplicated
|
||||
if (aliases.length !== [...new Set(aliases)].length) {
|
||||
throw new BadRequest(res.t('taskAliasAlreadyUsed'));
|
||||
}
|
||||
|
||||
await Bluebird.map(tasksWithAliases, (task) => {
|
||||
return task.validate();
|
||||
});
|
||||
}
|
||||
|
||||
// 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];
|
||||
@@ -42,7 +58,12 @@ async function _createTasks (req, res, 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
|
||||
});
|
||||
|
||||
// tasks with aliases need to be validated asyncronously
|
||||
await _validateTaskAlias(toSave, res);
|
||||
|
||||
toSave = toSave.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,
|
||||
}));
|
||||
|
||||
@@ -233,7 +254,7 @@ api.getChallengeTasks = {
|
||||
* @apiName GetTask
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
* @apiParam {UUID|string} taskId The task _id or alias
|
||||
*
|
||||
* @apiSuccess {object} data The task object
|
||||
*/
|
||||
@@ -243,15 +264,8 @@ api.getTask = {
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
|
||||
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,
|
||||
}).exec();
|
||||
let taskId = req.params.taskId;
|
||||
let task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
@@ -274,7 +288,7 @@ api.getTask = {
|
||||
* @apiName UpdateTask
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
* @apiParam {UUID|string} taskId The task _id or alias
|
||||
*
|
||||
* @apiSuccess {object} data The updated task
|
||||
*/
|
||||
@@ -286,14 +300,13 @@ api.updateTask = {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty();
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
}).exec();
|
||||
let taskId = req.params.taskId;
|
||||
let task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
@@ -308,7 +321,6 @@ api.updateTask = {
|
||||
// we have to convert task to an object because otherwise things don't get merged correctly. Bad for performances?
|
||||
let [updatedTaskObj] = common.ops.updateTask(task.toObject(), req);
|
||||
|
||||
|
||||
// Sanitize differently user tasks linked to a challenge
|
||||
let sanitizedObj;
|
||||
|
||||
@@ -362,7 +374,7 @@ function _generateWebhookTaskData (task, direction, delta, stats, user) {
|
||||
* @apiName ScoreTask
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
* @apiParam {UUID|string} taskId The task _id or alias
|
||||
* @apiParam {string="up","down"} direction The direction for scoring the task
|
||||
*
|
||||
* @apiSuccess {object} data._tmp If an item was dropped it'll be returned in te _tmp object
|
||||
@@ -374,19 +386,16 @@ api.scoreTask = {
|
||||
url: '/tasks/:taskId/score/:direction',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('direction', res.t('directionUpDown')).notEmpty().isIn(['up', 'down']);
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let user = res.locals.user;
|
||||
let direction = req.params.direction;
|
||||
let {taskId} = req.params;
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
userId: user._id,
|
||||
}).exec();
|
||||
let task = await Tasks.Task.findByIdOrAlias(taskId, user._id, {userId: user._id});
|
||||
let direction = req.params.direction;
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
|
||||
@@ -448,7 +457,7 @@ api.scoreTask = {
|
||||
* @apiName MoveTask
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
* @apiParam {UUID|string} taskId The task _id or alias
|
||||
* @apiParam {Number} position Query parameter - Where to move the task (-1 means push to bottom). First position is 0
|
||||
*
|
||||
* @apiSuccess {array} data The new tasks order (user.tasksOrder.{task.type}s)
|
||||
@@ -458,19 +467,17 @@ api.moveTask = {
|
||||
url: '/tasks/:taskId/move/to/:position',
|
||||
middlewares: [authWithHeaders()],
|
||||
async handler (req, res) {
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty();
|
||||
req.checkParams('position', res.t('positionRequired')).notEmpty().isNumeric();
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let user = res.locals.user;
|
||||
let taskId = req.params.taskId;
|
||||
let to = Number(req.params.position);
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
userId: user._id,
|
||||
}).exec();
|
||||
let task = await Tasks.Task.findByIdOrAlias(taskId, user._id, { userId: user._id });
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
if (task.type === 'todo' && task.completed) throw new BadRequest(res.t('cantMoveCompletedTodo'));
|
||||
@@ -503,7 +510,7 @@ api.moveTask = {
|
||||
* @apiName AddChecklistItem
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
* @apiParam {UUID|string} taskId The task _id or alias
|
||||
*
|
||||
* @apiSuccess {object} data The updated task
|
||||
*/
|
||||
@@ -515,14 +522,13 @@ api.addChecklistItem = {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty();
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
}).exec();
|
||||
let taskId = req.params.taskId;
|
||||
let task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
@@ -552,7 +558,7 @@ api.addChecklistItem = {
|
||||
* @apiName ScoreChecklistItem
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
* @apiParam {UUID|string} taskId The task _id or alias
|
||||
* @apiParam {UUID} itemId The checklist item _id
|
||||
*
|
||||
* @apiSuccess {object} data The updated task
|
||||
@@ -564,16 +570,14 @@ api.scoreCheckListItem = {
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty();
|
||||
req.checkParams('itemId', res.t('itemIdRequired')).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.findByIdOrAlias(taskId, user._id, { userId: user._id });
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
if (task.type !== 'daily' && task.type !== 'todo') throw new BadRequest(res.t('checklistOnlyDailyTodo'));
|
||||
@@ -594,7 +598,7 @@ api.scoreCheckListItem = {
|
||||
* @apiName UpdateChecklistItem
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
* @apiParam {UUID|string} taskId The task _id or alias
|
||||
* @apiParam {UUID} itemId The checklist item _id
|
||||
*
|
||||
* @apiSuccess {object} data The updated task
|
||||
@@ -607,15 +611,14 @@ api.updateChecklistItem = {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty();
|
||||
req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID();
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
}).exec();
|
||||
let taskId = req.params.taskId;
|
||||
let task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
@@ -647,7 +650,7 @@ api.updateChecklistItem = {
|
||||
* @apiName RemoveChecklistItem
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
* @apiParam {UUID|string} taskId The task _id or alias
|
||||
* @apiParam {UUID} itemId The checklist item _id
|
||||
*
|
||||
* @apiSuccess {object} data The updated task
|
||||
@@ -660,15 +663,14 @@ api.removeChecklistItem = {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty();
|
||||
req.checkParams('itemId', res.t('itemIdRequired')).notEmpty().isUUID();
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: req.params.taskId,
|
||||
}).exec();
|
||||
let taskId = req.params.taskId;
|
||||
let task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
@@ -698,7 +700,7 @@ api.removeChecklistItem = {
|
||||
* @apiName AddTagToTask
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
* @apiParam {UUID|string} taskId The task _id or alias
|
||||
* @apiParam {UUID} tagId The tag id
|
||||
*
|
||||
* @apiSuccess {object} data The updated task
|
||||
@@ -710,17 +712,15 @@ api.addTagToTask = {
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty();
|
||||
let userTags = user.tags.map(tag => tag.id);
|
||||
req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID().isIn(userTags);
|
||||
|
||||
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.findByIdOrAlias(taskId, user._id, { userId: user._id });
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
let tagId = req.params.tagId;
|
||||
@@ -741,7 +741,7 @@ api.addTagToTask = {
|
||||
* @apiName RemoveTagFromTask
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
* @apiParam {UUID|string} taskId The task _id or alias
|
||||
* @apiParam {UUID} tagId The tag id
|
||||
*
|
||||
* @apiSuccess {object} data The updated task
|
||||
@@ -753,16 +753,14 @@ api.removeTagFromTask = {
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty();
|
||||
req.checkParams('tagId', res.t('tagIdRequired')).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.findByIdOrAlias(taskId, user._id, { userId: user._id });
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
|
||||
@@ -842,7 +840,7 @@ api.unlinkAllTasks = {
|
||||
* @apiName UnlinkOneTask
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
* @apiParam {UUID|string} taskId The task _id or alias
|
||||
* @apiParam {string} keep Query parameter - keep or remove
|
||||
*
|
||||
* @apiSuccess {object} data An empty object
|
||||
@@ -862,10 +860,7 @@ api.unlinkOneTask = {
|
||||
let keep = req.query.keep;
|
||||
let taskId = req.params.taskId;
|
||||
|
||||
let task = await Tasks.Task.findOne({
|
||||
_id: taskId,
|
||||
userId: user._id,
|
||||
}).exec();
|
||||
let task = await Tasks.Task.findByIdOrAlias(taskId, user._id, { userId: user._id });
|
||||
|
||||
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||
if (!task.challenge.id) throw new BadRequest(res.t('cantOnlyUnlinkChalTask'));
|
||||
@@ -924,7 +919,7 @@ api.clearCompletedTodos = {
|
||||
* @apiName DeleteTask
|
||||
* @apiGroup Task
|
||||
*
|
||||
* @apiParam {UUID} taskId The task _id
|
||||
* @apiParam {UUID|string} taskId The task _id or alias
|
||||
*
|
||||
* @apiSuccess {object} data An empty object
|
||||
*/
|
||||
@@ -936,13 +931,8 @@ api.deleteTask = {
|
||||
let user = res.locals.user;
|
||||
let challenge;
|
||||
|
||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||
|
||||
let validationErrors = req.validationErrors();
|
||||
if (validationErrors) throw validationErrors;
|
||||
|
||||
let taskId = req.params.taskId;
|
||||
let task = await Tasks.Task.findById(taskId).exec();
|
||||
let task = await Tasks.Task.findByIdOrAlias(taskId, user._id);
|
||||
|
||||
if (!task) {
|
||||
throw new NotFound(res.t('taskNotFound'));
|
||||
|
||||
Reference in New Issue
Block a user