From f75e3662b9a358af4bb76eb59011bfbbd99b8335 Mon Sep 17 00:00:00 2001 From: Daniel Saewitz Date: Wed, 20 Feb 2013 20:04:09 -0500 Subject: [PATCH] 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()