2015-11-28 16:47:32 +00:00
import { authWithHeaders } from '../../middlewares/api-v3/auth' ;
2015-12-15 11:11:32 +00:00
import cron from '../../middlewares/api-v3/cron' ;
2015-12-14 18:23:13 +00:00
import { sendTaskWebhook } from '../../libs/api-v3/webhook' ;
2015-11-28 17:05:07 +00:00
import * as Tasks from '../../models/task' ;
2016-01-04 18:44:39 +00:00
import { model as Challenge } from '../../models/challenge' ;
2015-11-29 17:29:35 +00:00
import {
NotFound ,
NotAuthorized ,
2015-11-30 16:25:40 +00:00
BadRequest ,
2015-11-29 17:29:35 +00:00
} from '../../libs/api-v3/errors' ;
2015-12-06 17:03:51 +00:00
import shared from '../../../../common' ;
2015-11-28 17:18:28 +00:00
import Q from 'q' ;
2015-11-29 18:05:24 +00:00
import _ from 'lodash' ;
2015-12-08 13:41:42 +00:00
import scoreTask from '../../../../common/script/api-v3/scoreTask' ;
2015-12-29 17:35:34 +00:00
import { preenHistory } from '../../../../common/script/api-v3/preenHistory' ;
2015-11-28 16:47:32 +00:00
let api = { } ;
/ * *
2016-01-02 14:43:33 +00:00
* @ 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 .
2015-11-28 16:47:32 +00:00
* @ apiVersion 3.0 . 0
* @ apiName CreateTask
* @ apiGroup Task
*
2016-01-04 18:44:39 +00:00
* @ apiParam { string = "user" , "challenge" } tasksOwner Define if tasks will belong to the auhenticated user or to a challenge ( specifying the "challengeId" parameter ) .
* @ apiParam { UUID } challengeId Optional . If "tasksOwner" is "challenge" then specify the challenge id .
*
2016-01-02 14:43:33 +00:00
* @ apiSuccess { Object | Array } task The newly created task ( s )
2015-11-28 16:47:32 +00:00
* /
api . createTask = {
method : 'POST' ,
2016-01-04 18:44:39 +00:00
url : '/tasks/:tasksOwner/:challengeId?' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2016-01-02 14:43:33 +00:00
let tasksData = Array . isArray ( req . body ) ? req . body : [ req . body ] ;
2015-11-28 16:47:32 +00:00
let user = res . locals . user ;
2016-01-04 18:44:39 +00:00
let tasksOwner = req . params . tasksOwner ;
let challengeId = req . params . challengeId ;
let challenge ;
if ( tasksOwner === 'user' && challengeId ) throw new BadRequest ( res . t ( 'userTasksNoChallengeId' ) ) ;
if ( tasksOwner === 'challenge' ) {
if ( ! challengeId ) throw new BadRequest ( res . t ( 'challengeIdRequired' ) ) ;
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
2016-01-04 19:48:48 +00:00
if ( ! challenge ) throw new NotFound ( res . t ( 'challengeNotFound' ) ) ;
if ( challenge . leader !== user . _id ) throw new NotAuthorized ( res . t ( 'onlyChalLeaderEditTasks' ) ) ;
2016-01-04 18:44:39 +00:00
}
2015-11-28 16:47:32 +00:00
2016-01-02 14:43:33 +00:00
let toSave = tasksData . map ( taskData => {
2016-01-02 16:46:31 +00:00
// Validate that task.type is valid
2016-01-02 14:43:33 +00:00
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 ) ) ;
2016-01-04 18:44:39 +00:00
if ( challenge ) {
newTask . challenge . id = challengeId ;
} else {
newTask . userId = user . _id ;
}
2016-01-02 16:46:31 +00:00
// Validate that the task is valid and throw if it isn't
2016-01-04 18:44:39 +00:00
// 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
2016-01-02 16:46:31 +00:00
let validationErrors = newTask . validateSync ( ) ;
if ( validationErrors ) throw validationErrors ;
2016-01-04 18:44:39 +00:00
// Otherwise update the user/challenge
( challenge || user ) . tasksOrder [ ` ${ taskType } s ` ] . unshift ( newTask . _id ) ;
2015-11-28 16:47:32 +00:00
2016-01-02 16:46:31 +00:00
return newTask ;
2016-01-02 14:43:33 +00:00
} ) ;
2015-11-28 17:18:28 +00:00
2016-01-02 16:46:31 +00:00
// If all tasks are valid, save everything, withough running validation again
toSave = toSave . map ( task => task . save ( {
validateBeforeSave : false ,
} ) ) ;
2016-01-04 18:44:39 +00:00
toSave . unshift ( ( challenge || user ) . save ( ) ) ;
2015-12-31 10:46:21 +00:00
2016-01-04 18:44:39 +00:00
let tasks = await Q . all ( toSave ) ;
tasks . splice ( 0 , 1 ) ; // remove the user/challenge
res . respond ( 201 , tasks ) ;
// If adding tasks to a challenge -> sync users
2016-01-04 19:48:48 +00:00
if ( challenge ) challenge . addTasks ( tasks ) ; // TODO catch/log
2015-11-28 16:47:32 +00:00
} ,
} ;
/ * *
2016-01-04 18:44:39 +00:00
* @ api { get } / tasks / : tasksOwner / : challengeId Get an user ' s tasks
2015-11-28 16:47:32 +00:00
* @ apiVersion 3.0 . 0
* @ apiName GetTasks
* @ apiGroup Task
*
2016-01-04 18:44:39 +00:00
* @ apiParam { string = "user" , "challenge" } tasksOwner Url parameter to return tasks belonging to a challenge ( specifying the "challengeId" parameter ) or to the autheticated user .
* @ apiParam { UUID } challengeId Optional . If "tasksOwner" is "challenge" then specify the challenge id .
2015-11-30 20:00:09 +00:00
* @ apiParam { string = "habit" , "daily" , "todo" , "reward" } type Optional query parameter to return just a type of tasks
2016-01-04 18:44:39 +00:00
* @ apiParam { boolean } includeCompletedTodos Optional query parameter to include completed todos when "type" is "todo" . Only valid whe "tasksOwner" is "user" .
2015-11-28 16:47:32 +00:00
*
* @ apiSuccess { Array } tasks An array of task objects
* /
api . getTasks = {
method : 'GET' ,
2016-01-04 18:44:39 +00:00
url : '/tasks/:tasksOwner/:challengeId?' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2016-01-04 18:44:39 +00:00
req . checkParams ( 'tasksOwner' , res . t ( 'invalidTasksOwner' ) ) . isIn ( [ 'user' , 'challenge' ] ) ;
req . checkParams ( 'challengeId' , res . t ( 'challengeIdRequired' ) ) . optional ( ) . isUUID ( ) ;
2015-12-06 15:51:18 +00:00
req . checkQuery ( 'type' , res . t ( 'invalidTaskType' ) ) . optional ( ) . isIn ( Tasks . tasksTypes ) ;
2015-11-28 16:47:32 +00:00
2015-12-03 16:48:32 +00:00
let validationErrors = req . validationErrors ( ) ;
2015-12-31 10:46:21 +00:00
if ( validationErrors ) throw validationErrors ;
2015-12-03 16:48:32 +00:00
2015-11-28 16:47:32 +00:00
let user = res . locals . user ;
2016-01-04 18:44:39 +00:00
let tasksOwner = req . params . tasksOwner ;
let challengeId = req . params . challengeId ;
let challenge ;
if ( tasksOwner === 'user' && challengeId ) throw new BadRequest ( res . t ( 'userTasksNoChallengeId' ) ) ;
if ( tasksOwner === 'challenge' ) {
if ( ! challengeId ) throw new BadRequest ( res . t ( 'challengeIdRequired' ) ) ;
2016-01-04 18:57:20 +00:00
challenge = await Challenge . findOne ( { _id : challengeId } ) . select ( 'leader' ) . exec ( ) ;
2016-01-04 18:44:39 +00:00
// 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' ) ) ;
}
}
let query = challenge ? { 'challenge.id' : challengeId , userId : { $exists : false } } : { userId : user . _id } ;
2015-11-28 16:47:32 +00:00
let type = req . query . type ;
2015-11-30 20:00:09 +00:00
if ( type ) {
query . type = type ;
if ( type === 'todo' ) query . completed = false ; // Exclude completed todos
} else {
2015-12-06 15:51:18 +00:00
query . $or = [ // Exclude completed todos
2015-11-30 20:00:09 +00:00
{ type : 'todo' , completed : false } ,
{ type : { $in : [ 'habit' , 'daily' , 'reward' ] } } ,
] ;
}
if ( req . query . includeCompletedTodos === 'true' && ( ! type || type === 'todo' ) ) {
2016-01-04 18:44:39 +00:00
if ( challengeId ) throw new BadRequest ( res . t ( 'noCompletedTodosChallenge' ) ) ;
2015-11-30 20:00:09 +00:00
let queryCompleted = Tasks . Task . find ( {
type : 'todo' ,
completed : true ,
} ) . limit ( 30 ) . sort ( { // TODO add ability to pick more than 30 completed todos
dateCompleted : 1 ,
} ) ;
2015-12-31 10:46:21 +00:00
let results = await Q . all ( [
2015-11-30 20:00:09 +00:00
queryCompleted . exec ( ) ,
Tasks . Task . find ( query ) . exec ( ) ,
2015-12-31 10:46:21 +00:00
] ) ;
res . respond ( 200 , results [ 1 ] . concat ( results [ 0 ] ) ) ;
2015-11-30 20:00:09 +00:00
} else {
2015-12-31 10:46:21 +00:00
let tasks = await Tasks . Task . find ( query ) . exec ( ) ;
res . respond ( 200 , tasks ) ;
2015-11-30 20:00:09 +00:00
}
2015-11-28 16:47:32 +00:00
} ,
} ;
/ * *
* @ api { get } / task / : taskId Get a task given its id
* @ apiVersion 3.0 . 0
* @ apiName GetTask
* @ apiGroup Task
*
* @ apiParam { UUID } taskId The task _id
*
* @ apiSuccess { object } task The task object
* /
api . getTask = {
method : 'GET' ,
url : '/tasks/:taskId' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2015-11-28 16:47:32 +00:00
let user = res . locals . user ;
req . checkParams ( 'taskId' , res . t ( 'taskIdRequired' ) ) . notEmpty ( ) . isUUID ( ) ;
2015-12-03 16:48:32 +00:00
let validationErrors = req . validationErrors ( ) ;
2015-12-31 10:46:21 +00:00
if ( validationErrors ) throw validationErrors ;
2015-12-03 16:48:32 +00:00
2015-12-31 10:46:21 +00:00
let task = await Tasks . Task . findOne ( {
2015-11-28 16:47:32 +00:00
_id : req . params . taskId ,
2015-12-31 10:46:21 +00:00
} ) . exec ( ) ;
2016-01-04 19:48:48 +00:00
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
2016-01-04 18:57:20 +00:00
let challenge = await Challenge . find ( ) . selec ( { _id : task . challenge . id } ) . select ( 'leader' ) . exec ( ) ;
2016-01-04 19:48:48 +00:00
if ( ! challenge || ( user . challenges . indexOf ( task . challenge . id ) === - 1 && challenge . leader !== user . _id && ! user . contributor . admin ) ) { // eslint-disable-line no-extra-parens
2016-01-04 18:57:20 +00:00
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' ) ) ;
}
2015-12-31 10:46:21 +00:00
res . respond ( 200 , task ) ;
2015-11-28 16:47:32 +00:00
} ,
} ;
2015-11-29 16:47:10 +00:00
2015-11-29 18:05:24 +00:00
/ * *
* @ api { put } / task / : taskId Update a task
* @ apiVersion 3.0 . 0
2015-11-30 16:25:40 +00:00
* @ apiName UpdateTask
2015-11-29 18:05:24 +00:00
* @ apiGroup Task
*
* @ apiParam { UUID } taskId The task _id
*
* @ apiSuccess { object } task The updated task
* /
api . updateTask = {
2015-11-30 16:25:40 +00:00
method : 'PUT' ,
2015-11-29 18:05:24 +00:00
url : '/tasks/:taskId' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2015-11-29 18:05:24 +00:00
let user = res . locals . user ;
2016-01-04 19:48:48 +00:00
let challenge ;
2015-11-30 16:25:40 +00:00
2015-11-29 18:05:24 +00:00
req . checkParams ( 'taskId' , res . t ( 'taskIdRequired' ) ) . notEmpty ( ) . isUUID ( ) ;
2015-11-30 16:25:40 +00:00
// TODO check that req.body isn't empty
2015-12-03 18:19:53 +00:00
// TODO make sure tags are updated correctly (they aren't set as modified!) maybe use specific routes
2015-11-29 18:05:24 +00:00
2015-12-03 16:48:32 +00:00
let validationErrors = req . validationErrors ( ) ;
2015-12-31 10:46:21 +00:00
if ( validationErrors ) throw validationErrors ;
2015-12-03 16:48:32 +00:00
2015-12-31 10:46:21 +00:00
let task = await Tasks . Task . findOne ( {
2015-11-29 18:05:24 +00:00
_id : req . params . taskId ,
2015-12-31 10:46:21 +00:00
} ) . exec ( ) ;
2015-12-06 16:29:14 +00:00
2016-01-04 19:48:48 +00:00
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' ) ) ;
}
2015-12-06 16:29:14 +00:00
2015-12-31 10:46:21 +00:00
// If checklist is updated -> replace the original one
if ( req . body . checklist ) {
task . checklist = req . body . checklist ;
delete req . body . checklist ;
}
// If tags are updated -> replace the original ones
if ( req . body . tags ) {
task . tags = req . body . tags ;
delete req . body . tags ;
}
// TODO we have to convert task to an object because otherwise thigns doesn't get merged correctly, very bad for performances
// TODO regarding comment above make sure other models with nested fields are using this trick too
_ . assign ( task , _ . merge ( task . toObject ( ) , Tasks . Task . sanitizeUpdate ( req . body ) ) ) ;
// TODO console.log(task.modifiedPaths(), task.toObject().repeat === tep)
// repeat is always among modifiedPaths because mongoose changes the other of the keys when using .toObject()
// see https://github.com/Automattic/mongoose/issues/2749
let savedTask = await task . save ( ) ;
res . respond ( 200 , savedTask ) ;
2016-01-04 19:48:48 +00:00
if ( challenge ) challenge . updateTask ( savedTask ) ; // TODO catch/log
2015-11-29 18:05:24 +00:00
} ,
} ;
2015-12-06 17:03:51 +00:00
function _generateWebhookTaskData ( task , direction , delta , stats , user ) {
let extendedStats = _ . extend ( stats , {
toNextLevel : shared . tnl ( user . stats . lvl ) ,
maxHealth : shared . maxHealth ,
maxMP : user . _statsComputed . maxMP , // TODO refactor as method not getter
} ) ;
let userData = {
_id : user . _id ,
_tmp : user . _tmp ,
stats : extendedStats ,
} ;
let taskData = {
details : task ,
direction ,
delta ,
} ;
return {
task : taskData ,
user : userData ,
} ;
}
2015-12-02 10:22:53 +00:00
/ * *
2015-12-11 10:41:46 +00:00
* @ api { put } / tasks / : taskId / score / : direction Score a task
2015-12-02 10:22:53 +00:00
* @ apiVersion 3.0 . 0
* @ apiName ScoreTask
* @ apiGroup Task
*
* @ apiParam { UUID } taskId The task _id
* @ apiParam { string = "up" , "down" } direction The direction for scoring the task
*
* @ apiSuccess { object } empty An empty object
* /
api . scoreTask = {
method : 'POST' ,
2015-12-11 10:41:46 +00:00
url : '/tasks/:taskId/score/:direction' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2015-12-02 10:22:53 +00:00
req . checkParams ( 'taskId' , res . t ( 'taskIdRequired' ) ) . notEmpty ( ) . isUUID ( ) ;
2015-12-11 10:41:46 +00:00
req . checkParams ( 'direction' , res . t ( 'directionUpDown' ) ) . notEmpty ( ) . isIn ( [ 'up' , 'down' ] ) ; // TODO what about rewards? maybe separate route?
2015-12-02 10:22:53 +00:00
2015-12-03 16:48:32 +00:00
let validationErrors = req . validationErrors ( ) ;
2015-12-31 10:46:21 +00:00
if ( validationErrors ) throw validationErrors ;
2015-12-03 16:48:32 +00:00
2015-12-02 10:22:53 +00:00
let user = res . locals . user ;
2015-12-06 17:03:51 +00:00
let direction = req . params . direction ;
2015-12-02 10:22:53 +00:00
2015-12-31 10:46:21 +00:00
let task = await Tasks . Task . findOne ( {
2015-12-02 10:22:53 +00:00
_id : req . params . taskId ,
userId : user . _id ,
2015-12-31 10:46:21 +00:00
} ) . exec ( ) ;
2015-12-06 17:03:51 +00:00
2015-12-31 10:46:21 +00:00
if ( ! task ) throw new NotFound ( res . t ( 'taskNotFound' ) ) ;
2015-12-06 17:03:51 +00:00
2015-12-31 10:46:21 +00:00
let wasCompleted = task . completed ;
if ( task . type === 'daily' || task . type === 'todo' ) {
task . completed = direction === 'up' ; // TODO move into scoreTask
}
let delta = scoreTask ( { task , user , direction } , req ) ;
// Drop system (don't run on the client, as it would only be discarded since ops are sent to the API, not the results)
if ( direction === 'up' ) user . fns . randomDrop ( { task , delta } , req ) ;
// If a todo was completed or uncompleted move it in or out of the user.tasksOrder.todos list
if ( task . type === 'todo' ) {
if ( ! wasCompleted && task . completed ) {
let i = user . tasksOrder . todos . indexOf ( task . _id ) ;
if ( i !== - 1 ) user . tasksOrder . todos . splice ( i , 1 ) ;
} else if ( wasCompleted && ! task . completed ) {
let i = user . tasksOrder . todos . indexOf ( task . _id ) ;
if ( i === - 1 ) {
user . tasksOrder . todos . push ( task . _id ) ; // TODO push at the top?
} else { // If for some reason it hadn't been removed TODO ok?
user . tasksOrder . todos . splice ( i , 1 ) ;
user . tasksOrder . push ( task . _id ) ;
2015-12-16 11:57:19 +00:00
}
}
2015-12-31 10:46:21 +00:00
}
2015-12-16 11:57:19 +00:00
2015-12-31 10:46:21 +00:00
let results = await Q . all ( [
user . save ( ) ,
task . save ( ) ,
] ) ;
let savedUser = results [ 0 ] ;
let userStats = savedUser . stats . toJSON ( ) ;
let resJsonData = _ . extend ( { delta , _tmp : user . _tmp } , userStats ) ;
res . respond ( 200 , resJsonData ) ;
sendTaskWebhook ( user . preferences . webhooks , _generateWebhookTaskData ( task , direction , delta , userStats , user ) ) ;
// TODO test?
if ( task . challenge . id && task . challenge . taskId && ! task . challenge . broken && task . type !== 'reward' ) {
// Wrapping everything in a try/catch block because if an error occurs using `await` it MUST NOT bubble up because the request has already been handled
try {
let chalTask = await Tasks . Task . findOne ( {
_id : task . challenge . taskId ,
} ) . 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' ) ;
2015-12-29 17:35:34 +00:00
}
2015-12-31 10:46:21 +00:00
await chalTask . save ( ) ;
} catch ( e ) {
// TODO handle
}
}
2015-12-02 10:22:53 +00:00
} ,
} ;
2015-11-30 19:14:53 +00:00
// completed todos cannot be moved, they'll be returned ordered by date of completion
2015-12-02 10:22:53 +00:00
// TODO check that it works when a tag is selected or todos are split between dated and due
2016-01-04 19:48:48 +00:00
// TODO support challenges?
2015-11-30 19:14:53 +00:00
/ * *
2015-12-02 10:22:53 +00:00
* @ api { post } / tasks / move / : taskId / to / : position Move a task to a new position
2015-11-30 19:14:53 +00:00
* @ apiVersion 3.0 . 0
* @ apiName MoveTask
* @ apiGroup Task
*
* @ apiParam { UUID } taskId The task _id
* @ apiParam { Number } position Where to move the task ( - 1 means push to bottom )
*
2015-12-02 10:22:53 +00:00
* @ apiSuccess { object } empty An empty object
2015-11-30 19:14:53 +00:00
* /
api . moveTask = {
method : 'POST' ,
url : '/tasks/move/:taskId/to/:position' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2015-11-30 19:14:53 +00:00
req . checkParams ( 'taskId' , res . t ( 'taskIdRequired' ) ) . notEmpty ( ) . isUUID ( ) ;
req . checkParams ( 'position' , res . t ( 'positionRequired' ) ) . notEmpty ( ) . isNumeric ( ) ;
2015-12-03 16:48:32 +00:00
let validationErrors = req . validationErrors ( ) ;
2015-12-31 10:46:21 +00:00
if ( validationErrors ) throw validationErrors ;
2015-12-03 16:48:32 +00:00
2015-11-30 19:14:53 +00:00
let user = res . locals . user ;
let to = Number ( req . params . position ) ;
2015-12-31 10:46:21 +00:00
let task = await Tasks . Task . findOne ( {
2015-11-30 19:14:53 +00:00
_id : req . params . taskId ,
userId : user . _id ,
2015-12-31 10:46:21 +00:00
} ) . exec ( ) ;
if ( ! task ) throw new NotFound ( res . t ( 'taskNotFound' ) ) ;
if ( task . type === 'todo' && task . completed ) throw new NotFound ( res . t ( 'cantMoveCompletedTodo' ) ) ;
let order = user . tasksOrder [ ` ${ task . type } s ` ] ;
let currentIndex = order . indexOf ( task . _id ) ;
// If for some reason the task isn't ordered (should never happen)
// or if the task is moved to a non existing position
// or if the task is moved to postion -1 (push to bottom)
// -> push task at end of list
if ( currentIndex === - 1 || ! order [ to ] || to === - 1 ) {
order . push ( task . _id ) ;
} else {
let taskToMove = order . splice ( currentIndex , 1 ) [ 0 ] ;
order . splice ( to , 0 , taskToMove ) ;
}
2015-11-30 19:14:53 +00:00
2015-12-31 10:46:21 +00:00
await user . save ( ) ;
res . respond ( 200 , { } ) ; // TODO what to return
2015-11-30 19:14:53 +00:00
} ,
} ;
2015-11-30 16:25:40 +00:00
/ * *
2015-12-03 18:19:53 +00:00
* @ api { post } / tasks / : taskId / checklist Add an item to a checklist , creating the checklist if it doesn ' t exist
2015-11-30 16:25:40 +00:00
* @ apiVersion 3.0 . 0
* @ apiName AddChecklistItem
* @ apiGroup Task
*
* @ apiParam { UUID } taskId The task _id
*
* @ apiSuccess { object } task The updated task
* /
api . addChecklistItem = {
method : 'POST' ,
2015-12-03 18:19:53 +00:00
url : '/tasks/:taskId/checklist' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2015-11-30 16:25:40 +00:00
let user = res . locals . user ;
2016-01-04 19:48:48 +00:00
let challenge ;
2015-11-30 16:25:40 +00:00
req . checkParams ( 'taskId' , res . t ( 'taskIdRequired' ) ) . notEmpty ( ) . isUUID ( ) ;
// TODO check that req.body isn't empty and is an array
2015-12-03 16:48:32 +00:00
let validationErrors = req . validationErrors ( ) ;
2015-12-31 10:46:21 +00:00
if ( validationErrors ) throw validationErrors ;
2015-12-03 16:48:32 +00:00
2015-12-31 10:46:21 +00:00
let task = await Tasks . Task . findOne ( {
2015-11-30 16:25:40 +00:00
_id : req . params . taskId ,
2015-12-31 10:46:21 +00:00
} ) . exec ( ) ;
2016-01-04 19:48:48 +00:00
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' ) ) ;
}
2015-12-31 10:46:21 +00:00
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
2016-01-04 19:48:48 +00:00
if ( challenge ) challenge . updateTask ( savedTask ) ;
2015-11-30 16:25:40 +00:00
} ,
} ;
/ * *
* @ api { post } / tasks / : taskId / checklist / : itemId / score Score a checklist item
* @ apiVersion 3.0 . 0
* @ apiName ScoreChecklistItem
* @ apiGroup Task
*
* @ apiParam { UUID } taskId The task _id
* @ apiParam { UUID } itemId The checklist item _id
*
* @ apiSuccess { object } task The updated task
* /
api . scoreCheckListItem = {
method : 'POST' ,
url : '/tasks/:taskId/checklist/:itemId/score' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2015-11-30 16:25:40 +00:00
let user = res . locals . user ;
req . checkParams ( 'taskId' , res . t ( 'taskIdRequired' ) ) . notEmpty ( ) . isUUID ( ) ;
req . checkParams ( 'itemId' , res . t ( 'itemIdRequired' ) ) . notEmpty ( ) . isUUID ( ) ;
2015-12-03 16:48:32 +00:00
let validationErrors = req . validationErrors ( ) ;
2015-12-31 10:46:21 +00:00
if ( validationErrors ) throw validationErrors ;
2015-12-03 16:48:32 +00:00
2015-12-31 10:46:21 +00:00
let task = await Tasks . Task . findOne ( {
2015-11-30 16:25:40 +00:00
_id : req . params . taskId ,
userId : user . _id ,
2015-12-31 10:46:21 +00:00
} ) . exec ( ) ;
if ( ! task ) 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 } ) ;
if ( ! item ) throw new NotFound ( res . t ( 'checklistItemNotFound' ) ) ;
item . completed = ! item . completed ;
let savedTask = await task . save ( ) ;
res . respond ( 200 , savedTask ) ; // TODO what to return
2015-11-30 16:25:40 +00:00
} ,
} ;
/ * *
* @ api { put } / tasks / : taskId / checklist / : itemId Update a checklist item
* @ apiVersion 3.0 . 0
* @ apiName UpdateChecklistItem
* @ apiGroup Task
*
* @ apiParam { UUID } taskId The task _id
* @ apiParam { UUID } itemId The checklist item _id
*
* @ apiSuccess { object } task The updated task
* /
api . updateChecklistItem = {
method : 'PUT' ,
url : '/tasks/:taskId/checklist/:itemId' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2015-11-30 16:25:40 +00:00
let user = res . locals . user ;
2016-01-04 19:48:48 +00:00
let challenge ;
2015-11-30 16:25:40 +00:00
req . checkParams ( 'taskId' , res . t ( 'taskIdRequired' ) ) . notEmpty ( ) . isUUID ( ) ;
req . checkParams ( 'itemId' , res . t ( 'itemIdRequired' ) ) . notEmpty ( ) . isUUID ( ) ;
2015-12-03 16:48:32 +00:00
let validationErrors = req . validationErrors ( ) ;
2015-12-31 10:46:21 +00:00
if ( validationErrors ) throw validationErrors ;
2015-12-03 16:48:32 +00:00
2015-12-31 10:46:21 +00:00
let task = await Tasks . Task . findOne ( {
2015-11-30 16:25:40 +00:00
_id : req . params . taskId ,
2015-12-31 10:46:21 +00:00
} ) . exec ( ) ;
2016-01-04 19:48:48 +00:00
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' ) ) ;
}
2015-12-31 10:46:21 +00:00
if ( task . type !== 'daily' && task . type !== 'todo' ) throw new BadRequest ( res . t ( 'checklistOnlyDailyTodo' ) ) ;
let item = _ . find ( task . checklist , { _id : req . params . itemId } ) ;
if ( ! item ) throw new NotFound ( res . t ( 'checklistItemNotFound' ) ) ;
_ . merge ( item , Tasks . Task . sanitizeChecklist ( req . body ) ) ;
let savedTask = await task . save ( ) ;
res . respond ( 200 , savedTask ) ; // TODO what to return
2016-01-04 19:48:48 +00:00
if ( challenge ) challenge . updateTask ( savedTask ) ;
2015-11-30 16:25:40 +00:00
} ,
} ;
/ * *
* @ api { delete } / tasks / : taskId / checklist / : itemId Remove a checklist item
* @ apiVersion 3.0 . 0
* @ apiName RemoveChecklistItem
* @ apiGroup Task
*
* @ apiParam { UUID } taskId The task _id
* @ apiParam { UUID } itemId The checklist item _id
*
* @ apiSuccess { object } empty An empty object
* /
api . removeChecklistItem = {
method : 'DELETE' ,
url : '/tasks/:taskId/checklist/:itemId' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2015-11-30 16:25:40 +00:00
let user = res . locals . user ;
2016-01-04 19:48:48 +00:00
let challenge ;
2015-11-30 16:25:40 +00:00
req . checkParams ( 'taskId' , res . t ( 'taskIdRequired' ) ) . notEmpty ( ) . isUUID ( ) ;
req . checkParams ( 'itemId' , res . t ( 'itemIdRequired' ) ) . notEmpty ( ) . isUUID ( ) ;
2015-12-03 16:48:32 +00:00
let validationErrors = req . validationErrors ( ) ;
2015-12-31 10:46:21 +00:00
if ( validationErrors ) throw validationErrors ;
2015-12-03 16:48:32 +00:00
2015-12-31 10:46:21 +00:00
let task = await Tasks . Task . findOne ( {
2015-11-30 16:25:40 +00:00
_id : req . params . taskId ,
2015-12-31 10:46:21 +00:00
} ) . exec ( ) ;
2016-01-04 19:48:48 +00:00
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' ) ) ;
}
2015-12-31 10:46:21 +00:00
if ( task . type !== 'daily' && task . type !== 'todo' ) throw new BadRequest ( res . t ( 'checklistOnlyDailyTodo' ) ) ;
let itemI = _ . findIndex ( task . checklist , { _id : req . params . itemId } ) ;
if ( itemI === - 1 ) throw new NotFound ( res . t ( 'checklistItemNotFound' ) ) ;
task . checklist . splice ( itemI , 1 ) ;
2016-01-04 19:48:48 +00:00
let savedTask = await task . save ( ) ;
2015-12-31 10:46:21 +00:00
res . respond ( 200 , { } ) ; // TODO what to return
2016-01-04 19:48:48 +00:00
if ( challenge ) challenge . updateTask ( savedTask ) ;
2015-11-30 16:25:40 +00:00
} ,
} ;
2015-12-03 18:19:53 +00:00
/ * *
* @ 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' ,
2015-12-14 21:44:50 +00:00
url : '/tasks/:taskId/tags/:tagId' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2015-12-03 18:19:53 +00:00
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 ( ) ;
2015-12-31 10:46:21 +00:00
if ( validationErrors ) throw validationErrors ;
2015-12-03 18:19:53 +00:00
2015-12-31 10:46:21 +00:00
let task = await Tasks . Task . findOne ( {
2015-12-03 18:19:53 +00:00
_id : req . params . taskId ,
userId : user . _id ,
2015-12-31 10:46:21 +00:00
} ) . exec ( ) ;
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 ) ;
let savedTask = await task . save ( ) ;
res . respond ( 200 , savedTask ) ; // TODO what to return
2015-12-03 18:19:53 +00:00
} ,
} ;
/ * *
* @ 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' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2015-12-03 18:19:53 +00:00
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 ( ) ;
2015-12-31 10:46:21 +00:00
if ( validationErrors ) throw validationErrors ;
2015-12-03 18:19:53 +00:00
2015-12-31 10:46:21 +00:00
let task = await Tasks . Task . findOne ( {
2015-12-03 18:19:53 +00:00
_id : req . params . taskId ,
userId : user . _id ,
2015-12-31 10:46:21 +00:00
} ) . exec ( ) ;
if ( ! task ) throw new NotFound ( res . t ( 'taskNotFound' ) ) ;
let tagI = task . tags . indexOf ( req . params . tagId ) ;
if ( tagI === - 1 ) throw new NotFound ( res . t ( 'tagNotFound' ) ) ;
task . tags . splice ( tagI , 1 ) ;
await task . save ( ) ;
res . respond ( 200 , { } ) ; // TODO what to return
2015-12-03 18:19:53 +00:00
} ,
} ;
2016-01-04 19:48:48 +00:00
// Remove a task from (user|challenge).tasksOrder
function _removeTaskTasksOrder ( userOrChallenge , taskId ) {
2015-11-29 16:47:10 +00:00
// Loop through all lists and when the task is found, remove it and return
2015-11-30 18:38:53 +00:00
for ( let i = 0 ; i < Tasks . tasksTypes . length ; i ++ ) {
2016-01-04 19:48:48 +00:00
let list = userOrChallenge . tasksOrder [ ` ${ Tasks . tasksTypes [ i ] } s ` ] ;
2015-11-29 16:47:10 +00:00
let index = list . indexOf ( taskId ) ;
if ( index !== - 1 ) {
list . splice ( index , 1 ) ;
break ;
}
}
return ;
}
/ * *
2015-11-29 17:29:35 +00:00
* @ api { delete } / task / : taskId Delete a user task given its id
2015-11-29 16:47:10 +00:00
* @ apiVersion 3.0 . 0
* @ apiName DeleteTask
* @ apiGroup Task
*
* @ apiParam { UUID } taskId The task _id
*
* @ apiSuccess { object } empty An empty object
* /
api . deleteTask = {
2015-12-06 15:51:18 +00:00
method : 'DELETE' ,
2015-11-29 16:47:10 +00:00
url : '/tasks/:taskId' ,
2015-12-15 11:11:32 +00:00
middlewares : [ authWithHeaders ( ) , cron ] ,
2015-12-31 10:46:21 +00:00
async handler ( req , res ) {
2015-11-29 16:47:10 +00:00
let user = res . locals . user ;
2016-01-04 19:48:48 +00:00
let challenge ;
2015-11-29 16:47:10 +00:00
req . checkParams ( 'taskId' , res . t ( 'taskIdRequired' ) ) . notEmpty ( ) . isUUID ( ) ;
2015-12-03 16:48:32 +00:00
let validationErrors = req . validationErrors ( ) ;
2015-12-31 10:46:21 +00:00
if ( validationErrors ) throw validationErrors ;
2015-12-03 17:15:22 +00:00
2015-12-31 10:46:21 +00:00
let task = await Tasks . Task . findOne ( {
2015-11-29 17:29:35 +00:00
_id : req . params . taskId ,
2015-12-31 10:46:21 +00:00
} ) . exec ( ) ;
2016-01-04 19:48:48 +00:00
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' ) ) ;
}
2015-12-31 10:46:21 +00:00
2016-01-04 19:48:48 +00:00
_removeTaskTasksOrder ( challenge || user , req . params . taskId ) ;
2015-12-31 10:46:21 +00:00
await Q . all ( [ user . save ( ) , task . remove ( ) ] ) ;
res . respond ( 200 , { } ) ;
2016-01-04 19:48:48 +00:00
if ( challenge ) challenge . removeTask ( task ) ;
2015-11-29 16:47:10 +00:00
} ,
} ;
2015-11-28 16:47:32 +00:00
export default api ;