nitrotasks/nitro-server

View on GitHub
lib/controllers/tasks.js

Summary

Maintainability
F
3 days
Test Coverage
const passport = require('passport')
const logger = require('../logger.js')
const User = require('../models/user')
const List = require('../models/list')
const Task = require('../models/task')

const helpers = require('./helpers.js')
const tasksDetails = helpers.tasksDetails
const fullTaskAttributes = helpers.fullTaskAttributes

// allow updates to go through that are this many seconds behind
const UPDATE_THRESHOLD = 20000

const tasks = require('express').Router()
tasks.use(passport.authenticate('bearer', { session: false }))

tasks.get('/:listid', (req, res) => {
  tasksDetails(false, req.query, req.user, req.params.listid)
    .then(data => {
      if (typeof req.query.tasks !== 'undefined') {
        return res.send(data.tasks)
      }
      res.send(data)
    })
    .catch(err => {
      logger.error(
        {
          userId: req.user,
          listId: req.params.listid,
          err: err
        },
        'GET list'
      )
      res.status(err.code).send(err)
    })
})
tasks.get('/:listid/tasks', (req, res) => {
  tasksDetails(true, req.query, req.user, req.params.listid)
    .then(data => {
      res.send(data)
    })
    .catch(err => {
      logger.error(
        {
          userId: req.user,
          listId: req.params.listid,
          err: err
        },
        'GET tasks'
      )
      res.status(err.code).send(err)
    })
})

tasks.patch('/:listid', function(req, res) {
  const query = {
    attributes: [
      'id',
      'name',
      'sort',
      'updatedAt',
      'createdAt',
      'notes',
      'order'
    ],
    include: [
      {
        model: User,
        attributes: ['id', 'friendlyName', 'email'],
        where: {
          id: req.user
        }
      }
    ]
  }
  List.findById(req.params.listid, query)
    .then(function(list) {
      if (list) {
        if (
          list.updatedAt.getTime() - UPDATE_THRESHOLD <
          new Date(req.body.updatedAt).getTime()
        ) {
          const payload = {}
          const allowedUpdates = ['name', 'sort'] // notes not allowed for v1
          allowedUpdates.forEach(function(key) {
            if (key in req.body) {
              payload[key] = req.body[key]
              if (key === 'name' && list[key].slice(0, 9) === 'nitrosys-') {
                payload[key] = list[key]
              } else if (
                key === 'name' &&
                payload[key].slice(0, 9) === 'nitrosys-'
              ) {
                payload[key] = payload[key].slice(9)
              }
            }
          })
          // truncate names for STRING
          if ('name' in payload) {
            payload.name = (payload.name || '').substring(0, 255)
          }
          // order has a special update
          if ('order' in req.body) {
            // makes sure same length
            if (list.order.length === req.body.order.length) {
              let shouldUpdate = true
              // then goes through all the keys and sees if they're the same
              list.order.forEach(function(key) {
                if (req.body.order.indexOf(key) === -1) {
                  shouldUpdate = false
                }
              })
              if (shouldUpdate) {
                payload.order = req.body.order
              }
            }
          }
          list
            .update(payload)
            .then(function(list) {
              res.send(list)
            })
            .catch(function(err) {
              logger.error(
                {
                  userId: req.user,
                  listId: req.params.listid,
                  err: err
                },
                'PATCH list'
              )
              res.status(500).send({ message: 'Internal server error.' })
            })
        } else {
          res.send(list.toJSON())
        }
      } else {
        res.status(404).send({ message: 'List could not be found.' })
      }
    })
    .catch(function(err) {
      logger.error(
        {
          userId: req.user,
          listId: req.params.listid,
          err: err
        },
        'PATCH list'
      )
      res.status(400).send({ message: 'Invalid input syntax.' })
    })
})
tasks.post('/:listid', function(req, res) {
  const query = {
    attributes: ['id', 'order'],
    include: [
      {
        model: User,
        attributes: ['id'],
        where: {
          id: req.user
        }
      }
    ]
  }
  List.findById(req.params.listid, query)
    .then(function(list) {
      if (list) {
        // todo: proper validation?
        const adding = req.body.tasks.map(function(item) {
          return {
            name: (item.name || '').substring(0, 255),
            type: item.type,
            notes: (item.notes || '').substring(0, 51200), // 50kb
            completed: item.completed,
            date: item.date,
            deadline: item.deadline,
            // must be specified here, otherwise the relation isn't made
            listId: list.id
          }
        })
        // they have permission to add into that list
        Task.bulkCreate(adding, {
          validate: true
        })
          .then(function(done) {
            const result = JSON.parse(JSON.stringify(done))

            // pushes new tasks onto the end of the list order
            list
              .update({
                order: result
                  .map(function(item) {
                    return item.id
                  })
                  .reverse()
                  .concat(JSON.parse(JSON.stringify(list.order)))
              })
              .then(function(list) {
                res.send({
                  message: 'Created Tasks.',
                  tasks: result.map(function(item, key) {
                    item.originalId = req.body.tasks[key].id
                    item.priority = item.priority === null ? 0 : item.priority
                    return item
                  })
                })
              })
          })
          .catch(function(err) {
            logger.error(
              {
                userId: req.user,
                listId: req.params.listid,
                err: err
              },
              'POST list'
            )
            res.status(400).send(err)
          })
      } else {
        res.status(404).send({ message: 'List could not be found.' })
      }
    })
    .catch(function(err) {
      logger.error(
        {
          userId: req.user,
          listId: req.params.listid,
          err: err
        },
        'POST list'
      )
      res.status(400).send({ message: 'Invalid input syntax.' })
    })
})
tasks.patch('/:listid/tasks', function(req, res) {
  if (
    req.body &&
    'tasks' in req.body &&
    Object.keys(req.body.tasks).length > 0
  ) {
    List.findById(req.params.listid, {
      include: [
        {
          model: User,
          attributes: ['id'],
          where: {
            id: req.user
          }
        },
        {
          model: Task,
          attributes: fullTaskAttributes,
          where: {
            id: Object.keys(req.body.tasks)
          }
        }
      ]
    })
      .then(function(retrieved) {
        if (
          retrieved &&
          retrieved.tasks.length === Object.keys(req.body.tasks).length
        ) {
          const updateSingle = function(model) {
            return new Promise(function(resolve, reject) {
              const newData = req.body.tasks[model.id]
              if (
                model.updatedAt.getTime() - UPDATE_THRESHOLD <
                new Date(newData.updatedAt).getTime()
              ) {
                const payload = {}
                const allowedUpdates = [
                  'name',
                  'notes',
                  'type',
                  'completed',
                  'date',
                  'deadline',
                  'priority'
                ]
                allowedUpdates.forEach(function(key) {
                  if (key in newData) {
                    if (key === 'name') {
                      payload[key] = (newData[key] || '').substring(0, 255) // STRING
                    } else if (key === 'notes') {
                      payload[key] = (newData[key] || '').substring(0, 51200) // 50KB
                    } else if (key === 'priority') {
                      if (typeof newData[key] === 'number') {
                        payload[key] = Math.max(Math.min(newData[key], 3), 0) // needs to be between 3 & 0
                      }
                    } else {
                      payload[key] = newData[key]
                    }
                  }
                })
                // apply the updates
                model
                  .update(payload)
                  .then(resolve)
                  .catch(reject)
              } else {
                resolve(model)
              }
            })
          }
          const promises = retrieved.tasks.map(function(task) {
            return updateSingle(task)
          })
          Promise.all(promises)
            .then(function(data) {
              // set the update so the client can pick up on changes on this list
              retrieved.changed('updatedAt', true)
              retrieved
                .update({
                  updatedAt: new Date()
                })
                .then(function(list) {
                  res.send({
                    message: 'Update Success',
                    tasks: data.map(t => {
                      t.priority = t.priority === null ? 0 : t.priority
                      return t
                    })
                  })
                })
            })
            .catch(function(err) {
              logger.error(
                {
                  userId: req.user,
                  listId: req.params.listid,
                  err: err
                },
                'PATCH tasks'
              )
              res.status(500).send({ message: 'Internal Server Error' })
            })
        } else {
          const specifically = Object.keys(req.body.tasks)
          if (retrieved) {
            retrieved.tasks.forEach(function(item) {
              const index = specifically.indexOf(item.id)
              if (index > -1) {
                specifically.splice(index, 1)
              }
            })
          }
          res.status(404).send({
            message: 'Tasks could not be found.',
            items: specifically
          })
        }
      })
      .catch(function(err) {
        logger.error(
          {
            userId: req.user,
            listId: req.params.listid,
            err: err
          },
          'PATCH tasks'
        )
        res.status(400).send({ message: 'Invalid input syntax.' })
      })
  } else {
    res.status(400).send({ message: 'Tasks not supplied or empty.' })
  }
})
tasks.delete('/:listid', function(req, res) {
  if (req.body && 'tasks' in req.body && req.body.tasks.length > 0) {
    List.findById(req.params.listid, {
      attributes: ['id', 'order'],
      include: [
        {
          model: User,
          attributes: ['id'],
          where: {
            id: req.user
          }
        },
        {
          model: Task,
          attributes: ['id'],
          where: {
            id: req.body.tasks
          }
        }
      ]
    })
      .then(function(retrieved) {
        if (retrieved && retrieved.tasks.length === req.body.tasks.length) {
          // remove item if it's found
          const newOrder = JSON.parse(JSON.stringify(retrieved.order)).filter(
            function(item) {
              return req.body.tasks.indexOf(item) === -1
            }
          )
          retrieved
            .update({
              order: newOrder
            })
            .then(function() {
              Task.destroy({
                where: {
                  id: req.body.tasks
                }
              }).then(function(data) {
                res.send({
                  message: 'Successfully deleted tasks.',
                  data: req.body.tasks
                })
              })
            })
            .catch(function(err) {
              logger.error(
                {
                  userId: req.user,
                  listId: req.params.listid,
                  err: err
                },
                'DELETE list'
              )
              res.status(500).send({ message: 'An internal error occured.' })
            })
        } else {
          const specifically = req.body.tasks
          if (retrieved) {
            retrieved.tasks.forEach(function(item) {
              const index = specifically.indexOf(item.id)
              if (index > -1) {
                specifically.splice(index, 1)
              }
            })
          }
          res.status(404).send({
            message: 'Tasks could not be found.',
            items: specifically
          })
        }
      })
      .catch(function(err) {
        logger.error(
          {
            userId: req.user,
            listId: req.params.listid,
            err: err
          },
          'DELETE list'
        )
        res.status(400).send({ message: 'Invalid input syntax.' })
      })
  } else {
    res.status(400).send({ message: 'Tasks not supplied or empty.' })
  }
})
module.exports = tasks