mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 15:17:25 +01:00
add tags routes, misc fixes
This commit is contained in:
@@ -20,9 +20,12 @@
|
|||||||
"invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
|
"invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
|
||||||
"cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
|
"cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
|
||||||
"checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
|
"checklistOnlyDailyTodo": "Checklists are supported only on dailies and todos",
|
||||||
"checklistItemNotFound": "No checklist item was wound with given id.",
|
"checklistItemNotFound": "No checklist item was found with given id.",
|
||||||
"itemIdRequired": "\"itemId\" must be a valid UUID",
|
"itemIdRequired": "\"itemId\" must be a valid UUID.",
|
||||||
|
"tagNotFound": "No tag item was found with given id.",
|
||||||
|
"tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
|
||||||
"positionRequired": "\"position\" is required and must be a number.",
|
"positionRequired": "\"position\" is required and must be a number.",
|
||||||
"cantMoveCompletedTodo": "Can't move a completed todo.",
|
"cantMoveCompletedTodo": "Can't move a completed todo.",
|
||||||
"directionUpDown": "\"direction\" is required and must be 'up' or 'down'"
|
"directionUpDown": "\"direction\" is required and must be 'up' or 'down'",
|
||||||
|
"alreadyTagged": "The task is already tagged with give tag."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,4 +49,19 @@ describe('POST /tasks', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
context('correctly creates new tasks', () => {
|
||||||
|
it('habit', () => {
|
||||||
|
return api.post('/tasks', {
|
||||||
|
text: 'test habit',
|
||||||
|
type: 'habit',
|
||||||
|
up: false,
|
||||||
|
down: true,
|
||||||
|
history: 'i cannot be set',
|
||||||
|
notes: 1976,
|
||||||
|
}).then((task) => {
|
||||||
|
expect(task.userId).to.equal(user._id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ api.createTask = {
|
|||||||
let newTask = new Tasks[taskType](Tasks.Task.sanitizeCreate(req.body));
|
let newTask = new Tasks[taskType](Tasks.Task.sanitizeCreate(req.body));
|
||||||
newTask.userId = user._id;
|
newTask.userId = user._id;
|
||||||
|
|
||||||
user.tasksOrder[taskType + 's'].unshift(newTask._id);
|
user.tasksOrder[`${taskType}s`].unshift(newTask._id);
|
||||||
|
|
||||||
Q.all([
|
Q.all([
|
||||||
newTask.save(),
|
newTask.save(),
|
||||||
@@ -155,6 +155,7 @@ api.updateTask = {
|
|||||||
|
|
||||||
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||||
// TODO check that req.body isn't empty
|
// TODO check that req.body isn't empty
|
||||||
|
// TODO make sure tags are updated correctly (they aren't set as modified!) maybe use specific routes
|
||||||
|
|
||||||
let validationErrors = req.validationErrors();
|
let validationErrors = req.validationErrors();
|
||||||
if (validationErrors) return next(validationErrors);
|
if (validationErrors) return next(validationErrors);
|
||||||
@@ -172,7 +173,7 @@ api.updateTask = {
|
|||||||
task.checklist = req.body.checklist;
|
task.checklist = req.body.checklist;
|
||||||
}
|
}
|
||||||
// TODO merge goes deep into objects, it's ok?
|
// TODO merge goes deep into objects, it's ok?
|
||||||
// TODO also check that array fields are updated correctly without marking modified
|
// TODO also check that array and mixed fields are updated correctly without marking modified
|
||||||
_.merge(task, Tasks.Task.sanitizeUpdate(req.body));
|
_.merge(task, Tasks.Task.sanitizeUpdate(req.body));
|
||||||
return task.save();
|
return task.save();
|
||||||
})
|
})
|
||||||
@@ -273,7 +274,7 @@ api.moveTask = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {post} /tasks/:taskId/checklist/addItem Add an item to a checklist, creating the checklist if it doesn't exist
|
* @api {post} /tasks/:taskId/checklist Add an item to a checklist, creating the checklist if it doesn't exist
|
||||||
* @apiVersion 3.0.0
|
* @apiVersion 3.0.0
|
||||||
* @apiName AddChecklistItem
|
* @apiName AddChecklistItem
|
||||||
* @apiGroup Task
|
* @apiGroup Task
|
||||||
@@ -284,7 +285,7 @@ api.moveTask = {
|
|||||||
*/
|
*/
|
||||||
api.addChecklistItem = {
|
api.addChecklistItem = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
url: '/tasks/:taskId/checklist/addItem',
|
url: '/tasks/:taskId/checklist',
|
||||||
middlewares: [authWithHeaders()],
|
middlewares: [authWithHeaders()],
|
||||||
handler (req, res, next) {
|
handler (req, res, next) {
|
||||||
let user = res.locals.user;
|
let user = res.locals.user;
|
||||||
@@ -441,6 +442,92 @@ api.removeChecklistItem = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {post} /tasks/:taskId/tags/:tagId Add a tag to a task
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName AddTagToTask
|
||||||
|
* @apiGroup Task
|
||||||
|
*
|
||||||
|
* @apiParam {UUID} taskId The task _id
|
||||||
|
* @apiParam {UUID} tagId The tag id
|
||||||
|
*
|
||||||
|
* @apiSuccess {object} task The updated task
|
||||||
|
*/
|
||||||
|
api.addTagToTask = {
|
||||||
|
method: 'POST',
|
||||||
|
url: '/tasks/:taskId/tags',
|
||||||
|
middlewares: [authWithHeaders()],
|
||||||
|
handler (req, res, next) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
|
||||||
|
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||||
|
let userTags = user.tags.map(tag => tag._id);
|
||||||
|
req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID().isIn(userTags);
|
||||||
|
|
||||||
|
let validationErrors = req.validationErrors();
|
||||||
|
if (validationErrors) return next(validationErrors);
|
||||||
|
|
||||||
|
Tasks.Task.findOne({
|
||||||
|
_id: req.params.taskId,
|
||||||
|
userId: user._id,
|
||||||
|
}).exec()
|
||||||
|
.then((task) => {
|
||||||
|
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||||
|
let tagId = req.params.tagId;
|
||||||
|
|
||||||
|
let alreadyTagged = task.tags.indexOf(tagId) === -1;
|
||||||
|
if (alreadyTagged) throw new BadRequest(res.t('alreadyTagged'));
|
||||||
|
|
||||||
|
task.tags.push(tagId);
|
||||||
|
return task.save();
|
||||||
|
})
|
||||||
|
.then((savedTask) => res.respond(200, savedTask)) // TODO what to return
|
||||||
|
.catch(next);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {delete} /tasks/:taskId/tags/:tagId Remove a tag
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName RemoveTagFromTask
|
||||||
|
* @apiGroup Task
|
||||||
|
*
|
||||||
|
* @apiParam {UUID} taskId The task _id
|
||||||
|
* @apiParam {UUID} tagId The tag id
|
||||||
|
*
|
||||||
|
* @apiSuccess {object} empty An empty object
|
||||||
|
*/
|
||||||
|
api.removeTagFromTask = {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/tasks/:taskId/tags/:tagId',
|
||||||
|
middlewares: [authWithHeaders()],
|
||||||
|
handler (req, res, next) {
|
||||||
|
let user = res.locals.user;
|
||||||
|
|
||||||
|
req.checkParams('taskId', res.t('taskIdRequired')).notEmpty().isUUID();
|
||||||
|
req.checkParams('tagId', res.t('tagIdRequired')).notEmpty().isUUID();
|
||||||
|
|
||||||
|
let validationErrors = req.validationErrors();
|
||||||
|
if (validationErrors) return next(validationErrors);
|
||||||
|
|
||||||
|
Tasks.Task.findOne({
|
||||||
|
_id: req.params.taskId,
|
||||||
|
userId: user._id,
|
||||||
|
}).exec()
|
||||||
|
.then((task) => {
|
||||||
|
if (!task) throw new NotFound(res.t('taskNotFound'));
|
||||||
|
|
||||||
|
let tagI = _.findIndex(task.tags, {_id: req.params.tagId});
|
||||||
|
if (tagI === -1) throw new NotFound(res.t('tagNotFound'));
|
||||||
|
|
||||||
|
task.tags.splice(tagI, 1);
|
||||||
|
return task.save();
|
||||||
|
})
|
||||||
|
.then(() => res.respond(200, {})) // TODO what to return
|
||||||
|
.catch(next);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Remove a task from user.tasksOrder
|
// Remove a task from user.tasksOrder
|
||||||
function _removeTaskTasksOrder (user, taskId) {
|
function _removeTaskTasksOrder (user, taskId) {
|
||||||
// Loop through all lists and when the task is found, remove it and return
|
// Loop through all lists and when the task is found, remove it and return
|
||||||
|
|||||||
@@ -17,9 +17,12 @@ export let TaskSchema = new Schema({
|
|||||||
type: {type: String, enum: tasksTypes, required: true, default: tasksTypes[0]},
|
type: {type: String, enum: tasksTypes, required: true, default: tasksTypes[0]},
|
||||||
text: {type: String, required: true},
|
text: {type: String, required: true},
|
||||||
notes: {type: String, default: ''},
|
notes: {type: String, default: ''},
|
||||||
tags: {type: Schema.Types.Mixed, default: {}}, // TODO dictionary? { "4ddf03d9-54bd-41a3-b011-ca1f1d2e9371" : true }, validate
|
tags: [{
|
||||||
|
type: String,
|
||||||
|
validate: [validator.isUUID, 'Invalid uuid.'],
|
||||||
|
}],
|
||||||
value: {type: Number, default: 0}, // redness or cost for rewards
|
value: {type: Number, default: 0}, // redness or cost for rewards
|
||||||
priority: {type: Number, default: 1, required: true},
|
priority: {type: Number, default: 1, required: true}, // TODO enum?
|
||||||
attribute: {type: String, default: 'str', enum: ['str', 'con', 'int', 'per']},
|
attribute: {type: String, default: 'str', enum: ['str', 'con', 'int', 'per']},
|
||||||
userId: {type: String, ref: 'User'}, // When null it belongs to a challenge
|
userId: {type: String, ref: 'User'}, // When null it belongs to a challenge
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user