From e7d200f1b0ce5f1ef85858f96f273c88ba4412e2 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sat, 10 Aug 2013 16:19:59 -0400 Subject: [PATCH] update apiv2 to use async.series instead of the previous count method. much better flow control --- src/server/apiv2.coffee | 141 ++++++++++++++++++++-------------------- 1 file changed, 70 insertions(+), 71 deletions(-) diff --git a/src/server/apiv2.coffee b/src/server/apiv2.coffee index d131c309e2..96017b9424 100644 --- a/src/server/apiv2.coffee +++ b/src/server/apiv2.coffee @@ -1,6 +1,7 @@ express = require 'express' router = new express.Router() util = require 'util' +async = require 'async' _ = require 'lodash' algos = require 'habitrpg-shared/script/algos' @@ -26,78 +27,76 @@ POST new actions router.post '/', api.auth, (req, res, next) -> model = req.getModel() {user} = req - actions = req.body - doneCount = 1 + # cron - _.size(actions) # standard operations - done = (err) -> + performAction = (action, cb) -> + task = action.task ? {} + switch action.op + when "cron" + misc.batchTxn model, (uObj, paths) -> + # habitrpg-shared/algos requires uObj.habits, uObj.dailys etc instead of uObj.tasks + _.each ['habit','daily','todo','reward'], (type) -> uObj["#{type}s"] = _.where(uObj.tasks, {type}); true + algos.cron uObj, {paths} + , {user, cb, cron:true} + + when "score" + return cb() unless user.get "tasks.#{task.id}" + sendScore = -> api.score(model, user, task.id, action.dir, cb) + if task.type in ["daily","todo"] + # switch completed state. Since checkbox is not binded to model unlike when you click through Derby website. + completed = if action.dir is "up" then true else false + user.set "tasks.#{task.id}.completed", completed, sendScore + else sendScore() + + when "sortTask" + path = action.task.type + "Ids" + a = user.get(path) + a.splice(action.to, 0, a.splice(action.from, 1)[0]) + user.set path, a, cb + + when "addTask" + api.addTask user, task, cb + + when "delTask" + api.deleteTask user, task, cb + + # this API is only working with string or number variables. It should return error if object given or object is at the path. + when "set" + oldValue = user.get(action.path) + if _.isObject(action.value) or _.isObject(oldValue) + console.error "action.value was an object, which isn't currently supported. Tyler - double check this" + cb() + else + user.set action.path, action.value, cb + + when "revive" + [uObj, paths] = [user.get(), {}] + algos.revive uObj, {paths} + setOps = _.map paths, (v,k) -> + (reviveCb) -> user.set k, helpers.dotGet(k,uObj), reviveCb + console.log setOps + async.parallel setOps, cb + + else + cb() + + # Setup the array of functions we're going to call in parallel with async + req.body = [] if _.isEmpty req.body + actions = _.transform (req.body or []), (result, action) -> + unless _.isEmpty(action) + result.push (cb) -> performAction(action, cb) + # always run cron check + req.body.unshift({op: 'cron'}) unless _.isEmpty actions + + # call all the operations, then return the user object to the requester + async.series actions, (err) -> return next(err) if err - if --doneCount is 0 - uObj = misc.hydrate user.get() - #transform user structure FROM user.tasks{} + user.habitIds[] TO user.habits[] + user.todos[] etc. - _.each ['habit','daily','todo','reward'], (type) -> - uObj["#{type}s"] = _.transform uObj["#{type}Ids"], (result, tid) -> result.push(uObj.tasks[tid]) - delete uObj["#{type}Ids"] - delete uObj.tasks - res.json 200, uObj - console.log "Reply sent" - - notEmpty = _.find actions, ((action) -> !_.isEmpty(action)) - unless notEmpty - # yan's strange User.log({}) thing - doneCount = 1 - return done() - - misc.batchTxn model, (uObj, paths) -> - # habitrpg-shared/algos requires uObj.habits, uObj.dailys etc instead of uObj.tasks - _.each ['habit','daily','todo','reward'], (type) -> uObj["#{type}s"] = _.where(uObj.tasks, {type}); true - algos.cron uObj, {paths} - , {user, done, cron:true} - - if _.isArray actions - actions.forEach (action)-> - - task = action.task ? {} - - return done() if _.isEmpty(action) - - switch action.op - when "score" - return done() unless user.get "tasks.#{task.id}" - sendScore = -> api.score(model, user, task.id, action.dir, done) - if task.type in ["daily","todo"] - # switch completed state. Since checkbox is not binded to model unlike when you click through Derby website. - completed = if action.dir is "up" then true else false - user.set "tasks.#{task.id}.completed", completed, sendScore - else sendScore() - - when "sortTask" - path = action.task.type + "Ids" - a = user.get(path) - a.splice(action.to, 0, a.splice(action.from, 1)[0]) - user.set path, a, done - - when "addTask" - api.addTask user, task, done - - when "delTask" - api.deleteTask user, task, done - - # this API is only working with string or number variables. It should return error if object given or object is at the path. - when "set" - oldValue = user.get(action.path) - if _.isObject(action.value) or _.isObject(oldValue) - console.error "action.value was an object, which isn't currently supported. Tyler - double check this" - else - user.set action.path, action.value, done - - when "revive" - [uObj, paths] = [user.get(), {}] - algos.revive uObj, {paths} - doneCount += (_.size(paths) - 1) # once for each path, but +1 is already accounted for at top of this function (whole 'revive' counted as 1) - _.each paths, (v,k) -> - user.set k, helpers.dotGet(k,uObj), done - - else done() + uObj = misc.hydrate user.get() + #transform user structure FROM user.tasks{} + user.habitIds[] TO user.habits[] + user.todos[] etc. + _.each ['habit','daily','todo','reward'], (type) -> + uObj["#{type}s"] = _.transform uObj["#{type}Ids"], (result, tid) -> result.push(uObj.tasks[tid]) + delete uObj["#{type}Ids"] + delete uObj.tasks + res.json 200, uObj + console.log "Reply sent" module.exports = router