From 897cd3f2bb77523fb262c401c0c60e88d0704553 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Wed, 20 Feb 2013 13:35:21 -0500 Subject: [PATCH 01/40] wip --- package.json | 3 +- src/server/api.coffee | 27 ++++++++++++ test/api.mocha.coffee | 92 ++++++++++++++++++++++++++++++++++++++++ test/user.mocha.coffee | 96 +++++++++++++++++++++--------------------- 4 files changed, 169 insertions(+), 49 deletions(-) create mode 100644 test/api.mocha.coffee diff --git a/package.json b/package.json index 965ee1113e..9ead9904f8 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "nconf": "*", "icalendar": "git://github.com/lefnire/node-icalendar#master", "nodetime": "*", - "resolve": "~0.2.3" + "resolve": "~0.2.3", + "request": "~2.14.0" }, "private": true, "subdomain": "habitrpg", diff --git a/src/server/api.coffee b/src/server/api.coffee index f394945f09..50208154d8 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -13,6 +13,33 @@ icalendar = require('icalendar') curl -X POST -H "Content-Type:application/json" -d '{"apiToken":"{TOKEN}"}' localhost:3000/v1/users/{UID}/tasks/productivity/up ### +router.get '/users/:uid/tasks', (req, res) -> + {uid, taskId, direction} = req.params + {apiToken, title, service, icon} = req.body + console.log {params:req.params, body:req.body} if process.env.NODE_ENV == 'development' + + # Send error responses for improper API call + return res.send(500, 'request body "apiToken" required') unless apiToken + return res.send(500, ':uid required') unless uid + + model = req.getModel() + req._isServer = true + model.fetch model.query('users').withIdAndToken(uid, apiToken), (err, result) -> + return res.send(500, err) if err + user = result.at(0) + userObj = user.get() + if _.isEmpty(userObj) + return res.send(500, "User with uid=#{uid}, token=#{apiToken} not found. Make sure you're not using your username, but your User Id") + + model.ref('_user', user) + + # Create task if doesn't exist + # TODO add service & icon to task + unless model.get("_user.tasks") + model.refList "_habitList", "_user.tasks", "_user.habitIds" + return res.json model.get '_habitList' + + router.post '/users/:uid/tasks/:taskId/:direction', (req, res) -> {uid, taskId, direction} = req.params {apiToken, title, service, icon} = req.body diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee new file mode 100644 index 0000000000..95cbe7472b --- /dev/null +++ b/test/api.mocha.coffee @@ -0,0 +1,92 @@ +assert = require 'assert' +{BrowserModel: Model} = require 'racer/test/util/model' +derby = require 'derby' +_ = require 'underscore' +moment = require 'moment' +request = require 'request' + +# Custom modules +scoring = require '../src/app/scoring' +character = require '../src/app/character' + +###### Helpers & Variables ###### + +model = null +uuid = null +taskPath = null +baseURL = 'http://localhost:3000' + +## Helper which clones the content at a path so tests can compare before/after values +# Otherwise, using model.get(path) will give the same object before as after +pathSnapshots = (paths) -> + if _.isString(paths) + return clone(model.get(paths)) + _.map paths, (path) -> clone(model.get(path)) +statsTask = -> pathSnapshots(['_user.stats', taskPath]) # quick snapshot of user.stats & task + +cleanUserObj = -> + userObj = character.newUserObject() + userObj.tasks = {} + userObj.habitIds = [] + userObj.dailyIds = [] + userObj.todoIds = [] + userObj.rewardIds = [] + return userObj +resetUser = -> model.set '_user', cleanUserObj() + +freshTask = (taskObj) -> + resetUser() + # create a test task + uuid = derby.uuid() + taskPath = "_user.tasks.#{uuid}" + {type} = taskObj + model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" + [taskObj.id, taskObj.value] = [uuid, 0] + model.at("_#{type}List").push taskObj + +### +Helper function to determine if stats updates are numerically correct based on scoring +@direction: 'up' or 'down' +@options: The user stats modifiers and times to run, defaults to {times:1, modifiers:{lvl:1, weapon:0, armor:0}} +### +modificationsLookup = (direction, options = {}) -> + merged = _.defaults options, {times:1, lvl:1, weapon:0, armor:0} + {times, lvl, armor, weapon} = merged + userObj = cleanUserObj() + value = 0 + _.times times, (n) -> + delta = scoring.taskDeltaFormula(value, direction) + value += delta + if direction=='up' + gain = scoring.expModifier(delta, options) + userObj.stats.exp += gain + userObj.stats.money += gain + else + loss = scoring.hpModifier(delta, options) + userObj.stats.hp += loss + return {user:userObj, value:value} + +###### Specs ###### + +describe 'API', -> + model = null + + before -> + model = new Model + model.set '_user', character.newUserObject() + scoring.setModel model + + it '/v1/:uid/tasks returns correct user defaults', (done) -> + user = model.get '_user' + + request "#{baseURL}/#{user.id}/tasks", (err, res, body) -> + assert.ok !err + tasks = [] + + ['habit','daily'].map (type) -> + model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" + tasks.concat model.get "_#{type}List" + + console.log 'hi', tasks + assert.ok _.isEqual tasks, body + done() diff --git a/test/user.mocha.coffee b/test/user.mocha.coffee index 33b1917014..3f4cc1ec5c 100644 --- a/test/user.mocha.coffee +++ b/test/user.mocha.coffee @@ -8,7 +8,7 @@ moment = require 'moment' # Custom modules scoring = require '../src/app/scoring' schema = require '../src/app/schema' - + ###### Helpers & Variables ###### model = null @@ -19,11 +19,11 @@ taskPath = null # Otherwise, using model.get(path) will give the same object before as after pathSnapshots = (paths) -> if _.isString(paths) - return clone(model.get(paths)) + return clone(model.get(paths)) _.map paths, (path) -> clone(model.get(path)) statsTask = -> pathSnapshots(['_user.stats', taskPath]) # quick snapshot of user.stats & task -cleanUserObj = -> +cleanUserObj = -> userObj = schema.newUserObject() userObj.tasks = {} userObj.habitIds = [] @@ -42,14 +42,14 @@ freshTask = (taskObj) -> model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" [taskObj.id, taskObj.value] = [uuid, 0] model.at("_#{type}List").push taskObj - + ### Helper function to determine if stats updates are numerically correct based on scoring @direction: 'up' or 'down' -@options: The user stats modifiers and times to run, defaults to {times:1, modifiers:{lvl:1, weapon:0, armor:0}} +@options: The user stats modifiers and times to run, defaults to {times:1, modifiers:{lvl:1, weapon:0, armor:0}} ### modificationsLookup = (direction, options = {}) -> - merged = _.defaults options, {times:1, lvl:1, weapon:0, armor:0} + merged = _.defaults options, {times:1, lvl:1, weapon:0, armor:0} {times, lvl, armor, weapon} = merged userObj = cleanUserObj() value = 0 @@ -64,12 +64,12 @@ modificationsLookup = (direction, options = {}) -> loss = scoring.hpModifier(delta, options) userObj.stats.hp += loss return {user:userObj, value:value} - -###### Specs ###### + +###### Specs ###### describe 'User', -> model = null - + before -> model = new Model model.set '_user', schema.newUserObject() @@ -85,23 +85,23 @@ describe 'User', -> expect(_.size(user.dailyIds)).to.eql 3 expect(_.size(user.todoIds)).to.eql 1 expect(_.size(user.rewardIds)).to.eql 2 - - ##### Habits ##### + + ##### Habits ##### describe 'Tasks', -> - - beforeEach -> + + beforeEach -> resetUser() - + describe 'Habits', -> - - beforeEach -> + + beforeEach -> freshTask {type: 'habit', text: 'Habit', up: true, down: true} - + it 'created the habit', -> task = model.get(taskPath) expect(task.text).to.eql 'Habit' expect(task.value).to.eql 0 - + it 'test a few scoring numbers (this will change if constants / formulae change)', -> {user} = modificationsLookup('down') expect(user.stats.hp).to.eql 49 @@ -113,10 +113,10 @@ describe 'User', -> expect(user.stats.money).to.eql 1 {user} = modificationsLookup('up', {times:5}) expect(user.stats.exp).to.be.within(4,5) - + it 'made proper modifications when down-scored', -> ## Trial 1 - + shouldBe = modificationsLookup('down') scoring.score(uuid,'down') [stats, task] = statsTask() @@ -130,14 +130,14 @@ describe 'User', -> [stats, task] = statsTask() expect(stats.hp).to.be.eql shouldBe.user.stats.hp expect(task.value).to.eql shouldBe.value - + it 'made proper modifications when up-scored', -> # Up-score the habit [statsBefore, taskBefore] = statsTask() scoring.score(uuid, 'up') [statsAfter, taskAfter] = statsTask() - - # User should have gained Exp, GP + + # User should have gained Exp, GP expect(statsAfter.exp).to.be.greaterThan statsBefore.exp expect(statsAfter.money).to.be.greaterThan statsBefore.money # HP should not change @@ -145,9 +145,9 @@ describe 'User', -> # Task should have lost value expect(taskBefore.value).to.eql 0 expect(taskAfter.value).to.be.greaterThan taskBefore.value - + ## Trial 2 - taskBefore = pathSnapshots(taskPath) + taskBefore = pathSnapshots(taskPath) scoring.score(uuid, 'up') taskAfter = pathSnapshots(taskPath) # Should have lost in value @@ -155,54 +155,54 @@ describe 'User', -> # And lost more than trial 1 diff = Math.abs(taskAfter.value) - Math.abs(taskBefore.value) expect(diff).to.be.lessThan 1 - - it 'makes history entry for habit' + + it 'makes history entry for habit' it 'makes proper modifications each time when clicking + / - in rapid succession' # saw an issue here once, so test that it wasn't a fluke - + it 'should not modify certain attributes given certain conditions' # non up+down habits # what else? - + it 'should show "undo" notification if user unchecks completed daily' - - - describe 'Lvl & Items', -> - - beforeEach -> + + + describe 'Lvl & Items', -> + + beforeEach -> freshTask {type: 'habit', text: 'Habit', up: true, down: true} - + it 'modified damage based on lvl & armor' it 'modified exp/gp based on lvl & weapon' it 'always decreases hp with damage, regardless of stats/items' it 'always increases exp/gp with gain, regardless of stats/items' - + describe 'Dailies', -> - + beforeEach -> freshTask {type: 'daily', text: 'Daily', completed: false} - + it 'created the daily', -> task = model.get(taskPath) expect(task.text).to.eql 'Daily' expect(task.value).to.eql 0 - + it 'does proper calculations when daily is complete' it 'calculates dailys properly when they have repeat dates' - + runCron = (times, pass=1) -> # Set lastCron to days ago today = new moment() ago = new moment().subtract('days',times) model.set '_user.lastCron', ago.toDate() # Run run - scoring.cron() + scoring.cron() [stats, task] = statsTask() - + # Should have updated cron to today lastCron = moment(model.get('_user.lastCron')) expect(today.diff(lastCron, 'days')).to.eql 0 - + shouldBe = modificationsLookup('down', {times:times*pass}) # Should have updated points properly expect(Math.round(stats.hp)).to.be.eql Math.round(shouldBe.user.stats.hp) @@ -215,10 +215,10 @@ describe 'User', -> runCron(5) runCron(5, 2) - + #TODO clicking repeat dates on newly-created item doesn't refresh until you refresh the page #TODO dates on dailies is having issues, possibility: date cusps? my saturday exempts were set to exempt at 8pm friday - + describe 'Todos', -> describe 'Cron', -> it 'calls cron asyncronously' @@ -230,11 +230,11 @@ describe 'User', -> # stop passing in tallyFor, let moment().sod().toDate() be handled in scoring.score() it 'should defer saving user modifications until, save as aggregate values' # pass in commit parameter to scoring func, if true save right away, otherwise return aggregated array so can save in the end (so total hp loss, etc) - + describe 'Rewards', -> - + #### Require.js stuff, might be necessary to place in casper.coffee it "doesn't setup dependent functions until their modules are loaded, require.js callback" - # sortable, stripe, etc + # sortable, stripe, etc #TODO refactor as user->habits, user->dailys, user->todos, user->rewards \ No newline at end of file From f75e3662b9a358af4bb76eb59011bfbbd99b8335 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Wed, 20 Feb 2013 20:04:09 -0500 Subject: [PATCH 02/40] wi[ --- .gitignore | 3 +- package.json | 3 +- src/server/api.coffee | 78 +++++++----------------------------- src/server/deprecated.coffee | 72 +++++++++++++++++++++++++++++++++ src/server/index.coffee | 2 +- src/server/store.coffee | 14 +++---- test/api.mocha.coffee | 41 +++++++++++-------- 7 files changed, 124 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index f54c5f2a27..77291d9d0f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ node_modules #lib/ *.swp .idea* -config.json \ No newline at end of file +config.json +test/config.json \ No newline at end of file diff --git a/package.json b/package.json index 9ead9904f8..bc501098c9 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "icalendar": "git://github.com/lefnire/node-icalendar#master", "nodetime": "*", "resolve": "~0.2.3", - "request": "~2.14.0" + "request": "~2.14.0", + "querystring": "~0.1.0" }, "private": true, "subdomain": "habitrpg", diff --git a/src/server/api.coffee b/src/server/api.coffee index 50208154d8..b142ec09b3 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -5,6 +5,9 @@ scoring = require '../app/scoring' _ = require 'underscore' icalendar = require('icalendar') +NO_TOKEN_OR_UID = err: "You must include a token and uid (user id) in your request" +NO_USER_FOUND = err: "No user found." + # ---------- /v1 API ------------ # Every url added beneath router is prefaced by /v1 @@ -13,75 +16,24 @@ icalendar = require('icalendar') curl -X POST -H "Content-Type:application/json" -d '{"apiToken":"{TOKEN}"}' localhost:3000/v1/users/{UID}/tasks/productivity/up ### -router.get '/users/:uid/tasks', (req, res) -> - {uid, taskId, direction} = req.params - {apiToken, title, service, icon} = req.body - console.log {params:req.params, body:req.body} if process.env.NODE_ENV == 'development' +router.get '/status', (req, res) -> + res.json + status: 'up' - # Send error responses for improper API call - return res.send(500, 'request body "apiToken" required') unless apiToken - return res.send(500, ':uid required') unless uid +router.get '/user', (req, res) -> + { uid, token } = req.query + return res.json 500, NO_TOKEN_OR_UID unless uid || token model = req.getModel() - req._isServer = true - model.fetch model.query('users').withIdAndToken(uid, apiToken), (err, result) -> - return res.send(500, err) if err - user = result.at(0) - userObj = user.get() - if _.isEmpty(userObj) - return res.send(500, "User with uid=#{uid}, token=#{apiToken} not found. Make sure you're not using your username, but your User Id") + query = model.query('users').withIdAndToken(uid, token) - model.ref('_user', user) + model.fetch query, (err, user) -> + return res.json 500, err: err if err + return res.json 500, err: NO_USER_FOUND unless user - # Create task if doesn't exist - # TODO add service & icon to task - unless model.get("_user.tasks") - model.refList "_habitList", "_user.tasks", "_user.habitIds" - return res.json model.get '_habitList' + res.json user - -router.post '/users/:uid/tasks/:taskId/:direction', (req, res) -> - {uid, taskId, direction} = req.params - {apiToken, title, service, icon} = req.body - console.log {params:req.params, body:req.body} if process.env.NODE_ENV == 'development' - - # Send error responses for improper API call - return res.send(500, 'request body "apiToken" required') unless apiToken - return res.send(500, ':uid required') unless uid - return res.send(500, ':taskId required') unless taskId - return res.send(500, ":direction must be 'up' or 'down'") unless direction in ['up','down'] - - model = req.getModel() - req._isServer = true - model.fetch model.query('users').withIdAndToken(uid, apiToken), (err, result) -> - return res.send(500, err) if err - user = result.at(0) - userObj = user.get() - if _.isEmpty(userObj) - return res.send(500, "User with uid=#{uid}, token=#{apiToken} not found. Make sure you're not using your username, but your User Id") - - model.ref('_user', user) - - # Create task if doesn't exist - # TODO add service & icon to task - unless model.get("_user.tasks.#{taskId}") - model.refList "_habitList", "_user.tasks", "_user.habitIds" - model.at('_habitList').push - id: taskId - type: 'habit' - text: (title || taskId) - value: 0 - up: true - down: true - 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." - - scoring.setModel(model) - delta = scoring.score(taskId, direction) - result = model.get ('_user.stats') - result.delta = delta - res.send(result) - -router.get '/users/:uid/calendar.ics', (req, res) -> +router.get '/user/calendar.ics', (req, res) -> #return next() #disable for now {uid} = req.params {apiToken} = req.query diff --git a/src/server/deprecated.coffee b/src/server/deprecated.coffee index e8a5fb7340..44ae3d392c 100644 --- a/src/server/deprecated.coffee +++ b/src/server/deprecated.coffee @@ -1,6 +1,10 @@ express = require 'express' router = new express.Router() +scoring = require '../app/scoring' +_ = require 'underscore' +icalendar = require('icalendar') + # ---------- Deprecated Paths ------------ deprecatedMessage = 'This API is no longer supported, see https://github.com/lefnire/habitrpg/wiki/API for new protocol' @@ -9,4 +13,72 @@ router.get '/:uid/up/:score?', (req, res) -> res.send(500, deprecatedMessage) router.get '/:uid/down/:score?', (req, res) -> res.send(500, deprecatedMessage) router.post '/users/:uid/tasks/:taskId/:direction', (req, res) -> res.send(500, deprecatedMessage) +router.post '/v1/users/:uid/tasks/:taskId/:direction', (req, res) -> + {uid, taskId, direction} = req.params + {apiToken, title, service, icon} = req.body + console.log {params:req.params, body:req.body} if process.env.NODE_ENV == 'development' + + # Send error responses for improper API call + return res.send(500, 'request body "apiToken" required') unless apiToken + return res.send(500, ':uid required') unless uid + return res.send(500, ':taskId required') unless taskId + return res.send(500, ":direction must be 'up' or 'down'") unless direction in ['up','down'] + + model = req.getModel() + req._isServer = true + model.fetch model.query('users').withIdAndToken(uid, apiToken), (err, result) -> + return res.send(500, err) if err + user = result.at(0) + userObj = user.get() + if _.isEmpty(userObj) + return res.send(500, "User with uid=#{uid}, token=#{apiToken} not found. Make sure you're not using your username, but your User Id") + + model.ref('_user', user) + + # Create task if doesn't exist + # TODO add service & icon to task + unless model.get("_user.tasks.#{taskId}") + model.refList "_habitList", "_user.tasks", "_user.habitIds" + model.at('_habitList').push + id: taskId + type: 'habit' + text: (title || taskId) + value: 0 + up: true + down: true + 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." + + scoring.setModel(model) + delta = scoring.score(taskId, direction) + result = model.get ('_user.stats') + result.delta = delta + res.send(result) + +router.get '/v1/users/:uid/calendar.ics', (req, res) -> + #return next() #disable for now + {uid} = req.params + {apiToken} = req.query + + model = req.getModel() + query = model.query('users').withIdAndToken(uid, apiToken) + query.fetch (err, result) -> + return res.send(500, err) if err + tasks = result.at(0).get('tasks') + # tasks = result[0].tasks + tasksWithDates = _.filter tasks, (task) -> !!task.date + return res.send(500, "No events found") if _.isEmpty(tasksWithDates) + + ical = new icalendar.iCalendar() + ical.addProperty('NAME', 'HabitRPG') + _.each tasksWithDates, (task) -> + event = new icalendar.VEvent(task.id); + event.setSummary(task.text); + d = new Date(task.date) + d.date_only = true + event.setDate d + ical.addComponent event + res.type('text/calendar') + formattedIcal = ical.toString().replace(/DTSTART\:/g, 'DTSTART;VALUE=DATE:') + res.send(200, formattedIcal) + module.exports = router diff --git a/src/server/index.coffee b/src/server/index.coffee index 8a1b29826b..21b9a69d4a 100644 --- a/src/server/index.coffee +++ b/src/server/index.coffee @@ -107,7 +107,7 @@ mongo_store = new MongoStore {url: process.env.NODE_DB_URI}, -> .use(auth.middleware(strategies, options)) # Creates an express middleware from the app's routes .use(app.router()) - .use('/v1', require('./api').middleware) + .use('/api/v1', require('./api').middleware) .use(require('./static').middleware) .use(require('./deprecated').middleware) .use(expressApp.router) diff --git a/src/server/store.coffee b/src/server/store.coffee index 38476d72cc..1edada5125 100644 --- a/src/server/store.coffee +++ b/src/server/store.coffee @@ -53,14 +53,14 @@ userAccess = (store) -> Get user with API token ### REST = (store) -> - store.query.expose "users", "withIdAndToken", (id, apiToken) -> - @where("id").equals(id) - .where('apiToken').equals(apiToken) - .limit(1) + store.query.expose "users", "withIdAndToken", (uid, token) -> + @where('id').equals(uid) + .where('apiToken').equals(token) + .one - store.queryAccess "users", "withIdAndToken", (id, apiToken, accept, err) -> - return accept(true) unless @session?.userId # https://github.com/codeparty/racer/issues/37 - accept(true) # only user has id & token + store.queryAccess "users", "withIdAndToken", (id, token, accept, err) -> + return accept(true) if id && token + accept(false) # only user has id & token ### diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 95cbe7472b..ec3d5ca243 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -4,17 +4,22 @@ derby = require 'derby' _ = require 'underscore' moment = require 'moment' request = require 'request' +qs = require 'querystring' # Custom modules scoring = require '../src/app/scoring' character = require '../src/app/character' +config = require './config' ###### Helpers & Variables ###### model = null uuid = null taskPath = null -baseURL = 'http://localhost:3000' +baseURL = 'http://localhost:3000/api/v1' +UID_AND_TOKEN = + uid: config.uid + token: config.token ## Helper which clones the content at a path so tests can compare before/after values # Otherwise, using model.get(path) will give the same object before as after @@ -71,22 +76,26 @@ modificationsLookup = (direction, options = {}) -> describe 'API', -> model = null - before -> - model = new Model - model.set '_user', character.newUserObject() - scoring.setModel model + describe 'Without token or user id', -> - it '/v1/:uid/tasks returns correct user defaults', (done) -> - user = model.get '_user' + it '/api/v1/user', (done) -> + request.get { uri: "#{baseURL}/user" }, (err, res, body) -> + console.log "#{baseURL}/user", body + assert.ok !err + assert.equal res.statusCode, 500 + assert.ok body.err + done() - request "#{baseURL}/#{user.id}/tasks", (err, res, body) -> - assert.ok !err - tasks = [] + describe 'With token and user id', -> + before -> + model = new Model + model.set '_user', character.newUserObject() + scoring.setModel model - ['habit','daily'].map (type) -> - model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" - tasks.concat model.get "_#{type}List" + it '/api/v1/user', (done) -> + user = model.get '_user' - console.log 'hi', tasks - assert.ok _.isEqual tasks, body - done() + request.get { uri: "#{baseURL}/user?#{qs.stringify(UID_AND_TOKEN)}" }, (err, res, body) -> + assert.ok !err + assert.equal res.statusCode, 200 + done() From b92145f5a339146b9bbeb7aa3835420f07791040 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Wed, 20 Feb 2013 20:07:05 -0500 Subject: [PATCH 03/40] wip --- src/server/api.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index b142ec09b3..8d2d8e4875 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -29,7 +29,7 @@ router.get '/user', (req, res) -> model.fetch query, (err, user) -> return res.json 500, err: err if err - return res.json 500, err: NO_USER_FOUND unless user + return res.json 500, NO_USER_FOUND if !user || _.isEmpty(user) res.json user From ddff29ccf0d02899dce24b3afa75891e8820d3b2 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Thu, 21 Feb 2013 14:18:53 -0500 Subject: [PATCH 04/40] wip --- package.json | 4 +- server.js | 4 +- src/app/character.coffee | 80 ++++++++++++++++++------------------ src/server/api.coffee | 8 ++-- src/server/deprecated.coffee | 25 +++++++++++ src/server/index.coffee | 8 ++-- src/server/middleware.coffee | 3 +- src/server/store.coffee | 8 ++-- test/api.mocha.coffee | 67 ++++++++++++++++++++++-------- test/request/local.coffee | 25 +++++++++++ test/request/request.coffee | 74 +++++++++++++++++++++++++++++++++ 11 files changed, 234 insertions(+), 72 deletions(-) create mode 100644 test/request/local.coffee create mode 100644 test/request/request.coffee diff --git a/package.json b/package.json index bc501098c9..fb07f0753a 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,9 @@ "nodetime": "*", "resolve": "~0.2.3", "request": "~2.14.0", - "querystring": "~0.1.0" + "querystring": "~0.1.0", + "nock": "~0.15.2", + "superagent": "~0.12.4" }, "private": true, "subdomain": "habitrpg", diff --git a/server.js b/server.js index 5709a5db48..50493a836c 100644 --- a/server.js +++ b/server.js @@ -67,7 +67,9 @@ process.on('uncaughtException', function (error) { }); require('coffee-script') // remove intermediate compilation requirement -require('./src/server').listen(process.env.PORT || 3000, process.env.IP || '0.0.0.0'); +module.exports = server = require('./src/server') + +server.listen(process.env.PORT || 3000, process.env.IP || '0.0.0.0'); // Note: removed "up" module, which is default for development (but interferes with and production + PaaS) // Restore to 5310bb0 if I want it back (see https://github.com/codeparty/derby/issues/165#issuecomment-10405693) diff --git a/src/app/character.coffee b/src/app/character.coffee index 901e3cb518..16d1c40fc5 100644 --- a/src/app/character.coffee +++ b/src/app/character.coffee @@ -137,7 +137,7 @@ module.exports.updateUser = (model) -> batch = new BatchUpdate(model) user = batch.user obj = batch.obj() - tasks = obj.tasks + tasks = obj?.tasks # Remove corrupted tasks _.each tasks, (task, key) -> @@ -172,49 +172,49 @@ module.exports.BatchUpdate = BatchUpdate = (model) -> updates = {} { - user: user + user: user - obj: -> - obj ?= model.get 'users.'+user.get('id') - return obj + obj: -> + obj ?= model.get 'users.'+user.get('id') + return obj - startTransaction: -> - # start a batch transaction - nothing between now and @commit() will be set immediately - transactionInProgress = true - model._dontPersist = true - @obj() + startTransaction: -> + # start a batch transaction - nothing between now and @commit() will be set immediately + transactionInProgress = true + model._dontPersist = true + @obj() - ### - Handles updating the user model. If this is an en-mass operation (eg, server cron), changes are queued - but not actually set to the model. It also modifies userObj in case you need to access properties manually later. - If transaction not in progress, it just runs standard model.set() - ### - set: (path, val) -> - updates[path] = val if transactionInProgress - user.set(path, val) + ### + Handles updating the user model. If this is an en-mass operation (eg, server cron), changes are queued + but not actually set to the model. It also modifies userObj in case you need to access properties manually later. + If transaction not in progress, it just runs standard model.set() + ### + set: (path, val) -> + updates[path] = val if transactionInProgress + user.set(path, val) - ### - Hack to get around dom bindings being lost if parent objects are replaced whole-sale - eg, user.set('stats', {hp:50, exp:10...}) will break dom bindings, but user.set('stats.hp',50) is ok - ### - setStats: (stats) -> - stats ?= obj.stats - that = @ - _.each Object.keys(stats), (key) -> that.set "stats.#{key}", stats[key] + ### + Hack to get around dom bindings being lost if parent objects are replaced whole-sale + eg, user.set('stats', {hp:50, exp:10...}) will break dom bindings, but user.set('stats.hp',50) is ok + ### + setStats: (stats) -> + stats ?= obj.stats + that = @ + _.each Object.keys(stats), (key) -> that.set "stats.#{key}", stats[key] -# queue: (path, val) -> -# # Special function for setting object properties by string dot-notation. See http://stackoverflow.com/a/6394168/362790 -# arr = path.split('.') -# arr.reduce (curr, next, index) -> -# if (arr.length - 1) == index -# curr[next] = val -# curr[next] -# , obj + # queue: (path, val) -> + # # Special function for setting object properties by string dot-notation. See http://stackoverflow.com/a/6394168/362790 + # arr = path.split('.') + # arr.reduce (curr, next, index) -> + # if (arr.length - 1) == index + # curr[next] = val + # curr[next] + # , obj - commit: -> - model._dontPersist = false - # some hackery in our own branched racer-db-mongo, see findAndModify of lefnire/racer-db-mongo#habitrpg index.js - user.set "update__", updates - transactionInProgress = false - updates = {} + commit: -> + model._dontPersist = false + # some hackery in our own branched racer-db-mongo, see findAndModify of lefnire/racer-db-mongo#habitrpg index.js + user.set "update__", updates + transactionInProgress = false + updates = {} } diff --git a/src/server/api.coffee b/src/server/api.coffee index 8d2d8e4875..37da942522 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -21,17 +21,19 @@ router.get '/status', (req, res) -> status: 'up' router.get '/user', (req, res) -> + console.log 'hi' { uid, token } = req.query return res.json 500, NO_TOKEN_OR_UID unless uid || token model = req.getModel() query = model.query('users').withIdAndToken(uid, token) - model.fetch query, (err, user) -> + query.fetch (err, user) -> + self = user.get() return res.json 500, err: err if err - return res.json 500, NO_USER_FOUND if !user || _.isEmpty(user) + return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) - res.json user + res.json self router.get '/user/calendar.ics', (req, res) -> #return next() #disable for now diff --git a/src/server/deprecated.coffee b/src/server/deprecated.coffee index 44ae3d392c..8e497764ef 100644 --- a/src/server/deprecated.coffee +++ b/src/server/deprecated.coffee @@ -13,6 +13,31 @@ router.get '/:uid/up/:score?', (req, res) -> res.send(500, deprecatedMessage) router.get '/:uid/down/:score?', (req, res) -> res.send(500, deprecatedMessage) router.post '/users/:uid/tasks/:taskId/:direction', (req, res) -> res.send(500, deprecatedMessage) +router.get '/v1/users/:uid/tasks/:taskId/:direction', (req, res) -> + {uid, taskId, direction} = req.params + {apiToken} = req.query + {title, service, icon} = req.body + console.log {params:req.params, body:req.body} if process.env.NODE_ENV == 'development' + + # Send error responses for improper API call + return res.send(500, 'request body "apiToken" required') unless apiToken + return res.send(500, ':uid required') unless uid + return res.send(500, ':taskId required') unless taskId + return res.send(500, ":direction must be 'up' or 'down'") unless direction in ['up','down'] + + model = req.getModel() + req._isServer = true + model.fetch model.query('users').withIdAndToken(uid, apiToken), (err, result) -> + return res.send(500, err) if err + user = result.at(0) + userObj = user.get() + if _.isEmpty(userObj) + return res.send(500, "User with uid=#{uid}, token=#{apiToken} not found. Make sure you're not using your username, but your User Id") + + model.ref('_user', user) + + res.send(user) + router.post '/v1/users/:uid/tasks/:taskId/:direction', (req, res) -> {uid, taskId, direction} = req.params {apiToken, title, service, icon} = req.body diff --git a/src/server/index.coffee b/src/server/index.coffee index 54e2be229f..ab304c3c72 100644 --- a/src/server/index.coffee +++ b/src/server/index.coffee @@ -17,9 +17,9 @@ middleware = require './middleware' racer.io.set('transports', ['xhr-polling']) racer.ioClient.set('reconnection limit', 300000) # max reconect timeout to 5 minutes racer.set('bundleTimeout', 40000) -#unless process.env.NODE_ENV == 'production' -# racer.use(racer.logPlugin) -# derby.use(derby.logPlugin) +unless process.env.NODE_ENV == 'production' + racer.use(racer.logPlugin) + derby.use(derby.logPlugin) ## SERVER CONFIGURATION ## @@ -28,7 +28,7 @@ server = http.createServer expressApp module.exports = server derby.use require('racer-db-mongo') -store = derby.createStore +module.exports.habitStore = store = derby.createStore db: {type: 'Mongo', uri: process.env.NODE_DB_URI, safe:true} listen: server diff --git a/src/server/middleware.coffee b/src/server/middleware.coffee index 44f53b90d0..d74eb1953e 100644 --- a/src/server/middleware.coffee +++ b/src/server/middleware.coffee @@ -1,7 +1,6 @@ module.exports.splash = (req, res, next) -> # This was an API call, not a page load - return next() if req.is('json') - + return next() if /^\/api/.test req.path if !req.session.userId? and !req.query?.play? res.redirect('/splash.html') else diff --git a/src/server/store.coffee b/src/server/store.coffee index 1edada5125..0a10b1b7e8 100644 --- a/src/server/store.coffee +++ b/src/server/store.coffee @@ -54,12 +54,12 @@ userAccess = (store) -> ### REST = (store) -> store.query.expose "users", "withIdAndToken", (uid, token) -> - @where('id').equals(uid) + @byId(uid) .where('apiToken').equals(token) .one - store.queryAccess "users", "withIdAndToken", (id, token, accept, err) -> - return accept(true) if id && token + store.queryAccess "users", "withIdAndToken", (uid, token, accept, err) -> + return accept(true) if uid && token accept(false) # only user has id & token @@ -90,4 +90,4 @@ partySystem = (store) -> store.writeAccess "*", "parties.*", -> accept = arguments[arguments.length-2] - accept(true) \ No newline at end of file + accept(true) diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index ec3d5ca243..7da76094b5 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -1,11 +1,16 @@ assert = require 'assert' {BrowserModel: Model} = require 'racer/test/util/model' derby = require 'derby' +racer = require 'racer' _ = require 'underscore' moment = require 'moment' -request = require 'request' +request = require 'superagent' qs = require 'querystring' +app = require '../server' + +store = app.habitStore + # Custom modules scoring = require '../src/app/scoring' character = require '../src/app/character' @@ -75,27 +80,55 @@ modificationsLookup = (direction, options = {}) -> describe 'API', -> model = null + user = null + params = null describe 'Without token or user id', -> it '/api/v1/user', (done) -> - request.get { uri: "#{baseURL}/user" }, (err, res, body) -> - console.log "#{baseURL}/user", body - assert.ok !err - assert.equal res.statusCode, 500 - assert.ok body.err - done() + request(app) + .get("/api/v1/user") + .set('Accept', 'application/json') + .on('error', (err) -> + console.log 'err', err + ) + .end (res) -> + assert.equal res.statusCode, 500 + assert.ok JSON.parse(res.text).err + done() describe 'With token and user id', -> - before -> - model = new Model - model.set '_user', character.newUserObject() - scoring.setModel model + before (done) -> + model = store.createModel() + store.flush() + + uid = model.id() + user = character.newUserObject() + user.apiToken = derby.uuid() + model.set "users.#{uid}", user + user = model.get("users.#{uid}") + + params = + uid: user.id + token: user.apiToken + + done() + + after (done) -> + store.flush done it '/api/v1/user', (done) -> - user = model.get '_user' - - request.get { uri: "#{baseURL}/user?#{qs.stringify(UID_AND_TOKEN)}" }, (err, res, body) -> - assert.ok !err - assert.equal res.statusCode, 200 - done() + console.log params + request(app) + .get("/api/v1/user") + .set('Accept', 'application/json') + .query(params) + .on('error', (err) -> + console.log 'err', err + ) + .end (res) -> + console.log 'test', res.body + assert.ok !res.body.err + assert.equal res.statusCode, 200 + assert.ok res.body + done() diff --git a/test/request/local.coffee b/test/request/local.coffee new file mode 100644 index 0000000000..1431d12cd5 --- /dev/null +++ b/test/request/local.coffee @@ -0,0 +1,25 @@ +Request = require './request' +qs = require 'querystring' + +makeUrl = (path, params) -> + if typeof params is "object" + params = qs.stringify(params) + else + params = '' + return "http://localhost:3000/#{path}?#{params}" + +# url, params (optional), callback +get = (obj, callback) -> + obj.params ||= {} + obj.path ||= '' + + requestUrl = makeUrl obj.path, obj.params + + request = new Request requestUrl + + request.done (res) -> + callback null, JSON.parse(res) + + request.fire() + +module.exports = { get } diff --git a/test/request/request.coffee b/test/request/request.coffee new file mode 100644 index 0000000000..5eadd9178b --- /dev/null +++ b/test/request/request.coffee @@ -0,0 +1,74 @@ +http = require 'http' + +# ### Request +# +# The Request class is just a wrapper +# around the `http.request` function, +# providing a cleaner and more reusable +# interface. +# +# `constructor(options)` +# +# `options` can be anything `http.request` takes. +# +# Callbacks can be queued up by calling `request.done` +# and passing it a callback function to be invoked when the +# http request has completed +# +# Example: +# +# callback = (json) -> +# doSomethingWithJSON( json ) +# +# request = new Request('http://api.phish.net/api.js?') +# request.done (response) -> +# callback JSON.parse(response)[0] +# +# request.fire(data) # data refers to the post data +# +class Request + constructor: (@options) -> + @callbacks = {} + + fire: (data) -> + request = http.request(@options, @_handler) + request.on 'error', (err) => + for callback in @callbacks['fail'] + callback(err) + + # Send data with request + if data + request.write data + + request.end() + + this + + done: (callback) -> + push.call(this, 'done', callback) + + this + + fail: (callback) -> + push.call(this, 'fail', callback) + + this + + ########### + # PRIVATE # + ########### + + _handler: (response) => + data = '' + response.on 'data', (chunk) -> + data += chunk + response.on 'end', => + for callback in @callbacks['done'] + callback(data) + + + push = (type, callback) -> + @callbacks[type] ?= [] + @callbacks[type].push callback + +module.exports = Request From 08d57e32ffbadd4ba1638e0c6f1e4ca89cfef511 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Thu, 21 Feb 2013 20:17:23 -0500 Subject: [PATCH 05/40] wip --- src/server/api.coffee | 10 +++++++--- src/server/store.coffee | 2 +- test/api.mocha.coffee | 27 +++++++++++---------------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index 37da942522..808c25f32d 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -21,19 +21,23 @@ router.get '/status', (req, res) -> status: 'up' router.get '/user', (req, res) -> - console.log 'hi' - { uid, token } = req.query + console.log 'hi', { uid, token } = req.query return res.json 500, NO_TOKEN_OR_UID unless uid || token model = req.getModel() query = model.query('users').withIdAndToken(uid, token) query.fetch (err, user) -> + throw err if err + console.log user.at(0).get() self = user.get() + console.log self return res.json 500, err: err if err return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) - res.json self + return res.json self + res.json {err: 'Something went wrong'} + router.get '/user/calendar.ics', (req, res) -> #return next() #disable for now diff --git a/src/server/store.coffee b/src/server/store.coffee index 0a10b1b7e8..41ec5dd9ad 100644 --- a/src/server/store.coffee +++ b/src/server/store.coffee @@ -56,7 +56,7 @@ REST = (store) -> store.query.expose "users", "withIdAndToken", (uid, token) -> @byId(uid) .where('apiToken').equals(token) - .one + .limit(1) store.queryAccess "users", "withIdAndToken", (uid, token, accept, err) -> return accept(true) if uid && token diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 7da76094b5..4e73aeb3f6 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -7,9 +7,12 @@ moment = require 'moment' request = require 'superagent' qs = require 'querystring' -app = require '../server' +racer.use require 'racer-db-mongo' -store = app.habitStore +store = racer.createStore + db: + type: 'Mongo' + uri: process.env.NODE_DB_URI # Custom modules scoring = require '../src/app/scoring' @@ -86,12 +89,8 @@ describe 'API', -> describe 'Without token or user id', -> it '/api/v1/user', (done) -> - request(app) - .get("/api/v1/user") + request.get("#{baseURL}/user") .set('Accept', 'application/json') - .on('error', (err) -> - console.log 'err', err - ) .end (res) -> assert.equal res.statusCode, 500 assert.ok JSON.parse(res.text).err @@ -100,9 +99,9 @@ describe 'API', -> describe 'With token and user id', -> before (done) -> model = store.createModel() - store.flush() + #store.flush() - uid = model.id() + model.set '_userId', uid = model.id() user = character.newUserObject() user.apiToken = derby.uuid() model.set "users.#{uid}", user @@ -114,20 +113,16 @@ describe 'API', -> done() - after (done) -> - store.flush done - it '/api/v1/user', (done) -> - console.log params - request(app) - .get("/api/v1/user") + console.log "#{baseURL}/user?#{qs.stringify(params)}" + request.get("#{baseURL}/user") .set('Accept', 'application/json') .query(params) .on('error', (err) -> console.log 'err', err ) .end (res) -> - console.log 'test', res.body + console.log res.body assert.ok !res.body.err assert.equal res.statusCode, 200 assert.ok res.body From b8d43fabcc2e863dc8a2f81a16400b0ebce4773c Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Thu, 21 Feb 2013 20:38:12 -0500 Subject: [PATCH 06/40] wip --- src/server/api.coffee | 20 ++++++++++++++++---- src/server/store.coffee | 2 ++ test/api.mocha.coffee | 30 +++++++++++++++++++++++------- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index 808c25f32d..c2a227d346 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -28,16 +28,28 @@ router.get '/user', (req, res) -> query = model.query('users').withIdAndToken(uid, token) query.fetch (err, user) -> - throw err if err - console.log user.at(0).get() - self = user.get() - console.log self return res.json 500, err: err if err + self = user.at(0).get() + console.log self return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) return res.json self res.json {err: 'Something went wrong'} +router.post '/task', (req, res) -> + { uid, token } = req.body + return res.json 500, NO_TOKEN_OR_UID unless uid || token + + model = req.getModel() + query = model.query('users').withIdAndToken(uid, token) + + query.fetch (err, user) -> + return res.json 500, err: err if err + self = user.at(0).get() + return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) + + return res.json self + res.json {err: 'Something went wrong'} router.get '/user/calendar.ics', (req, res) -> #return next() #disable for now diff --git a/src/server/store.coffee b/src/server/store.coffee index 41ec5dd9ad..0dc2af090e 100644 --- a/src/server/store.coffee +++ b/src/server/store.coffee @@ -62,6 +62,8 @@ REST = (store) -> return accept(true) if uid && token accept(false) # only user has id & token + #store.query.expose + ### Party permissions diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 4e73aeb3f6..7a0b769529 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -110,20 +110,36 @@ describe 'API', -> params = uid: user.id token: user.apiToken + setTimeout -> + done() + , 800 - done() - - it '/api/v1/user', (done) -> + ### + test '/api/v1/user', (done) -> console.log "#{baseURL}/user?#{qs.stringify(params)}" - request.get("#{baseURL}/user") + _.defer -> + request.get("#{baseURL}/user") + .set('Accept', 'application/json') + .query(params) + .on('error', (err) -> + console.log 'err', err + ) + .end (res) -> + assert.ok !res.body.err + assert.equal res.statusCode, 200 + assert.ok res.body.tasks + done() + ### + it '/api/v1/task', (done) -> + request.post("#{baseURL}/task") .set('Accept', 'application/json') - .query(params) + .send(params) .on('error', (err) -> console.log 'err', err ) .end (res) -> - console.log res.body + #console.log 'task', res.body assert.ok !res.body.err assert.equal res.statusCode, 200 - assert.ok res.body + assert.ok res.body.tasks done() From 9f044d2cf4f2003be5f8ce073236e51fbc7f70f4 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Fri, 22 Feb 2013 13:54:35 -0500 Subject: [PATCH 07/40] tests --- test/api.mocha.coffee | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 7a0b769529..14d17ec457 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -110,9 +110,7 @@ describe 'API', -> params = uid: user.id token: user.apiToken - setTimeout -> - done() - , 800 + done() ### test '/api/v1/user', (done) -> From 55afba34ae56be12815c72fac6e5c0edf4b24d25 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Fri, 22 Feb 2013 13:55:09 -0500 Subject: [PATCH 08/40] Nothing went wrong --- src/server/api.coffee | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index c2a227d346..95cd0ddf79 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -34,7 +34,6 @@ router.get '/user', (req, res) -> return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) return res.json self - res.json {err: 'Something went wrong'} router.post '/task', (req, res) -> { uid, token } = req.body @@ -49,7 +48,6 @@ router.post '/task', (req, res) -> return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) return res.json self - res.json {err: 'Something went wrong'} router.get '/user/calendar.ics', (req, res) -> #return next() #disable for now From e557bf89a74b23b3ade79c06b995c1a056dd9d31 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Fri, 22 Feb 2013 13:56:24 -0500 Subject: [PATCH 09/40] Status test --- test/api.mocha.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 14d17ec457..a346c06370 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -88,6 +88,14 @@ describe 'API', -> describe 'Without token or user id', -> + it '/api/v1/status', (done) -> + request.get("#{baseURL}/status") + .set('Accept', 'application/json') + .end (res) -> + assert.equal res.statusCode, 200 + assert.equal res.body.status, 'up' + done() + it '/api/v1/user', (done) -> request.get("#{baseURL}/user") .set('Accept', 'application/json') From 32323a39afbab009b5615d72236fc1dd1ac21aa8 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Fri, 22 Feb 2013 22:11:46 -0500 Subject: [PATCH 10/40] wip --- server.js | 6 ++---- src/server/api.coffee | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/server.js b/server.js index 50493a836c..7aae084865 100644 --- a/server.js +++ b/server.js @@ -1,10 +1,8 @@ // Load nconf and define default configuration values if config.json or ENV vars are not found var conf = require('nconf'); conf.argv().env().file({ file: __dirname + "/config.json" }).defaults({ - 'PORT': 3000, - 'IP': '0.0.0.0', - 'BASE_URL': 'http://localhost', - 'NODE_ENV': 'development' + 'PORT': process.env.PORT, + 'IP': process.env.IP }); // Override normal ENV values with nconf ENV values (ENV values are used the same way without nconf) diff --git a/src/server/api.coffee b/src/server/api.coffee index 95cd0ddf79..7bbc457611 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -3,7 +3,10 @@ router = new express.Router() scoring = require '../app/scoring' _ = require 'underscore' -icalendar = require('icalendar') +validator = require 'validator' +check = validator.check +snaitize = validator.sanitize +icalendar = require 'icalendar' NO_TOKEN_OR_UID = err: "You must include a token and uid (user id) in your request" NO_USER_FOUND = err: "No user found." @@ -35,8 +38,9 @@ router.get '/user', (req, res) -> return res.json self -router.post '/task', (req, res) -> +router.post '/user/tasks', (req, res) -> { uid, token } = req.body + task = { title, text, type, value, note } = req.body return res.json 500, NO_TOKEN_OR_UID unless uid || token model = req.getModel() @@ -46,6 +50,13 @@ router.post '/task', (req, res) -> return res.json 500, err: err if err self = user.at(0).get() return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) + return res.json 500, err: "type must be habit, todo, daily, reward" unless check(type).is /habit|todo|daily|reward/ + return res.json 500, err: "must have a title" unless check(title).notEmpty() + return res.json 500, err: "must have text" unless check(text).notEmpty() + value ||= 0 + + model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" + model.push "_#{type}List", task return res.json self From 4358455ef237d2a99699c159485fe7550905b882 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Fri, 22 Feb 2013 22:24:21 -0500 Subject: [PATCH 11/40] update --- src/server/api.coffee | 4 ++-- test/api.mocha.coffee | 29 ++++++++++++++--------------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index 7bbc457611..979e1ead6b 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -54,11 +54,11 @@ router.post '/user/tasks', (req, res) -> return res.json 500, err: "must have a title" unless check(title).notEmpty() return res.json 500, err: "must have text" unless check(text).notEmpty() value ||= 0 - + model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" model.push "_#{type}List", task - return res.json self + return res.json task router.get '/user/calendar.ics', (req, res) -> #return next() #disable for now diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index a346c06370..3936315482 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -120,22 +120,21 @@ describe 'API', -> token: user.apiToken done() - ### - test '/api/v1/user', (done) -> + it '/api/v1/user', (done) -> console.log "#{baseURL}/user?#{qs.stringify(params)}" - _.defer -> - request.get("#{baseURL}/user") - .set('Accept', 'application/json') - .query(params) - .on('error', (err) -> - console.log 'err', err - ) - .end (res) -> - assert.ok !res.body.err - assert.equal res.statusCode, 200 - assert.ok res.body.tasks - done() - ### + request.get("#{baseURL}/user") + .set('Accept', 'application/json') + .query(params) + .on('error', (err) -> + console.log 'err', err + ) + .end (res) -> + assert.ok !res.body.err + assert.equal res.statusCode, 200 + assert.ok res.body + console.log res.body + done() + it '/api/v1/task', (done) -> request.post("#{baseURL}/task") .set('Accept', 'application/json') From 02569c2dfa663c47441ae8a1af41dd92a50e8c06 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Fri, 22 Feb 2013 22:30:28 -0500 Subject: [PATCH 12/40] Fix errors --- package.json | 3 ++- src/server/middleware.coffee | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/package.json b/package.json index d2b409f62d..4f5e2c3e1d 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "querystring": "~0.1.0", "superagent": "~0.12.4", "resolve": "~0.2.3", - "browserify": "1.17.3" + "browserify": "1.17.3", + "validator": "~0.4.24" }, "private": true, "subdomain": "habitrpg", diff --git a/src/server/middleware.coffee b/src/server/middleware.coffee index 6fa8210a1d..e2183f2ea9 100644 --- a/src/server/middleware.coffee +++ b/src/server/middleware.coffee @@ -1,17 +1,17 @@ module.exports.splash = (req, res, next) -> # This was an API call, not a page load -return next() if /^\/(api)|(v1)/.test req.path + return next() if /^\/(api)|(v1)/.test req.path -unless req.query?.play? or req.getModel().get('_userId') - res.redirect('/splash.html') -else - next() + unless req.query?.play? or req.getModel().get('_userId') + res.redirect('/splash.html') + else + next() module.exports.view = (req, res, next) -> - model = req.getModel() - _view = model.get('_view') || {} - ## Set _mobileDevice to true or false so view can exclude portions from mobile device - _view.mobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header 'User-Agent') - _view.nodeEnv = process.env.NODE_ENV - model.set '_view', _view - next() + model = req.getModel() + _view = model.get('_view') || {} + ## Set _mobileDevice to true or false so view can exclude portions from mobile device + _view.mobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(req.header 'User-Agent') + _view.nodeEnv = process.env.NODE_ENV + model.set '_view', _view + next() From 177c7e4f170e656bdf222433bad09c3987cb0b78 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Sat, 23 Feb 2013 03:20:16 -0500 Subject: [PATCH 13/40] Working again --- package.json | 5 ++--- server.js | 6 ++++-- src/server/api.coffee | 12 ++++++------ src/server/index.coffee | 3 ++- src/server/middleware.coffee | 3 --- test/api.mocha.coffee | 28 ++++++++++++++++------------ 6 files changed, 30 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 4f5e2c3e1d..1c8fa39a9d 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "./server.js", "dependencies": { "derby": "git://github.com/lefnire/derby#habitrpg", - "racer": "git://github.com/lefnire/racer#habitrpg", + "racer": "git://github.com/switz/racer#master", "racer-db-mongo": "git://github.com/lefnire/racer-db-mongo#habitrpg", "derby-ui-boot": "git://github.com/codeparty/derby-ui-boot#master", "derby-auth": "git://github.com/lefnire/derby-auth#master", @@ -27,8 +27,7 @@ "querystring": "~0.1.0", "superagent": "~0.12.4", "resolve": "~0.2.3", - "browserify": "1.17.3", - "validator": "~0.4.24" + "browserify": "1.17.3" }, "private": true, "subdomain": "habitrpg", diff --git a/server.js b/server.js index c19fa948ec..98b139b185 100644 --- a/server.js +++ b/server.js @@ -1,8 +1,8 @@ // Load nconf and define default configuration values if config.json or ENV vars are not found var conf = require('nconf'); conf.argv().env().file({ file: __dirname + "/config.json" }).defaults({ - 'PORT': process.env.PORT, - 'IP': process.env.IP + 'PORT': 3000, + 'IP': '0.0.0.0' }); // Override normal ENV values with nconf ENV values (ENV values are used the same way without nconf) @@ -59,6 +59,8 @@ process.on('uncaughtException', function (error) { require('coffee-script') // remove intermediate compilation requirement module.exports = server = require('./src/server') +Error.stackTraceLimit = Infinity; + server.listen(process.env.PORT || 3000, process.env.IP || '0.0.0.0'); // Note: removed "up" module, which is default for development (but interferes with and production + PaaS) diff --git a/src/server/api.coffee b/src/server/api.coffee index 262712e193..8d9db75b52 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -3,7 +3,7 @@ router = new express.Router() scoring = require '../app/scoring' _ = require 'underscore' -validator = require 'validator' +validator = require 'derby-auth/node_modules/validator' check = validator.check snaitize = validator.sanitize icalendar = require 'icalendar' @@ -24,7 +24,7 @@ router.get '/status', (req, res) -> status: 'up' router.get '/user', (req, res) -> - console.log 'hi', { uid, token } = req.query + { uid, token } = req.query return res.json 500, NO_TOKEN_OR_UID unless uid || token model = req.getModel() @@ -33,12 +33,11 @@ router.get '/user', (req, res) -> query.fetch (err, user) -> return res.json 500, err: err if err self = user.at(0).get() - console.log self return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) return res.json self -router.post '/user/tasks', (req, res) -> +router.post '/user/task', (req, res) -> { uid, token } = req.body task = { title, text, type, value, note } = req.body return res.json 500, NO_TOKEN_OR_UID unless uid || token @@ -50,15 +49,16 @@ router.post '/user/tasks', (req, res) -> return res.json 500, err: err if err self = user.at(0).get() return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) - return res.json 500, err: "type must be habit, todo, daily, reward" unless check(type).is /habit|todo|daily|reward/ + return res.json 500, err: "type must be habit, todo, daily, reward" unless /habit|todo|daily|reward/.test type return res.json 500, err: "must have a title" unless check(title).notEmpty() return res.json 500, err: "must have text" unless check(text).notEmpty() value ||= 0 + #model.ref '_user', user model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" model.push "_#{type}List", task - return res.json task + return res.json 201, task router.get '/users/:uid/calendar.ics', (req, res) -> #return next() #disable for now diff --git a/src/server/index.coffee b/src/server/index.coffee index aaaabe8f00..c760e9628e 100644 --- a/src/server/index.coffee +++ b/src/server/index.coffee @@ -71,6 +71,8 @@ mongo_store = new MongoStore {url: process.env.NODE_DB_URI}, -> ) # Adds req.getModel method .use(store.modelMiddleware()) + # API should be hit before all other routes + .use('/api/v1', require('./api').middleware) # Show splash page for newcomers .use(middleware.splash) .use(priv.middleware) @@ -78,7 +80,6 @@ mongo_store = new MongoStore {url: process.env.NODE_DB_URI}, -> .use(auth.middleware(strategies, options)) # Creates an express middleware from the app's routes .use(app.router()) - .use('/api/v1', require('./api').middleware) .use(require('./static').middleware) .use(require('./deprecated').middleware) .use(expressApp.router) diff --git a/src/server/middleware.coffee b/src/server/middleware.coffee index e2183f2ea9..9ad133788d 100644 --- a/src/server/middleware.coffee +++ b/src/server/middleware.coffee @@ -1,7 +1,4 @@ module.exports.splash = (req, res, next) -> - # This was an API call, not a page load - return next() if /^\/(api)|(v1)/.test req.path - unless req.query?.play? or req.getModel().get('_userId') res.redirect('/splash.html') else diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 3936315482..33a7148068 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -113,13 +113,18 @@ describe 'API', -> user = character.newUserObject() user.apiToken = derby.uuid() model.set "users.#{uid}", user - user = model.get("users.#{uid}") + user = model.at("users.#{uid}") + currentUser = user.get() params = - uid: user.id - token: user.apiToken + uid: currentUser.id + token: currentUser.apiToken + title: 'Title' + text: 'Text' + type: 'habit' done() + ### it '/api/v1/user', (done) -> console.log "#{baseURL}/user?#{qs.stringify(params)}" request.get("#{baseURL}/user") @@ -133,18 +138,17 @@ describe 'API', -> assert.equal res.statusCode, 200 assert.ok res.body console.log res.body - done() + done() + ### - it '/api/v1/task', (done) -> - request.post("#{baseURL}/task") + it '/api/v1/user/task', (done) -> + request.post("#{baseURL}/user/task") .set('Accept', 'application/json') .send(params) - .on('error', (err) -> - console.log 'err', err - ) .end (res) -> - #console.log 'task', res.body + currentUser = user.get() assert.ok !res.body.err - assert.equal res.statusCode, 200 - assert.ok res.body.tasks + assert.equal res.statusCode, 201 + assert.ok res.body.id + assert.ok currentUser.tasks[res.body.id] done() From bddd0bd148e0900d958fafe2e7bfdb52094f38ee Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Sat, 23 Feb 2013 04:45:00 -0500 Subject: [PATCH 14/40] wip --- package.json | 3 ++- src/server/api.coffee | 2 +- src/server/store.coffee | 7 ++++++- test/api.mocha.coffee | 21 ++++++++++++--------- 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index 1c8fa39a9d..467223683a 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,8 @@ "querystring": "~0.1.0", "superagent": "~0.12.4", "resolve": "~0.2.3", - "browserify": "1.17.3" + "browserify": "1.17.3", + "expect.js": "~0.2.0" }, "private": true, "subdomain": "habitrpg", diff --git a/src/server/api.coffee b/src/server/api.coffee index 8d9db75b52..d1280731a5 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -54,7 +54,7 @@ router.post '/user/task', (req, res) -> return res.json 500, err: "must have text" unless check(text).notEmpty() value ||= 0 - #model.ref '_user', user + model.ref '_user', user.at(0) model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" model.push "_#{type}List", task diff --git a/src/server/store.coffee b/src/server/store.coffee index cebd70e5cd..4d71f36262 100644 --- a/src/server/store.coffee +++ b/src/server/store.coffee @@ -28,7 +28,12 @@ userAccess = (store) -> store.writeAccess "*", "users.*", -> # captures, value, accept, err -> accept = arguments[arguments.length-2] err = arguments[arguments.length - 1] -# return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@) + # return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@) + + # TEMPORARY token check to allow api + # Must fix before release + return accept(true) + return accept(false) if derbyAuth.bustedSession(@) captures = arguments[0].split('.') diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 33a7148068..f3499740df 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -1,4 +1,4 @@ -assert = require 'assert' +expect = require 'expect.js' {BrowserModel: Model} = require 'racer/test/util/model' derby = require 'derby' racer = require 'racer' @@ -92,16 +92,16 @@ describe 'API', -> request.get("#{baseURL}/status") .set('Accept', 'application/json') .end (res) -> - assert.equal res.statusCode, 200 - assert.equal res.body.status, 'up' + expect(res.statusCode).to.be 200 + expect(res.body.status).to.be 'up' done() it '/api/v1/user', (done) -> request.get("#{baseURL}/user") .set('Accept', 'application/json') .end (res) -> - assert.equal res.statusCode, 500 - assert.ok JSON.parse(res.text).err + expect(res.statusCode).to.be 500 + expect(res.body.err).to.be 'You must include a token and uid (user id) in your request' done() describe 'With token and user id', -> @@ -114,6 +114,7 @@ describe 'API', -> user.apiToken = derby.uuid() model.set "users.#{uid}", user user = model.at("users.#{uid}") + user.set 'tasks', {} currentUser = user.get() params = @@ -147,8 +148,10 @@ describe 'API', -> .send(params) .end (res) -> currentUser = user.get() - assert.ok !res.body.err - assert.equal res.statusCode, 201 - assert.ok res.body.id - assert.ok currentUser.tasks[res.body.id] + expect(currentUser).to.eql res.body + expect(res.body.err).to.be.empty() + expect(res.statusCode).to.be 201 + expect(res.body.id).not.to.be.empty() + # Ensure that user owns the newly created object + #expect(currentUser.tasks[res.body.id]).to.be.an('object') done() From faa94b3692560f2574e7b74d48828dce191e9dfc Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Sun, 24 Feb 2013 23:00:22 -0500 Subject: [PATCH 15/40] Almost perfect --- src/server/api.coffee | 69 +++++++++++++++++++++++++------------------ test/api.mocha.coffee | 58 +++++++++++++++++++++--------------- 2 files changed, 75 insertions(+), 52 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index d1280731a5..cdf5ce1bda 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -19,46 +19,57 @@ NO_USER_FOUND = err: "No user found." curl -X POST -H "Content-Type:application/json" -d '{"apiToken":"{TOKEN}"}' localhost:3000/v1/users/{UID}/tasks/productivity/up ### +auth = (req, res, next) -> + express.basicAuth((uid, token, callback) -> + return res.json 500, NO_TOKEN_OR_UID unless uid || token + + model = req.getModel() + query = model.query('users').withIdAndToken(uid, token) + + query.fetch (err, user) -> + return callback err if err + callback null, user.at(0) + )(req, res, next) + router.get '/status', (req, res) -> - res.json - status: 'up' + res.json status: 'up' -router.get '/user', (req, res) -> - { uid, token } = req.query - return res.json 500, NO_TOKEN_OR_UID unless uid || token +router.get '/user', auth, (req, res) -> + self = req.user.get() + return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) - model = req.getModel() - query = model.query('users').withIdAndToken(uid, token) + return res.json self - query.fetch (err, user) -> - return res.json 500, err: err if err - self = user.at(0).get() - return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) - - return res.json self - -router.post '/user/task', (req, res) -> - { uid, token } = req.body +router.post '/user/task', auth, (req, res) -> task = { title, text, type, value, note } = req.body - return res.json 500, NO_TOKEN_OR_UID unless uid || token + return res.json 500, err: "type must be habit, todo, daily, or reward" unless /habit|todo|daily|reward/.test type + return res.json 500, err: "must have a title" unless check(title).notEmpty() + return res.json 500, err: "must have text" unless check(text).notEmpty() + + self = req.user.get() + return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) + + value ||= 0 model = req.getModel() - query = model.query('users').withIdAndToken(uid, token) + model.ref '_user', req.user + model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" + model.at("_#{type}List").push task - query.fetch (err, user) -> - return res.json 500, err: err if err - self = user.at(0).get() - return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) - return res.json 500, err: "type must be habit, todo, daily, reward" unless /habit|todo|daily|reward/.test type - return res.json 500, err: "must have a title" unless check(title).notEmpty() - return res.json 500, err: "must have text" unless check(text).notEmpty() - value ||= 0 + return res.json 201, task - model.ref '_user', user.at(0) +router.get '/user/tasks', auth, (req, res) -> + self = req.user.get() + return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) + + model = req.getModel() + model.ref '_user', req.user + tasks = [] + for type in ['habit','todo','daily','reward'] model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" - model.push "_#{type}List", task + tasks = tasks.concat model.get("_#{type}List") - return res.json 201, task + return res.json 200, tasks router.get '/users/:uid/calendar.ics', (req, res) -> #return next() #disable for now diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index f3499740df..3b5d658636 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -82,10 +82,6 @@ modificationsLookup = (direction, options = {}) -> ###### Specs ###### describe 'API', -> - model = null - user = null - params = null - describe 'Without token or user id', -> it '/api/v1/status', (done) -> @@ -105,6 +101,11 @@ describe 'API', -> done() describe 'With token and user id', -> + params = null + currentUser = null + user = null + model = null + before (done) -> model = store.createModel() #store.flush() @@ -114,9 +115,7 @@ describe 'API', -> user.apiToken = derby.uuid() model.set "users.#{uid}", user user = model.at("users.#{uid}") - user.set 'tasks', {} currentUser = user.get() - params = uid: currentUser.id token: currentUser.apiToken @@ -125,33 +124,46 @@ describe 'API', -> type: 'habit' done() - ### - it '/api/v1/user', (done) -> - console.log "#{baseURL}/user?#{qs.stringify(params)}" + beforeEach -> + currentUser = user.get() + + it 'GET /api/v1/user', (done) -> request.get("#{baseURL}/user") .set('Accept', 'application/json') - .query(params) - .on('error', (err) -> - console.log 'err', err - ) + .auth(params.uid, params.token) .end (res) -> - assert.ok !res.body.err - assert.equal res.statusCode, 200 - assert.ok res.body - console.log res.body - done() - ### + expect(res.body.err).to.be undefined + expect(res.statusCode).to.be 200 + expect(res.body.id).not.to.be.empty() + expect(res.body).to.eql(currentUser) + done() - it '/api/v1/user/task', (done) -> + it 'POST /api/v1/user/task', (done) -> request.post("#{baseURL}/user/task") .set('Accept', 'application/json') + .auth(params.uid, params.token) .send(params) .end (res) -> - currentUser = user.get() - expect(currentUser).to.eql res.body - expect(res.body.err).to.be.empty() + expect(res.body.err).to.be undefined expect(res.statusCode).to.be 201 expect(res.body.id).not.to.be.empty() # Ensure that user owns the newly created object #expect(currentUser.tasks[res.body.id]).to.be.an('object') done() + + it 'GET /api/v1/user/tasks', (done) -> + request.get("#{baseURL}/user/tasks") + .set('Accept', 'application/json') + .auth(params.uid, params.token) + .end (res) -> + expect(res.body.err).to.be undefined + expect(res.statusCode).to.be 200 + model.set '_user', user.get() + tasks = [] + for type in ['habit','todo','daily','reward'] + model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" + tasks = tasks.concat model.get("_#{type}List") + # Ensure that user owns the tasks + console.log _.difference(_.pluck(res.body,'id'), _.pluck(tasks,'id')) + expect(res.body.length).to.equal tasks.length + done() From bfd30cd4b72d2e6f64a5505cff5d94a49a0aadef Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Mon, 25 Feb 2013 00:24:11 -0500 Subject: [PATCH 16/40] wip --- src/server/api.coffee | 3 ++- test/api.mocha.coffee | 21 +++++++++++---------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index cdf5ce1bda..f974d71bca 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -28,7 +28,8 @@ auth = (req, res, next) -> query.fetch (err, user) -> return callback err if err - callback null, user.at(0) + user = user.at(0) + callback null, user )(req, res, next) router.get '/status', (req, res) -> diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 3b5d658636..694a41578d 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -106,9 +106,9 @@ describe 'API', -> user = null model = null - before (done) -> - model = store.createModel() + before -> #store.flush() + model = store.createModel() model.set '_userId', uid = model.id() user = character.newUserObject() @@ -117,12 +117,9 @@ describe 'API', -> user = model.at("users.#{uid}") currentUser = user.get() params = - uid: currentUser.id - token: currentUser.apiToken title: 'Title' text: 'Text' type: 'habit' - done() beforeEach -> currentUser = user.get() @@ -130,7 +127,7 @@ describe 'API', -> it 'GET /api/v1/user', (done) -> request.get("#{baseURL}/user") .set('Accept', 'application/json') - .auth(params.uid, params.token) + .auth(currentUser.id, currentUser.apiToken) .end (res) -> expect(res.body.err).to.be undefined expect(res.statusCode).to.be 200 @@ -141,24 +138,28 @@ describe 'API', -> it 'POST /api/v1/user/task', (done) -> request.post("#{baseURL}/user/task") .set('Accept', 'application/json') - .auth(params.uid, params.token) + .auth(currentUser.id, currentUser.apiToken) .send(params) .end (res) -> expect(res.body.err).to.be undefined + console.log res.body expect(res.statusCode).to.be 201 expect(res.body.id).not.to.be.empty() # Ensure that user owns the newly created object - #expect(currentUser.tasks[res.body.id]).to.be.an('object') + console.log _.size(user.get().tasks) + expect(user.get().tasks[res.body.id]).to.be.an('object') done() it 'GET /api/v1/user/tasks', (done) -> request.get("#{baseURL}/user/tasks") .set('Accept', 'application/json') - .auth(params.uid, params.token) + .auth(currentUser.id, currentUser.apiToken) .end (res) -> expect(res.body.err).to.be undefined expect(res.statusCode).to.be 200 - model.set '_user', user.get() + currentUser = user.get() + console.log _.size(currentUser.tasks) + model.ref '_user', user tasks = [] for type in ['habit','todo','daily','reward'] model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" From 1be9fb97fec7f4323395bdca069320ac0d3f6189 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Mon, 25 Feb 2013 02:43:50 -0500 Subject: [PATCH 17/40] Use headers instead of auth --- src/server/api.coffee | 18 +++++++++--------- test/api.mocha.coffee | 36 ++++++++++++++++++++++++++++++------ 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index f974d71bca..d7bf239475 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -20,17 +20,17 @@ NO_USER_FOUND = err: "No user found." ### auth = (req, res, next) -> - express.basicAuth((uid, token, callback) -> - return res.json 500, NO_TOKEN_OR_UID unless uid || token + uid = req.headers['x-api-user'] + token = req.headers['x-api-key'] + return res.json 500, NO_TOKEN_OR_UID unless uid || token - model = req.getModel() - query = model.query('users').withIdAndToken(uid, token) + model = req.getModel() + query = model.query('users').withIdAndToken(uid, token) - query.fetch (err, user) -> - return callback err if err - user = user.at(0) - callback null, user - )(req, res, next) + query.fetch (err, user) -> + return res.json err: err if err + req.user = user.at(0) + next() router.get '/status', (req, res) -> res.json status: 'up' diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 694a41578d..077a7df450 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -7,6 +7,25 @@ moment = require 'moment' request = require 'superagent' qs = require 'querystring' +## monkey-patch expect.js for better diffs on mocha +## see: https://github.com/LearnBoost/expect.js/pull/34 + +origBe = expect.Assertion::be +expect.Assertion::be = expect.Assertion::equal = (obj) -> + @_expected = obj + origBe.call this, obj + +expect.Assertion::assert = (truth, msg, error) -> + msg = (if @flags.not then error else msg) + ok = (if @flags.not then not truth else truth) + unless ok + err = new Error(msg.call(this)) + if "_expected" of this + err.expected = @_expected + err.actual = @obj + throw err + @and = new expect.Assertion(@obj) + racer.use require 'racer-db-mongo' store = racer.createStore @@ -127,7 +146,8 @@ describe 'API', -> it 'GET /api/v1/user', (done) -> request.get("#{baseURL}/user") .set('Accept', 'application/json') - .auth(currentUser.id, currentUser.apiToken) + .set('X-API-User', currentUser.id) + .set('X-API-Key', currentUser.apiToken) .end (res) -> expect(res.body.err).to.be undefined expect(res.statusCode).to.be 200 @@ -138,11 +158,11 @@ describe 'API', -> it 'POST /api/v1/user/task', (done) -> request.post("#{baseURL}/user/task") .set('Accept', 'application/json') - .auth(currentUser.id, currentUser.apiToken) + .set('X-API-User', currentUser.id) + .set('X-API-Key', currentUser.apiToken) .send(params) .end (res) -> expect(res.body.err).to.be undefined - console.log res.body expect(res.statusCode).to.be 201 expect(res.body.id).not.to.be.empty() # Ensure that user owns the newly created object @@ -153,7 +173,8 @@ describe 'API', -> it 'GET /api/v1/user/tasks', (done) -> request.get("#{baseURL}/user/tasks") .set('Accept', 'application/json') - .auth(currentUser.id, currentUser.apiToken) + .set('X-API-User', currentUser.id) + .set('X-API-Key', currentUser.apiToken) .end (res) -> expect(res.body.err).to.be undefined expect(res.statusCode).to.be 200 @@ -166,5 +187,8 @@ describe 'API', -> tasks = tasks.concat model.get("_#{type}List") # Ensure that user owns the tasks console.log _.difference(_.pluck(res.body,'id'), _.pluck(tasks,'id')) - expect(res.body.length).to.equal tasks.length - done() + #expect(res.body.length).to.equal tasks.length + setTimeout -> + console.log 'hi', _.size(user.get().tasks) + done() + , 1100 From 7ce767781662e4b5611e6f1ee33056f9c369a43e Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Mon, 25 Feb 2013 02:44:57 -0500 Subject: [PATCH 18/40] Cleanup --- test/api.mocha.coffee | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 077a7df450..42387f2217 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -187,8 +187,5 @@ describe 'API', -> tasks = tasks.concat model.get("_#{type}List") # Ensure that user owns the tasks console.log _.difference(_.pluck(res.body,'id'), _.pluck(tasks,'id')) - #expect(res.body.length).to.equal tasks.length - setTimeout -> - console.log 'hi', _.size(user.get().tasks) - done() - , 1100 + expect(res.body.length).to.equal tasks.length + done() From e4f42270726f5f147f7df9c4496cd28de035d8cb Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Mon, 25 Feb 2013 14:32:46 -0500 Subject: [PATCH 19/40] Cleanup --- src/server/api.coffee | 5 ++--- test/api.mocha.coffee | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index d7bf239475..e6aa77476d 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -11,12 +11,11 @@ icalendar = require 'icalendar' NO_TOKEN_OR_UID = err: "You must include a token and uid (user id) in your request" NO_USER_FOUND = err: "No user found." -# ---------- /v1 API ------------ -# Every url added beneath router is prefaced by /v1 +# ---------- /api/v1 API ------------ +# Every url added beneath router is prefaced by /api/v1 ### v1 API. Requires user-id and apiToken, task-id, direction. Test with: - curl -X POST -H "Content-Type:application/json" -d '{"apiToken":"{TOKEN}"}' localhost:3000/v1/users/{UID}/tasks/productivity/up ### auth = (req, res, next) -> diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 42387f2217..d9d6e95a67 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -186,6 +186,7 @@ describe 'API', -> model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" tasks = tasks.concat model.get("_#{type}List") # Ensure that user owns the tasks - console.log _.difference(_.pluck(res.body,'id'), _.pluck(tasks,'id')) expect(res.body.length).to.equal tasks.length + # Ensure that the two sets are equal + expect(_.difference(_.pluck(res.body,'id'), _.pluck(tasks,'id')).length).to.equal 0 done() From f9f57a6b91ffb736f9b2ba16d8ae5aba8c3f7287 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Tue, 26 Feb 2013 02:36:51 -0500 Subject: [PATCH 20/40] Cleanup and more checks --- server.js | 4 +- src/server/api.coffee | 26 +++++++++---- test/api.mocha.coffee | 26 ++++++++++++- test/request/local.coffee | 25 ------------- test/request/request.coffee | 74 ------------------------------------- 5 files changed, 46 insertions(+), 109 deletions(-) delete mode 100644 test/request/local.coffee delete mode 100644 test/request/request.coffee diff --git a/server.js b/server.js index 98b139b185..d7fc162cf7 100644 --- a/server.js +++ b/server.js @@ -19,7 +19,7 @@ process.env.SMTP_PASS = conf.get("SMTP_PASS"); process.env.SMTP_SERVICE = conf.get("SMTP_SERVICE"); process.env.STRIPE_API_KEY = conf.get("STRIPE_API_KEY"); process.env.STRIPE_PUB_KEY = conf.get("STRIPE_PUB_KEY"); - +/* process.on('uncaughtException', function (error) { function sendEmail(mailData) { @@ -55,7 +55,7 @@ process.on('uncaughtException', function (error) { }); console.log(error.stack); }); - +*/ require('coffee-script') // remove intermediate compilation requirement module.exports = server = require('./src/server') diff --git a/src/server/api.coffee b/src/server/api.coffee index e6aa77476d..88bf0cae77 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -15,7 +15,9 @@ NO_USER_FOUND = err: "No user found." # Every url added beneath router is prefaced by /api/v1 ### - v1 API. Requires user-id and apiToken, task-id, direction. Test with: + v1 API. Requires api-v1-user (user id) and api-v1-key (api key) headers, Test with: + $ cd node_modules/racer && npm install && cd ../.. + $ mocha test/api.mocha.coffee ### auth = (req, res, next) -> @@ -28,26 +30,36 @@ auth = (req, res, next) -> query.fetch (err, user) -> return res.json err: err if err - req.user = user.at(0) + user = user.at(0) + req.user = user + req.userObj = user.get() + return res.json 500, NO_USER_FOUND if !req.userObj || _.isEmpty(req.userObj) next() router.get '/status', (req, res) -> res.json status: 'up' router.get '/user', auth, (req, res) -> - self = req.user.get() - return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) + self = req.userObj + + delete self[val] for val in ['tasks', 'apiToken', 'flags', 'lastCron'] return res.json self +router.get '/task/:id', auth, (req, res) -> + task = req.user.get("tasks.#{req.params.id}") + console.log task + return res.json 500, err: "No task found." if !task || _.isEmpty(task) + + return res.json 200, task + router.post '/user/task', auth, (req, res) -> task = { title, text, type, value, note } = req.body return res.json 500, err: "type must be habit, todo, daily, or reward" unless /habit|todo|daily|reward/.test type return res.json 500, err: "must have a title" unless check(title).notEmpty() return res.json 500, err: "must have text" unless check(text).notEmpty() - self = req.user.get() - return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) + self = req.userObj value ||= 0 @@ -59,7 +71,7 @@ router.post '/user/task', auth, (req, res) -> return res.json 201, task router.get '/user/tasks', auth, (req, res) -> - self = req.user.get() + self = req.userObj return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) model = req.getModel() diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index d9d6e95a67..fc9a403e24 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -101,6 +101,7 @@ modificationsLookup = (direction, options = {}) -> ###### Specs ###### describe 'API', -> + describe 'Without token or user id', -> it '/api/v1/status', (done) -> @@ -124,6 +125,7 @@ describe 'API', -> currentUser = null user = null model = null + uid = null before -> #store.flush() @@ -141,6 +143,7 @@ describe 'API', -> type: 'habit' beforeEach -> + model = store.createModel() currentUser = user.get() it 'GET /api/v1/user', (done) -> @@ -152,9 +155,28 @@ describe 'API', -> expect(res.body.err).to.be undefined expect(res.statusCode).to.be 200 expect(res.body.id).not.to.be.empty() + model.set '_user', currentUser + ### + currentUser.tasks = [] + for type in ['habit','todo','daily','reward'] + model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" + currentUser.tasks = currentUser.tasks.concat model.get("_#{type}List") + ### expect(res.body).to.eql(currentUser) done() + it 'GET /api/v1/task/:id', (done) -> + tid = _.values(currentUser.tasks)[0].id + request.post("#{baseURL}/task/#{tid}") + .set('Accept', 'application/json') + .set('X-API-User', currentUser.id) + .set('X-API-Key', currentUser.apiToken) + .end (res) -> + expect(res.body.err).to.be undefined + expect(res.statusCode).to.be 200 + expect(res.body).to.eql currentUser.tasks[tid] + done() + it 'POST /api/v1/user/task', (done) -> request.post("#{baseURL}/user/task") .set('Accept', 'application/json') @@ -166,7 +188,7 @@ describe 'API', -> expect(res.statusCode).to.be 201 expect(res.body.id).not.to.be.empty() # Ensure that user owns the newly created object - console.log _.size(user.get().tasks) + console.log 'test', _.size(user.get().tasks) expect(user.get().tasks[res.body.id]).to.be.an('object') done() @@ -180,6 +202,8 @@ describe 'API', -> expect(res.statusCode).to.be 200 currentUser = user.get() console.log _.size(currentUser.tasks) + console.log uid + console.log 'hellomate', model.get("users.#{uid}") model.ref '_user', user tasks = [] for type in ['habit','todo','daily','reward'] diff --git a/test/request/local.coffee b/test/request/local.coffee deleted file mode 100644 index 1431d12cd5..0000000000 --- a/test/request/local.coffee +++ /dev/null @@ -1,25 +0,0 @@ -Request = require './request' -qs = require 'querystring' - -makeUrl = (path, params) -> - if typeof params is "object" - params = qs.stringify(params) - else - params = '' - return "http://localhost:3000/#{path}?#{params}" - -# url, params (optional), callback -get = (obj, callback) -> - obj.params ||= {} - obj.path ||= '' - - requestUrl = makeUrl obj.path, obj.params - - request = new Request requestUrl - - request.done (res) -> - callback null, JSON.parse(res) - - request.fire() - -module.exports = { get } diff --git a/test/request/request.coffee b/test/request/request.coffee deleted file mode 100644 index 5eadd9178b..0000000000 --- a/test/request/request.coffee +++ /dev/null @@ -1,74 +0,0 @@ -http = require 'http' - -# ### Request -# -# The Request class is just a wrapper -# around the `http.request` function, -# providing a cleaner and more reusable -# interface. -# -# `constructor(options)` -# -# `options` can be anything `http.request` takes. -# -# Callbacks can be queued up by calling `request.done` -# and passing it a callback function to be invoked when the -# http request has completed -# -# Example: -# -# callback = (json) -> -# doSomethingWithJSON( json ) -# -# request = new Request('http://api.phish.net/api.js?') -# request.done (response) -> -# callback JSON.parse(response)[0] -# -# request.fire(data) # data refers to the post data -# -class Request - constructor: (@options) -> - @callbacks = {} - - fire: (data) -> - request = http.request(@options, @_handler) - request.on 'error', (err) => - for callback in @callbacks['fail'] - callback(err) - - # Send data with request - if data - request.write data - - request.end() - - this - - done: (callback) -> - push.call(this, 'done', callback) - - this - - fail: (callback) -> - push.call(this, 'fail', callback) - - this - - ########### - # PRIVATE # - ########### - - _handler: (response) => - data = '' - response.on 'data', (chunk) -> - data += chunk - response.on 'end', => - for callback in @callbacks['done'] - callback(data) - - - push = (type, callback) -> - @callbacks[type] ?= [] - @callbacks[type].push callback - -module.exports = Request From 17a5040b3b7567df06b8738f9ce753fb03c6760e Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Tue, 26 Feb 2013 02:55:33 -0500 Subject: [PATCH 21/40] Server runs inline with tests --- test/api.mocha.coffee | 15 +++++++++++++-- test/mocha.opts | 3 ++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index fc9a403e24..f0c86f6825 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -6,6 +6,7 @@ _ = require 'underscore' moment = require 'moment' request = require 'superagent' qs = require 'querystring' +require 'coffee-script' ## monkey-patch expect.js for better diffs on mocha ## see: https://github.com/LearnBoost/expect.js/pull/34 @@ -101,6 +102,12 @@ modificationsLookup = (direction, options = {}) -> ###### Specs ###### describe 'API', -> + before (done) -> + server = require '../src/server' + server.listen '3000', '0.0.0.0' + server.on 'listening', (data) -> + # Crappy hack to let server start before tests run + setTimeout done, 2000 describe 'Without token or user id', -> @@ -143,7 +150,6 @@ describe 'API', -> type: 'habit' beforeEach -> - model = store.createModel() currentUser = user.get() it 'GET /api/v1/user', (done) -> @@ -198,12 +204,17 @@ describe 'API', -> .set('X-API-User', currentUser.id) .set('X-API-Key', currentUser.apiToken) .end (res) -> + store = racer.createStore + db: + type: 'Mongo' + uri: process.env.NODE_DB_URI + model = store.createModel() expect(res.body.err).to.be undefined expect(res.statusCode).to.be 200 currentUser = user.get() console.log _.size(currentUser.tasks) console.log uid - console.log 'hellomate', model.get("users.#{uid}") + console.log 'hellomate', user.get() model.ref '_user', user tasks = [] for type in ['habit','todo','daily','reward'] diff --git a/test/mocha.opts b/test/mocha.opts index d6ef947989..74e33e85ea 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,6 +1,7 @@ --colors --reporter spec ---timeout 1200 +--timeout 2500 +--ignore-leaks --growl --debug --compilers coffee:coffee-script From 52c54444530bfaa8b1dc38a72652093291788bef Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Tue, 26 Feb 2013 02:56:47 -0500 Subject: [PATCH 22/40] Remove second store --- test/api.mocha.coffee | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index f0c86f6825..2d965c48db 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -44,7 +44,7 @@ config = require './config' model = null uuid = null taskPath = null -baseURL = 'http://localhost:3000/api/v1' +baseURL = 'http://localhost:1337/api/v1' UID_AND_TOKEN = uid: config.uid token: config.token @@ -104,7 +104,7 @@ modificationsLookup = (direction, options = {}) -> describe 'API', -> before (done) -> server = require '../src/server' - server.listen '3000', '0.0.0.0' + server.listen '1337', '0.0.0.0' server.on 'listening', (data) -> # Crappy hack to let server start before tests run setTimeout done, 2000 @@ -204,11 +204,6 @@ describe 'API', -> .set('X-API-User', currentUser.id) .set('X-API-Key', currentUser.apiToken) .end (res) -> - store = racer.createStore - db: - type: 'Mongo' - uri: process.env.NODE_DB_URI - model = store.createModel() expect(res.body.err).to.be undefined expect(res.statusCode).to.be 200 currentUser = user.get() From 07648cd60b65181862e1251ba13aba6847c6391a Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Tue, 26 Feb 2013 03:48:10 -0500 Subject: [PATCH 23/40] all tests passing - woo --- src/server/api.coffee | 3 +- test/api.mocha.coffee | 90 +++++++++++++++++++++---------------------- 2 files changed, 44 insertions(+), 49 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index 88bf0cae77..6ad138ffea 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -47,8 +47,7 @@ router.get '/user', auth, (req, res) -> return res.json self router.get '/task/:id', auth, (req, res) -> - task = req.user.get("tasks.#{req.params.id}") - console.log task + task = req.userObj.tasks[req.params.id] return res.json 500, err: "No task found." if !task || _.isEmpty(task) return res.json 200, task diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 2d965c48db..f9844d4de6 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -26,14 +26,14 @@ expect.Assertion::assert = (truth, msg, error) -> err.actual = @obj throw err @and = new expect.Assertion(@obj) - +### racer.use require 'racer-db-mongo' store = racer.createStore db: type: 'Mongo' uri: process.env.NODE_DB_URI - +### # Custom modules scoring = require '../src/app/scoring' character = require '../src/app/character' @@ -45,9 +45,6 @@ model = null uuid = null taskPath = null baseURL = 'http://localhost:1337/api/v1' -UID_AND_TOKEN = - uid: config.uid - token: config.token ## Helper which clones the content at a path so tests can compare before/after values # Otherwise, using model.get(path) will give the same object before as after @@ -102,10 +99,23 @@ modificationsLookup = (direction, options = {}) -> ###### Specs ###### describe 'API', -> + server = null + store = null + model = null + user = null + uid = null + before (done) -> server = require '../src/server' server.listen '1337', '0.0.0.0' server.on 'listening', (data) -> + store = server.habitStore + #store.flush() + model = store.createModel() + model.set '_userId', uid = model.id() + user = character.newUserObject() + user.apiToken = derby.uuid() + model.set "users.#{uid}", user # Crappy hack to let server start before tests run setTimeout done, 2000 @@ -130,18 +140,8 @@ describe 'API', -> describe 'With token and user id', -> params = null currentUser = null - user = null - model = null - uid = null before -> - #store.flush() - model = store.createModel() - - model.set '_userId', uid = model.id() - user = character.newUserObject() - user.apiToken = derby.uuid() - model.set "users.#{uid}", user user = model.at("users.#{uid}") currentUser = user.get() params = @@ -161,19 +161,15 @@ describe 'API', -> expect(res.body.err).to.be undefined expect(res.statusCode).to.be 200 expect(res.body.id).not.to.be.empty() - model.set '_user', currentUser - ### - currentUser.tasks = [] - for type in ['habit','todo','daily','reward'] - model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" - currentUser.tasks = currentUser.tasks.concat model.get("_#{type}List") - ### - expect(res.body).to.eql(currentUser) + self = _.clone(currentUser) + delete self[val] for val in ['tasks', 'apiToken', 'flags', 'lastCron'] + + expect(res.body).to.eql self done() it 'GET /api/v1/task/:id', (done) -> tid = _.values(currentUser.tasks)[0].id - request.post("#{baseURL}/task/#{tid}") + request.get("#{baseURL}/task/#{tid}") .set('Accept', 'application/json') .set('X-API-User', currentUser.id) .set('X-API-Key', currentUser.apiToken) @@ -190,13 +186,14 @@ describe 'API', -> .set('X-API-Key', currentUser.apiToken) .send(params) .end (res) -> - expect(res.body.err).to.be undefined - expect(res.statusCode).to.be 201 - expect(res.body.id).not.to.be.empty() - # Ensure that user owns the newly created object - console.log 'test', _.size(user.get().tasks) - expect(user.get().tasks[res.body.id]).to.be.an('object') - done() + query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) + query.fetch (err, user) -> + expect(res.body.err).to.be undefined + expect(res.statusCode).to.be 201 + expect(res.body.id).not.to.be.empty() + # Ensure that user owns the newly created object + expect(user.at(0).get().tasks[res.body.id]).to.be.an('object') + done() it 'GET /api/v1/user/tasks', (done) -> request.get("#{baseURL}/user/tasks") @@ -204,19 +201,18 @@ describe 'API', -> .set('X-API-User', currentUser.id) .set('X-API-Key', currentUser.apiToken) .end (res) -> - expect(res.body.err).to.be undefined - expect(res.statusCode).to.be 200 - currentUser = user.get() - console.log _.size(currentUser.tasks) - console.log uid - console.log 'hellomate', user.get() - model.ref '_user', user - tasks = [] - for type in ['habit','todo','daily','reward'] - model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" - tasks = tasks.concat model.get("_#{type}List") - # Ensure that user owns the tasks - expect(res.body.length).to.equal tasks.length - # Ensure that the two sets are equal - expect(_.difference(_.pluck(res.body,'id'), _.pluck(tasks,'id')).length).to.equal 0 - done() + query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) + query.fetch (err, user) -> + expect(res.body.err).to.be undefined + expect(res.statusCode).to.be 200 + currentUser = user.at(0).get() + model.ref '_user', user.at(0) + tasks = [] + for type in ['habit','todo','daily','reward'] + model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" + tasks = tasks.concat model.get("_#{type}List") + # Ensure that user owns the tasks + expect(res.body.length).to.equal tasks.length + # Ensure that the two sets are equal + expect(_.difference(_.pluck(res.body,'id'), _.pluck(tasks,'id')).length).to.equal 0 + done() From c38f215db5ab2ce46020c5399ddc03f2f9092ed6 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Tue, 26 Feb 2013 16:44:26 -0500 Subject: [PATCH 24/40] Add type parameter to /user/tasks --- .gitignore | 1 - package.json | 3 +- server.js | 10 +---- src/server/api.coffee | 38 +++++++++------- test/api.mocha.coffee | 100 ++++++++++-------------------------------- test/mocha.opts | 2 +- 6 files changed, 50 insertions(+), 104 deletions(-) diff --git a/.gitignore b/.gitignore index 77291d9d0f..029b274b27 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,3 @@ node_modules *.swp .idea* config.json -test/config.json \ No newline at end of file diff --git a/package.json b/package.json index 08d33fc7ab..a5ab0f7bbe 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "./server.js", "dependencies": { "derby": "git://github.com/lefnire/derby#habitrpg", - "racer": "git://github.com/switz/racer#master", + "racer": "git://github.com/lefnire/racer#habitrpg", "racer-db-mongo": "git://github.com/lefnire/racer-db-mongo#habitrpg", "derby-ui-boot": "git://github.com/codeparty/derby-ui-boot#master", "derby-auth": "git://github.com/lefnire/derby-auth#master", @@ -24,7 +24,6 @@ "nconf": "*", "icalendar": "git://github.com/lefnire/node-icalendar#master", "nodetime": "*", - "querystring": "~0.1.0", "superagent": "~0.12.4", "resolve": "~0.2.3", "browserify": "1.17.3", diff --git a/server.js b/server.js index a95b925c12..f9d64274a7 100644 --- a/server.js +++ b/server.js @@ -20,8 +20,6 @@ process.env.SMTP_SERVICE = conf.get("SMTP_SERVICE"); process.env.STRIPE_API_KEY = conf.get("STRIPE_API_KEY"); process.env.STRIPE_PUB_KEY = conf.get("STRIPE_PUB_KEY"); -/* - var agent; if (process.env.NODE_ENV === 'development') { // Follow these instructions for profiling / debugging leaks @@ -68,13 +66,9 @@ process.on('uncaughtException', function (error) { }); console.log(error.stack); }); -*/ + require('coffee-script') // remove intermediate compilation requirement -module.exports = server = require('./src/server') - -Error.stackTraceLimit = Infinity; - -server.listen(process.env.PORT || 3000, process.env.IP || '0.0.0.0'); +require('./src/server').listen(process.env.PORT || 3000, process.env.IP || '0.0.0.0'); // Note: removed "up" module, which is default for development (but interferes with and production + PaaS) // Restore to 5310bb0 if I want it back (see https://github.com/codeparty/derby/issues/165#issuecomment-10405693) diff --git a/src/server/api.coffee b/src/server/api.coffee index 65c48a7d21..fba15fc09f 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -5,7 +5,7 @@ scoring = require '../app/scoring' _ = require 'underscore' validator = require 'derby-auth/node_modules/validator' check = validator.check -snaitize = validator.sanitize +sanitize = validator.sanitize icalendar = require 'icalendar' NO_TOKEN_OR_UID = err: "You must include a token and uid (user id) in your request" @@ -23,7 +23,7 @@ NO_USER_FOUND = err: "No user found." auth = (req, res, next) -> uid = req.headers['x-api-user'] token = req.headers['x-api-key'] - return res.json 500, NO_TOKEN_OR_UID unless uid || token + return res.json 401, NO_TOKEN_OR_UID unless uid || token model = req.getModel() query = model.query('users').withIdAndToken(uid, token) @@ -33,7 +33,7 @@ auth = (req, res, next) -> user = user.at(0) req.user = user req.userObj = user.get() - return res.json 500, NO_USER_FOUND if !req.userObj || _.isEmpty(req.userObj) + return res.json 401, NO_USER_FOUND if !req.userObj || _.isEmpty(req.userObj) next() router.get '/status', (req, res) -> @@ -48,27 +48,30 @@ router.get '/user', auth, (req, res) -> router.get '/task/:id', auth, (req, res) -> task = req.userObj.tasks[req.params.id] - return res.json 500, err: "No task found." if !task || _.isEmpty(task) + return res.json 400, err: "No task found." if !task || _.isEmpty(task) res.json 200, task router.put '/task/:id', auth, (req, res) -> task = req.userObj.tasks[req.params.id] - return res.json 500, err: "No task found." if !task || _.isEmpty(task) + return res.json 400, err: "No task found." if !task || _.isEmpty(task) - task.title = req.body.title if req.body.title - task.text = req.body.text if req.body.text - task.type = req.body.type if req.body.type + title = sanitize(req.body.title).xss() + text = sanitize(req.body.text).xss() + + task.title = title if title + task.text = text if text + #task.type = req.body.type if /^(habit|todo|daily|reward)$/.test req.body.type req.user.set "tasks.#{task.id}", task - res.send task + res.json 200, task router.post '/user/task', auth, (req, res) -> task = { title, text, type, value, note } = req.body - return res.json 500, err: "type must be habit, todo, daily, or reward" unless /habit|todo|daily|reward/.test type - return res.json 500, err: "must have a title" unless check(title).notEmpty() - return res.json 500, err: "must have text" unless check(text).notEmpty() + return res.json 400, err: "type must be habit, todo, daily, or reward" unless /^habit|todo|daily|reward$/.test type + return res.json 400, err: "must have a title" unless check(title).notEmpty() + return res.json 400, err: "must have text" unless check(text).notEmpty() self = req.userObj @@ -83,12 +86,15 @@ router.post '/user/task', auth, (req, res) -> router.get '/user/tasks', auth, (req, res) -> self = req.userObj - return res.json 500, NO_USER_FOUND if !self || _.isEmpty(self) + return res.json 400, NO_USER_FOUND if !self || _.isEmpty(self) model = req.getModel() model.ref '_user', req.user tasks = [] - for type in ['habit','todo','daily','reward'] + types = ['habit','todo','daily','reward'] + if /^habit|todo|daily|reward$/.test req.query.type + types = [req.query.type] + for type in types model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" tasks = tasks.concat model.get("_#{type}List") @@ -102,11 +108,11 @@ router.get '/users/:uid/calendar.ics', (req, res) -> model = req.getModel() query = model.query('users').withIdAndToken(uid, apiToken) query.fetch (err, result) -> - return res.send(500, err) if err + return res.send(400, err) if err tasks = result.at(0).get('tasks') # tasks = result[0].tasks tasksWithDates = _.filter tasks, (task) -> !!task.date - return res.send(500, "No events found") if _.isEmpty(tasksWithDates) + return res.send(400, "No events found") if _.isEmpty(tasksWithDates) ical = new icalendar.iCalendar() ical.addProperty('NAME', 'HabitRPG') diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index e520688d24..1195eb1f2e 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -1,11 +1,6 @@ -expect = require 'expect.js' -{BrowserModel: Model} = require 'racer/test/util/model' -derby = require 'derby' -racer = require 'racer' _ = require 'underscore' -moment = require 'moment' request = require 'superagent' -qs = require 'querystring' +expect = require 'expect.js' require 'coffee-script' ## monkey-patch expect.js for better diffs on mocha @@ -16,24 +11,6 @@ expect.Assertion::be = expect.Assertion::equal = (obj) -> @_expected = obj origBe.call this, obj -expect.Assertion::assert = (truth, msg, error) -> - msg = (if @flags.not then error else msg) - ok = (if @flags.not then not truth else truth) - unless ok - err = new Error(msg.call(this)) - if "_expected" of this - err.expected = @_expected - err.actual = @obj - throw err - @and = new expect.Assertion(@obj) -### -racer.use require 'racer-db-mongo' - -store = racer.createStore - db: - type: 'Mongo' - uri: process.env.NODE_DB_URI -### # Custom modules scoring = require '../src/app/scoring' character = require '../src/app/character' @@ -46,56 +23,6 @@ uuid = null taskPath = null baseURL = 'http://localhost:1337/api/v1' -## Helper which clones the content at a path so tests can compare before/after values -# Otherwise, using model.get(path) will give the same object before as after -pathSnapshots = (paths) -> - if _.isString(paths) - return clone(model.get(paths)) - _.map paths, (path) -> clone(model.get(path)) -statsTask = -> pathSnapshots(['_user.stats', taskPath]) # quick snapshot of user.stats & task - -cleanUserObj = -> - userObj = character.newUserObject() - userObj.tasks = {} - userObj.habitIds = [] - userObj.dailyIds = [] - userObj.todoIds = [] - userObj.rewardIds = [] - return userObj -resetUser = -> model.set '_user', cleanUserObj() - -freshTask = (taskObj) -> - resetUser() - # create a test task - uuid = derby.uuid() - taskPath = "_user.tasks.#{uuid}" - {type} = taskObj - model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" - [taskObj.id, taskObj.value] = [uuid, 0] - model.at("_#{type}List").push taskObj - -### -Helper function to determine if stats updates are numerically correct based on scoring -@direction: 'up' or 'down' -@options: The user stats modifiers and times to run, defaults to {times:1, modifiers:{lvl:1, weapon:0, armor:0}} -### -modificationsLookup = (direction, options = {}) -> - merged = _.defaults options, {times:1, lvl:1, weapon:0, armor:0} - {times, lvl, armor, weapon} = merged - userObj = cleanUserObj() - value = 0 - _.times times, (n) -> - delta = scoring.taskDeltaFormula(value, direction) - value += delta - if direction=='up' - gain = scoring.expModifier(delta, options) - userObj.stats.exp += gain - userObj.stats.money += gain - else - loss = scoring.hpModifier(delta, options) - userObj.stats.hp += loss - return {user:userObj, value:value} - ###### Specs ###### describe 'API', -> @@ -114,7 +41,7 @@ describe 'API', -> model = store.createModel() model.set '_userId', uid = model.id() user = character.newUserObject() - user.apiToken = derby.uuid() + user.apiToken = model.id() model.set "users.#{uid}", user # Crappy hack to let server start before tests run setTimeout done, 2000 @@ -133,7 +60,7 @@ describe 'API', -> request.get("#{baseURL}/user") .set('Accept', 'application/json') .end (res) -> - expect(res.statusCode).to.be 500 + expect(res.statusCode).to.be 401 expect(res.body.err).to.be 'You must include a token and uid (user id) in your request' done() @@ -230,3 +157,24 @@ describe 'API', -> # Ensure that the two sets are equal expect(_.difference(_.pluck(res.body,'id'), _.pluck(tasks,'id')).length).to.equal 0 done() + + it 'GET /api/v1/user/tasks (todos)', (done) -> + request.get("#{baseURL}/user/tasks") + .set('Accept', 'application/json') + .set('X-API-User', currentUser.id) + .set('X-API-Key', currentUser.apiToken) + .query(type:'todo') + .end (res) -> + query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) + query.fetch (err, user) -> + expect(res.body.err).to.be undefined + expect(res.statusCode).to.be 200 + currentUser = user.at(0).get() + model.ref '_user', user.at(0) + model.refList "_todoList", "_user.tasks", "_user.todoIds" + tasks = model.get("_todoList") + # Ensure that user owns the tasks + expect(res.body.length).to.equal tasks.length + # Ensure that the two sets are equal + expect(_.difference(_.pluck(res.body,'id'), _.pluck(tasks,'id')).length).to.equal 0 + done() diff --git a/test/mocha.opts b/test/mocha.opts index 74e33e85ea..72889931b5 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,6 +1,6 @@ --colors --reporter spec ---timeout 2500 +--timeout 2800 --ignore-leaks --growl --debug From 624d3360db2baa95025eb68231ed87f2c04601f2 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Tue, 26 Feb 2013 19:27:57 -0500 Subject: [PATCH 25/40] Cleanup pull request diff --- server.js | 5 +++-- src/server/index.coffee | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/server.js b/server.js index f9d64274a7..b9950075fe 100644 --- a/server.js +++ b/server.js @@ -1,8 +1,9 @@ // Load nconf and define default configuration values if config.json or ENV vars are not found var conf = require('nconf'); conf.argv().env().file({ file: __dirname + "/config.json" }).defaults({ - 'PORT': 3000, - 'IP': '0.0.0.0' + 'IP': '0.0.0.0', + 'BASE_URL': 'http://localhost', + 'NODE_ENV': 'development' }); // Override normal ENV values with nconf ENV values (ENV values are used the same way without nconf) diff --git a/src/server/index.coffee b/src/server/index.coffee index 01488aca35..673f54ae75 100644 --- a/src/server/index.coffee +++ b/src/server/index.coffee @@ -17,9 +17,9 @@ middleware = require './middleware' racer.io.set('transports', ['xhr-polling']) racer.ioClient.set('reconnection limit', 300000) # max reconect timeout to 5 minutes racer.set('bundleTimeout', 40000) -unless process.env.NODE_ENV == 'production' - racer.use(racer.logPlugin) - derby.use(derby.logPlugin) +#unless process.env.NODE_ENV == 'production' +# racer.use(racer.logPlugin) +# derby.use(derby.logPlugin) # Infinite stack trace Error.stackTraceLimit = Infinity if process.env.NODE_ENV is 'development' From 0aedef12e2675e592660c4883eeeb2250a6e7c37 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Tue, 26 Feb 2013 19:30:08 -0500 Subject: [PATCH 26/40] More cleanup --- server.js | 1 + src/app/character.coffee | 80 ++++++++++++++++++++-------------------- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/server.js b/server.js index b9950075fe..51db9480cf 100644 --- a/server.js +++ b/server.js @@ -1,6 +1,7 @@ // Load nconf and define default configuration values if config.json or ENV vars are not found var conf = require('nconf'); conf.argv().env().file({ file: __dirname + "/config.json" }).defaults({ + 'PORT': 3000, 'IP': '0.0.0.0', 'BASE_URL': 'http://localhost', 'NODE_ENV': 'development' diff --git a/src/app/character.coffee b/src/app/character.coffee index 16d1c40fc5..901e3cb518 100644 --- a/src/app/character.coffee +++ b/src/app/character.coffee @@ -137,7 +137,7 @@ module.exports.updateUser = (model) -> batch = new BatchUpdate(model) user = batch.user obj = batch.obj() - tasks = obj?.tasks + tasks = obj.tasks # Remove corrupted tasks _.each tasks, (task, key) -> @@ -172,49 +172,49 @@ module.exports.BatchUpdate = BatchUpdate = (model) -> updates = {} { - user: user + user: user - obj: -> - obj ?= model.get 'users.'+user.get('id') - return obj + obj: -> + obj ?= model.get 'users.'+user.get('id') + return obj - startTransaction: -> - # start a batch transaction - nothing between now and @commit() will be set immediately - transactionInProgress = true - model._dontPersist = true - @obj() + startTransaction: -> + # start a batch transaction - nothing between now and @commit() will be set immediately + transactionInProgress = true + model._dontPersist = true + @obj() - ### - Handles updating the user model. If this is an en-mass operation (eg, server cron), changes are queued - but not actually set to the model. It also modifies userObj in case you need to access properties manually later. - If transaction not in progress, it just runs standard model.set() - ### - set: (path, val) -> - updates[path] = val if transactionInProgress - user.set(path, val) + ### + Handles updating the user model. If this is an en-mass operation (eg, server cron), changes are queued + but not actually set to the model. It also modifies userObj in case you need to access properties manually later. + If transaction not in progress, it just runs standard model.set() + ### + set: (path, val) -> + updates[path] = val if transactionInProgress + user.set(path, val) - ### - Hack to get around dom bindings being lost if parent objects are replaced whole-sale - eg, user.set('stats', {hp:50, exp:10...}) will break dom bindings, but user.set('stats.hp',50) is ok - ### - setStats: (stats) -> - stats ?= obj.stats - that = @ - _.each Object.keys(stats), (key) -> that.set "stats.#{key}", stats[key] + ### + Hack to get around dom bindings being lost if parent objects are replaced whole-sale + eg, user.set('stats', {hp:50, exp:10...}) will break dom bindings, but user.set('stats.hp',50) is ok + ### + setStats: (stats) -> + stats ?= obj.stats + that = @ + _.each Object.keys(stats), (key) -> that.set "stats.#{key}", stats[key] - # queue: (path, val) -> - # # Special function for setting object properties by string dot-notation. See http://stackoverflow.com/a/6394168/362790 - # arr = path.split('.') - # arr.reduce (curr, next, index) -> - # if (arr.length - 1) == index - # curr[next] = val - # curr[next] - # , obj +# queue: (path, val) -> +# # Special function for setting object properties by string dot-notation. See http://stackoverflow.com/a/6394168/362790 +# arr = path.split('.') +# arr.reduce (curr, next, index) -> +# if (arr.length - 1) == index +# curr[next] = val +# curr[next] +# , obj - commit: -> - model._dontPersist = false - # some hackery in our own branched racer-db-mongo, see findAndModify of lefnire/racer-db-mongo#habitrpg index.js - user.set "update__", updates - transactionInProgress = false - updates = {} + commit: -> + model._dontPersist = false + # some hackery in our own branched racer-db-mongo, see findAndModify of lefnire/racer-db-mongo#habitrpg index.js + user.set "update__", updates + transactionInProgress = false + updates = {} } From 8ba137d3ce5d2ac2e5f60d8286fbe53f5d456063 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Wed, 27 Feb 2013 16:58:57 -0500 Subject: [PATCH 27/40] Remove config and scoring --- package.json | 3 ++- test/api.mocha.coffee | 2 -- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index a5ab0f7bbe..83fce307e2 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "npm": "1.1.x" }, "scripts": { - "start": "server.js" + "start": "server.js", + "test": "mocha test/api.mocha.coffee" } } diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 2b1931fcce..03c4779f21 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -12,9 +12,7 @@ expect.Assertion::be = expect.Assertion::equal = (obj) -> origBe.call this, obj # Custom modules -scoring = require '../src/app/scoring' character = require '../src/app/character' -config = require './config' ###### Helpers & Variables ###### From ab52c59ee46885387e2375948f8199dd0205085f Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 27 Feb 2013 17:06:39 -0500 Subject: [PATCH 28/40] pin coffeescript --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 83fce307e2..f3f52343c6 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "stripe": "*", "async": "*", "lodash": "*", - "coffee-script": "*", + "coffee-script": "1.4.x", "underscore": "*", "mongoskin": "*", "nconf": "*", From 4b2b0281ee2323f19d8dadac8c9340d72f1445e3 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 27 Feb 2013 17:06:47 -0500 Subject: [PATCH 29/40] add nconf to test --- test/api.mocha.coffee | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 03c4779f21..ac5bedddc9 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -3,6 +3,29 @@ request = require 'superagent' expect = require 'expect.js' require 'coffee-script' +conf = require("nconf") +conf.argv().env().file(file: __dirname + "/config.json").defaults + PORT: 3000 + IP: "0.0.0.0" + BASE_URL: "http://localhost" + NODE_ENV: "development" + + +# Override normal ENV values with nconf ENV values (ENV values are used the same way without nconf) +process.env.IP = conf.get("IP") +process.env.PORT = conf.get("PORT") +process.env.BASE_URL = conf.get("BASE_URL") +process.env.FACEBOOK_KEY = conf.get("FACEBOOK_KEY") +process.env.FACEBOOK_SECRET = conf.get("FACEBOOK_SECRET") +process.env.NODE_DB_URI = conf.get("NODE_DB_URI") +process.env.NODE_ENV = conf.get("NODE_ENV") +process.env.SESSION_SECRET = conf.get("SESSION_SECRET") +process.env.SMTP_USER = conf.get("SMTP_USER") +process.env.SMTP_PASS = conf.get("SMTP_PASS") +process.env.SMTP_SERVICE = conf.get("SMTP_SERVICE") +process.env.STRIPE_API_KEY = conf.get("STRIPE_API_KEY") +process.env.STRIPE_PUB_KEY = conf.get("STRIPE_PUB_KEY") + ## monkey-patch expect.js for better diffs on mocha ## see: https://github.com/LearnBoost/expect.js/pull/34 From 9c6e061e8dd6eacdd194767cfd07b42a2d0d7272 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 27 Feb 2013 17:10:20 -0500 Subject: [PATCH 30/40] return all objects on user API call --- src/server/api.coffee | 2 +- test/api.mocha.coffee | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index c20e0d9582..c98384f47b 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -41,7 +41,7 @@ router.get '/status', (req, res) -> router.get '/user', auth, (req, res) -> self = req.userObj - delete self[val] for val in ['tasks', 'apiToken', 'flags', 'lastCron'] + delete self.apiToken res.json self diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index ac5bedddc9..969eb168f6 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -110,7 +110,7 @@ describe 'API', -> expect(res.statusCode).to.be 200 expect(res.body.id).not.to.be.empty() self = _.clone(currentUser) - delete self[val] for val in ['tasks', 'apiToken', 'flags', 'lastCron'] + delete self.apiToken expect(res.body).to.eql self done() From afe95975e1c31a4eb1b94c2a19b3c92c8575f745 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 28 Feb 2013 18:03:35 -0500 Subject: [PATCH 31/40] fix up deprecated to work again (order of middleware) --- src/server/deprecated.coffee | 35 +++++------------------------------ src/server/index.coffee | 2 +- 2 files changed, 6 insertions(+), 31 deletions(-) diff --git a/src/server/deprecated.coffee b/src/server/deprecated.coffee index 8e497764ef..d4e5d3b694 100644 --- a/src/server/deprecated.coffee +++ b/src/server/deprecated.coffee @@ -13,31 +13,6 @@ router.get '/:uid/up/:score?', (req, res) -> res.send(500, deprecatedMessage) router.get '/:uid/down/:score?', (req, res) -> res.send(500, deprecatedMessage) router.post '/users/:uid/tasks/:taskId/:direction', (req, res) -> res.send(500, deprecatedMessage) -router.get '/v1/users/:uid/tasks/:taskId/:direction', (req, res) -> - {uid, taskId, direction} = req.params - {apiToken} = req.query - {title, service, icon} = req.body - console.log {params:req.params, body:req.body} if process.env.NODE_ENV == 'development' - - # Send error responses for improper API call - return res.send(500, 'request body "apiToken" required') unless apiToken - return res.send(500, ':uid required') unless uid - return res.send(500, ':taskId required') unless taskId - return res.send(500, ":direction must be 'up' or 'down'") unless direction in ['up','down'] - - model = req.getModel() - req._isServer = true - model.fetch model.query('users').withIdAndToken(uid, apiToken), (err, result) -> - return res.send(500, err) if err - user = result.at(0) - userObj = user.get() - if _.isEmpty(userObj) - return res.send(500, "User with uid=#{uid}, token=#{apiToken} not found. Make sure you're not using your username, but your User Id") - - model.ref('_user', user) - - res.send(user) - router.post '/v1/users/:uid/tasks/:taskId/:direction', (req, res) -> {uid, taskId, direction} = req.params {apiToken, title, service, icon} = req.body @@ -50,15 +25,15 @@ router.post '/v1/users/:uid/tasks/:taskId/:direction', (req, res) -> return res.send(500, ":direction must be 'up' or 'down'") unless direction in ['up','down'] model = req.getModel() - req._isServer = true model.fetch model.query('users').withIdAndToken(uid, apiToken), (err, result) -> return res.send(500, err) if err - user = result.at(0) + user = result userObj = user.get() if _.isEmpty(userObj) return res.send(500, "User with uid=#{uid}, token=#{apiToken} not found. Make sure you're not using your username, but your User Id") model.ref('_user', user) + req._isServer = true # Create task if doesn't exist # TODO add service & icon to task @@ -73,8 +48,8 @@ router.post '/v1/users/:uid/tasks/:taskId/:direction', (req, res) -> down: true 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." - scoring.setModel(model) - delta = scoring.score(taskId, direction) + score = new scoring.Scoring(model) + delta = score.score(taskId, direction) result = model.get ('_user.stats') result.delta = delta res.send(result) @@ -88,7 +63,7 @@ router.get '/v1/users/:uid/calendar.ics', (req, res) -> query = model.query('users').withIdAndToken(uid, apiToken) query.fetch (err, result) -> return res.send(500, err) if err - tasks = result.at(0).get('tasks') + tasks = result.get('tasks') # tasks = result[0].tasks tasksWithDates = _.filter tasks, (task) -> !!task.date return res.send(500, "No events found") if _.isEmpty(tasksWithDates) diff --git a/src/server/index.coffee b/src/server/index.coffee index 673f54ae75..41b447f677 100644 --- a/src/server/index.coffee +++ b/src/server/index.coffee @@ -76,6 +76,7 @@ mongo_store = new MongoStore {url: process.env.NODE_DB_URI}, -> .use(store.modelMiddleware()) # API should be hit before all other routes .use('/api/v1', require('./api').middleware) + .use(require('./deprecated').middleware) # Show splash page for newcomers .use(middleware.splash) .use(priv.middleware) @@ -84,7 +85,6 @@ mongo_store = new MongoStore {url: process.env.NODE_DB_URI}, -> # Creates an express middleware from the app's routes .use(app.router()) .use(require('./static').middleware) - .use(require('./deprecated').middleware) .use(expressApp.router) .use(serverError(root)) From 3017f5a592d96c6617a5196b001b9e4077f2ea01 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 28 Feb 2013 18:24:14 -0500 Subject: [PATCH 32/40] small test tweaks --- test/api.mocha.coffee | 19 +++---------------- test/casper/rest.casper.coffee | 2 +- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 969eb168f6..f1a7abec1e 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -4,27 +4,14 @@ expect = require 'expect.js' require 'coffee-script' conf = require("nconf") -conf.argv().env().file(file: __dirname + "/config.json").defaults - PORT: 3000 - IP: "0.0.0.0" - BASE_URL: "http://localhost" - NODE_ENV: "development" - +conf.argv().env().file({file: __dirname + '../config.json'}).defaults # Override normal ENV values with nconf ENV values (ENV values are used the same way without nconf) -process.env.IP = conf.get("IP") -process.env.PORT = conf.get("PORT") +#FIXME can't get nconf file above to load... process.env.BASE_URL = conf.get("BASE_URL") process.env.FACEBOOK_KEY = conf.get("FACEBOOK_KEY") process.env.FACEBOOK_SECRET = conf.get("FACEBOOK_SECRET") -process.env.NODE_DB_URI = conf.get("NODE_DB_URI") -process.env.NODE_ENV = conf.get("NODE_ENV") -process.env.SESSION_SECRET = conf.get("SESSION_SECRET") -process.env.SMTP_USER = conf.get("SMTP_USER") -process.env.SMTP_PASS = conf.get("SMTP_PASS") -process.env.SMTP_SERVICE = conf.get("SMTP_SERVICE") -process.env.STRIPE_API_KEY = conf.get("STRIPE_API_KEY") -process.env.STRIPE_PUB_KEY = conf.get("STRIPE_PUB_KEY") +process.env.NODE_DB_URI = 'mongodb://localhost/habirpg' ## monkey-patch expect.js for better diffs on mocha ## see: https://github.com/LearnBoost/expect.js/pull/34 diff --git a/test/casper/rest.casper.coffee b/test/casper/rest.casper.coffee index 18aaf3afde..767d93489e 100644 --- a/test/casper/rest.casper.coffee +++ b/test/casper/rest.casper.coffee @@ -12,8 +12,8 @@ casper.start "#{url}/?play=1", -> @fill 'form#derby-auth-register', username: user1.id email: "{user1.id}@gmail.com" - 'email-confirmation': "{user1.id}@gmail.com" password: 'habitrpg123' + 'password-confirmation': "habitrpg123" , true casper.thenOpen "#{url}/logout" casper.thenOpen "#{url}/?play=1", -> From 6fe375114c4b5e49c49d1a0e91f22fabc10dfae2 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 28 Feb 2013 20:00:08 -0500 Subject: [PATCH 33/40] set req._isServer=true for API calls, use that as access check --- src/server/api.coffee | 1 + src/server/store.coffee | 7 ++----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index c98384f47b..09824e294c 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -33,6 +33,7 @@ auth = (req, res, next) -> req.user = user req.userObj = user.get() return res.json 401, NO_USER_FOUND if !req.userObj || _.isEmpty(req.userObj) + req._isServer = true next() router.get '/status', (req, res) -> diff --git a/src/server/store.coffee b/src/server/store.coffee index 659d890685..ac4ab491af 100644 --- a/src/server/store.coffee +++ b/src/server/store.coffee @@ -30,9 +30,7 @@ userAccess = (store) -> err = arguments[arguments.length - 1] # return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@) - # TEMPORARY token check to allow api - # Must fix before release - return accept(true) + return accept(true) if derbyAuth.isServer(@) return accept(false) if derbyAuth.bustedSession(@) @@ -45,8 +43,7 @@ userAccess = (store) -> return accept(true) # Same session (user.id = this.session.userId) - if (uid is @session.userId) or derbyAuth.isServer(@) - return accept(true) + return accept(true) if uid is @session.userId accept(false) From 26de70c17c34c16742d80562d82d2a53533ddd78 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 28 Feb 2013 20:42:52 -0500 Subject: [PATCH 34/40] temporary set session so we can save new user in tests --- test/api.mocha.coffee | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index f1a7abec1e..145542a525 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -50,7 +50,9 @@ describe 'API', -> model.set '_userId', uid = model.id() user = character.newUserObject() user.apiToken = model.id() + model.session = {userId:uid} model.set "users.#{uid}", user + delete model.session # Crappy hack to let server start before tests run setTimeout done, 2000 From c080ddc6832cad40e252497b421bacc4e268a5c5 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Fri, 1 Mar 2013 05:16:55 -0500 Subject: [PATCH 35/40] Separate out logic for tasks plus specs --- src/server/api.coffee | 55 ++++++++++++++++++++++++++----------------- test/api.mocha.coffee | 47 ++++++++++++++++++++++++++++++++++-- 2 files changed, 78 insertions(+), 24 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index 09824e294c..3672799667 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -40,11 +40,11 @@ router.get '/status', (req, res) -> res.json status: 'up' router.get '/user', auth, (req, res) -> - self = req.userObj + user = req.userObj - delete self.apiToken + delete user.apiToken - res.json self + res.json user router.get '/task/:id', auth, (req, res) -> task = req.userObj.tasks[req.params.id] @@ -52,30 +52,41 @@ router.get '/task/:id', auth, (req, res) -> res.json 200, task -router.put '/task/:id', auth, (req, res) -> - task = req.userObj.tasks[req.params.id] - return res.json 400, err: "No task found." if !task || _.isEmpty(task) +validateTask = (req, res, next) -> + task = {} + # If we're updating, get the task from the user + if req.method is 'PUT' + task = req.userObj?.tasks[req.params.id] + return res.json 400, err: "No task found." if !task || _.isEmpty(task) - title = sanitize(req.body.title).xss() - text = sanitize(req.body.text).xss() + newTask = { type, text, notes, value, up, down, completed } = req.body - task.title = title if title - task.text = text if text - #task.type = req.body.type if /^(habit|todo|daily|reward)$/.test req.body.type + text = sanitize(text).xss() + notes = sanitize(notes).xss() + value = sanitize(value).toInt() - req.user.set "tasks.#{task.id}", task + switch type + when 'habit' + newTask.up = true unless typeof up is 'boolean' + newTask.down = true unless typeof down is 'boolean' + when 'daily', 'todo' + newTask.completed = false unless typeof completed is 'boolean' - res.json 200, task + _.extend task, newTask + req.task = task + next() -router.post '/user/task', auth, (req, res) -> - task = { title, text, type, value, note } = req.body - return res.json 400, err: "type must be habit, todo, daily, or reward" unless /^habit|todo|daily|reward$/.test type - return res.json 400, err: "must have a title" unless check(title).notEmpty() - return res.json 400, err: "must have text" unless check(text).notEmpty() +router.put '/task/:id', auth, validateTask, (req, res) -> + req.user.set "tasks.#{req.task.id}", req.task - self = req.userObj + res.json 200, req.task - value ||= 0 +router.post '/user/task', auth, validateTask, (req, res) -> + task = req.task + type = task.type + + unless /^habit|todo|daily|reward$/.test type + return res.json 400, err: 'type must be habit, todo, daily, or reward' model = req.getModel() model.ref '_user', req.user @@ -85,8 +96,8 @@ router.post '/user/task', auth, (req, res) -> res.json 201, task router.get '/user/tasks', auth, (req, res) -> - self = req.userObj - return res.json 400, NO_USER_FOUND if !self || _.isEmpty(self) + user = req.userObj + return res.json 400, NO_USER_FOUND if !user || _.isEmpty(user) model = req.getModel() model.ref '_user', req.user diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 145542a525..229603a815 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -132,18 +132,61 @@ describe 'API', -> expect(user.get().tasks[res.body.id]).to.be.an('object') done() + it 'POST /api/v1/user/task (without type)', (done) -> + request.post("#{baseURL}/user/task") + .set('Accept', 'application/json') + .set('X-API-User', currentUser.id) + .set('X-API-Key', currentUser.apiToken) + .send({}) + .end (res) -> + query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) + query.fetch (err, user) -> + expect(res.body.err).to.be 'type must be habit, todo, daily, or reward' + expect(res.statusCode).to.be 400 + done() + + it 'POST /api/v1/user/task (only type)', (done) -> + request.post("#{baseURL}/user/task") + .set('Accept', 'application/json') + .set('X-API-User', currentUser.id) + .set('X-API-Key', currentUser.apiToken) + .send(type: 'habit') + .end (res) -> + query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) + query.fetch (err, user) -> + expect(res.body.err).to.be undefined + expect(res.statusCode).to.be 201 + expect(res.body.id).not.to.be.empty() + # Ensure that user owns the newly created object + expect(user.get().tasks[res.body.id]).to.be.an('object') + done() + it 'PUT /api/v1/task/:id', (done) -> tid = _.pluck(currentUser.tasks, 'id')[0] request.put("#{baseURL}/task/#{tid}") .set('Accept', 'application/json') .set('X-API-User', currentUser.id) .set('X-API-Key', currentUser.apiToken) - .send(title: 'a new title',text: 'hi') + .send(text: 'bye') + .end (res) -> + expect(res.body.err).to.be undefined + expect(res.statusCode).to.be 200 + currentUser.tasks[tid].text = 'bye' + expect(res.body).to.eql currentUser.tasks[tid] + done() + + it 'PUT /api/v1/task/:id (update notes)', (done) -> + tid = _.pluck(currentUser.tasks, 'id')[0] + request.put("#{baseURL}/task/#{tid}") + .set('Accept', 'application/json') + .set('X-API-User', currentUser.id) + .set('X-API-Key', currentUser.apiToken) + .send(text: 'hi',notes:'foobar matey') .end (res) -> expect(res.body.err).to.be undefined expect(res.statusCode).to.be 200 - currentUser.tasks[tid].title = 'a new title' currentUser.tasks[tid].text = 'hi' + currentUser.tasks[tid].notes = 'foobar matey' expect(res.body).to.eql currentUser.tasks[tid] done() From 4e6c741594a6347bd2b5425138154abdd4de0b13 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Fri, 1 Mar 2013 05:48:49 -0500 Subject: [PATCH 36/40] Shouldn't update type in task PUT --- src/server/api.coffee | 13 ++++++++----- test/api.mocha.coffee | 17 ++++++++++++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index 3672799667..1234f0cebb 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -54,12 +54,18 @@ router.get '/task/:id', auth, (req, res) -> validateTask = (req, res, next) -> task = {} + newTask = { type, text, notes, value, up, down, completed } = req.body + # If we're updating, get the task from the user if req.method is 'PUT' task = req.userObj?.tasks[req.params.id] return res.json 400, err: "No task found." if !task || _.isEmpty(task) - - newTask = { type, text, notes, value, up, down, completed } = req.body + # Strip for now + type = undefined + delete newTask.type + else if req.method is 'POST' + unless /^habit|todo|daily|reward$/.test type + return res.json 400, err: 'type must be habit, todo, daily, or reward' text = sanitize(text).xss() notes = sanitize(notes).xss() @@ -85,9 +91,6 @@ router.post '/user/task', auth, validateTask, (req, res) -> task = req.task type = task.type - unless /^habit|todo|daily|reward$/.test type - return res.json 400, err: 'type must be habit, todo, daily, or reward' - model = req.getModel() model.ref '_user', req.user model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 229603a815..1d02e9e593 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -175,8 +175,23 @@ describe 'API', -> expect(res.body).to.eql currentUser.tasks[tid] done() + it 'PUT /api/v1/task/:id (shouldnt update type)', (done) -> + tid = _.pluck(currentUser.tasks, 'id')[1] + type = if currentUser.tasks[tid].type is 'habit' then 'daily' else 'habit' + request.put("#{baseURL}/task/#{tid}") + .set('Accept', 'application/json') + .set('X-API-User', currentUser.id) + .set('X-API-Key', currentUser.apiToken) + .send(type: type, text: 'fishman') + .end (res) -> + expect(res.body.err).to.be undefined + expect(res.statusCode).to.be 200 + currentUser.tasks[tid].text = 'fishman' + expect(res.body).to.eql currentUser.tasks[tid] + done() + it 'PUT /api/v1/task/:id (update notes)', (done) -> - tid = _.pluck(currentUser.tasks, 'id')[0] + tid = _.pluck(currentUser.tasks, 'id')[2] request.put("#{baseURL}/task/#{tid}") .set('Accept', 'application/json') .set('X-API-User', currentUser.id) From 33b529731cab908c5c8e6f589f6f553f0bfbb25c Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Fri, 1 Mar 2013 06:03:05 -0500 Subject: [PATCH 37/40] Remove extra query --- test/api.mocha.coffee | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 1d02e9e593..6dcbdaa2df 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -139,11 +139,9 @@ describe 'API', -> .set('X-API-Key', currentUser.apiToken) .send({}) .end (res) -> - query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken) - query.fetch (err, user) -> - expect(res.body.err).to.be 'type must be habit, todo, daily, or reward' - expect(res.statusCode).to.be 400 - done() + expect(res.body.err).to.be 'type must be habit, todo, daily, or reward' + expect(res.statusCode).to.be 400 + done() it 'POST /api/v1/user/task (only type)', (done) -> request.post("#{baseURL}/user/task") From 5ce5152757e12184e9d7c11d893db3601f2a119a Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Fri, 1 Mar 2013 06:11:32 -0500 Subject: [PATCH 38/40] More secure regex type check --- src/server/api.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index 1234f0cebb..7ad2e2330d 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -64,7 +64,7 @@ validateTask = (req, res, next) -> type = undefined delete newTask.type else if req.method is 'POST' - unless /^habit|todo|daily|reward$/.test type + unless /^(habit|todo|daily|reward)$/.test type return res.json 400, err: 'type must be habit, todo, daily, or reward' text = sanitize(text).xss() @@ -106,7 +106,7 @@ router.get '/user/tasks', auth, (req, res) -> model.ref '_user', req.user tasks = [] types = ['habit','todo','daily','reward'] - if /^habit|todo|daily|reward$/.test req.query.type + if /^(habit|todo|daily|reward)$/.test req.query.type types = [req.query.type] for type in types model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" From 4377ff918a2ac0fa0ae2b32155872d8e571d380a Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Fri, 1 Mar 2013 15:11:35 -0500 Subject: [PATCH 39/40] Prepend /user to the /task/:id routes --- src/server/api.coffee | 10 +++++----- test/api.mocha.coffee | 16 ++++++++-------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/server/api.coffee b/src/server/api.coffee index 7ad2e2330d..287b354f5d 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -20,6 +20,9 @@ NO_USER_FOUND = err: "No user found." $ mocha test/api.mocha.coffee ### +router.get '/status', (req, res) -> + res.json status: 'up' + auth = (req, res, next) -> uid = req.headers['x-api-user'] token = req.headers['x-api-key'] @@ -36,9 +39,6 @@ auth = (req, res, next) -> req._isServer = true next() -router.get '/status', (req, res) -> - res.json status: 'up' - router.get '/user', auth, (req, res) -> user = req.userObj @@ -46,7 +46,7 @@ router.get '/user', auth, (req, res) -> res.json user -router.get '/task/:id', auth, (req, res) -> +router.get '/user/task/:id', auth, (req, res) -> task = req.userObj.tasks[req.params.id] return res.json 400, err: "No task found." if !task || _.isEmpty(task) @@ -82,7 +82,7 @@ validateTask = (req, res, next) -> req.task = task next() -router.put '/task/:id', auth, validateTask, (req, res) -> +router.put '/user/task/:id', auth, validateTask, (req, res) -> req.user.set "tasks.#{req.task.id}", req.task res.json 200, req.task diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 6dcbdaa2df..6d82d71516 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -104,9 +104,9 @@ describe 'API', -> expect(res.body).to.eql self done() - it 'GET /api/v1/task/:id', (done) -> + it 'GET /api/v1/user/task/:id', (done) -> tid = _.pluck(currentUser.tasks, 'id')[0] - request.get("#{baseURL}/task/#{tid}") + request.get("#{baseURL}/user/task/#{tid}") .set('Accept', 'application/json') .set('X-API-User', currentUser.id) .set('X-API-Key', currentUser.apiToken) @@ -159,9 +159,9 @@ describe 'API', -> expect(user.get().tasks[res.body.id]).to.be.an('object') done() - it 'PUT /api/v1/task/:id', (done) -> + it 'PUT /api/v1/user/task/:id', (done) -> tid = _.pluck(currentUser.tasks, 'id')[0] - request.put("#{baseURL}/task/#{tid}") + request.put("#{baseURL}/user/task/#{tid}") .set('Accept', 'application/json') .set('X-API-User', currentUser.id) .set('X-API-Key', currentUser.apiToken) @@ -173,10 +173,10 @@ describe 'API', -> expect(res.body).to.eql currentUser.tasks[tid] done() - it 'PUT /api/v1/task/:id (shouldnt update type)', (done) -> + it 'PUT /api/v1/user/task/:id (shouldnt update type)', (done) -> tid = _.pluck(currentUser.tasks, 'id')[1] type = if currentUser.tasks[tid].type is 'habit' then 'daily' else 'habit' - request.put("#{baseURL}/task/#{tid}") + request.put("#{baseURL}/user/task/#{tid}") .set('Accept', 'application/json') .set('X-API-User', currentUser.id) .set('X-API-Key', currentUser.apiToken) @@ -188,9 +188,9 @@ describe 'API', -> expect(res.body).to.eql currentUser.tasks[tid] done() - it 'PUT /api/v1/task/:id (update notes)', (done) -> + it 'PUT /api/v1/user/task/:id (update notes)', (done) -> tid = _.pluck(currentUser.tasks, 'id')[2] - request.put("#{baseURL}/task/#{tid}") + request.put("#{baseURL}/user/task/#{tid}") .set('Accept', 'application/json') .set('X-API-User', currentUser.id) .set('X-API-Key', currentUser.apiToken) From bc76aee85e736739551e412bc90360090cfab7ab Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Fri, 1 Mar 2013 15:13:53 -0500 Subject: [PATCH 40/40] Remove nodetime --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index f3f52343c6..a80458cca0 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "mongoskin": "*", "nconf": "*", "icalendar": "git://github.com/lefnire/node-icalendar#master", - "nodetime": "*", "superagent": "~0.12.4", "resolve": "~0.2.3", "browserify": "1.17.3",