mirror of
https://github.com/Permissionless-Software-Foundation/bch-email-api.git
synced 2026-09-21 16:52:04 -07:00
forked from koa-boilerplate
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
# http://editorconfig.org
|
||||
|
||||
# A special property that should be specified at the top of the file outside of
|
||||
# any sections. Set to true to stop .editor config file search on current file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
# Indentation style
|
||||
# Possible values - tab, space
|
||||
indent_style = space
|
||||
|
||||
# Indentation size in single-spaced characters
|
||||
# Possible values - an integer, tab
|
||||
indent_size = 2
|
||||
|
||||
# Line ending file format
|
||||
# Possible values - lf, crlf, cr
|
||||
end_of_line = lf
|
||||
|
||||
# File character encoding
|
||||
# Possible values - latin1, utf-8, utf-16be, utf-16le
|
||||
charset = utf-8
|
||||
|
||||
# Denotes whether to trim whitespace at the end of lines
|
||||
# Possible values - true, false
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
# Denotes whether file should end with a newline
|
||||
# Possible values - true, false
|
||||
insert_final_newline = true
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "standard",
|
||||
"env": {
|
||||
"node": true,
|
||||
"mocha": true
|
||||
}
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
|
||||
|
||||
# Created by https://www.gitignore.io/api/node,sublimetext
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
node_modules
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
|
||||
### SublimeText ###
|
||||
# cache files for sublime text
|
||||
*.tmlanguage.cache
|
||||
*.tmPreferences.cache
|
||||
*.stTheme.cache
|
||||
|
||||
# workspace files are user-specific
|
||||
*.sublime-workspace
|
||||
|
||||
# project files should be checked into the repository, unless a significant
|
||||
# proportion of contributors will probably not be using SublimeText
|
||||
*.sublime-project
|
||||
|
||||
# sftp configuration file
|
||||
sftp-config.json
|
||||
|
||||
#Documentation
|
||||
docs
|
||||
|
||||
.nyc_output
|
||||
coverage
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
# This is a node.js v8+ JavaScript project
|
||||
language: node_js
|
||||
node_js:
|
||||
- "8"
|
||||
|
||||
# Build on Ubuntu Trusty (14.04)
|
||||
# https://docs.travis-ci.com/user/reference/trusty/#javascript-and-nodejs-images
|
||||
dist: trusty
|
||||
sudo: required
|
||||
|
||||
# Use Docker
|
||||
services:
|
||||
- docker
|
||||
|
||||
before_install:
|
||||
- ./install-mongo
|
||||
#- npm install -g mocha
|
||||
|
||||
# Send coverage data to Coveralls
|
||||
after_success:
|
||||
- npm run coverage
|
||||
|
||||
deploy:
|
||||
provider: script
|
||||
skip_cleanup: true
|
||||
script:
|
||||
- npx semantic-release
|
||||
@@ -0,0 +1,8 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2016 Adrian Obelmejias <adrian@obel.me> & Chris Troutner <chris.troutner@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,91 @@
|
||||
# babel-free-koa2-api-boilerplate
|
||||
[](http://standardjs.com)
|
||||
|
||||
[](https://coveralls.io/github/christroutner/babel-free-koa2-api-boilerplate?branch=unstable)
|
||||
|
||||
[](https://github.com/semantic-release/semantic-release)
|
||||
|
||||
[](https://greenkeeper.io/)
|
||||
|
||||
|
||||
Boilerplate for building APIs with [koa2](https://github.com/koajs/koa/tree/v2.x) and mongodb.
|
||||
This repository is forked from Adrian Obelmejias' [koa-api-boilerplate repository](https://github.com/adrianObel/koa2-api-boilerplate).
|
||||
It makes the following modifications:
|
||||
|
||||
- Remove babel as a dependency. This repository is now natively compatible with
|
||||
node v8.9 or higher.
|
||||
|
||||
- Replaced `bcrypt` dependency with `bcryptjs`. This improves compatibility across
|
||||
versions of node.js and across OSs.
|
||||
|
||||
- Configured for Travis CI (continuous integration), Coveralls (code coverage), GreenKeeper (automated dependency management), and Semantic Release (automated versioning).
|
||||
|
||||
## Features
|
||||
This project covers basic necessities of most APIs.
|
||||
* Authentication (passport & jwt)
|
||||
* Database (mongoose)
|
||||
* Testing (mocha)
|
||||
* Doc generation with apidoc
|
||||
* linting using standard
|
||||
|
||||
|
||||
|
||||
## Requirements
|
||||
* node __^8.9.4__
|
||||
* npm __^5.7.1__
|
||||
|
||||
## Installation
|
||||
```bash
|
||||
git clone https://github.com/christroutner/babel-free-koa2-api-boilerplate.git
|
||||
```
|
||||
|
||||
## Structure
|
||||
```
|
||||
├── bin
|
||||
│ └── server.js # Bootstrapping and entry point
|
||||
├── config # Server configuration settings
|
||||
│ ├── env # Environment specific config
|
||||
│ │ ├── common.js
|
||||
│ │ ├── development.js
|
||||
│ │ ├── production.js
|
||||
│ │ └── test.js
|
||||
│ ├── index.js # Config entrypoint - exports config according to envionrment and commons
|
||||
│ └── passport.js # Passportjs config of strategies
|
||||
├── src # Source code
|
||||
│ ├── modules
|
||||
│ │ ├── controller.js # Module-specific controllers
|
||||
│ │ └── router.js # Router definitions for module
|
||||
│ ├── models # Mongoose models
|
||||
│ └── middleware # Custom middleware
|
||||
│ └── validators # Validation middleware
|
||||
└── test # Unit tests
|
||||
```
|
||||
|
||||
## Usage
|
||||
* `npm start` Start server on live mode
|
||||
* `npm run dev` Start server on dev mode with nodemon
|
||||
* `npm run docs` Generate API documentation
|
||||
* `npm test` Run mocha tests
|
||||
|
||||
## Documentation
|
||||
API documentation is written inline and generated by [apidoc](http://apidocjs.com/).
|
||||
|
||||
Visit `http://localhost:5000/docs/` to view docs
|
||||
|
||||
|
||||
## Dependencies
|
||||
* [koa2](https://github.com/koajs/koa/tree/v2.x)
|
||||
* [koa-router](https://github.com/alexmingoia/koa-router)
|
||||
* [koa-bodyparser](https://github.com/koajs/bodyparser)
|
||||
* [koa-generic-session](https://github.com/koajs/generic-session)
|
||||
* [koa-logger](https://github.com/koajs/logger)
|
||||
* [MongoDB](http://mongodb.org/)
|
||||
* [Mongoose](http://mongoosejs.com/)
|
||||
* [Passport](http://passportjs.org/)
|
||||
* [Nodemon](http://nodemon.io/)
|
||||
* [Mocha](https://mochajs.org/)
|
||||
* [apidoc](http://apidocjs.com/)
|
||||
* [ESLint](http://eslint.org/)
|
||||
|
||||
## License
|
||||
MIT
|
||||
@@ -0,0 +1,63 @@
|
||||
const Koa = require('koa')
|
||||
const bodyParser = require('koa-bodyparser')
|
||||
const convert = require('koa-convert')
|
||||
const logger = require('koa-logger')
|
||||
const mongoose = require('mongoose')
|
||||
const session = require('koa-generic-session')
|
||||
const passport = require('koa-passport')
|
||||
const mount = require('koa-mount')
|
||||
const serve = require('koa-static')
|
||||
const cors = require('kcors')
|
||||
|
||||
const config = require('../config')
|
||||
const errorMiddleware = require('../src/middleware')
|
||||
|
||||
async function startServer () {
|
||||
// Create a Koa instance.
|
||||
const app = new Koa()
|
||||
app.keys = [config.session]
|
||||
|
||||
// Connect to the Mongo Database.
|
||||
mongoose.Promise = global.Promise
|
||||
await mongoose.connect(config.database, { useNewUrlParser: true })
|
||||
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
|
||||
// MIDDLEWARE START
|
||||
|
||||
app.use(convert(logger()))
|
||||
app.use(bodyParser())
|
||||
app.use(session())
|
||||
app.use(errorMiddleware())
|
||||
|
||||
// Used to generate the docs.
|
||||
app.use(convert(mount('/docs', serve(`${process.cwd()}/docs`))))
|
||||
|
||||
// User Authentication
|
||||
require('../config/passport')
|
||||
app.use(passport.initialize())
|
||||
app.use(passport.session())
|
||||
|
||||
// Custom Middleware Modules
|
||||
const modules = require('../src/modules')
|
||||
modules(app)
|
||||
|
||||
// Enable CORS for testing
|
||||
// app.use(cors({origin: '*'}))
|
||||
|
||||
// MIDDLEWARE END
|
||||
|
||||
// app.listen(config.port, () => {
|
||||
// console.log(`Server started on ${config.port}`)
|
||||
// })
|
||||
await app.listen(config.port)
|
||||
console.log(`Server started on ${config.port}`)
|
||||
|
||||
return app
|
||||
}
|
||||
// startServer()
|
||||
|
||||
// export default app
|
||||
// module.exports = app
|
||||
module.exports = {
|
||||
startServer
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
// export default {
|
||||
// port: process.env.PORT || 5000
|
||||
// }
|
||||
|
||||
module.exports = {
|
||||
port: process.env.PORT || 5000
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
export default {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/p2pvps-server-dev'
|
||||
}
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/p2pvps-server-dev'
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
export default {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/p2pvps-server-prod'
|
||||
}
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/p2pvps-server-prod'
|
||||
}
|
||||
Vendored
+13
@@ -0,0 +1,13 @@
|
||||
/*
|
||||
export default {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/p2pvps-server-test'
|
||||
}
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/p2pvps-server-test'
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
const common = require('./env/common')
|
||||
|
||||
const env = process.env.NODE_ENV || 'development'
|
||||
const config = require(`./env/${env}`)
|
||||
|
||||
module.exports = Object.assign({}, common, config)
|
||||
@@ -0,0 +1,39 @@
|
||||
const passport = require('koa-passport')
|
||||
const User = require('../src/models/users')
|
||||
const Strategy = require('passport-local')
|
||||
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user.id)
|
||||
})
|
||||
|
||||
passport.deserializeUser(async (id, done) => {
|
||||
try {
|
||||
const user = await User.findById(id, '-password')
|
||||
done(null, user)
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
})
|
||||
|
||||
passport.use('local', new Strategy({
|
||||
usernameField: 'username',
|
||||
passwordField: 'password'
|
||||
}, async (username, password, done) => {
|
||||
try {
|
||||
const user = await User.findOne({ username })
|
||||
if (!user) { return done(null, false) }
|
||||
|
||||
try {
|
||||
const isMatch = await user.validatePassword(password)
|
||||
|
||||
if (!isMatch) { return done(null, false) }
|
||||
|
||||
done(null, user)
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
return done(err)
|
||||
}
|
||||
}))
|
||||
@@ -0,0 +1,3 @@
|
||||
const server = require('./bin/server.js')
|
||||
|
||||
server.startServer()
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 0C49F3730359A14518585931BC711F9BA15703C6
|
||||
echo "deb [ arch=amd64,arm64 ] http://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.4 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.4.list
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y mongodb-org
|
||||
sudo service mongod start
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"name": "babel-free-koa2-api-boilerplate",
|
||||
"version": "3.0.0",
|
||||
"description": "Koa2 boilerplate covering essentials for APIs, babel removed as a dependency",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"test": "NODE_ENV=test nyc --reporter=text ./node_modules/.bin/mocha --exit",
|
||||
"lint": "eslint src/**/*.js",
|
||||
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "nyc --reporter=html mocha --exit"
|
||||
},
|
||||
"keywords": [
|
||||
"koa2-api-boilerplate",
|
||||
"api",
|
||||
"koa",
|
||||
"koa2",
|
||||
"boilerplate",
|
||||
"es6",
|
||||
"mongoose",
|
||||
"passportjs",
|
||||
"apidoc"
|
||||
],
|
||||
"author": "Chris Troutner <chris.troutner@gmail.com>",
|
||||
"license": "MIT",
|
||||
"apidoc": {
|
||||
"title": "babel-free-koa2-api-boilerplate",
|
||||
"url": "localhost:5000"
|
||||
},
|
||||
"repository": "christroutner/babel-free-koa2-api-boilerplate",
|
||||
"dependencies": {
|
||||
"apidoc": "^0.17.6",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"glob": "^7.0.0",
|
||||
"jsonwebtoken": "^8.3.0",
|
||||
"kcors": "^2.2.1",
|
||||
"koa": "^2.5.0",
|
||||
"koa-bodyparser": "^4.2.0",
|
||||
"koa-convert": "^1.2.0",
|
||||
"koa-generic-session": "^2.0.1",
|
||||
"koa-logger": "^3.1.0",
|
||||
"koa-mount": "^4.0.0",
|
||||
"koa-passport": "^4.1.1",
|
||||
"koa-router": "^7.0.1",
|
||||
"koa-static": "^5.0.0",
|
||||
"mongoose": "^5.0.8",
|
||||
"passport-local": "^1.0.0",
|
||||
"request": "^2.85.0",
|
||||
"request-promise": "^4.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.1.2",
|
||||
"coveralls": "^3.0.2",
|
||||
"eslint": "^5.8.0",
|
||||
"eslint-config-promise": "^1.2.6",
|
||||
"eslint-config-standard": "^12.0.0",
|
||||
"eslint-plugin-import": "^2.14.0",
|
||||
"eslint-plugin-node": "^8.0.0",
|
||||
"eslint-plugin-promise": "^4.0.1",
|
||||
"eslint-plugin-standard": "^4.0.0",
|
||||
"mocha": "^5.2.0",
|
||||
"nyc": "^13.1.0",
|
||||
"semantic-release": "^15.10.8"
|
||||
},
|
||||
"release": {
|
||||
"publish": [
|
||||
{
|
||||
"path": "@semantic-release/npm",
|
||||
"npmPublish": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
module.exports = function errorMiddleware () {
|
||||
return async (ctx, next) => {
|
||||
try {
|
||||
await next()
|
||||
} catch (err) {
|
||||
ctx.status = err.status || 500
|
||||
ctx.body = err.message
|
||||
ctx.app.emit('error', err, ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
const User = require('../models/users')
|
||||
const config = require('../../config')
|
||||
const getToken = require('../utils/auth')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
module.exports = async function ensureUser (ctx, next) {
|
||||
//console.log(`getToken: ${typeof (getToken)}`)
|
||||
const token = getToken(ctx)
|
||||
|
||||
if (!token) {
|
||||
//console.log(`Err: Token not provided.`)
|
||||
ctx.throw(401)
|
||||
}
|
||||
|
||||
let decoded = null
|
||||
try {
|
||||
//console.log(`token: ${JSON.stringify(token, null, 2)}`)
|
||||
//console.log(`config: ${JSON.stringify(config, null, 2)}`)
|
||||
decoded = jwt.verify(token, config.token)
|
||||
} catch (err) {
|
||||
//console.log(`Err: Token could not be decoded: ${err}`)
|
||||
ctx.throw(401)
|
||||
}
|
||||
|
||||
ctx.state.user = await User.findById(decoded.id, '-password')
|
||||
if (!ctx.state.user) {
|
||||
//console.log(`Err: Could not find user.`)
|
||||
ctx.throw(401)
|
||||
}
|
||||
|
||||
return next()
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
const mongoose = require('mongoose')
|
||||
const bcrypt = require('bcryptjs')
|
||||
const config = require('../../config')
|
||||
const jwt = require('jsonwebtoken')
|
||||
|
||||
const User = new mongoose.Schema({
|
||||
type: { type: String, default: 'User' },
|
||||
name: { type: String },
|
||||
username: { type: String, required: true, unique: true },
|
||||
password: { type: String, required: true }
|
||||
})
|
||||
|
||||
User.pre('save', function preSave (next) {
|
||||
const user = this
|
||||
|
||||
if (!user.isModified('password')) {
|
||||
return next()
|
||||
}
|
||||
|
||||
new Promise((resolve, reject) => {
|
||||
bcrypt.genSalt(10, (err, salt) => {
|
||||
if (err) { return reject(err) }
|
||||
resolve(salt)
|
||||
})
|
||||
})
|
||||
.then(salt => {
|
||||
bcrypt.hash(user.password, salt, (err, hash) => {
|
||||
if (err) { throw new Error(err) }
|
||||
|
||||
user.password = hash
|
||||
|
||||
next(null)
|
||||
})
|
||||
})
|
||||
.catch(err => next(err))
|
||||
})
|
||||
|
||||
User.methods.validatePassword = function validatePassword (password) {
|
||||
const user = this
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
bcrypt.compare(password, user.password, (err, isMatch) => {
|
||||
if (err) { return reject(err) }
|
||||
|
||||
resolve(isMatch)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
User.methods.generateToken = function generateToken () {
|
||||
const user = this
|
||||
|
||||
const token = jwt.sign({ id: user.id }, config.token)
|
||||
// console.log(`config.token: ${config.token}`)
|
||||
// console.log(`generated token: ${token}`)
|
||||
return token
|
||||
}
|
||||
|
||||
// export default mongoose.model('user', User)
|
||||
module.exports = mongoose.model('user', User)
|
||||
@@ -0,0 +1,74 @@
|
||||
const passport = require('koa-passport')
|
||||
|
||||
/**
|
||||
* @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": "johndoe"
|
||||
* },
|
||||
* "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
|
||||
* }
|
||||
*
|
||||
* @apiError Unauthorized Incorrect credentials
|
||||
*
|
||||
* @apiErrorExample {json} Error-Response:
|
||||
* HTTP/1.1 401 Unauthorized
|
||||
* {
|
||||
* "status": 401,
|
||||
* "error": "Unauthorized"
|
||||
* }
|
||||
*/
|
||||
|
||||
async function authUser (ctx, next) {
|
||||
return passport.authenticate('local', (err, user, info, status) => {
|
||||
if (err) throw err
|
||||
|
||||
if (!user) {
|
||||
ctx.throw(401)
|
||||
}
|
||||
|
||||
const token = user.generateToken()
|
||||
|
||||
const response = user.toJSON()
|
||||
|
||||
delete response.password
|
||||
|
||||
ctx.body = {
|
||||
token,
|
||||
user: response
|
||||
}
|
||||
})(ctx, next)
|
||||
}
|
||||
|
||||
module.exports.authUser = authUser
|
||||
@@ -0,0 +1,16 @@
|
||||
// import * as auth from './controller'
|
||||
const auth = require('./controller')
|
||||
|
||||
// export const baseUrl = '/auth'
|
||||
module.exports.baseUrl = '/auth'
|
||||
|
||||
// export default [
|
||||
module.exports.routes = [
|
||||
{
|
||||
method: 'POST',
|
||||
route: '/',
|
||||
handlers: [
|
||||
auth.authUser
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,46 @@
|
||||
const glob = require('glob')
|
||||
const Router = require('koa-router')
|
||||
|
||||
module.exports = function initModules (app) {
|
||||
glob(`${__dirname}/*`, { ignore: '**/index.js' }, (err, matches) => {
|
||||
if (err) { throw err }
|
||||
|
||||
// Loop through each sub-directory in the modules directory.
|
||||
matches.forEach((mod) => {
|
||||
// console.log(`router = ${mod}/router`)
|
||||
const router = require(`${mod}/router`)
|
||||
|
||||
const routes = router.routes
|
||||
const baseUrl = router.baseUrl
|
||||
const instance = new Router({ prefix: baseUrl })
|
||||
|
||||
// console.log(`routes: ${JSON.stringify(routes, null, 2)}`)
|
||||
|
||||
// Loop through each route defined in the router.js file.
|
||||
routes.forEach((config) => {
|
||||
// console.log(`modules/index.js config: ${JSON.stringify(config, null, 2)}`)
|
||||
// const {
|
||||
// method = '',
|
||||
// route = '',
|
||||
// handlers = []
|
||||
// } = config
|
||||
const method = config.method || ''
|
||||
const route = config.route || ''
|
||||
const handlers = config.handlers || []
|
||||
|
||||
const lastHandler = handlers.pop()
|
||||
|
||||
instance[method.toLowerCase()](route, ...handlers, async function (ctx) {
|
||||
// console.log(`typeof lastHandler: ${typeof (lastHandler)}`)
|
||||
return await lastHandler(ctx)
|
||||
})
|
||||
|
||||
// console.log(`instance: ${JSON.stringify(instance, null, 2)}`)
|
||||
|
||||
app
|
||||
.use(instance.routes())
|
||||
.use(instance.allowedMethods())
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
const User = require('../../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"
|
||||
* }
|
||||
*/
|
||||
async function createUser (ctx) {
|
||||
const user = new User(ctx.request.body.user)
|
||||
try {
|
||||
await user.save()
|
||||
} catch (err) {
|
||||
ctx.throw(422, err.message)
|
||||
}
|
||||
|
||||
const token = user.generateToken()
|
||||
const response = user.toJSON()
|
||||
|
||||
delete response.password
|
||||
|
||||
ctx.body = {
|
||||
user: response,
|
||||
token
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
async function getUsers (ctx) {
|
||||
const users = await User.find({}, '-password')
|
||||
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
|
||||
*/
|
||||
async function getUser (ctx, next) {
|
||||
try {
|
||||
const user = await User.findById(ctx.params.id, '-password')
|
||||
if (!user) {
|
||||
ctx.throw(404)
|
||||
}
|
||||
|
||||
ctx.body = {
|
||||
user
|
||||
}
|
||||
} catch (err) {
|
||||
if (err === 404 || err.name === 'CastError') {
|
||||
ctx.throw(404)
|
||||
}
|
||||
|
||||
ctx.throw(500)
|
||||
}
|
||||
|
||||
if (next) { return 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
|
||||
*/
|
||||
async function updateUser (ctx) {
|
||||
const user = ctx.body.user
|
||||
|
||||
Object.assign(user, ctx.request.body.user)
|
||||
|
||||
await user.save()
|
||||
|
||||
ctx.body = {
|
||||
user
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
*/
|
||||
|
||||
async function deleteUser (ctx) {
|
||||
const user = ctx.body.user
|
||||
|
||||
await user.remove()
|
||||
|
||||
ctx.status = 200
|
||||
ctx.body = {
|
||||
success: true
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
createUser,
|
||||
getUsers,
|
||||
getUser,
|
||||
updateUser,
|
||||
deleteUser
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
const ensureUser = require('../../middleware/validators')
|
||||
const user = require('./controller')
|
||||
|
||||
// export const baseUrl = '/users'
|
||||
module.exports.baseUrl = '/users'
|
||||
|
||||
module.exports.routes = [
|
||||
{
|
||||
method: 'POST',
|
||||
route: '/',
|
||||
handlers: [
|
||||
user.createUser
|
||||
]
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
route: '/',
|
||||
handlers: [
|
||||
ensureUser,
|
||||
user.getUsers
|
||||
]
|
||||
},
|
||||
{
|
||||
method: 'GET',
|
||||
route: '/:id',
|
||||
handlers: [
|
||||
ensureUser,
|
||||
user.getUser
|
||||
]
|
||||
},
|
||||
{
|
||||
method: 'PUT',
|
||||
route: '/:id',
|
||||
handlers: [
|
||||
ensureUser,
|
||||
user.getUser,
|
||||
user.updateUser
|
||||
]
|
||||
},
|
||||
{
|
||||
method: 'DELETE',
|
||||
route: '/:id',
|
||||
handlers: [
|
||||
ensureUser,
|
||||
user.getUser,
|
||||
user.deleteUser
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,16 @@
|
||||
module.exports = function getToken (ctx) {
|
||||
const header = ctx.request.header.authorization
|
||||
if (!header) {
|
||||
return null
|
||||
}
|
||||
const parts = header.split(' ')
|
||||
if (parts.length !== 2) {
|
||||
return null
|
||||
}
|
||||
const scheme = parts[0]
|
||||
const token = parts[1]
|
||||
if (/^Bearer$/i.test(scheme)) {
|
||||
return token
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
The files in this directory are named so as to control the order in which
|
||||
the files are executed by mocha. a01... runs first. The order is important,
|
||||
as some tests downstream depend on tests upstream.
|
||||
@@ -0,0 +1,106 @@
|
||||
const app = require('../bin/server')
|
||||
// const supertest = require('supertest')
|
||||
// const expect = require('chai').expect
|
||||
const should = require('chai').should
|
||||
// const cleanDb = require('./utils').cleanDb
|
||||
// const authUser = require('./utils').authUser
|
||||
const utils = require('./utils')
|
||||
|
||||
const rp = require('request-promise')
|
||||
const assert = require('chai').assert
|
||||
|
||||
should()
|
||||
// const request = supertest.agent(app.listen())
|
||||
const context = {}
|
||||
|
||||
const LOCALHOST = 'http://localhost:5000'
|
||||
|
||||
|
||||
|
||||
describe('Auth', () => {
|
||||
before(async () => {
|
||||
|
||||
await app.startServer()
|
||||
|
||||
utils.cleanDb()
|
||||
|
||||
/*
|
||||
authUser(request, (err, { user, token }) => {
|
||||
if (err) { return done(err) }
|
||||
|
||||
context.user = user
|
||||
context.token = token
|
||||
done()
|
||||
})
|
||||
*/
|
||||
|
||||
const userObj = {
|
||||
username: 'test',
|
||||
password: 'pass'
|
||||
}
|
||||
const testUser = await utils.createUser(userObj)
|
||||
|
||||
context.user = testUser.user
|
||||
context.token = testUser.token
|
||||
})
|
||||
|
||||
describe('POST /auth', () => {
|
||||
it('should throw 401 if credentials are incorrect', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
uri: `${LOCALHOST}/auth`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
body: {
|
||||
username: 'test',
|
||||
password: 'wrongpassword'
|
||||
}
|
||||
}
|
||||
|
||||
let result = await rp(options)
|
||||
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
if (err.statusCode === 422) {
|
||||
assert(err.statusCode === 422, 'Error code 422 expected.')
|
||||
} else if (err.statusCode === 401) {
|
||||
assert(err.statusCode === 401, 'Error code 401 expected.')
|
||||
} else {
|
||||
console.error('Error: ', err)
|
||||
console.log('Error stringified: ' + JSON.stringify(err, null, 2))
|
||||
throw err
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should auth user', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
uri: `${LOCALHOST}/auth`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
body: {
|
||||
username: 'test',
|
||||
password: 'pass'
|
||||
}
|
||||
}
|
||||
|
||||
let result = await rp(options)
|
||||
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
assert(result.statusCode === 200, 'Status Code 200 expected.')
|
||||
assert(result.body.user.username === 'test', 'Username of test expected')
|
||||
assert(result.body.user.password === undefined, 'Password expected to be omited')
|
||||
} catch (err) {
|
||||
console.log('Error authenticating test user: ' + JSON.stringify(err, null, 2))
|
||||
throw err
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,403 @@
|
||||
const expect = require('chai').expect
|
||||
const should = require('chai').should
|
||||
const utils = require('./utils')
|
||||
|
||||
const rp = require('request-promise')
|
||||
const assert = require('chai').assert
|
||||
|
||||
const util = require('util')
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const LOCALHOST = 'http://localhost:5000'
|
||||
|
||||
should()
|
||||
const context = {}
|
||||
|
||||
describe('Users', () => {
|
||||
before(async () => {
|
||||
utils.cleanDb()
|
||||
})
|
||||
|
||||
describe('POST /users', () => {
|
||||
it('should reject signup when data is incomplete', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
uri: `${LOCALHOST}/users`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
body: {
|
||||
username: 'supercoolname'
|
||||
}
|
||||
}
|
||||
|
||||
let result = await rp(options)
|
||||
|
||||
console.log(`result stringified: ${JSON.stringify(result, null, 2)}`)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
if (err.statusCode === 422) {
|
||||
assert(err.statusCode === 422, 'Error code 422 expected.')
|
||||
} else if (err.statusCode === 401) {
|
||||
assert(err.statusCode === 401, 'Error code 401 expected.')
|
||||
} else {
|
||||
console.error('Error: ', err)
|
||||
console.log('Error stringified: ' + JSON.stringify(err, null, 2))
|
||||
throw err
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('should sign up', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
uri: `${LOCALHOST}/users`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
body: {
|
||||
user: { username: 'supercoolname', password: 'supersecretpassword' }
|
||||
}
|
||||
}
|
||||
|
||||
let result = await rp(options)
|
||||
|
||||
result.body.user.should.have.property('username')
|
||||
result.body.user.username.should.equal('supercoolname')
|
||||
expect(result.body.user.password).to.not.exist
|
||||
|
||||
context.user = result.body.user
|
||||
context.token = result.body.token
|
||||
} catch (err) {
|
||||
console.log(
|
||||
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
|
||||
)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /users', () => {
|
||||
it('should not fetch users if the authorization header is missing', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
uri: `${LOCALHOST}/users`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
await rp(options)
|
||||
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.statusCode, 401)
|
||||
}
|
||||
})
|
||||
|
||||
it('should not fetch users if the authorization header is missing the scheme', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
uri: `${LOCALHOST}/users`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: '1'
|
||||
}
|
||||
}
|
||||
|
||||
await rp(options)
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.statusCode, 401)
|
||||
}
|
||||
})
|
||||
|
||||
it('should not fetch users if the authorization header has invalid scheme', async () => {
|
||||
const { token } = context
|
||||
try {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
uri: `${LOCALHOST}/users`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Unknown ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
await rp(options)
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.statusCode, 401)
|
||||
}
|
||||
})
|
||||
|
||||
it('should not fetch users if token is invalid', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
uri: `${LOCALHOST}/users`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer 1`
|
||||
}
|
||||
}
|
||||
|
||||
await rp(options)
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.statusCode, 401)
|
||||
}
|
||||
})
|
||||
|
||||
it('should fetch all users', async () => {
|
||||
const { token } = context
|
||||
|
||||
const options = {
|
||||
method: 'GET',
|
||||
uri: `${LOCALHOST}/users`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
const result = await rp(options)
|
||||
const users = result.body.users
|
||||
// console.log(`users: ${util.inspect(users)}`)
|
||||
|
||||
assert.hasAnyKeys(users[0], ['type', '_id', 'username'])
|
||||
assert.equal(users.length, 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('GET /users/:id', () => {
|
||||
it('should not fetch user if token is invalid', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
uri: `${LOCALHOST}/users/1`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer 1`
|
||||
}
|
||||
}
|
||||
|
||||
await rp(options)
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.statusCode, 401)
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw 404 if user doesn't exist", async () => {
|
||||
const { token } = context
|
||||
|
||||
try {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
uri: `${LOCALHOST}/users/1`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
await rp(options)
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.statusCode, 404)
|
||||
}
|
||||
})
|
||||
|
||||
it('should fetch user', async () => {
|
||||
const {
|
||||
user: { _id },
|
||||
token
|
||||
} = context
|
||||
|
||||
const options = {
|
||||
method: 'GET',
|
||||
uri: `${LOCALHOST}/users/${_id}`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
const result = await rp(options)
|
||||
const user = result.body.user
|
||||
// console.log(`user: ${util.inspect(user)}`)
|
||||
|
||||
assert.hasAnyKeys(user, ['type', '_id', 'username'])
|
||||
assert.equal(user._id, _id)
|
||||
assert.notProperty(
|
||||
user,
|
||||
'password',
|
||||
'Password property should not be returned'
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('PUT /users/:id', () => {
|
||||
it('should not update user if token is invalid', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'PUT',
|
||||
uri: `${LOCALHOST}/users/1`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer 1`
|
||||
}
|
||||
}
|
||||
|
||||
await rp(options)
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.statusCode, 401)
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw 404 if user doesn't exist", async () => {
|
||||
const { token } = context
|
||||
|
||||
try {
|
||||
const options = {
|
||||
method: 'PUT',
|
||||
uri: `${LOCALHOST}/users/1`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
await rp(options)
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.statusCode, 404)
|
||||
}
|
||||
})
|
||||
|
||||
it('should update user', async () => {
|
||||
const {
|
||||
user: { _id },
|
||||
token
|
||||
} = context
|
||||
|
||||
const options = {
|
||||
method: 'PUT',
|
||||
uri: `${LOCALHOST}/users/${_id}`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
},
|
||||
body: {
|
||||
user: { username: 'updatedcoolname' }
|
||||
}
|
||||
}
|
||||
|
||||
const result = await rp(options)
|
||||
const user = result.body.user
|
||||
// console.log(`user: ${util.inspect(user)}`)
|
||||
|
||||
assert.hasAnyKeys(user, ['type', '_id', 'username'])
|
||||
assert.equal(user._id, _id)
|
||||
assert.notProperty(
|
||||
user,
|
||||
'password',
|
||||
'Password property should not be returned'
|
||||
)
|
||||
assert.equal(user.username, 'updatedcoolname')
|
||||
})
|
||||
})
|
||||
|
||||
describe('DELETE /users/:id', () => {
|
||||
it('should not delete user if token is invalid', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'DELETE',
|
||||
uri: `${LOCALHOST}/users/1`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer 1`
|
||||
}
|
||||
}
|
||||
|
||||
await rp(options)
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.statusCode, 401)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw 404 if user doesn\'t exist', async () => {
|
||||
const { token } = context
|
||||
|
||||
try {
|
||||
const options = {
|
||||
method: 'DELETE',
|
||||
uri: `${LOCALHOST}/users/1`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
await rp(options)
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.statusCode, 404)
|
||||
}
|
||||
})
|
||||
|
||||
it('should delete user', async () => {
|
||||
const {
|
||||
user: { _id },
|
||||
token
|
||||
} = context
|
||||
|
||||
const options = {
|
||||
method: 'DELETE',
|
||||
uri: `${LOCALHOST}/users/${_id}`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
|
||||
const result = await rp(options)
|
||||
// console.log(`result: ${util.inspect(result.body)}`)
|
||||
|
||||
assert.equal(result.body.success, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,68 @@
|
||||
const mongoose = require('mongoose')
|
||||
const rp = require('request-promise')
|
||||
|
||||
const LOCALHOST = 'http://localhost:5000'
|
||||
|
||||
// Remove all collections from the DB.
|
||||
function cleanDb () {
|
||||
for (const collection in mongoose.connection.collections) {
|
||||
if (mongoose.connection.collections.hasOwnProperty(collection)) {
|
||||
mongoose.connection.collections[collection].deleteMany()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// This function is used to create new users.
|
||||
// userObj = {
|
||||
// username,
|
||||
// password
|
||||
// }
|
||||
async function createUser (userObj) {
|
||||
try {
|
||||
const options = {
|
||||
method: 'POST',
|
||||
uri: `${LOCALHOST}/users`,
|
||||
resolveWithFullResponse: true,
|
||||
json: true,
|
||||
body: {
|
||||
user: {
|
||||
username: userObj.username,
|
||||
password: userObj.password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let result = await rp(options)
|
||||
|
||||
const retObj = {
|
||||
user: result.body.user,
|
||||
token: result.body.token
|
||||
}
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
console.log('Error in utils.js/createUser(): ' + JSON.stringify(err, null, 2))
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
cleanDb,
|
||||
authUser,
|
||||
createUser
|
||||
}
|
||||
Reference in New Issue
Block a user