2013-08-29 05:45:19 +00:00
/* @see ./routes.coffee for routing*/
2013-10-06 23:52:54 +00:00
var url = require ( 'url' ) ;
2013-10-13 16:46:22 +00:00
var ipn = require ( 'paypal-ipn' ) ;
2013-08-29 05:45:19 +00:00
var _ = require ( 'lodash' ) ;
var nconf = require ( 'nconf' ) ;
var async = require ( 'async' ) ;
2013-12-11 16:30:39 +00:00
var shared = require ( 'habitrpg-shared' ) ;
2013-08-29 05:45:19 +00:00
var User = require ( './../models/user' ) . model ;
2014-02-10 18:35:33 +00:00
var ga = require ( './../utils' ) . ga ;
2013-08-29 05:45:19 +00:00
var Group = require ( './../models/group' ) . model ;
2013-10-27 22:27:01 +00:00
var Challenge = require ( './../models/challenge' ) . model ;
2014-02-01 08:01:19 +00:00
var logging = require ( './../logging' ) ;
2013-12-10 15:55:04 +00:00
var acceptablePUTPaths ;
2013-08-29 05:45:19 +00:00
var api = module . exports ;
2014-03-22 22:04:30 +00:00
2013-12-15 21:49:22 +00:00
// api.purchase // Shared.ops
2013-09-06 04:28:18 +00:00
2013-12-22 19:09:44 +00:00
api . getContent = function ( req , res , next ) {
2014-05-15 21:15:57 +00:00
var language = req . query . language ; //|| 'en' in i18n
var content = _ . cloneDeep ( shared . content ) ;
var walk = function ( obj ) {
_ . each ( obj , function ( item , key , source ) {
if ( _ . isPlainObject ( item ) || _ . isArray ( item ) ) return walk ( item ) ;
if ( _ . isFunction ( item ) && item . i18nLangFunc ) source [ key ] = item ( language ) ;
} ) ;
}
walk ( content ) ;
res . json ( content ) ;
2013-12-22 19:09:44 +00:00
}
2014-07-17 20:09:39 +00:00
api . getModelPaths = function ( req , res , next ) {
res . json ( _ . reduce ( User . schema . paths , function ( m , v , k ) {
m [ k ] = v . instance || 'Boolean' ;
return m ;
} , { } ) ) ;
}
2013-08-29 05:45:19 +00:00
/ *
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Tasks
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* /
/ *
Local Methods
-- -- -- -- -- -- -- -
* /
2014-05-06 12:46:06 +00:00
var findTask = function ( req , res ) {
return res . locals . user . tasks [ req . params . id ] ;
2013-08-29 05:45:19 +00:00
} ;
/ *
API Routes
-- -- -- -- -- -- -- -
* /
2013-09-04 00:08:49 +00:00
/ * *
2013-08-29 05:45:19 +00:00
This is called form deprecated . coffee ' s score function , and the req . headers are setup properly to handle the login
Export it also so we can call it from deprecated . coffee
* /
2013-12-11 16:30:39 +00:00
api . score = function ( req , res , next ) {
2013-10-27 00:23:52 +00:00
var id = req . params . id ,
direction = req . params . direction ,
user = res . locals . user ,
task ;
2013-08-29 05:45:19 +00:00
2013-09-04 00:08:49 +00:00
// Send error responses for improper API call
2013-12-16 00:22:35 +00:00
if ( ! id ) return res . json ( 400 , { err : ':id required' } ) ;
2013-08-29 05:45:19 +00:00
if ( direction !== 'up' && direction !== 'down' ) {
2014-03-22 02:28:41 +00:00
if ( direction == 'unlink' || direction == 'sort' ) return next ( ) ;
2013-12-16 00:22:35 +00:00
return res . json ( 400 , { err : ":direction must be 'up' or 'down'" } ) ;
2013-08-29 05:45:19 +00:00
}
2013-10-27 00:23:52 +00:00
// If exists already, score it
2013-11-01 19:45:13 +00:00
if ( task = user . tasks [ id ] ) {
2013-10-27 00:23:52 +00:00
// Set completed if type is daily or todo and task exists
2013-11-01 19:45:13 +00:00
if ( task . type === 'daily' || task . type === 'todo' ) {
task . completed = direction === 'up' ;
2013-08-29 05:45:19 +00:00
}
} else {
2013-10-27 00:23:52 +00:00
// If it doesn't exist, this is likely a 3rd party up/down - create a new one, then score it
2013-12-25 17:36:18 +00:00
// Defaults. Other defaults are handled in user.ops.addTask()
2013-08-29 05:45:19 +00:00
task = {
id : id ,
2013-12-25 17:36:18 +00:00
type : req . body && req . body . type ,
text : req . body && req . body . text ,
2013-12-27 22:39:49 +00:00
notes : ( req . body && req . body . notes ) || "This task was created by a third-party service. Feel free to edit, it won't harm the connection to that service. Additionally, multiple services may piggy-back off this task."
2013-08-29 05:45:19 +00:00
} ;
2013-12-11 16:30:39 +00:00
task = user . ops . addTask ( { body : task } ) ;
2013-12-25 17:36:18 +00:00
if ( task . type === 'daily' || task . type === 'todo' )
task . completed = direction === 'up' ;
2013-08-29 05:45:19 +00:00
}
2014-04-15 16:35:54 +00:00
var delta = user . ops . score ( { params : { id : task . id , direction : direction } , language : req . language } ) ;
2013-12-20 22:43:10 +00:00
user . save ( function ( err , saved ) {
2014-02-06 00:26:09 +00:00
if ( err ) return next ( err ) ;
2013-12-28 18:37:20 +00:00
// TODO this should be return {_v,task,stats,_tmp}, instead of merging everything togther at top-level response
// However, this is the most commonly used API route, and changing it will mess with all 3rd party consumers. Bad idea :(
2013-09-17 18:44:02 +00:00
res . json ( 200 , _ . extend ( {
2013-12-20 22:43:10 +00:00
delta : delta ,
2013-12-28 18:37:20 +00:00
_tmp : user . _tmp
2013-08-29 05:45:19 +00:00
} , saved . toJSON ( ) . stats ) ) ;
2013-10-27 22:27:01 +00:00
2014-02-15 02:44:12 +00:00
// If it's a challenge task, sync the score. Do it in the background, we've already sent down a response
// and the user doesn't care what happens back there
if ( ! task . challenge || ! task . challenge . id || task . challenge . broken ) return ;
if ( task . type == 'reward' ) return ; // we don't want to update the reward GP cost
Challenge . findById ( task . challenge . id , 'habits dailys todos rewards' , function ( err , chal ) {
if ( err ) return next ( err ) ;
if ( ! chal ) {
task . challenge . broken = 'CHALLENGE_DELETED' ;
return user . save ( ) ;
}
var t = chal . tasks [ task . id ] ;
if ( ! t ) return chal . syncToUser ( user ) ; // this task was removed from the challenge, notify user
t . value += delta ;
if ( t . type == 'habit' || t . type == 'daily' )
t . history . push ( { value : t . value , date : + new Date } ) ;
chal . save ( ) ;
} ) ;
} ) ;
2013-08-29 05:45:19 +00:00
} ;
2013-10-27 00:23:52 +00:00
/ * *
* Get all tasks
* /
2013-08-29 05:45:19 +00:00
api . getTasks = function ( req , res , next ) {
2013-11-01 20:13:42 +00:00
var user = res . locals . user ;
2013-10-27 00:23:52 +00:00
if ( req . query . type ) {
return res . json ( user [ req . query . type + 's' ] ) ;
} else {
return res . json ( _ . toArray ( user . tasks ) ) ;
}
2013-08-29 05:45:19 +00:00
} ;
2013-10-27 00:23:52 +00:00
/ * *
* Get Task
* /
2013-08-29 05:45:19 +00:00
api . getTask = function ( req , res , next ) {
2013-12-15 21:49:22 +00:00
var task = findTask ( req , res ) ;
if ( ! task ) return res . json ( 404 , { err : "No task found." } ) ;
2013-08-29 05:45:19 +00:00
return res . json ( 200 , task ) ;
} ;
/ *
Update Task
* /
2013-12-15 21:49:22 +00:00
//api.deleteTask // see Shared.ops
2013-12-11 16:30:39 +00:00
// api.updateTask // handled in Shared.ops
// api.addTask // handled in Shared.ops
2013-12-13 00:32:54 +00:00
// api.sortTask // handled in Shared.ops #TODO updated api, mention in docs
2013-08-29 05:45:19 +00:00
/ *
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Items
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* /
2013-12-11 16:30:39 +00:00
// api.buy // handled in Shard.ops
2013-08-29 05:45:19 +00:00
2014-08-25 16:23:04 +00:00
api . getBuyList = function ( req , res , next ) {
var list = shared . updateStore ( res . locals . user ) ;
return res . json ( 200 , list ) ;
} ;
2013-08-29 05:45:19 +00:00
/ *
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
User
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* /
2013-10-27 00:23:52 +00:00
/ * *
* Get User
* /
2013-08-29 05:45:19 +00:00
api . getUser = function ( req , res , next ) {
2013-09-04 14:19:55 +00:00
var user = res . locals . user . toJSON ( ) ;
2013-12-11 16:30:39 +00:00
user . stats . toNextLevel = shared . tnl ( user . stats . lvl ) ;
2013-08-29 05:45:19 +00:00
user . stats . maxHealth = 50 ;
2013-12-30 00:13:39 +00:00
user . stats . maxMP = res . locals . user . _statsComputed . maxMP ;
2013-08-29 05:45:19 +00:00
delete user . apiToken ;
if ( user . auth ) {
delete user . auth . hashed _password ;
delete user . auth . salt ;
}
return res . json ( 200 , user ) ;
} ;
2013-12-10 15:55:04 +00:00
/ * *
* This tells us for which paths users can call ` PUT /user ` ( or batch - update equiv , which use ` User.set() ` on our client ) .
* The trick here is to only accept leaf paths , not root / intermediate paths ( see http : //goo.gl/OEzkAs)
* FIXME - one - by - one we want to widdle down this list , instead replacing each needed set path with API operations
* /
acceptablePUTPaths = _ . reduce ( require ( './../models/user' ) . schema . paths , function ( m , v , leaf ) {
2013-12-15 21:01:57 +00:00
var found = _ . find ( 'achievements filters flags invitations lastCron party preferences profile stats' . split ( ' ' ) , function ( root ) {
2013-12-10 15:55:04 +00:00
return leaf . indexOf ( root ) == 0 ;
} ) ;
if ( found ) m [ leaf ] = true ;
return m ;
} , { } )
2014-01-31 19:54:37 +00:00
//// Uncomment this if we we want to disable GP-restoring (eg, holiday events)
//_.each('stats.gp'.split(' '), function(removePath){
// delete acceptablePUTPaths[removePath];
//})
2013-12-10 15:55:04 +00:00
2013-10-27 00:23:52 +00:00
/ * *
* Update user
2013-12-10 15:55:04 +00:00
* Send up PUT / user as ` req.body={path1:val, path2:val, etc} ` . Example :
* PUT / user { 'stats.hp' : 50 , 'tasks.TASK_ID.repeat.m' : false }
* See acceptablePUTPaths for which user paths are supported
2013-08-29 05:45:19 +00:00
* /
2013-12-11 16:30:39 +00:00
api . update = function ( req , res , next ) {
2013-12-10 15:55:04 +00:00
var user = res . locals . user ;
var errors = [ ] ;
if ( _ . isEmpty ( req . body ) ) return res . json ( 200 , user ) ;
2013-08-29 05:45:19 +00:00
_ . each ( req . body , function ( v , k ) {
2013-12-10 15:55:04 +00:00
if ( acceptablePUTPaths [ k ] )
2013-12-11 16:30:39 +00:00
user . fns . dotSet ( k , v ) ;
2013-12-10 15:55:04 +00:00
else
2013-12-16 00:22:35 +00:00
errors . push ( "path `" + k + "` was not saved, as it's a protected path. See https://github.com/HabitRPG/habitrpg/blob/develop/API.md for PUT /api/v2/user." ) ;
2013-08-29 05:45:19 +00:00
return true ;
} ) ;
2013-11-10 04:15:55 +00:00
user . save ( function ( err ) {
2014-01-31 04:39:19 +00:00
if ( ! _ . isEmpty ( errors ) ) return res . json ( 401 , { err : errors } ) ;
2014-02-06 00:26:09 +00:00
if ( err ) return next ( err ) ;
2013-11-10 04:15:55 +00:00
res . json ( 200 , user ) ;
2013-08-29 05:45:19 +00:00
} ) ;
} ;
api . cron = function ( req , res , next ) {
2013-12-24 17:25:09 +00:00
var user = res . locals . user ,
2013-12-25 04:57:56 +00:00
progress = user . fns . cron ( ) ,
2013-12-24 17:25:09 +00:00
ranCron = user . isModified ( ) ,
quest = shared . content . quests [ user . party . quest . key ] ;
2014-06-07 00:30:10 +00:00
2013-12-24 17:25:09 +00:00
if ( ranCron ) res . locals . wasModified = true ;
if ( ! ranCron ) return next ( null , user ) ;
2014-06-07 00:30:10 +00:00
Group . tavernBoss ( user , progress ) ;
2013-12-24 17:25:09 +00:00
if ( ! quest ) return user . save ( next ) ;
// If user is on a quest, roll for boss & player, or handle collections
// FIXME this saves user, runs db updates, loads user. Is there a better way to handle this?
async . waterfall ( [
function ( cb ) {
user . save ( cb ) ; // make sure to save the cron effects
} ,
2013-12-26 22:39:09 +00:00
function ( saved , count , cb ) {
2013-12-24 17:25:09 +00:00
var type = quest . boss ? 'boss' : 'collect' ;
2013-12-26 22:39:09 +00:00
Group [ type + 'Quest' ] ( user , progress , cb ) ;
2013-12-24 17:25:09 +00:00
} ,
function ( ) {
var cb = arguments [ arguments . length - 1 ] ;
// User has been updated in boss-grapple, reload
User . findById ( user . _id , cb ) ;
}
] , function ( err , saved ) {
user = res . locals . user = saved ;
next ( err , saved ) ;
} ) ;
2013-12-20 22:43:10 +00:00
2013-08-29 05:45:19 +00:00
} ;
2013-12-15 21:49:22 +00:00
// api.reroll // Shared.ops
// api.reset // Shared.ops
2013-09-08 19:11:04 +00:00
2014-02-06 04:28:46 +00:00
api [ 'delete' ] = function ( req , res , next ) {
2014-07-07 19:37:16 +00:00
var plan = res . locals . user . purchased . plan ;
if ( plan && plan . customerId && ! plan . dateTerminated )
2014-04-15 05:07:30 +00:00
return res . json ( 400 , { err : "You have an active subscription, cancel your plan before deleting your account." } ) ;
2013-09-08 19:17:49 +00:00
res . locals . user . remove ( function ( err ) {
2014-02-06 00:26:09 +00:00
if ( err ) return next ( err ) ;
2013-09-08 19:17:49 +00:00
res . send ( 200 ) ;
} )
2013-09-08 19:11:04 +00:00
}
2013-10-22 19:19:43 +00:00
/ *
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
2014-03-25 02:41:50 +00:00
Gems
2013-10-22 19:19:43 +00:00
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* /
2013-12-11 16:30:39 +00:00
// api.unlock // see Shared.ops
2013-10-22 19:19:43 +00:00
2014-02-06 04:28:46 +00:00
api . addTenGems = function ( req , res , next ) {
2013-10-31 22:14:19 +00:00
var user = res . locals . user ;
user . balance += 2.5 ;
user . save ( function ( err ) {
2014-02-06 00:26:09 +00:00
if ( err ) return next ( err ) ;
2013-10-31 22:14:19 +00:00
res . send ( 204 ) ;
} )
}
2013-08-29 05:45:19 +00:00
2013-09-09 04:04:02 +00:00
/ *
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Tags
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* /
2013-12-15 21:01:57 +00:00
// api.deleteTag // handled in Shared.ops
// api.addTag // handled in Shared.ops
// api.updateTag // handled in Shared.ops
2014-08-10 03:54:10 +00:00
// api.sortTag // handled in Shared.ops
2013-09-09 04:04:02 +00:00
2013-11-16 10:22:12 +00:00
/ *
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Spells
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* /
2014-02-06 04:28:46 +00:00
api . cast = function ( req , res , next ) {
2013-11-16 10:22:12 +00:00
var user = res . locals . user ;
2014-01-22 08:16:52 +00:00
var targetType = req . query . targetType ;
var targetId = req . query . targetId ;
2013-12-21 01:35:50 +00:00
var klass = shared . content . spells . special [ req . params . spell ] ? 'special' : user . stats . class
2013-12-20 17:25:23 +00:00
var spell = shared . content . spells [ klass ] [ req . params . spell ] ;
2014-01-30 04:37:59 +00:00
if ( ! spell ) return res . json ( 404 , { err : 'Spell "' + req . params . spell + '" not found.' } ) ;
2014-05-26 16:28:23 +00:00
if ( spell . mana > user . stats . mp ) return res . json ( 400 , { err : 'Not enough mana to cast spell' } ) ;
2013-11-16 10:22:12 +00:00
var done = function ( ) {
var err = arguments [ 0 ] ;
var saved = _ . size ( arguments == 3 ) ? arguments [ 2 ] : arguments [ 1 ] ;
2014-02-06 00:26:09 +00:00
if ( err ) return next ( err ) ;
2013-11-16 10:22:12 +00:00
res . json ( saved ) ;
}
2014-01-22 08:16:52 +00:00
switch ( targetType ) {
2013-11-16 10:22:12 +00:00
case 'task' :
2014-01-30 04:37:59 +00:00
if ( ! user . tasks [ targetId ] ) return res . json ( 404 , { err : 'Task "' + targetId + '" not found.' } ) ;
2014-01-22 08:16:52 +00:00
spell . cast ( user , user . tasks [ targetId ] ) ;
2013-11-16 10:22:12 +00:00
user . save ( done ) ;
break ;
case 'self' :
spell . cast ( user ) ;
user . save ( done ) ;
break ;
case 'party' :
2013-12-18 01:19:27 +00:00
case 'user' :
2013-11-16 10:22:12 +00:00
async . waterfall ( [
function ( cb ) {
2014-02-14 00:57:00 +00:00
Group . findOne ( { type : 'party' , members : { '$in' : [ user . _id ] } } ) . populate ( 'members' , 'profile.name stats achievements items.special' ) . exec ( cb ) ;
2013-11-16 10:22:12 +00:00
} ,
function ( group , cb ) {
2013-12-18 01:19:27 +00:00
// Solo player? let's just create a faux group for simpler code
var g = group ? group : { members : [ user ] } ;
var series = [ ] , found ;
2014-01-22 08:16:52 +00:00
if ( targetType == 'party' ) {
2013-12-18 01:19:27 +00:00
spell . cast ( user , g . members ) ;
series = _ . transform ( g . members , function ( m , v , k ) {
m . push ( function ( cb2 ) { v . save ( cb2 ) } ) ;
} ) ;
} else {
2014-01-22 08:16:52 +00:00
found = _ . find ( g . members , { _id : targetId } )
2013-12-18 01:19:27 +00:00
spell . cast ( user , found ) ;
series . push ( function ( cb2 ) { found . save ( cb2 ) } ) ;
}
if ( group ) {
series . push ( function ( cb2 ) {
2014-03-10 14:26:05 +00:00
var message = '`' + user . profile . name + ' casts ' + spell . text ( ) + ( targetType == 'user' ? ' on ' + found . profile . name : ' for the party' ) + '.`' ;
2013-12-20 22:43:10 +00:00
group . sendChat ( message ) ;
2013-12-18 01:19:27 +00:00
group . save ( cb2 ) ;
} )
}
2013-11-16 10:22:12 +00:00
async . series ( series , cb ) ;
2013-12-05 18:05:47 +00:00
} ,
function ( whatever , cb ) {
user . save ( cb ) ;
2013-11-16 10:22:12 +00:00
}
] , done ) ;
break ;
}
}
2013-12-11 16:30:39 +00:00
/ * *
* All other user . ops which can easily be mapped to habitrpg - shared / index . coffee , not requiring custom API - wrapping
* /
_ . each ( shared . wrap ( { } ) . ops , function ( op , k ) {
if ( ! api [ k ] ) {
api [ k ] = function ( req , res , next ) {
2013-12-28 18:37:20 +00:00
res . locals . user . ops [ k ] ( req , function ( err , response ) {
2013-12-13 00:38:01 +00:00
// If we want to send something other than 500, pass err as {code: 200, message: "Not enough GP"}
2013-12-11 16:30:39 +00:00
if ( err ) {
2014-02-06 00:26:09 +00:00
if ( ! err . code ) return next ( err ) ;
2013-12-13 00:38:01 +00:00
if ( err . code >= 400 ) return res . json ( err . code , { err : err . message } ) ;
// In the case of 200s, they're friendly alert messages like "You're pet has hatched!" - still send the op
2013-12-11 16:30:39 +00:00
}
2013-12-13 00:38:01 +00:00
res . locals . user . save ( function ( err ) {
2014-02-06 00:26:09 +00:00
if ( err ) return next ( err ) ;
2013-12-28 18:37:20 +00:00
res . json ( 200 , response ) ;
2013-12-13 00:38:01 +00:00
} )
2014-02-12 00:49:19 +00:00
} , ga ) ;
2013-12-11 16:30:39 +00:00
}
}
} )
2013-08-29 05:45:19 +00:00
/ *
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
Batch Update
Run a bunch of updates all at once
-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
* /
api . batchUpdate = function ( req , res , next ) {
2014-01-03 17:26:46 +00:00
if ( _ . isEmpty ( req . body ) ) req . body = [ ] ; // cases of {} or null
2013-12-12 22:58:01 +00:00
if ( req . body [ 0 ] && req . body [ 0 ] . data )
2014-01-31 17:27:02 +00:00
return res . json ( 501 , { err : "API has been updated, please refresh your browser or upgrade your mobile app." } )
2013-12-12 22:58:01 +00:00
2013-10-27 00:23:52 +00:00
var user = res . locals . user ;
var oldSend = res . send ;
var oldJson = res . json ;
2013-12-11 16:30:39 +00:00
2014-02-06 00:31:37 +00:00
// Stash user.save, we'll queue the save op till the end (so we don't overload the server)
var oldSave = user . save ;
user . save = function ( cb ) { cb ( null , user ) }
2013-08-29 05:45:19 +00:00
2013-10-27 00:23:52 +00:00
// Setup the array of functions we're going to call in parallel with async
2014-02-06 00:31:37 +00:00
res . locals . ops = [ ] ;
var ops = _ . transform ( req . body , function ( m , _req ) {
if ( _ . isEmpty ( _req ) ) return ;
2014-04-15 15:48:58 +00:00
_req . language = req . language ;
2014-02-06 00:31:37 +00:00
m . push ( function ( ) {
var cb = arguments [ arguments . length - 1 ] ;
res . locals . ops . push ( _req ) ;
res . send = res . json = function ( code , data ) {
if ( _ . isNumber ( code ) && code >= 500 )
return cb ( code + ": " + ( data . message ? data . message : data . err ? data . err : JSON . stringify ( data ) ) ) ;
return cb ( ) ;
} ;
2014-02-06 04:28:46 +00:00
api [ _req . op ] ( _req , res , cb ) ;
2014-02-06 00:31:37 +00:00
} ) ;
} )
// Finally, save user at the end
. concat ( function ( ) {
user . save = oldSave ;
user . save ( arguments [ arguments . length - 1 ] ) ;
2013-08-29 05:45:19 +00:00
} ) ;
2013-10-27 00:23:52 +00:00
// call all the operations, then return the user object to the requester
2014-10-01 21:43:50 +00:00
async . waterfall ( ops , function ( err , _user ) {
2013-08-29 05:45:19 +00:00
res . json = oldJson ;
res . send = oldSend ;
2014-01-31 04:33:28 +00:00
if ( err ) return next ( err ) ;
2014-01-15 01:03:16 +00:00
2014-10-01 21:43:50 +00:00
var response = _user . toJSON ( ) ;
2013-08-29 05:45:19 +00:00
response . wasModified = res . locals . wasModified ;
2014-01-15 01:03:16 +00:00
2014-10-01 21:43:50 +00:00
user . fns . nullify ( ) ;
user = res . locals . user = oldSend = oldJson = oldSave = null ;
2014-01-15 01:03:16 +00:00
// return only drops & streaks
2013-11-20 20:36:18 +00:00
if ( response . _tmp && response . _tmp . drop ) {
res . json ( 200 , { _tmp : { drop : response . _tmp . drop } , _v : response . _v } ) ;
2014-01-15 01:03:16 +00:00
// Fetch full user object
2013-11-20 20:36:18 +00:00
} else if ( response . wasModified ) {
2014-01-15 01:03:16 +00:00
// Preen 3-day past-completed To-Dos from Angular & mobile app
response . todos = _ . where ( response . todos , function ( t ) {
2014-05-16 05:47:38 +00:00
return ! t . completed || ( t . challenge && t . challenge . id ) || moment ( t . dateCompleted ) . isAfter ( moment ( ) . subtract ( 'days' , 3 ) ) ;
2014-01-15 01:03:16 +00:00
} ) ;
2013-09-20 14:01:09 +00:00
res . json ( 200 , response ) ;
2014-01-15 01:03:16 +00:00
// return only the version number
2013-09-20 14:01:09 +00:00
} else {
2013-09-20 14:09:55 +00:00
res . json ( 200 , { _v : response . _v } ) ;
2013-09-20 14:01:09 +00:00
}
2013-08-29 05:45:19 +00:00
} ) ;
2014-02-01 08:01:19 +00:00
} ;