From a2b30dcb1a42a61dce75915892376001cc87aecb Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 1 Jan 2014 20:04:48 -0700 Subject: [PATCH] #2217 merge apiv2 route definitions with swagger decorations --- bower.json | 2 +- package.json | 2 +- src/apidoc.coffee | 329 ------------------------- src/routes/apiv2.coffee | 525 ++++++++++++++++++++++++++++++++++++++++ src/routes/apiv2.js | 140 ----------- src/server.js | 6 +- views/static/api.jade | 2 +- 7 files changed, 531 insertions(+), 475 deletions(-) delete mode 100644 src/apidoc.coffee create mode 100644 src/routes/apiv2.coffee delete mode 100644 src/routes/apiv2.js diff --git a/bower.json b/bower.json index 8afe312ae1..d66189fe30 100644 --- a/bower.json +++ b/bower.json @@ -39,7 +39,7 @@ "sticky": "*", "bootstrap-tour": "~0.8.0", "angular-ui-utils": "~0.1.0", - "swagger-ui": "~2.0.3" + "swagger-ui": "https://github.com/wordnik/swagger-ui.git#105c516f5f055b140e935e9cfe0c36f841921dff" }, "resolutions": { "jquery": "~2.0.3", diff --git a/package.json b/package.json index a187a7f85d..bd31016872 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "js2xmlparser": "~0.1.2", "mongoose": "~3.8.1", "domain-middleware": "~0.1.0", - "swagger-node-express": "~1.3.2" + "swagger-node-express": "git://github.com/lefnire/swagger-node-express#habitrpg" }, "private": true, "subdomain": "habitrpg", diff --git a/src/apidoc.coffee b/src/apidoc.coffee deleted file mode 100644 index 46c9e99fd6..0000000000 --- a/src/apidoc.coffee +++ /dev/null @@ -1,329 +0,0 @@ -# see https://github.com/wordnik/swagger-node-express - -_ = require('lodash') -nconf = require('nconf') -content = require('habitrpg-shared').content - -module.exports = (swagger) -> - [path,body,query] = [swagger.pathParam, swagger.bodyParam, swagger.queryParam] - - swagger.configureSwaggerPaths("", "/api-docs", "") - - api = - "/content": - description: "Get all available content objects. This is essential, since Habit often depends on item keys (eg, when purchasing a weapon)." - method: 'GET' - - "/export/history": - description: "Export user history" - method: 'GET' - - # --------------------------------- - # User - # --------------------------------- - - # Scoring - - "/user/tasks/{id}/{direction}": - description: "Simple scoring of a task" - params: [ - path("id", "ID of the task to score. If this task doesn't exist, a task will be created automatically", "string") - path("direction", "Either 'up' or 'down'", "string") - ] - method: 'POST' - - # Tasks - "/user/tasks": - description: "Get all user's tasks" - - "/user/tasks/{id}": - description: "Get an individual task" - params: [ - path("id", "Task ID", "string") - ] - - "/user/tasks/{id}": - description: "Update a user's task" - method: 'PUT' - params: [ - path("id", "Task ID", "string") - body("","Send up the whole task","object") - ] - - "/user/tasks/{id}": - description: "Delete a task" - method: 'DELETE' - params: [ path("id", "Task ID", "string") ] - - "/user/tasks": - description: "Create a task" - method: 'POST' - params: [ body("","Send up the whole task","object") ] - - "/user/tasks/{id}/sort": - method: 'POST' - description: 'Sort tasks' - params: [ - path("id", "Task ID", "string") - query("from","Index where you're sorting from (0-based)","integer") - query("to","Index where you're sorting to (0-based)","integer") - ] - - "/user/tasks/clear-completed": - method: 'POST' - description: "Clears competed To-Dos (needed periodically for performance." - - "/user/tasks/{id}/unlink": - method: 'POST' - description: 'Unlink a task from its challenge' - # TODO query params? - params: [path("id", "Task ID", "string")] - - - # Inventory - "/user/inventory/buy/{key}": - method: 'POST' - description: "Buy a gear piece and equip it automatically" - params:[ - path 'key',"The key of the item to buy (call /content route for available keys)",'string', _.keys(content.gear.flat) - #TODO embed keys - ] - - "/user/inventory/sell/{type}/{key}": - method: 'POST' - description: "Sell inventory items back to Alexander" - params: [ - #TODO verify these are the correct types - path('type',"The type of object you're selling back.",'string',['gear','eggs','hatchingPotions','food']) - path('key',"The object key you're selling back (call /content route for available keys)",'string') - ] - - "/user/inventory/purchase/{type}/{key}": - method: 'POST' - description: "Purchase a gem-purchaseable item from Alexander" - params:[ - path('type',"The type of object you're purchasing.",'string',['gear','eggs','hatchingPotions','food']) - path('key',"The object key you're purchasing (call /content route for available keys)",'string') - ] - - "/user/inventory/feed/{pet}/{food}": - method: 'POST' - description: "Feed your pet some food" - params: [ - path 'pet',"The key of the pet you're feeding",'string'#,_.keys(content.pets)) - path 'food',"The key of the food to feed your pet",'string',_.keys(content.food) - ] - - "/user/inventory/equip/{type}/{key}": - method: 'POST' - description: "Equip an item (either pets, mounts, or gear)" - params: [ - path 'type',"Type to equip",'string',['pets','mounts','gear'] - path 'key',"The object key you're equipping (call /content route for available keys)",'string' - ] - - "/user/inventory/hatch/{egg}/{hatchingPotion}": - method: 'POST' - description: "Pour a hatching potion on an egg" - params: [ - path 'egg',"The egg key to hatch",'string',_.keys(content.eggs) - path 'hatchingPotion',"The hatching potion to pour",'string',_.keys(content.hatchingPotions) - ] - - # User - "/user:GET": - path: '/user' - description: "Get the full user object" - - "/user:PUT": - path: '/user' - method: 'PUT' - description: "Update the user object (only certain attributes are supported)" - params: [ - body '','The user object','object' - ] - - "/user:DELETE": - path: '/user' - method: 'DELETE' - description: "Delete a user object entirely, USE WITH CAUTION!" - - "/user/revive": - method: 'POST' - description: "Revive your dead user" - - "/user/reroll": - method: 'POST' - description: 'Drink the Fortify Potion (Note, it used to be called re-roll)' - - "/user/reset": - method: 'POST' - description: "Completely reset your account" - - "/user/sleep": - method: 'POST' - description: "Toggle whether you're resting in the inn" - - "/user/rebirth": - method: 'POST' - description: "Rebirth your avatar" - - "/user/class/change": - method: 'POST' - description: "Either remove your avatar's class, or change it to something new" - params: [ - query 'class',"The key of the class to change to. If not provided, user's class is removed.",'string',['warrior','healer','rogue','wizard',''] - ] - - "/user/class/allocate": - method: 'POST' - description: "Allocate one point towards an attribute" - params: [ - query 'stat','The stat to allocate towards','string' - ] - - "/user/class/cast/{spell}": - method: 'POST' - description: "Cast a spell" - #TODO finish - - "/user/unlock": - method: 'POST' - description: "Unlock a certain gem-purchaseable path (or multiple paths)" - params: [ - query 'path',"The path to unlock, such as hair.green or shirts.red,shirts.blue",'string' - ] - - "/user/batch-update": - method: 'POST' - description: "This is an advanced route which is useful for apps which might for example need offline support. You can send a whole batch of user-based operations, which allows you to queue them up offline and send them all at once. The format is {op:'nameOfOperation',params:{},body:{},query:{}}" - params:[ - body '','The array of batch-operations to perform','object' - ] - - # Tags - "/user/tags": - method: 'POST' - description: 'Create a new tag' - params: [ - #TODO document - body '','New tag','object' - ] - - "/user/tags/{id}:PUT": - path: 'user/tags/{id}' - method: 'PUT' - description: "Edit a tag" - params: [ - path 'id','The id of the tag to edit','string' - body '','Tag edits','object' - ] - - "/user/tags/{id}:DELETE": - path: 'user/tags/{id}' - method: 'DELETE' - description: 'Delete a tag' - params: [ - path 'id','Id of tag to delete','string' - ] - - - # --------------------------------- - # Groups - # --------------------------------- - "/groups:GET": - path: '/groups' - "/groups:POST": - path: '/groups' - method: 'POST' - "/groups/{gid}:GET": - path: '/groups/{gid}' - "/groups/{gid}:POST": - path: '/groups/{gid}' - method: 'POST' - "/groups/{gid}": - path: '/groups/{gid}' - method: 'PUT' - - "/groups/{gid}/join": - method: 'POST' - "/groups/{gid}/leave": - method: 'POST' - "/groups/{gid}/invite": - method: 'POST' - "/groups/{gid}/removeMember": - method: 'POST' - "/groups/{gid}/questAccept": - method: 'POST' - # query={key} (optional. if provided, trigger new invite, if not, accept existing invite) - "/groups/{gid}/questReject": - method: 'POST' - "/groups/{gid}/questAbort": - method: 'POST' - - #GET /groups/:gid/chat - "/groups/{gid}/chat": - method: 'POST' - "/groups/{gid}/chat/{messageId}": - method: 'DELETE' - - - # --------------------------------- - # Members - # --------------------------------- - "/members/{uid}":{} - - # --------------------------------- - # Challenges - # --------------------------------- - - # Note: while challenges belong to groups, and would therefore make sense as a nested resource - # (eg /groups/:gid/challenges/:cid), they will also be referenced by users from the "challenges" tab - # without knowing which group they belong to. So to prevent unecessary lookups, we have them as a top-level resource - "/challenges:GET": - path: '/challenges' - "/challenges:POST": - path: '/challenges' - method: 'POST' - "/challenges/{cid}:GET": {} - "/challenges/{cid}:POST": - path: '/challenges/{cid}' - method: 'POST' - "/challenges/{cid}:DELETE": - path: '/challenges/{cid}' - method: 'DELETE' - "/challenges/{cid}/close": - method: 'POST' - "/challenges/{cid}/join": - method: 'POST' - "/challenges/{cid}/leave": - method: 'POST' - "/challenges/{cid}/member/{uid}":{} - - _.each api, (spec, path) -> - ## Spec format is: - # spec: - # path: "/pet/{petId}" - # description: "Operations about pets" - # notes: "Returns a pet based on ID" - # summary: "Find pet by ID" - # method: "GET" - # params: [path("petId", "ID of pet that needs to be fetched", "string")] - # type: "Pet" - # errorResponses: [swagger.errors.invalid("id"), swagger.errors.notFound("pet")] - # nickname: "getPetById" - - spec.description ?= '' - _.defaults spec, - path: path - nickname: path - notes: spec.description - summary: spec.description - params: [] - #type: 'Pet' - errorResponses: [] - method: 'GET' - route = {spec} - swagger["add#{route.spec.method}"](route);true - - swagger.configure(nconf.get('BASE_URL'), "0.1") \ No newline at end of file diff --git a/src/routes/apiv2.coffee b/src/routes/apiv2.coffee new file mode 100644 index 0000000000..068c9b78ec --- /dev/null +++ b/src/routes/apiv2.coffee @@ -0,0 +1,525 @@ +### +---------- /api/v2 API ------------ +see https://github.com/wordnik/swagger-node-express +Every url added to router is prefaced by /api/v2 +Note: Many user-route ops exist in habitrpg-shard/script/index.coffee#user.ops, so that they can (1) be called both +client and server. +v1 user. Requires x-api-user (user id) and x-api-key (api key) headers, Test with: +$ mocha test/user.mocha.coffee +### + +user = require("../controllers/user") +groups = require("../controllers/groups") +auth = require("../controllers/auth") +admin = require("../controllers/admin") +challenges = require("../controllers/challenges") +dataexport = require("../controllers/dataexport") +nconf = require("nconf") +middleware = require("../middleware") +cron = user.cron +_ = require('lodash') +content = require('habitrpg-shared').content + + +module.exports = (swagger, v2, errorHandler) -> + [path,body,query] = [swagger.pathParam, swagger.bodyParam, swagger.queryParam] + + swagger.setAppHandler(v2); + swagger.setErrorHandler("next"); + swagger.configureSwaggerPaths("", "/api-docs", "") + + api = + + '/status': + spec: + description: "Returns the status of the server (up or down)" + action: (req, res) -> + test() + res.json status: "up" + + '/content': + spec: + description: "Get all available content objects. This is essential, since Habit often depends on item keys (eg, when purchasing a weapon)." + action: user.getContent + + + "/export/history": + spec: + description: "Export user history" + method: 'GET' + middleware: auth.auth + action: dataexport.history #[todo] encode data output options in the data controller and use these to build routes + + # --------------------------------- + # User + # --------------------------------- + + # Scoring + + "/user/tasks/{id}/{direction}": + spec: + description: "Simple scoring of a task" + params: [ + path("id", "ID of the task to score. If this task doesn't exist, a task will be created automatically", "string") + path("direction", "Either 'up' or 'down'", "string") + ] + method: 'POST' + action: user.score + + # Tasks + "/user/tasks": + spec: + description: "Get all user's tasks" + action: user.getTasks + + "/user/tasks/{id}": + spec: + description: "Get an individual task" + params: [ + path("id", "Task ID", "string") + ] + action: user.getTask + + "/user/tasks/{id}": + spec: + description: "Update a user's task" + method: 'PUT' + params: [ + path("id", "Task ID", "string") + body("","Send up the whole task","object") + ] + action: user.updateTask + + "/user/tasks/{id}": + spec: + description: "Delete a task" + method: 'DELETE' + params: [ path("id", "Task ID", "string") ] + action: user.deleteTask + + "/user/tasks": + spec: + description: "Create a task" + method: 'POST' + params: [ body("","Send up the whole task","object") ] + action: user.addTask + + + "/user/tasks/{id}/sort": + spec: + method: 'POST' + description: 'Sort tasks' + params: [ + path("id", "Task ID", "string") + query("from","Index where you're sorting from (0-based)","integer") + query("to","Index where you're sorting to (0-based)","integer") + ] + action: user.sortTask + + "/user/tasks/clear-completed": + spec: + method: 'POST' + description: "Clears competed To-Dos (needed periodically for performance." + action: user.clearCompleted + + + "/user/tasks/{id}/unlink": + spec: + method: 'POST' + description: 'Unlink a task from its challenge' + # TODO query params? + params: [path("id", "Task ID", "string")] + middleware: auth.auth ## removing cron since they may want to remove task first + action: challenges.unlink + + + # Inventory + "/user/inventory/buy/{key}": + spec: + method: 'POST' + description: "Buy a gear piece and equip it automatically" + params:[ + path 'key',"The key of the item to buy (call /content route for available keys)",'string', _.keys(content.gear.flat) + #TODO embed keys + ] + action: user.buy + + "/user/inventory/sell/{type}/{key}": + spec: + method: 'POST' + description: "Sell inventory items back to Alexander" + params: [ + #TODO verify these are the correct types + path('type',"The type of object you're selling back.",'string',['gear','eggs','hatchingPotions','food']) + path('key',"The object key you're selling back (call /content route for available keys)",'string') + ] + action: user.sell + + "/user/inventory/purchase/{type}/{key}": + spec: + method: 'POST' + description: "Purchase a gem-purchaseable item from Alexander" + params:[ + path('type',"The type of object you're purchasing.",'string',['gear','eggs','hatchingPotions','food']) + path('key',"The object key you're purchasing (call /content route for available keys)",'string') + ] + action: user.purchase + + + "/user/inventory/feed/{pet}/{food}": + spec: + method: 'POST' + description: "Feed your pet some food" + params: [ + path 'pet',"The key of the pet you're feeding",'string'#,_.keys(content.pets)) + path 'food',"The key of the food to feed your pet",'string',_.keys(content.food) + ] + action: user.feed + + "/user/inventory/equip/{type}/{key}": + spec: + method: 'POST' + description: "Equip an item (either pets, mounts, or gear)" + params: [ + path 'type',"Type to equip",'string',['pets','mounts','gear'] + path 'key',"The object key you're equipping (call /content route for available keys)",'string' + ] + action: user.equip + + "/user/inventory/hatch/{egg}/{hatchingPotion}": + spec: + method: 'POST' + description: "Pour a hatching potion on an egg" + params: [ + path 'egg',"The egg key to hatch",'string',_.keys(content.eggs) + path 'hatchingPotion',"The hatching potion to pour",'string',_.keys(content.hatchingPotions) + ] + action: user.hatch + + + # User + "/user:GET": + spec: + path: '/user' + description: "Get the full user object" + action: user.getUser + + "/user:PUT": + spec: + path: '/user' + method: 'PUT' + description: "Update the user object (only certain attributes are supported)" + params: [ + body '','The user object','object' + ] + action: user.update + + "/user:DELETE": + spec: + path: '/user' + method: 'DELETE' + description: "Delete a user object entirely, USE WITH CAUTION!" + middleware: auth.auth + action: user["delete"] + + "/user/revive": + spec: + method: 'POST' + description: "Revive your dead user" + action: user.revive + + "/user/reroll": + spec: + method: 'POST' + description: 'Drink the Fortify Potion (Note, it used to be called re-roll)' + action: user.reroll + + "/user/reset": + spec: + method: 'POST' + description: "Completely reset your account" + action: user.reset + + "/user/sleep": + spec: + method: 'POST' + description: "Toggle whether you're resting in the inn" + action: user.sleep + + "/user/rebirth": + spec: + method: 'POST' + description: "Rebirth your avatar" + action: user.rebirth + + "/user/class/change": + spec: + method: 'POST' + description: "Either remove your avatar's class, or change it to something new" + params: [ + query 'class',"The key of the class to change to. If not provided, user's class is removed.",'string',['warrior','healer','rogue','wizard',''] + ] + action: user.changeClass + + "/user/class/allocate": + spec: + method: 'POST' + description: "Allocate one point towards an attribute" + params: [ + query 'stat','The stat to allocate towards','string' + ] + action:user.allocate + + "/user/class/cast/{spell}": + spec: + method: 'POST' + description: "Cast a spell" + #TODO finish + action: user.cast + + "/user/unlock": + spec: + method: 'POST' + description: "Unlock a certain gem-purchaseable path (or multiple paths)" + params: [ + query 'path',"The path to unlock, such as hair.green or shirts.red,shirts.blue",'string' + ] + action: user.unlock + + "/user/buy-gems": + spec: method: 'POST', description: "Do not use this route!" + middleware: auth.auth + action:user.buyGems + + "/user/buy-gems/paypal-ipn": + spec: method: 'POST', description: "Don't use this route!" + action: user.buyGemsPaypalIPN + + "/user/batch-update": + spec: + method: 'POST' + description: "This is an advanced route which is useful for apps which might for example need offline support. You can send a whole batch of user-based operations, which allows you to queue them up offline and send them all at once. The format is {op:'nameOfOperation',params:{},body:{},query:{}}" + params:[ + body '','The array of batch-operations to perform','object' + ] + middleware: [middleware.forceRefresh, auth.auth, cron] + action: user.batchUpdate + + # Tags + "/user/tags": + spec: + method: 'POST' + description: 'Create a new tag' + params: [ + #TODO document + body '','New tag','object' + ] + action: user.addTag + + "/user/tags/{id}:PUT": + spec: + path: 'user/tags/{id}' + method: 'PUT' + description: "Edit a tag" + params: [ + path 'id','The id of the tag to edit','string' + body '','Tag edits','object' + ] + action: user.updateTag + + "/user/tags/{id}:DELETE": + spec: + path: 'user/tags/{id}' + method: 'DELETE' + description: 'Delete a tag' + params: [ + path 'id','Id of tag to delete','string' + ] + action: user.deleteTag + + # --------------------------------- + # Groups + # --------------------------------- + "/groups:GET": + spec: path: '/groups' + middleware: auth.auth + action: groups.list + + "/groups:POST": + spec: path: '/groups', method: 'POST' + middleware: auth.auth + action: groups.create + + "/groups/{gid}:GET": + spec: path: '/groups/{gid}' + middleware: auth.auth + action: groups.get + + "/groups/{gid}": + spec: path: '/groups/{gid}', method: 'PUT' + middleware: [auth.auth, groups.attachGroup] + action: groups.update + + "/groups/{gid}/join": + spec: method: 'POST' + middleware: [auth.auth, groups.attachGroup] + action: groups.join + + "/groups/{gid}/leave": + spec: method: 'POST' + middleware: [auth.auth, groups.attachGroup] + action: groups.leave + + "/groups/{gid}/invite": + spec: method: 'POST' + middleware: [auth.auth, groups.attachGroup] + action:groups.invite + + "/groups/{gid}/removeMember": + spec:method: 'POST' + middleware: [auth.auth, groups.attachGroup] + action:groups.removeMember + + "/groups/{gid}/questAccept": + spec: + method: 'POST' + params: [ + query 'key',"optional. if provided, trigger new invite, if not, accept existing invite",'string' + ] + middleware: [auth.auth, groups.attachGroup] + action:groups.questAccept + + "/groups/{gid}/questReject": + spec: method: 'POST' + middleware: [auth.auth, groups.attachGroup] + action: groups.questReject + + "/groups/{gid}/questAbort": + spec: method: 'POST' + middleware: [auth.auth, groups.attachGroup] + action: groups.questAbort + + #TODO GET /groups/:gid/chat + #TODO PUT /groups/:gid/chat/:messageId + + "/groups/{gid}/chat": + spec: method: 'POST' + middleware: [auth.auth, groups.attachGroup] + action: groups.postChat + + "/groups/{gid}/chat/{messageId}": + spec: method: 'DELETE' + middleware: [auth.auth, groups.attachGroup] + action: groups.deleteChatMessage + + # --------------------------------- + # Members + # --------------------------------- + "/members/{uid}": + spec:{} + action: groups.getMember + + # --------------------------------- + # Admin + # --------------------------------- + "/admin/members": + spec: {} + middleware:[auth.auth, admin.ensureAdmin] + action: admin.listMembers + + "/admin/members/{uid}": + spec: {} + middleware: [auth.auth, admin.ensureAdmin] + action: admin.getMember + + "/admin/members/{uid}": + spec: method: 'POST' + middleware: [auth.auth, admin.ensureAdmin] + action: admin.updateMember + + + # --------------------------------- + # Challenges + # --------------------------------- + + # Note: while challenges belong to groups, and would therefore make sense as a nested resource + # (eg /groups/:gid/challenges/:cid), they will also be referenced by users from the "challenges" tab + # without knowing which group they belong to. So to prevent unecessary lookups, we have them as a top-level resource + "/challenges:GET": + spec: path: '/challenges' + middleware: [auth.auth] + action: challenges.list + + "/challenges:POST": + spec: path: '/challenges', method: 'POST' + middleware: [auth.auth] + action: challenges.create + + "/challenges/{cid}:GET": + spec: {} + action: challenges.get + + "/challenges/{cid}:POST": + spec: path: '/challenges/{cid}', method: 'POST' + middleware: [auth.auth] + action: challenges.update + + "/challenges/{cid}:DELETE": + spec: path: '/challenges/{cid}', method: 'DELETE' + middleware: [auth.auth] + action: challenges["delete"] + + "/challenges/{cid}/close": + spec: method: 'POST' + middleware: [auth.auth] + action: challenges.selectWinner + + "/challenges/{cid}/join": + spec: method: 'POST' + middleware: [auth.auth] + action: challenges.join + + "/challenges/{cid}/leave": + spec: method: 'POST' + middleware: [auth.auth] + action: challenges.leave + + "/challenges/{cid}/member/{uid}": + spec: {} + middleware: [auth.auth] + action: challenges.getMember + + + if nconf.get("NODE_ENV") is "development" + api["/user/addTenGems"] = + spec: method:'POST' + action: user.addTenGems + + _.each api, (route, path) -> + ## Spec format is: + # spec: + # path: "/pet/{petId}" + # description: "Operations about pets" + # notes: "Returns a pet based on ID" + # summary: "Find pet by ID" + # method: "GET" + # params: [path("petId", "ID of pet that needs to be fetched", "string")] + # type: "Pet" + # errorResponses: [swagger.errors.invalid("id"), swagger.errors.notFound("pet")] + # nickname: "getPetById" + + route.spec.description ?= '' + _.defaults route.spec, + path: path + nickname: path + notes: route.spec.description + summary: route.spec.description + params: [] + #type: 'Pet' + errorResponses: [] + method: 'GET' + middleware: if path.indexOf('/user') is 0 then [auth.auth, cron] else [] + swagger["add#{route.spec.method}"](route);true + + + swagger.configure(nconf.get('BASE_URL'), "2") \ No newline at end of file diff --git a/src/routes/apiv2.js b/src/routes/apiv2.js deleted file mode 100644 index 065d152c8c..0000000000 --- a/src/routes/apiv2.js +++ /dev/null @@ -1,140 +0,0 @@ -/*** - * ---------- /api/v2 API ------------ - * Every url added to router is prefaced by /api/v2 - * Note: Many user-route ops exist in habitrpg-shard/script/index.coffee#user.ops, so that they can (1) be called both - * client and server. - * v1 user. Requires x-api-user (user id) and x-api-key (api key) headers, Test with: - * $ mocha test/user.mocha.coffee - */ - -var express = require('express'); -var router = new express.Router(); -var user = require('../controllers/user'); -var groups = require('../controllers/groups'); -var auth = require('../controllers/auth'); -var admin = require('../controllers/admin'); -var challenges = require('../controllers/challenges'); -var dataexport = require('../controllers/dataexport'); -var nconf = require('nconf'); -var middleware = require('../middleware'); - -var cron = user.cron; - -router.get('/status', function(req, res) { - return res.json({ - status: 'up' - }); -}); - -// --------------------------------- -// User -// --------------------------------- - -// List all keys and objects in content.coffee that clients ned to know about -router.get('/content', user.getContent); - -// Data Export -router.get('/export/history',auth.auth,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes - -// Scoring -router.post('/user/tasks/:id/:direction', auth.auth, cron, user.score); - -// Tasks -router.get('/user/tasks', auth.auth, cron, user.getTasks); -router.get('/user/tasks/:id', auth.auth, cron, user.getTask); -router.put('/user/tasks/:id', auth.auth, cron, user.updateTask); //Shared.ops | body={} -router["delete"]('/user/tasks/:id', auth.auth, cron, user.deleteTask); //Shared.ops -router.post('/user/tasks', auth.auth, cron, user.addTask); //Shared.ops | body={} -router.post('/user/tasks/:id/sort', auth.auth, cron, user.sortTask); //Shared.ops | query={to,from} -router.post('/user/tasks/clear-completed', auth.auth, cron, user.clearCompleted); //Shared.ops -router.post('/user/tasks/:id/unlink', auth.auth, challenges.unlink); // removing cron since they may want to remove task first - -// Inventory -router.post('/user/inventory/buy/:key', auth.auth, cron, user.buy); // Shared.ops -router.post('/user/inventory/sell/:type/:key', auth.auth, cron, user.sell); // Shared.ops -router.post('/user/inventory/purchase/:type/:key', auth.auth, user.purchase); //Shared.ops -router.post('/user/inventory/feed/:pet/:food', auth.auth, user.feed); //Shared.ops -router.post('/user/inventory/equip/:type/:key', auth.auth, user.equip); //Shared.ops -router.post('/user/inventory/hatch/:egg/:hatchingPotion', auth.auth, user.hatch); //Shared.ops - -// User -router.get('/user', auth.auth, cron, user.getUser); -router.put('/user', auth.auth, cron, user.update); // body={} -router['delete']('/user', auth.auth, user['delete']); - -router.post('/user/revive', auth.auth, cron, user.revive); // Shared.ops -router.post('/user/reroll', auth.auth, cron, user.reroll); // Shared.ops -router.post('/user/reset', auth.auth, user.reset); // Shared.ops -router.post('/user/sleep', auth.auth, cron, user.sleep); //Shared.ops -router.post('/user/rebirth', auth.auth, cron, user.rebirth); //Shared.ops - -router.post('/user/class/change', auth.auth, cron, user.changeClass); //Shared.ops | query={class} -router.post('/user/class/allocate', auth.auth, cron, user.allocate); //Shared.ops | query={stat} -router.post('/user/class/cast/:spell', auth.auth, user.cast); - -router.post('/user/unlock', auth.auth, cron, user.unlock); // Shared.ops -router.post('/user/buy-gems', auth.auth, user.buyGems); -router.post('/user/buy-gems/paypal-ipn', user.buyGemsPaypalIPN); - -router.post('/user/batch-update', middleware.forceRefresh, auth.auth, cron, user.batchUpdate); - -if (nconf.get('NODE_ENV') == 'development') router.post('/user/addTenGems', auth.auth, user.addTenGems); - -// Tags -router.post('/user/tags', auth.auth, user.addTag); //Shared.ops | body={} -router.put('/user/tags/:id', auth.auth, user.updateTag); //Shared.ops | body={} -router['delete']('/user/tags/:id', auth.auth, user.deleteTag); //Shared.ops | body={} - -// --------------------------------- -// Groups -// --------------------------------- -router.get('/groups', auth.auth, groups.list); -router.post('/groups', auth.auth, groups.create); -router.get('/groups/:gid', auth.auth, groups.get); -router.post('/groups/:gid', auth.auth, groups.attachGroup, groups.update); -router.put('/groups/:gid', auth.auth, groups.attachGroup, groups.update); -//DELETE /groups/:gid - -router.post('/groups/:gid/join', auth.auth, groups.attachGroup, groups.join); -router.post('/groups/:gid/leave', auth.auth, groups.attachGroup, groups.leave); -router.post('/groups/:gid/invite', auth.auth, groups.attachGroup, groups.invite); -router.post('/groups/:gid/removeMember', auth.auth, groups.attachGroup, groups.removeMember); -router.post('/groups/:gid/questAccept', auth.auth, groups.attachGroup, groups.questAccept); // query={key} (optional. if provided, trigger new invite, if not, accept existing invite) -router.post('/groups/:gid/questReject', auth.auth, groups.attachGroup, groups.questReject); -router.post('/groups/:gid/questAbort', auth.auth, groups.attachGroup, groups.questAbort); - -//GET /groups/:gid/chat -router.post('/groups/:gid/chat', auth.auth, groups.attachGroup, groups.postChat); -router["delete"]('/groups/:gid/chat/:messageId', auth.auth, groups.attachGroup, groups.deleteChatMessage); -//PUT /groups/:gid/chat/:messageId - -// --------------------------------- -// Members -// --------------------------------- -router.get('/members/:uid', groups.getMember); - -// --------------------------------- -// Admin -// --------------------------------- -router.get('/admin/members', auth.auth, admin.ensureAdmin, admin.listMembers); -router.get('/admin/members/:uid', auth.auth, admin.ensureAdmin, admin.getMember); -router.post('/admin/members/:uid', auth.auth, admin.ensureAdmin, admin.updateMember); - -// --------------------------------- -// Challenges -// --------------------------------- - -// Note: while challenges belong to groups, and would therefore make sense as a nested resource -// (eg /groups/:gid/challenges/:cid), they will also be referenced by users from the "challenges" tab -// without knowing which group they belong to. So to prevent unecessary lookups, we have them as a top-level resource -router.get('/challenges', auth.auth, challenges.list) -router.post('/challenges', auth.auth, challenges.create) -router.get('/challenges/:cid', auth.auth, challenges.get) -router.post('/challenges/:cid', auth.auth, challenges.update) -router['delete']('/challenges/:cid', auth.auth, challenges['delete']) -router.post('/challenges/:cid/close', auth.auth, challenges.selectWinner) -router.post('/challenges/:cid/join', auth.auth, challenges.join) -router.post('/challenges/:cid/leave', auth.auth, challenges.leave) -router.get('/challenges/:cid/member/:uid', auth.auth, challenges.getMember) - -module.exports = router; \ No newline at end of file diff --git a/src/server.js b/src/server.js index 3d5ad385a2..ecf1ef9ec2 100644 --- a/src/server.js +++ b/src/server.js @@ -105,14 +105,14 @@ app.use(express['static'](path.join(__dirname, "/../public"))); // Custom Directives app.use(require('./routes/pages').middleware); app.use(require('./routes/auth').middleware); -app.use('/api/v2', require('./routes/apiv2').middleware); +var v2 = express(); +app.use('/api/v2', v2); app.use('/api/v1', require('./routes/apiv1').middleware); app.use('/export', require('./routes/dataexport').middleware); app.use(utils.errorHandler); -swagger.setAppHandler(app); -require('./apidoc.coffee')(swagger); +require('./routes/apiv2.coffee')(swagger, v2); server = http.createServer(app).listen(app.get("port"), function() { return console.log("Express server listening on port " + app.get("port")); diff --git a/views/static/api.jade b/views/static/api.jade index aa89f44b51..e833694f4c 100644 --- a/views/static/api.jade +++ b/views/static/api.jade @@ -19,7 +19,7 @@ html script(type='text/javascript') $(function () { window.swaggerUi = new SwaggerUi({ - url: "/api-docs", + url: "/api/v2/api-docs", dom_id: "swagger-ui-container", supportedSubmitMethods: ['get', 'post', 'put', 'delete'], onComplete: function(swaggerApi, swaggerUi){