From a102abfef802ff52882fef7dae3ed3551e7c2686 Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Sat, 5 Mar 2016 13:36:57 -0500 Subject: [PATCH 01/20] Change router import to module init --- bin/server.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/server.js b/bin/server.js index cd425eb..c9b1055 100644 --- a/bin/server.js +++ b/bin/server.js @@ -27,8 +27,8 @@ require('../config/passport') app.use(passport.initialize()) app.use(passport.session()) -const router = require('../src/controllers') -router(app) +const modules = require('../src/modules') +modules(app) app.listen(config.port, () => { console.log(`Server started on ${config.port}`) From 184451f9fc9d13bbee153890490b47f9b2a9044e Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Sat, 5 Mar 2016 13:38:08 -0500 Subject: [PATCH 02/20] Create router for `/users` --- src/modules/users/router.js | 48 +++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/modules/users/router.js diff --git a/src/modules/users/router.js b/src/modules/users/router.js new file mode 100644 index 0000000..1ee0fc2 --- /dev/null +++ b/src/modules/users/router.js @@ -0,0 +1,48 @@ +import { createUser } from './controller' + +export default { + base: '/users', + +/** + * @api {post} /users Create a new user + * @apiPermission + * @apiVersion 1.0.0 + * @apiName CreateUser + * @apiGroup Users + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X POST -d '{ "user": { "username": "johndoe", "password": "secretpasas" } }' localhost:5000/users + * + * @apiParam {Object} user User object (required) + * @apiParam {String} user.username Username. + * @apiParam {String} user.password Password. + * + * @apiSuccess {Object} users User object + * @apiSuccess {ObjectId} users._id User id + * @apiSuccess {String} users.name User name + * @apiSuccess {String} users.username User username + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "user": { + * "_id": "56bd1da600a526986cf65c80" + * "name": "John Doe" + * "username": "johndoe" + * } + * } + * + * @apiError UnprocessableEntity Missing required parameters + * + * @apiErrorExample {json} Error-Response: + * HTTP/1.1 422 Unprocessable Entity + * { + * "status": 422, + * "error": "Unprocessable Entity" + * } + */ + '/': { + method: 'POST', + controller: createUser + } +} From d4434e2ef8315ad3a79f7f27c54d5ac1859140ab Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Sat, 5 Mar 2016 14:12:09 -0500 Subject: [PATCH 03/20] Module intializer - generates routes and connects to controller --- src/modules/index.js | 30 ++++++++++++++++++++++++++++++ src/modules/users/controller.js | 23 +++++++++++++++++++++++ src/modules/users/router.js | 3 ++- 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 src/modules/index.js create mode 100644 src/modules/users/controller.js diff --git a/src/modules/index.js b/src/modules/index.js new file mode 100644 index 0000000..5c268cd --- /dev/null +++ b/src/modules/index.js @@ -0,0 +1,30 @@ +import glob from 'glob' +import Router from 'koa-router' + +exports = module.exports = function initModules(app) { + glob(`${__dirname}/*`, { ignore: '**/index.js' }, (err, matches) => { + if (err) { throw err } + + matches.forEach((mod) => { + const routerConfig = require(`${mod}/router`).default + const router = new Router({ prefix: routerConfig.base }) + + for (const [key, props] of Object.entries(routerConfig)) { + if (key === 'base') { continue } + + const { + method = '', + route = '', + middleware = [], + controller + } = props + + router[method.toLowerCase()](route, controller) + + app + .use(router.routes()) + .use(router.allowedMethods()) + } + }) + }) +} diff --git a/src/modules/users/controller.js b/src/modules/users/controller.js new file mode 100644 index 0000000..1de195f --- /dev/null +++ b/src/modules/users/controller.js @@ -0,0 +1,23 @@ +import User from '../../models/users' +import config from '../../../config' +import jwt from 'jsonwebtoken' + +export async function createUser(ctx) { + const user = new User(ctx.request.body.user) + try { + await user.save() + const token = jwt.sign({ id: user.id }, config.token) + + const response = user.toJSON() + + delete response.password + delete response.salt + + ctx.body = { + user: response, + token + } + } catch (err) { + ctx.throw(422, err) + } +} diff --git a/src/modules/users/router.js b/src/modules/users/router.js index 1ee0fc2..909c0e8 100644 --- a/src/modules/users/router.js +++ b/src/modules/users/router.js @@ -41,8 +41,9 @@ export default { * "error": "Unprocessable Entity" * } */ - '/': { + 'Create new users': { method: 'POST', + route: '/', controller: createUser } } From 1030f803605b435f8d97a25939501e40325726a4 Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Sat, 5 Mar 2016 15:11:02 -0500 Subject: [PATCH 04/20] Mount middleware stack per route --- src/modules/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modules/index.js b/src/modules/index.js index 5c268cd..28260f4 100644 --- a/src/modules/index.js +++ b/src/modules/index.js @@ -1,5 +1,6 @@ import glob from 'glob' import Router from 'koa-router' +import convert from 'koa-convert' exports = module.exports = function initModules(app) { glob(`${__dirname}/*`, { ignore: '**/index.js' }, (err, matches) => { @@ -19,7 +20,7 @@ exports = module.exports = function initModules(app) { controller } = props - router[method.toLowerCase()](route, controller) + router[method.toLowerCase()](route, ...middleware, controller) app .use(router.routes()) From 9f8a04e556459c416efe7c21433cffd81668e1bf Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Sat, 5 Mar 2016 15:13:41 -0500 Subject: [PATCH 05/20] Add `GET /users` route --- src/modules/users/controller.js | 5 ++ src/modules/users/router.js | 118 +++++++++++++++++++++----------- 2 files changed, 84 insertions(+), 39 deletions(-) diff --git a/src/modules/users/controller.js b/src/modules/users/controller.js index 1de195f..97e5c96 100644 --- a/src/modules/users/controller.js +++ b/src/modules/users/controller.js @@ -21,3 +21,8 @@ export async function createUser(ctx) { ctx.throw(422, err) } } + +export async function getUsers (ctx) { + const users = await User.find({}, '-password -salt') + ctx.body = users +} diff --git a/src/modules/users/router.js b/src/modules/users/router.js index 909c0e8..e47a889 100644 --- a/src/modules/users/router.js +++ b/src/modules/users/router.js @@ -1,49 +1,89 @@ -import { createUser } from './controller' +import { ensureUser } from '../../middleware/validators' +import { + createUser, + getUsers +} from './controller' export default { base: '/users', -/** - * @api {post} /users Create a new user - * @apiPermission - * @apiVersion 1.0.0 - * @apiName CreateUser - * @apiGroup Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X POST -d '{ "user": { "username": "johndoe", "password": "secretpasas" } }' localhost:5000/users - * - * @apiParam {Object} user User object (required) - * @apiParam {String} user.username Username. - * @apiParam {String} user.password Password. - * - * @apiSuccess {Object} users User object - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "user": { - * "_id": "56bd1da600a526986cf65c80" - * "name": "John Doe" - * "username": "johndoe" - * } - * } - * - * @apiError UnprocessableEntity Missing required parameters - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 422 Unprocessable Entity - * { - * "status": 422, - * "error": "Unprocessable Entity" - * } - */ + /** + * @api {post} /users Create a new user + * @apiPermission + * @apiVersion 1.0.0 + * @apiName CreateUser + * @apiGroup Users + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X POST -d '{ "user": { "username": "johndoe", "password": "secretpasas" } }' localhost:5000/users + * + * @apiParam {Object} user User object (required) + * @apiParam {String} user.username Username. + * @apiParam {String} user.password Password. + * + * @apiSuccess {Object} users User object + * @apiSuccess {ObjectId} users._id User id + * @apiSuccess {String} users.name User name + * @apiSuccess {String} users.username User username + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "user": { + * "_id": "56bd1da600a526986cf65c80" + * "name": "John Doe" + * "username": "johndoe" + * } + * } + * + * @apiError UnprocessableEntity Missing required parameters + * + * @apiErrorExample {json} Error-Response: + * HTTP/1.1 422 Unprocessable Entity + * { + * "status": 422, + * "error": "Unprocessable Entity" + * } + */ 'Create new users': { method: 'POST', route: '/', controller: createUser + }, + + /** + * @api {get} /users Get all users + * @apiPermission user + * @apiVersion 1.0.0 + * @apiName GetUsers + * @apiGroup Users + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X GET localhost:5000/users + * + * @apiSuccess {Object[]} users Array of user objects + * @apiSuccess {ObjectId} users._id User id + * @apiSuccess {String} users.name User name + * @apiSuccess {String} users.username User username + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "users": [{ + * "_id": "56bd1da600a526986cf65c80" + * "name": "John Doe" + * "username": "johndoe" + * }] + * } + * + * @apiUse TokenError + */ + 'Get all users': { + method: 'GET', + route: '/', + controller: getUsers, + middleware: [ + ensureUser + ] } } From 462d584aa325396fce1ea0e6504f9457b157a896 Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Sat, 5 Mar 2016 15:23:41 -0500 Subject: [PATCH 06/20] Add `GET /users/:id` route --- src/modules/users/controller.js | 17 ++++++++++++++ src/modules/users/router.js | 39 ++++++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/modules/users/controller.js b/src/modules/users/controller.js index 97e5c96..ab4d2e5 100644 --- a/src/modules/users/controller.js +++ b/src/modules/users/controller.js @@ -26,3 +26,20 @@ export async function getUsers (ctx) { const users = await User.find({}, '-password -salt') ctx.body = users } + +export async function getUser (ctx) { + try { + const user = await User.findById(ctx.params.id, '-password -salt') + if (!user) { + ctx.throw(404) + } + + ctx.body = user + } catch (err) { + if (err === 404 || err.name === 'CastError') { + ctx.throw(404) + } + + ctx.throw(500) + } +} diff --git a/src/modules/users/router.js b/src/modules/users/router.js index e47a889..95df861 100644 --- a/src/modules/users/router.js +++ b/src/modules/users/router.js @@ -1,7 +1,8 @@ import { ensureUser } from '../../middleware/validators' import { createUser, - getUsers + getUsers, + getUser } from './controller' export default { @@ -85,5 +86,41 @@ export default { middleware: [ ensureUser ] + }, + + /** + * @api {get} /users/:id Get user by id + * @apiPermission user + * @apiVersion 1.0.0 + * @apiName GetUser + * @apiGroup Users + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X GET localhost:5000/users/56bd1da600a526986cf65c80 + * + * @apiSuccess {Object} users User object + * @apiSuccess {ObjectId} users._id User id + * @apiSuccess {String} users.name User name + * @apiSuccess {String} users.username User username + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "user": { + * "_id": "56bd1da600a526986cf65c80" + * "name": "John Doe" + * "username": "johndoe" + * } + * } + * + * @apiUse TokenError + */ + 'Get a single user': { + method: 'GET', + route: '/:id', + controller: getUser, + middleware: [ + ensureUser + ] } } From b10f6acf19dd6480478ef5bad9952cc8d5f1f259 Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Wed, 16 Mar 2016 20:18:00 -0400 Subject: [PATCH 07/20] Update router generation with `handlers` --- src/modules/index.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/modules/index.js b/src/modules/index.js index 28260f4..c062dbf 100644 --- a/src/modules/index.js +++ b/src/modules/index.js @@ -1,6 +1,5 @@ import glob from 'glob' import Router from 'koa-router' -import convert from 'koa-convert' exports = module.exports = function initModules(app) { glob(`${__dirname}/*`, { ignore: '**/index.js' }, (err, matches) => { @@ -16,11 +15,10 @@ exports = module.exports = function initModules(app) { const { method = '', route = '', - middleware = [], - controller + handlers = [] } = props - router[method.toLowerCase()](route, ...middleware, controller) + router[method.toLowerCase()](route, ...handlers) app .use(router.routes()) From 2d8e171d076328c01f81b6a24714c28dbd3e8cf0 Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Wed, 16 Mar 2016 20:18:27 -0400 Subject: [PATCH 08/20] Add PUT /users/:id --- src/modules/users/controller.js | 26 ++++++- src/modules/users/router.js | 123 +++++--------------------------- 2 files changed, 43 insertions(+), 106 deletions(-) diff --git a/src/modules/users/controller.js b/src/modules/users/controller.js index ab4d2e5..6b7c38f 100644 --- a/src/modules/users/controller.js +++ b/src/modules/users/controller.js @@ -22,12 +22,12 @@ export async function createUser(ctx) { } } -export async function getUsers (ctx) { +export async function getUsers(ctx) { const users = await User.find({}, '-password -salt') ctx.body = users } -export async function getUser (ctx) { +export async function getUser(ctx) { try { const user = await User.findById(ctx.params.id, '-password -salt') if (!user) { @@ -43,3 +43,25 @@ export async function getUser (ctx) { ctx.throw(500) } } + +export async function updateUser(ctx) { + try { + const user = await User.findById(ctx.params.id, '-password -salt') + if (!user) { + ctx.throw(404) + } + + Object.assign(user, ctx.request.body.user) + + await user.save() + ctx.body = { + user + } + } catch (err) { + if (err === 404 || err.name === 'CastError') { + ctx.throw(404) + } + + ctx.throw(500) + } +} diff --git a/src/modules/users/router.js b/src/modules/users/router.js index 95df861..e729c24 100644 --- a/src/modules/users/router.js +++ b/src/modules/users/router.js @@ -1,126 +1,41 @@ import { ensureUser } from '../../middleware/validators' -import { - createUser, - getUsers, - getUser -} from './controller' +import * as user from './controller' export default { base: '/users', - /** - * @api {post} /users Create a new user - * @apiPermission - * @apiVersion 1.0.0 - * @apiName CreateUser - * @apiGroup Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X POST -d '{ "user": { "username": "johndoe", "password": "secretpasas" } }' localhost:5000/users - * - * @apiParam {Object} user User object (required) - * @apiParam {String} user.username Username. - * @apiParam {String} user.password Password. - * - * @apiSuccess {Object} users User object - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "user": { - * "_id": "56bd1da600a526986cf65c80" - * "name": "John Doe" - * "username": "johndoe" - * } - * } - * - * @apiError UnprocessableEntity Missing required parameters - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 422 Unprocessable Entity - * { - * "status": 422, - * "error": "Unprocessable Entity" - * } - */ 'Create new users': { method: 'POST', route: '/', - controller: createUser + handlers: [ + user.createUser + ] }, - /** - * @api {get} /users Get all users - * @apiPermission user - * @apiVersion 1.0.0 - * @apiName GetUsers - * @apiGroup Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X GET localhost:5000/users - * - * @apiSuccess {Object[]} users Array of user objects - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "users": [{ - * "_id": "56bd1da600a526986cf65c80" - * "name": "John Doe" - * "username": "johndoe" - * }] - * } - * - * @apiUse TokenError - */ 'Get all users': { method: 'GET', route: '/', - controller: getUsers, - middleware: [ - ensureUser + handlers: [ + ensureUser, + user.getUsers ] }, - /** - * @api {get} /users/:id Get user by id - * @apiPermission user - * @apiVersion 1.0.0 - * @apiName GetUser - * @apiGroup Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X GET localhost:5000/users/56bd1da600a526986cf65c80 - * - * @apiSuccess {Object} users User object - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "user": { - * "_id": "56bd1da600a526986cf65c80" - * "name": "John Doe" - * "username": "johndoe" - * } - * } - * - * @apiUse TokenError - */ 'Get a single user': { method: 'GET', route: '/:id', - controller: getUser, - middleware: [ - ensureUser + handlers: [ + ensureUser, + user.getUser + ] + }, + + 'Update user': { + method: 'PUT', + route: '/:id', + handlers: [ + ensureUser, + user.updateUser ] } } From b08cef1ffcc5afa6bdd1a83d6c8f132b18e49545 Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Wed, 16 Mar 2016 20:21:16 -0400 Subject: [PATCH 09/20] Add DELETE /users/:id --- src/modules/users/controller.js | 19 +++++++++++++++++++ src/modules/users/router.js | 9 +++++++++ 2 files changed, 28 insertions(+) diff --git a/src/modules/users/controller.js b/src/modules/users/controller.js index 6b7c38f..ab346ee 100644 --- a/src/modules/users/controller.js +++ b/src/modules/users/controller.js @@ -65,3 +65,22 @@ export async function updateUser(ctx) { ctx.throw(500) } } + +export async function deleteUser(ctx) { + try { + const user = await User.findById(ctx.params.id) + if (!user) { + ctx.throw(404) + } + + await user.remove() + + ctx.body = 200 + } catch (err) { + if (err === 404 || err.name === 'CastError') { + ctx.throw(404) + } + + ctx.throw(500) + } +} diff --git a/src/modules/users/router.js b/src/modules/users/router.js index e729c24..ad84a61 100644 --- a/src/modules/users/router.js +++ b/src/modules/users/router.js @@ -37,5 +37,14 @@ export default { ensureUser, user.updateUser ] + }, + + 'Delete user': { + method: 'DELETE', + route: '/:id', + handlers: [ + ensureUser, + user.deleteUser + ] } } From e79a98f8da0f98f56fd28e75d2491199bc63af34 Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Wed, 16 Mar 2016 20:28:26 -0400 Subject: [PATCH 10/20] Move auth controllers to new modules --- src/modules/auth/controller.js | 23 +++++++++++++++++++++++ src/modules/auth/router.js | 13 +++++++++++++ 2 files changed, 36 insertions(+) create mode 100644 src/modules/auth/controller.js create mode 100644 src/modules/auth/router.js diff --git a/src/modules/auth/controller.js b/src/modules/auth/controller.js new file mode 100644 index 0000000..e244550 --- /dev/null +++ b/src/modules/auth/controller.js @@ -0,0 +1,23 @@ +import passport from 'koa-passport' +import jwt from 'jsonwebtoken' +import config from '../../config' + +export async function authUser(ctx, next) { + return passport.authenticate('local', (user) => { + if (!user) { + ctx.throw(401) + } + + const token = jwt.sign({ id: user.id }, config.token) + + const response = user.toJSON() + + delete response.password + delete response.salt + + ctx.body = { + token, + user: response + } + })(ctx, next) +} diff --git a/src/modules/auth/router.js b/src/modules/auth/router.js new file mode 100644 index 0000000..fab2e6b --- /dev/null +++ b/src/modules/auth/router.js @@ -0,0 +1,13 @@ +import * as auth from './controller' + +export default { + base: '/auth', + + 'Authenticate user': { + method: 'POST', + route: '/', + handlers: [ + auth.authUser + ] + } +} From ae2bf52fcf3be16705c75b54f8a383f9aa51146a Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Wed, 16 Mar 2016 20:28:41 -0400 Subject: [PATCH 11/20] Remove old controllers --- src/controllers/auth.js | 78 ------------ src/controllers/index.js | 14 --- src/controllers/users.js | 258 --------------------------------------- 3 files changed, 350 deletions(-) delete mode 100644 src/controllers/auth.js delete mode 100644 src/controllers/index.js delete mode 100644 src/controllers/users.js diff --git a/src/controllers/auth.js b/src/controllers/auth.js deleted file mode 100644 index bfef202..0000000 --- a/src/controllers/auth.js +++ /dev/null @@ -1,78 +0,0 @@ -import Router from 'koa-router' -import passport from 'koa-passport' -import jwt from 'jsonwebtoken' -import config from '../../config' - -const router = new Router({ prefix: '/auth' }) - -/** - * @apiDefine TokenError - * @apiError Unauthorized Invalid JWT token - * - * @apiErrorExample {json} Unauthorized-Error: - * HTTP/1.1 401 Unauthorized - * { - * "status": 401, - * "error": "Unauthorized" - * } - */ - -/** - * @api {post} /auth Authenticate user - * @apiVersion 1.0.0 - * @apiName AuthUser - * @apiGroup Auth - * - * @apiParam {String} username User username. - * @apiParam {String} password User password. - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X POST -d '{ "username": "johndoe@gmail.com", "password": "foo" }' localhost:5000/auth - * - * @apiSuccess {Object} user User object - * @apiSuccess {ObjectId} user._id User id - * @apiSuccess {String} user.name User name - * @apiSuccess {String} user.username User username - * @apiSuccess {String} token Encoded JWT - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "user": { - * "_id": "56bd1da600a526986cf65c80" - * "username": "foo" - * "username": "johndoe" - * }, - * "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" - * } - * - * @apiError Unauthorized Incorrect credentials - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 401 Unauthorized - * { - * "status": 401, - * "error": "Unauthorized" - * } - */ -router.post('/', async (ctx, next) => - passport.authenticate('local', (user) => { - if (!user) { - ctx.throw(401) - } - - const token = jwt.sign({ id: user.id }, config.token) - - const response = user.toJSON() - - delete response.password - delete response.salt - - ctx.body = { - token, - user: response - } - })(ctx, next) -) - -export default router diff --git a/src/controllers/index.js b/src/controllers/index.js deleted file mode 100644 index a49672d..0000000 --- a/src/controllers/index.js +++ /dev/null @@ -1,14 +0,0 @@ -import glob from 'glob' - -exports = module.exports = function Controllers(app) { - glob(`${__dirname}/*.js`, { ignore: '**/index.js' }, (err, matches) => { - if (err) { throw err } - - matches.forEach((file) => { - const controller = require(file).default - app - .use(controller.routes()) - .use(controller.allowedMethods()) - }) - }) -} diff --git a/src/controllers/users.js b/src/controllers/users.js deleted file mode 100644 index 70739e3..0000000 --- a/src/controllers/users.js +++ /dev/null @@ -1,258 +0,0 @@ -import Router from 'koa-router' -import User from '../models/users' -import config from '../../config' -import jwt from 'jsonwebtoken' -import { ensureUser } from '../middleware/validators' - -const router = new Router({ prefix: '/users' }) - -/** - * @api {get} /users Get all users - * @apiPermission user - * @apiVersion 1.0.0 - * @apiName GetUsers - * @apiGroup Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X GET localhost:5000/users - * - * @apiSuccess {Object[]} users Array of user objects - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "users": [{ - * "_id": "56bd1da600a526986cf65c80" - * "name": "John Doe" - * "username": "johndoe" - * }] - * } - * - * @apiUse TokenError - */ -router.get('/', - ensureUser, - async (ctx) => { - const users = await User.find({}, '-password -salt') - ctx.body = users - } -) - -/** - * @api {get} /users/:id Get user by id - * @apiPermission user - * @apiVersion 1.0.0 - * @apiName GetUser - * @apiGroup Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X GET localhost:5000/users/56bd1da600a526986cf65c80 - * - * @apiSuccess {Object} users User object - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "user": { - * "_id": "56bd1da600a526986cf65c80" - * "name": "John Doe" - * "username": "johndoe" - * } - * } - * - * @apiUse TokenError - */ -router.get('/:id', - ensureUser, - async (ctx) => { - try { - const user = await User.findById(ctx.params.id, '-password -salt') - if (!user) { - ctx.throw(404) - } - - ctx.body = user - } catch (err) { - if (err === 404 || err.name === 'CastError') { - ctx.throw(404) - } - - ctx.throw(500) - } - } -) - -/** - * @api {post} /users Create a new user - * @apiPermission - * @apiVersion 1.0.0 - * @apiName CreateUser - * @apiGroup Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X POST -d '{ "user": { "username": "johndoe", "password": "secretpasas" } }' localhost:5000/users - * - * @apiParam {Object} user User object (required) - * @apiParam {String} user.username Username. - * @apiParam {String} user.password Password. - * - * @apiSuccess {Object} users User object - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} users.name User name - * @apiSuccess {String} users.username User username - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "user": { - * "_id": "56bd1da600a526986cf65c80" - * "name": "John Doe" - * "username": "johndoe" - * } - * } - * - * @apiError UnprocessableEntity Missing required parameters - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 422 Unprocessable Entity - * { - * "status": 422, - * "error": "Unprocessable Entity" - * } - */ -router.post('/', - async (ctx) => { - const user = new User(ctx.request.body.user) - try { - await user.save() - const token = jwt.sign({ id: user.id }, config.token) - - const response = user.toJSON() - - delete response.password - delete response.salt - - ctx.body = { - user: response, - token - } - } catch (err) { - ctx.throw(422, err) - } - } -) - -/** - * @api {put} /users/:id Update a user - * @apiPermission - * @apiVersion 1.0.0 - * @apiName UpdateUser - * @apiGroup Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X PUT -d '{ "user": { "name": "Cool new Name" } }' localhost:5000/users/56bd1da600a526986cf65c80 - * - * @apiParam {Object} user User object (required) - * @apiParam {String} user.name Name. - * @apiParam {String} user.username Username. - * - * @apiSuccess {Object} users User object - * @apiSuccess {ObjectId} users._id User id - * @apiSuccess {String} users.name Updated name - * @apiSuccess {String} users.username Updated username - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "user": { - * "_id": "56bd1da600a526986cf65c80" - * "name": "Cool new name" - * "username": "johndoe" - * } - * } - * - * @apiError UnprocessableEntity Missing required parameters - * - * @apiErrorExample {json} Error-Response: - * HTTP/1.1 422 Unprocessable Entity - * { - * "status": 422, - * "error": "Unprocessable Entity" - * } - * - * @apiUse TokenError - */ -router.put('/:id', - ensureUser, - async (ctx) => { - try { - const user = await User.findById(ctx.params.id, '-password -salt') - if (!user) { - ctx.throw(404) - } - - Object.assign(user, ctx.request.body.user) - - await user.save() - ctx.body = { - user - } - } catch (err) { - if (err === 404 || err.name === 'CastError') { - ctx.throw(404) - } - - ctx.throw(500) - } - } -) - -/** - * @api {delete} /users/:id Delete a user - * @apiPermission - * @apiVersion 1.0.0 - * @apiName DeleteUser - * @apiGroup Users - * - * @apiExample Example usage: - * curl -H "Content-Type: application/json" -X DELETE localhost:5000/users/56bd1da600a526986cf65c80 - * - * @apiSuccess {StatusCode} 200 - * - * @apiSuccessExample {json} Success-Response: - * HTTP/1.1 200 OK - * { - * "status": 200 - * } - * - * @apiUse TokenError - */ -router.delete('/:id', - ensureUser, - async (ctx) => { - try { - const user = await User.findById(ctx.params.id) - if (!user) { - ctx.throw(404) - } - - await user.remove() - - ctx.body = 200 - } catch (err) { - if (err === 404 || err.name === 'CastError') { - ctx.throw(404) - } - - ctx.throw(500) - } - } -) - -export default router From 0f3384eb571276442be168117cc6f97c57ba302d Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Wed, 16 Mar 2016 20:32:51 -0400 Subject: [PATCH 12/20] Fix import --- src/modules/auth/controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/auth/controller.js b/src/modules/auth/controller.js index e244550..41f3bf8 100644 --- a/src/modules/auth/controller.js +++ b/src/modules/auth/controller.js @@ -1,6 +1,6 @@ import passport from 'koa-passport' import jwt from 'jsonwebtoken' -import config from '../../config' +import config from '../../../config' export async function authUser(ctx, next) { return passport.authenticate('local', (user) => { From 9dfad9dca564a3340aa9901fc6e26e7b9cb0b11e Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Wed, 16 Mar 2016 21:20:23 -0400 Subject: [PATCH 13/20] Add tests for authentication --- test/auth.spec.js | 51 +++++++++++++++++++++++++++++++++++++++++++++++ test/utils.js | 21 +++++++++++++++++-- 2 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 test/auth.spec.js diff --git a/test/auth.spec.js b/test/auth.spec.js new file mode 100644 index 0000000..fa9e5f8 --- /dev/null +++ b/test/auth.spec.js @@ -0,0 +1,51 @@ +import app from '../bin/server' +import supertest from 'supertest' +import { expect, should } from 'chai' +import { cleanDb, authUser } from './utils' + +should() +const request = supertest.agent(app.listen()) +const context = {} + +describe('Auth', () => { + before((done) => { + cleanDb() + authUser(request, (err, { user, token }) => { + if (err) { return done(err) } + + context.user = user + context.token = token + done() + }) + }) + + describe('POST /auth', () => { + it('should throw 401 if credentials are incorrect', (done) => { + request + .post('/auth') + .set('Accept', 'application/json') + .send({ username: 'supercoolname', password: 'wrongpassword' }) + .expect(401, done) + }) + + it('should auth user', (done) => { + request + .post('/auth') + .set('Accept', 'application/json') + .send({ username: 'test', password: 'pass' }) + .expect(200, (err, res) => { + if (err) { return done(err) } + + res.body.user.should.have.property('username') + res.body.user.username.should.equal('test') + expect(res.body.user.password).to.not.exist + expect(res.body.user.salt).to.not.exist + + context.user = res.body.user + context.token = res.body.token + + done() + }) + }) + }) +}) diff --git a/test/utils.js b/test/utils.js index 3ac5f12..f02445c 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,7 +1,24 @@ import mongoose from 'mongoose' -export function cleanDb () { +export function cleanDb() { for (const collection in mongoose.connection.collections) { - mongoose.connection.collections[collection].remove(); + if (mongoose.connection.collections.hasOwnProperty(collection)) { + mongoose.connection.collections[collection].remove() + } } } + +export function authUser(agent, callback) { + agent + .post('/users') + .set('Accept', 'application/json') + .send({ user: { username: 'test', password: 'pass' } }) + .end((err, res) => { + if (err) { return callback(err) } + + callback(null, { + user: res.body.user, + token: res.body.token + }) + }) +} From 8ffe8a40ae67990ff1250fdcd7b60b63bfa1c4dc Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Fri, 18 Mar 2016 22:59:01 -0400 Subject: [PATCH 14/20] Replace object with proper array implementation --- src/modules/auth/router.js | 8 ++++---- src/modules/index.js | 19 ++++++++++--------- src/modules/users/router.js | 20 ++++++++------------ 3 files changed, 22 insertions(+), 25 deletions(-) diff --git a/src/modules/auth/router.js b/src/modules/auth/router.js index fab2e6b..06b1d84 100644 --- a/src/modules/auth/router.js +++ b/src/modules/auth/router.js @@ -1,13 +1,13 @@ import * as auth from './controller' -export default { - base: '/auth', +export const baseUrl = '/auth' - 'Authenticate user': { +export default [ + { method: 'POST', route: '/', handlers: [ auth.authUser ] } -} +] diff --git a/src/modules/index.js b/src/modules/index.js index c062dbf..6e9dd6e 100644 --- a/src/modules/index.js +++ b/src/modules/index.js @@ -6,24 +6,25 @@ exports = module.exports = function initModules(app) { if (err) { throw err } matches.forEach((mod) => { - const routerConfig = require(`${mod}/router`).default - const router = new Router({ prefix: routerConfig.base }) + const router = require(`${mod}/router`) - for (const [key, props] of Object.entries(routerConfig)) { - if (key === 'base') { continue } + const routes = router.default + const baseUrl = router.baseUrl + const instance = new Router({ prefix: baseUrl }) + routes.forEach((config) => { const { method = '', route = '', handlers = [] - } = props + } = config - router[method.toLowerCase()](route, ...handlers) + instance[method.toLowerCase()](route, ...handlers) app - .use(router.routes()) - .use(router.allowedMethods()) - } + .use(instance.routes()) + .use(instance.allowedMethods()) + }) }) }) } diff --git a/src/modules/users/router.js b/src/modules/users/router.js index ad84a61..5ac210f 100644 --- a/src/modules/users/router.js +++ b/src/modules/users/router.js @@ -1,18 +1,17 @@ import { ensureUser } from '../../middleware/validators' import * as user from './controller' -export default { - base: '/users', +export const baseUrl = '/users' - 'Create new users': { +export default [ + { method: 'POST', route: '/', handlers: [ user.createUser ] }, - - 'Get all users': { + { method: 'GET', route: '/', handlers: [ @@ -20,8 +19,7 @@ export default { user.getUsers ] }, - - 'Get a single user': { + { method: 'GET', route: '/:id', handlers: [ @@ -29,8 +27,7 @@ export default { user.getUser ] }, - - 'Update user': { + { method: 'PUT', route: '/:id', handlers: [ @@ -38,8 +35,7 @@ export default { user.updateUser ] }, - - 'Delete user': { + { method: 'DELETE', route: '/:id', handlers: [ @@ -47,4 +43,4 @@ export default { user.deleteUser ] } -} +] From 01d200759071f24c4c209a682854ddbae81d2f4d Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Fri, 18 Mar 2016 23:03:57 -0400 Subject: [PATCH 15/20] Update tests --- test/users.spec.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/users.spec.js b/test/users.spec.js index 0fe356f..b07e38e 100644 --- a/test/users.spec.js +++ b/test/users.spec.js @@ -85,7 +85,16 @@ describe('Users', () => { request .get(`/users/${_id}?token=${token}`) .set('Accept', 'application/json') - .expect(200, done) + .expect(200, (err, res) => { + if (err) { return done(err) } + + res.body.should.have.property('user') + + expect(res.body.user.password).to.not.exist + expect(res.body.user.salt).to.not.exist + + done() + }) }) }) From 16fa1ebd0a531b09f76d6dd7b2a9d1c5fe3c2a6c Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Fri, 18 Mar 2016 23:04:14 -0400 Subject: [PATCH 16/20] Pass user object within a user key --- src/modules/users/controller.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/modules/users/controller.js b/src/modules/users/controller.js index ab346ee..ddecff1 100644 --- a/src/modules/users/controller.js +++ b/src/modules/users/controller.js @@ -34,7 +34,9 @@ export async function getUser(ctx) { ctx.throw(404) } - ctx.body = user + ctx.body = { + user + } } catch (err) { if (err === 404 || err.name === 'CastError') { ctx.throw(404) @@ -75,7 +77,10 @@ export async function deleteUser(ctx) { await user.remove() - ctx.body = 200 + ctx.status = 200 + ctx.body = { + success: true + } } catch (err) { if (err === 404 || err.name === 'CastError') { ctx.throw(404) From 3539d66d5067cb3b675dd4325bf8712c4d0c2d8c Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Fri, 18 Mar 2016 23:14:57 -0400 Subject: [PATCH 17/20] Use `getUser` as middleware for code reuse --- src/modules/users/controller.js | 47 ++++++++++----------------------- src/modules/users/router.js | 2 ++ test/users.spec.js | 10 ++++++- 3 files changed, 25 insertions(+), 34 deletions(-) diff --git a/src/modules/users/controller.js b/src/modules/users/controller.js index ddecff1..86712a0 100644 --- a/src/modules/users/controller.js +++ b/src/modules/users/controller.js @@ -24,10 +24,10 @@ export async function createUser(ctx) { export async function getUsers(ctx) { const users = await User.find({}, '-password -salt') - ctx.body = users + ctx.body = { users } } -export async function getUser(ctx) { +export async function getUser(ctx, next) { try { const user = await User.findById(ctx.params.id, '-password -salt') if (!user) { @@ -44,48 +44,29 @@ export async function getUser(ctx) { ctx.throw(500) } + + next() } export async function updateUser(ctx) { - try { - const user = await User.findById(ctx.params.id, '-password -salt') - if (!user) { - ctx.throw(404) - } + const user = ctx.body.user - Object.assign(user, ctx.request.body.user) + Object.assign(user, ctx.request.body.user) - await user.save() - ctx.body = { - user - } - } catch (err) { - if (err === 404 || err.name === 'CastError') { - ctx.throw(404) - } + await user.save() - ctx.throw(500) + ctx.body = { + user } } export async function deleteUser(ctx) { - try { - const user = await User.findById(ctx.params.id) - if (!user) { - ctx.throw(404) - } + const user = ctx.body.user - await user.remove() + await user.remove() - ctx.status = 200 - ctx.body = { - success: true - } - } catch (err) { - if (err === 404 || err.name === 'CastError') { - ctx.throw(404) - } - - ctx.throw(500) + ctx.status = 200 + ctx.body = { + sucess: true } } diff --git a/src/modules/users/router.js b/src/modules/users/router.js index 5ac210f..f03aed9 100644 --- a/src/modules/users/router.js +++ b/src/modules/users/router.js @@ -32,6 +32,7 @@ export default [ route: '/:id', handlers: [ ensureUser, + user.getUser, user.updateUser ] }, @@ -40,6 +41,7 @@ export default [ route: '/:id', handlers: [ ensureUser, + user.getUser, user.deleteUser ] } diff --git a/test/users.spec.js b/test/users.spec.js index b07e38e..99931ce 100644 --- a/test/users.spec.js +++ b/test/users.spec.js @@ -56,7 +56,15 @@ describe('Users', () => { request .get(`/users?token=${token}`) .set('Accept', 'application/json') - .expect(200, done) + .expect(200, (err, res) => { + if (err) { return done(err) } + + res.body.should.have.property('users') + + res.body.users.should.have.length(1) + + done() + }) }) }) From d247a26716200510e23fb49ce4f9864bda4fd24c Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Fri, 18 Mar 2016 23:23:45 -0400 Subject: [PATCH 18/20] Move token generation to model --- src/models/users.js | 8 ++++++++ src/modules/users/controller.js | 4 +--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/models/users.js b/src/models/users.js index be2f4f4..a78add1 100644 --- a/src/models/users.js +++ b/src/models/users.js @@ -1,5 +1,7 @@ import mongoose from 'mongoose' import bcrypt from 'bcrypt' +import config from '../../config' +import jwt from 'jsonwebtoken' const User = new mongoose.Schema({ type: { type: String, default: 'User' }, @@ -47,4 +49,10 @@ User.methods.validatePassword = function validatePassword(password) { }) } +User.methods.generateToken = function generateToken() { + const user = this + + return jwt.sign({ id: user.id }, config.token) +} + export default mongoose.model('user', User) diff --git a/src/modules/users/controller.js b/src/modules/users/controller.js index 86712a0..d0ed964 100644 --- a/src/modules/users/controller.js +++ b/src/modules/users/controller.js @@ -1,12 +1,10 @@ import User from '../../models/users' -import config from '../../../config' -import jwt from 'jsonwebtoken' export async function createUser(ctx) { const user = new User(ctx.request.body.user) try { await user.save() - const token = jwt.sign({ id: user.id }, config.token) + const token = user.generateToken() const response = user.toJSON() From 6524773c533d66b8a8815160a5ed0fda5bff1ee1 Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Fri, 18 Mar 2016 23:34:57 -0400 Subject: [PATCH 19/20] Improve logging --- src/modules/users/controller.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/modules/users/controller.js b/src/modules/users/controller.js index d0ed964..2603e39 100644 --- a/src/modules/users/controller.js +++ b/src/modules/users/controller.js @@ -4,19 +4,19 @@ export async function createUser(ctx) { const user = new User(ctx.request.body.user) try { await user.save() - const token = user.generateToken() - - const response = user.toJSON() - - delete response.password - delete response.salt - - ctx.body = { - user: response, - token - } } catch (err) { - ctx.throw(422, err) + ctx.throw(422, err.message) + } + + const token = user.generateToken() + const response = user.toJSON() + + delete response.password + delete response.salt + + ctx.body = { + user: response, + token } } @@ -65,6 +65,6 @@ export async function deleteUser(ctx) { ctx.status = 200 ctx.body = { - sucess: true + success: true } } From f29e6e04325b82166da75dec86f8c704c3191068 Mon Sep 17 00:00:00 2001 From: Adrian Obelmejias Date: Sat, 19 Mar 2016 11:26:39 -0400 Subject: [PATCH 20/20] Add docs --- src/modules/auth/controller.js | 51 +++++++++++ src/modules/users/controller.js | 153 ++++++++++++++++++++++++++++++++ 2 files changed, 204 insertions(+) diff --git a/src/modules/auth/controller.js b/src/modules/auth/controller.js index 41f3bf8..6965ff5 100644 --- a/src/modules/auth/controller.js +++ b/src/modules/auth/controller.js @@ -2,6 +2,57 @@ import passport from 'koa-passport' import jwt from 'jsonwebtoken' import config from '../../../config' +/** + * @apiDefine TokenError + * @apiError Unauthorized Invalid JWT token + * + * @apiErrorExample {json} Unauthorized-Error: + * HTTP/1.1 401 Unauthorized + * { + * "status": 401, + * "error": "Unauthorized" + * } + */ + +/** + * @api {post} /auth Authenticate user + * @apiVersion 1.0.0 + * @apiName AuthUser + * @apiGroup Auth + * + * @apiParam {String} username User username. + * @apiParam {String} password User password. + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X POST -d '{ "username": "johndoe@gmail.com", "password": "foo" }' localhost:5000/auth + * + * @apiSuccess {Object} user User object + * @apiSuccess {ObjectId} user._id User id + * @apiSuccess {String} user.name User name + * @apiSuccess {String} user.username User username + * @apiSuccess {String} token Encoded JWT + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "user": { + * "_id": "56bd1da600a526986cf65c80" + * "username": "foo" + * "username": "johndoe" + * }, + * "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" + * } + * + * @apiError Unauthorized Incorrect credentials + * + * @apiErrorExample {json} Error-Response: + * HTTP/1.1 401 Unauthorized + * { + * "status": 401, + * "error": "Unauthorized" + * } + */ + export async function authUser(ctx, next) { return passport.authenticate('local', (user) => { if (!user) { diff --git a/src/modules/users/controller.js b/src/modules/users/controller.js index 2603e39..f03cf64 100644 --- a/src/modules/users/controller.js +++ b/src/modules/users/controller.js @@ -1,5 +1,43 @@ import User from '../../models/users' +/** + * @api {post} /users Create a new user + * @apiPermission + * @apiVersion 1.0.0 + * @apiName CreateUser + * @apiGroup Users + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X POST -d '{ "user": { "username": "johndoe", "password": "secretpasas" } }' localhost:5000/users + * + * @apiParam {Object} user User object (required) + * @apiParam {String} user.username Username. + * @apiParam {String} user.password Password. + * + * @apiSuccess {Object} users User object + * @apiSuccess {ObjectId} users._id User id + * @apiSuccess {String} users.name User name + * @apiSuccess {String} users.username User username + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "user": { + * "_id": "56bd1da600a526986cf65c80" + * "name": "John Doe" + * "username": "johndoe" + * } + * } + * + * @apiError UnprocessableEntity Missing required parameters + * + * @apiErrorExample {json} Error-Response: + * HTTP/1.1 422 Unprocessable Entity + * { + * "status": 422, + * "error": "Unprocessable Entity" + * } + */ export async function createUser(ctx) { const user = new User(ctx.request.body.user) try { @@ -20,11 +58,65 @@ export async function createUser(ctx) { } } +/** + * @api {get} /users Get all users + * @apiPermission user + * @apiVersion 1.0.0 + * @apiName GetUsers + * @apiGroup Users + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X GET localhost:5000/users + * + * @apiSuccess {Object[]} users Array of user objects + * @apiSuccess {ObjectId} users._id User id + * @apiSuccess {String} users.name User name + * @apiSuccess {String} users.username User username + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "users": [{ + * "_id": "56bd1da600a526986cf65c80" + * "name": "John Doe" + * "username": "johndoe" + * }] + * } + * + * @apiUse TokenError + */ export async function getUsers(ctx) { const users = await User.find({}, '-password -salt') ctx.body = { users } } +/** + * @api {get} /users/:id Get user by id + * @apiPermission user + * @apiVersion 1.0.0 + * @apiName GetUser + * @apiGroup Users + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X GET localhost:5000/users/56bd1da600a526986cf65c80 + * + * @apiSuccess {Object} users User object + * @apiSuccess {ObjectId} users._id User id + * @apiSuccess {String} users.name User name + * @apiSuccess {String} users.username User username + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "user": { + * "_id": "56bd1da600a526986cf65c80" + * "name": "John Doe" + * "username": "johndoe" + * } + * } + * + * @apiUse TokenError + */ export async function getUser(ctx, next) { try { const user = await User.findById(ctx.params.id, '-password -salt') @@ -46,6 +138,46 @@ export async function getUser(ctx, next) { next() } +/** + * @api {put} /users/:id Update a user + * @apiPermission + * @apiVersion 1.0.0 + * @apiName UpdateUser + * @apiGroup Users + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X PUT -d '{ "user": { "name": "Cool new Name" } }' localhost:5000/users/56bd1da600a526986cf65c80 + * + * @apiParam {Object} user User object (required) + * @apiParam {String} user.name Name. + * @apiParam {String} user.username Username. + * + * @apiSuccess {Object} users User object + * @apiSuccess {ObjectId} users._id User id + * @apiSuccess {String} users.name Updated name + * @apiSuccess {String} users.username Updated username + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "user": { + * "_id": "56bd1da600a526986cf65c80" + * "name": "Cool new name" + * "username": "johndoe" + * } + * } + * + * @apiError UnprocessableEntity Missing required parameters + * + * @apiErrorExample {json} Error-Response: + * HTTP/1.1 422 Unprocessable Entity + * { + * "status": 422, + * "error": "Unprocessable Entity" + * } + * + * @apiUse TokenError + */ export async function updateUser(ctx) { const user = ctx.body.user @@ -58,6 +190,27 @@ export async function updateUser(ctx) { } } +/** + * @api {delete} /users/:id Delete a user + * @apiPermission + * @apiVersion 1.0.0 + * @apiName DeleteUser + * @apiGroup Users + * + * @apiExample Example usage: + * curl -H "Content-Type: application/json" -X DELETE localhost:5000/users/56bd1da600a526986cf65c80 + * + * @apiSuccess {StatusCode} 200 + * + * @apiSuccessExample {json} Success-Response: + * HTTP/1.1 200 OK + * { + * "success": true + * } + * + * @apiUse TokenError + */ + export async function deleteUser(ctx) { const user = ctx.body.user