diff --git a/bower.json b/bower.json index 2769a82370..d66189fe30 100644 --- a/bower.json +++ b/bower.json @@ -38,13 +38,13 @@ "gemoji": "git://github.com/github/gemoji", "sticky": "*", "bootstrap-tour": "~0.8.0", - "angular-ui-utils": "~0.1.0" + "angular-ui-utils": "~0.1.0", + "swagger-ui": "https://github.com/wordnik/swagger-ui.git#105c516f5f055b140e935e9cfe0c36f841921dff" }, "resolutions": { "jquery": "~2.0.3", "bootstrap": "v2.3.2", - "angular": "~1.2.1", - "angular-ui-utils": "~0.1.0" + "angular": "~1.2.1" }, "devDependencies": { "angular-mocks": "~1.2.1" diff --git a/package.json b/package.json index a81bbd57d2..dadfe289f7 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "pretty-data": "git://github.com/vkiryukhin/pretty-data#master", "js2xmlparser": "~0.1.2", "mongoose": "~3.8.1", - "domain-middleware": "~0.1.0" + "domain-middleware": "~0.1.0", + "swagger-node-express": "git://github.com/lefnire/swagger-node-express#habitrpg" }, "private": true, "subdomain": "habitrpg", diff --git a/src/controllers/auth.js b/src/controllers/auth.js index 462cf4ac59..dd508039ad 100644 --- a/src/controllers/auth.js +++ b/src/controllers/auth.js @@ -20,9 +20,8 @@ var NO_SESSION_FOUND = { err: "You must be logged in." }; */ api.auth = function(req, res, next) { - var token, uid; - uid = req.headers['x-api-user']; - token = req.headers['x-api-key']; + var uid = req.headers['x-api-user']; + var token = req.headers['x-api-key']; if (!(uid && token)) return res.json(401, NO_TOKEN_OR_UID); User.findOne({_id: uid,apiToken: token}, function(err, user) { if (err) return res.json(500, {err: err}); diff --git a/src/routes/apiv2.coffee b/src/routes/apiv2.coffee new file mode 100644 index 0000000000..e33a68b4e5 --- /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) -> + [path,body,query] = [swagger.pathParam, swagger.bodyParam, swagger.queryParam] + + swagger.setAppHandler(v2) + swagger.setErrorHandler("next") + swagger.setHeaders = -> #disable setHeaders, since we have our own thing going on in middleware.js (and which requires `req`, which swagger doesn't pass in) + swagger.configureSwaggerPaths("", "/api-docs", "") + + api = + + '/status': + spec: + description: "Returns the status of the server (up or down)" + action: (req, res) -> + 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" + parameters: [ + 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" + parameters: [ + path("id", "Task ID", "string") + ] + action: user.getTask + + "/user/tasks/{id}": + spec: + description: "Update a user's task" + method: 'PUT' + parameters: [ + 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' + parameters: [ path("id", "Task ID", "string") ] + action: user.deleteTask + + "/user/tasks": + spec: + description: "Create a task" + method: 'POST' + parameters: [ body("","Send up the whole task","object") ] + action: user.addTask + + + "/user/tasks/{id}/sort": + spec: + method: 'POST' + description: 'Sort tasks' + parameters: [ + 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? + parameters: [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" + parameters:[ + 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" + parameters: [ + #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" + parameters:[ + 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" + parameters: [ + 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)" + parameters: [ + 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" + parameters: [ + 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)" + parameters: [ + 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" + parameters: [ + 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" + parameters: [ + 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)" + parameters: [ + 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',parameters:{},body:{},query:{}}" + parameters:[ + 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' + parameters: [ + #TODO document + body '','New tag','object' + ] + action: user.addTag + + "/user/tags/{id}:PUT": + spec: + path: 'user/tags/{id}' + method: 'PUT' + description: "Edit a tag" + parameters: [ + 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' + parameters: [ + 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' + parameters: [ + 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" + # parameters: [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 + parameters: [] + #type: 'Pet' + errorResponses: [] + method: 'GET' + route.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')}/api/v2", "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/routes/pages.js b/src/routes/pages.js index 72e3f4fb10..de22cf95c8 100644 --- a/src/routes/pages.js +++ b/src/routes/pages.js @@ -31,6 +31,10 @@ router.get('/static/terms', middleware.locals, function(req, res) { res.render('static/terms', {env: res.locals.habitrpg}); }); +router.get('/static/api', middleware.locals, function(req, res) { + res.render('static/api', {env: res.locals.habitrpg}); +}); + // --------- Redirects -------- router.get('/splash.html', function(req, res) { diff --git a/src/server.js b/src/server.js index 91fa6ea9a9..ecf1ef9ec2 100644 --- a/src/server.js +++ b/src/server.js @@ -8,6 +8,7 @@ var nconf = require('nconf'); var utils = require('./utils'); var middleware = require('./middleware'); var domainMiddleware = require('domain-middleware'); +var swagger = require("swagger-node-express"); var server; var TWO_WEEKS = 1000 * 60 * 60 * 24 * 14; @@ -104,12 +105,15 @@ 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); +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 new file mode 100644 index 0000000000..df1a73fad4 --- /dev/null +++ b/views/static/api.jade @@ -0,0 +1,77 @@ +!!! 5 +html + head + title Swagger UI + link(href='//fonts.googleapis.com/css?family=Droid+Sans:400,700', rel='stylesheet', type='text/css') + link(href='/bower_components/swagger-ui/dist/css/highlight.default.css', media='screen', rel='stylesheet', type='text/css') + link(href='/bower_components/swagger-ui/dist/css/screen.css', media='screen', rel='stylesheet', type='text/css') + script(src='/bower_components/swagger-ui/dist/lib/shred.bundle.js', type='text/javascript') + script(src='/bower_components/swagger-ui/dist/lib/jquery-1.8.0.min.js', type='text/javascript') + script(src='/bower_components/swagger-ui/dist/lib/jquery.slideto.min.js', type='text/javascript') + script(src='/bower_components/swagger-ui/dist/lib/jquery.wiggle.min.js', type='text/javascript') + script(src='/bower_components/swagger-ui/dist/lib/jquery.ba-bbq.min.js', type='text/javascript') + script(src='/bower_components/swagger-ui/dist/lib/handlebars-1.0.0.js', type='text/javascript') + script(src='/bower_components/swagger-ui/dist/lib/underscore-min.js', type='text/javascript') + script(src='/bower_components/swagger-ui/dist/lib/backbone-min.js', type='text/javascript') + script(src='/bower_components/swagger-ui/dist/lib/swagger.js', type='text/javascript') + script(src='/bower_components/swagger-ui/dist/swagger-ui.js', type='text/javascript') + script(src='/bower_components/swagger-ui/dist/lib/highlight.7.3.pack.js', type='text/javascript') + script(type='text/javascript') + $(function () { + window.swaggerUi = new SwaggerUi({ + url: "/api/v2/api-docs", + dom_id: "swagger-ui-container", + supportedSubmitMethods: ['get', 'post', 'put', 'delete'], + onComplete: function(swaggerApi, swaggerUi){ + if(console) { + console.log("Loaded SwaggerUI") + } + $('pre code').each(function(i, e) {hljs.highlightBlock(e)}); + }, + onFailure: function(data) { + if(console) { + console.log("Unable to Load SwaggerUI"); + console.log(data); + } + }, + docExpansion: "none" + }); + + debugger; + + $('#input_apiKey').change(function() { + var key = $('#input_apiKey')[0].value; + console.log("apiKey: " + key); + if(key && key.trim() != "") { + console.log("added key " + key); + window.authorizations.add("apiKey", new ApiKeyAuthorization("x-api-key", key, "header")); + } + }) + $('#input_uuid').change(function() { + var key = $('#input_uuid')[0].value; + console.log("uuid: " + key); + if(key && key.trim() != "") { + console.log("added key " + key); + window.authorizations.add("uuid", new ApiKeyAuthorization("x-api-user", key, "header")); + } + }) + window.swaggerUi.load(); + }); + body + #header + .swagger-ui-wrap + a#logo(href='http://swagger.wordnik.com') HabitRPG API Documentation (Under Construction) + + .swagger-ui-wrap(style='padding:50px') + form#api_selector + .input + input#input_uuid(placeholder='UUID', name='uuid', type='text') + input#input_apiKey(placeholder='API Key', name='apiKey', type='text') + //.input + input#input_baseUrl(placeholder='http://example.com/api', name='baseUrl', type='text') + //.input + a#explore(href='#') Explore + p(style='color:red') This documentation is a work in progress. If you'd like to help finish it, see #2217. + + #message-bar.swagger-ui-wrap + #swagger-ui-container.swagger-ui-wrap