Comparing version 0.0.1 to 0.0.2
102
index.js
@@ -1,101 +0,1 @@ | ||
var Mongoose = require('mongoose'); | ||
var Express = require('express'); | ||
var BodyParser = require('body-parser'); | ||
var SELECT = '-_id -__v' | ||
var FIELDS = {}; | ||
SELECT.split(' ').forEach(function(field) { | ||
if (field.indexOf('-') === 0) { | ||
FIELDS[field.substring(1)] = false; | ||
} else { | ||
FIELDS[field] = true; | ||
} | ||
}) | ||
var API = function(options) { | ||
if (typeof options === 'string') options = {databaseURL: options}; | ||
this.router = Express.Router(); | ||
this.router.use(BodyParser.json()); | ||
this.mongoose = Mongoose.createConnection(options.databaseURL); | ||
} | ||
module.exports = API; | ||
var Definition = function(api, label, schema) { | ||
this.api = api; | ||
this.db = api.mongoose.model(label, schema); | ||
} | ||
API.Schema = Mongoose.Schema; | ||
API.prototype.define = function(label, schema) { | ||
// TODO: add schema to swagger | ||
if (Object.keys(this).indexOf(label) !== -1) { | ||
throw new Error("Invalid label " + label + "\nLabel is restricted or already defined") | ||
} | ||
this[label] = new Definition(this, label, schema); | ||
} | ||
var getRouteFunction = function(method, many) { | ||
return function() { | ||
var self = this; | ||
arguments = Array.prototype.slice.call(arguments); | ||
var path = arguments[0]; | ||
var expressPath = path.replace(/{(.*)}/g, ':$1'); | ||
var middleware = arguments.splice(1, arguments.length); | ||
var options = typeof middleware[0] === 'object' ? middleware.shift() : {}; | ||
// TODO: add endpoint to swagger | ||
var dbAction = function(req, res, next) { | ||
var query = req.query; | ||
for (key in req.params) { | ||
query[key] = req.params[key]; | ||
} | ||
if (method === 'get') { | ||
var find = many ? self.db.find : self.db.findOne; | ||
find.apply(self.db, [query, SELECT]).exec(function(err, thing) { | ||
if (err) res.status(500).json({error: err.toString()}) | ||
else if (!thing) res.status(404).json({error: 'Not Found'}) | ||
else res.json(thing); | ||
}) | ||
} else if (method === 'post') { | ||
var docs = many ? req.body : [req.body]; | ||
self.db.collection.insert(docs, function(err, docs) { | ||
if (err) res.status(500).json({error: err.toString()}) | ||
else res.json({success: true}) | ||
}) | ||
} else if (method === 'put') { | ||
if (many) { | ||
self.db.update(query, req.body, {multi: true}).select(SELECT).exec(function(err) { | ||
if (err) res.status(500).json({error: err.toString()}); | ||
else res.json({success: true}); | ||
}) | ||
} else { | ||
self.db.findOneAndUpdate(query, req.body, {new: true}).select(SELECT).exec(function(err, thing) { | ||
if (err) res.status(500).json({error: err.toString()}); | ||
else if (!thing) res.status(404).json({error: 'Not Found'}); | ||
else res.json(thing); | ||
}); | ||
} | ||
} else if (method === 'delete') { | ||
var remove = many ? self.db.remove : self.db.findOneAndRemove; | ||
remove.apply(self.db, [query]).exec(function(err, thing) { | ||
if (err) res.status(500).json({error: err.toString()}); | ||
else if (!thing) res.status(404).json({error: 'Not Found'}); | ||
else res.json({success: true}); | ||
}) | ||
} | ||
} | ||
var firstAction = function(req, res, next) { | ||
next(); | ||
} | ||
middleware.push(dbAction); | ||
middleware.unshift(firstAction); | ||
middleware.unshift(expressPath); | ||
this.api.router[method].apply(this.api.router, middleware); | ||
} | ||
} | ||
var methods = ['get', 'put', 'post', 'delete']; | ||
methods.forEach(function(method) { | ||
Definition.prototype[method] = getRouteFunction(method); | ||
Definition.prototype[method + 'Many'] = getRouteFunction(method, true) | ||
}) | ||
module.exports = require('./lib/api.js'); |
{ | ||
"name": "jammin", | ||
"version": "0.0.1", | ||
"version": "0.0.2", | ||
"description": "REST API Generator using Express and Mongoose", | ||
"main": "index.js", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/lucybot/jammin.git" | ||
}, | ||
"dependencies": { | ||
"async": "^0.9.0", | ||
"body-parser": "^1.12.3", | ||
@@ -12,2 +17,3 @@ "express": "^4.12.3", | ||
"devDependencies": { | ||
"async": "^0.9.0", | ||
"chai": "^2.2.0" | ||
@@ -14,0 +20,0 @@ }, |
116
README.md
## Installation | ||
```npm install jammin``` | ||
**Note: Jammin is still in alpha. Not all features have been implemented.** | ||
**Note: Jammin is still in alpha. The API is not stable.** | ||
*Unimplemented features are tagged with ```TODO```* | ||
## About | ||
@@ -23,5 +21,5 @@ Jammin is the fastest way (that I know of) to build a JSON REST API with Node, Express, and MongoDB. It consists of a light-weight wrapper around [Mongoose](http://mongoosejs.com/) for database operations and an [Express](http://expressjs.com/) router to expose HTTP methods. It is fully extensible via middleware to support things like authentication, resource ownership, and complex queries. | ||
API.define('pet', PetSchema); | ||
API.pet.get('/pets/{name}'); | ||
API.pet.post('/pets'); | ||
API.define('Pet', PetSchema); | ||
API.Pet.get('/pets/{name}'); | ||
API.Pet.post('/pets'); | ||
@@ -59,10 +57,10 @@ App.use('/api', API); | ||
### PUT | ||
### PATCH | ||
Jammin will use ```req.params``` and ```req.query``` to find an item in the database, and use ```req.body``` to **update that item**. | ||
```js | ||
API.pet.put('/pets/{name}'); | ||
API.pet.patch('/pets/{name}'); | ||
``` | ||
Use ```putMany``` to update every matching item in the database. | ||
Use ```patchMany``` to update every matching item in the database. | ||
```js | ||
API.pet.putMany('/pets'); | ||
API.pet.patchMany('/pets'); | ||
``` | ||
@@ -92,3 +90,3 @@ | ||
### Swagger ```TODO``` | ||
### Swagger | ||
Serve a [Swagger specification](http://swagger.io) for your API at the specified path. You can use this to document your API via [Swagger UI](https://github.com/swagger-api/swagger-ui) or a [LucyBot portal](https://lucybot.com) | ||
@@ -113,8 +111,7 @@ ```js | ||
```js | ||
var Hash = require('password-hash'); | ||
var App = require('express')(); | ||
var Jammin = require('jammin'); | ||
var DatabaseURL = 'mongodb://<username>:<password>@<mongodb_host>'; | ||
var Jammin = require('jammin') | ||
var API = new Jammin({ | ||
@@ -129,3 +126,2 @@ databaseURL: DatabaseURL, | ||
// Jammin.Schema is an alias for Mongoose.Schema | ||
var UserSchema = new Jammin.Schema({ | ||
@@ -135,37 +131,10 @@ username: {type: String, required: true, unique: true, match: /^\w+$/}, | ||
}) | ||
var PetSchema = new Jammin.Schema({ | ||
id: {type: Number, required: true, unique: true}, | ||
name: String, | ||
owner: {type: Jammin.Schema.ObjectId, ref: 'User'}, | ||
animalType: String, | ||
imageURLs: [String] | ||
owner: String, | ||
animalType: {type: String, default: 'unknown'} | ||
}) | ||
// define is an alias for Mongoose.model | ||
API.define('pet', PetSchema); | ||
API.define('user', UserSchema); | ||
// Gets a pet by id | ||
API.pet.get('/pets/{id}'); | ||
// Creates a new user | ||
API.user.post('/user', function(req, res, next) { | ||
req.body.password_hash = Hash.generate(req.body.password); | ||
next(); | ||
}); | ||
// Searches pets by name | ||
API.pet.getMany('/search/pets', { | ||
swagger: { | ||
parameters: [{name: 'q', in: 'query', type: 'string'}] | ||
} | ||
}, function(req, res, next) { | ||
var userQuery = Util._extend({}, req.query); | ||
req.query = { | ||
name: { "$regex": new RegExp(userQuery.q) } | ||
}; | ||
next(); | ||
}) | ||
// Middleware for authenticating the request | ||
var authenticateUser = function(req, res, next) { | ||
@@ -189,17 +158,60 @@ var query = { | ||
// Creates a new pet | ||
API.pet.post('/pets', authenticateUser, function(req, res, next) { | ||
req.body.owner = req.user._id; | ||
API.define('pet', PetSchema); | ||
API.define('user', UserSchema); | ||
// Creates a new user. | ||
API.user.post('/user', function(req, res, next) { | ||
req.body.password_hash = Hash.generate(req.body.password); | ||
next(); | ||
}); | ||
// Deletes a pet. | ||
API.pet.delete('/pets/{id}', authenticateUser, function(req, res, next) { | ||
// Gets a pet by id. | ||
API.pet.get('/pets/{id}'); | ||
// Gets an array of pets that match the query. | ||
API.pet.getMany('/pets'); | ||
// Searches pets by name | ||
API.pet.getMany('/search/pets', { | ||
swagger: { | ||
description: "Search all pets by name", | ||
parameters: [ | ||
{name: 'q', in: 'query', type: 'string', description: 'Any regex'} | ||
] | ||
} | ||
}, function(req, res, next) { | ||
req.query = { | ||
id: req.params.id, | ||
// By setting 'owner', we ensure the user can only delete his own pets. | ||
owner: req.user._id | ||
name: { "$regex": new RegExp(req.query.q) } | ||
}; | ||
next(); | ||
}) | ||
// Creates one or more new pets. | ||
API.pet.postMany('/pets', authenticateUser, function(req, res, next) { | ||
if (!Array.isArray(req.body)) req.body = [req.body]; | ||
req.body.forEach(function(pet) { | ||
pet.owner = req.user.username; | ||
}); | ||
next(); | ||
}); | ||
// Setting req.query.owner will ensure that DB calls only return | ||
// documents owned by the user. | ||
var ensureOwnership = function(req, res, next) { | ||
req.query.owner = req.user.username; | ||
next(); | ||
} | ||
// Changes a pet. | ||
API.pet.put('/pets/{id}', authenticateUser, ensureOwnership) | ||
// Changes every pet that matches the query. | ||
API.pet.putMany('/pets', authenticateUser, ensureOwnership) | ||
// Deletes a pet by ID. | ||
API.pet.delete('/pets/{id}', authenticateUser, ensureOwnership); | ||
// Deletes every pet that matches the query. | ||
API.pet.deleteMany('/pets', authenticateUser, ensureOwnership) | ||
App.use('/api', API.router); | ||
@@ -206,0 +218,0 @@ App.listen(3000); |
var FS = require('fs'); | ||
var Hash = require('password-hash'); | ||
var App = require('express')(); | ||
App.use(require('cors')()); | ||
@@ -10,4 +11,4 @@ module.exports.listen = function(port) { | ||
module.exports.dropAllEntries = function(callback) { | ||
API.pet.db.remove({}, function(err) { | ||
API.user.db.remove({}, function(err) { | ||
API.Pet.db.remove({}, function(err) { | ||
API.User.db.remove({}, function(err) { | ||
callback(); | ||
@@ -23,14 +24,25 @@ }) | ||
swagger: { | ||
info: {title: 'Pet Store'}, | ||
info: {title: 'Pet Store', version: '0.1'}, | ||
host: 'api.example.com', | ||
basePath: '/api' | ||
basePath: '/api', | ||
securityDefinitions: { | ||
username: { name: 'username', in: 'header', type: 'string'}, | ||
password: { name: 'password', in: 'header', type: 'string'} | ||
}, | ||
definitions: { | ||
User: { | ||
properties: { | ||
username: {type: 'string'}, | ||
} | ||
}, | ||
} | ||
} | ||
}); | ||
var UserSchema = new Jammin.Schema({ | ||
var UserSchema = { | ||
username: {type: String, required: true, unique: true, match: /^\w+$/}, | ||
password_hash: {type: String, required: true}, | ||
}) | ||
} | ||
var PetSchema = new Jammin.Schema({ | ||
var PetSchema = { | ||
id: {type: Number, required: true, unique: true}, | ||
@@ -40,3 +52,3 @@ name: String, | ||
animalType: {type: String, default: 'unknown'} | ||
}) | ||
} | ||
@@ -47,3 +59,3 @@ var authenticateUser = function(req, res, next) { | ||
}; | ||
API.user.db.findOne(query, function(err, user) { | ||
API.User.db.findOne(query, function(err, user) { | ||
if (err) { | ||
@@ -62,7 +74,26 @@ res.status(500).json({error: err.toString()}) | ||
API.define('pet', PetSchema); | ||
API.define('user', UserSchema); | ||
var SwaggerLogin = { | ||
swagger: { | ||
security: {username: [], password: []}, | ||
} | ||
} | ||
API.define('Pet', PetSchema); | ||
API.define('User', UserSchema); | ||
// Creates a new user. | ||
API.user.post('/user', function(req, res, next) { | ||
API.User.post('/user', { | ||
swagger: { | ||
parameters: [{ | ||
name: 'body', | ||
in: 'body', | ||
schema: { | ||
properties: { | ||
username: {type: 'string'}, | ||
password: {type: 'string'} | ||
} | ||
} | ||
}] | ||
} | ||
}, function(req, res, next) { | ||
req.body.password_hash = Hash.generate(req.body.password); | ||
@@ -73,9 +104,9 @@ next(); | ||
// Gets a pet by id. | ||
API.pet.get('/pets/{id}'); | ||
API.Pet.get('/pets/{id}'); | ||
// Gets an array of pets that match the query. | ||
API.pet.getMany('/pets'); | ||
API.Pet.getMany('/pets'); | ||
// Searches pets by name | ||
API.pet.getMany('/search/pets', { | ||
API.Pet.getMany('/search/pets', { | ||
swagger: { | ||
@@ -94,4 +125,10 @@ description: "Search all pets by name", | ||
API.Pet.post('/pets/{id}', SwaggerLogin, authenticateUser, function(req, res, next) { | ||
req.body.owner = req.user.username; | ||
req.body.id = req.params.id; | ||
next(); | ||
}) | ||
// Creates one or more new pets. | ||
API.pet.postMany('/pets', authenticateUser, function(req, res, next) { | ||
API.Pet.postMany('/pets', SwaggerLogin, authenticateUser, function(req, res, next) { | ||
if (!Array.isArray(req.body)) req.body = [req.body]; | ||
@@ -104,27 +141,23 @@ req.body.forEach(function(pet) { | ||
// Changes a pet. | ||
API.pet.put('/pets/{id}', authenticateUser, function(req, res, next) { | ||
// Setting req.query.owner ensures that only the logged-in user's | ||
// pets will be returned by any queries Jammin makes to the DB. | ||
var enforceOwnership = function(req, res, next) { | ||
req.query.owner = req.user.username; | ||
next(); | ||
}) | ||
} | ||
// Changes a pet. | ||
API.Pet.patch('/pets/{id}', SwaggerLogin, authenticateUser, enforceOwnership); | ||
// Changes every pet that matches the query. | ||
API.pet.putMany('/pets', authenticateUser, function(req, res, next) { | ||
req.query.owner = req.user.username; | ||
next(); | ||
}) | ||
API.Pet.patchMany('/pets', SwaggerLogin, authenticateUser, enforceOwnership); | ||
// Deletes a pet by ID. | ||
API.pet.delete('/pets/{id}', authenticateUser, function(req, res, next) { | ||
req.query.owner = req.user.username; | ||
next(); | ||
}); | ||
API.Pet.delete('/pets/{id}', SwaggerLogin, authenticateUser, enforceOwnership); | ||
// Deletes every pet that matches the query. | ||
API.pet.deleteMany('/pets', authenticateUser, function(req, res, next) { | ||
req.query.owner = req.user.username; | ||
next(); | ||
}) | ||
API.Pet.deleteMany('/pets', SwaggerLogin, authenticateUser, enforceOwnership); | ||
API.swagger('/swagger.json'); | ||
App.use('/api', API.router); | ||
@@ -0,1 +1,2 @@ | ||
var FS = require('fs'); | ||
var Request = require('request'); | ||
@@ -5,2 +6,3 @@ var Expect = require('chai').expect; | ||
var SWAGGER_GOLDEN_FILE = __dirname + '/golden/petstore.swagger.json'; | ||
var BASE_URL = 'http://127.0.0.1:3000/api'; | ||
@@ -20,3 +22,8 @@ | ||
} else { | ||
Expect(body).to.deep.equal(expectedBody); | ||
if (Array.isArray(expectedBody)) { | ||
Expect(body).to.deep.have.members(expectedBody); | ||
Expect(expectedBody).to.deep.have.members(body); | ||
} else { | ||
Expect(body).to.deep.equal(expectedBody); | ||
} | ||
} | ||
@@ -39,9 +46,5 @@ done(); | ||
Petstore.listen(3000); | ||
done(); | ||
Petstore.dropAllEntries(done); | ||
}); | ||
after(function(done) { | ||
Petstore.dropAllEntries(done); | ||
}) | ||
it('should allow new users', function(done) { | ||
@@ -91,3 +94,3 @@ Request.post({ | ||
Request({ | ||
method: 'put', | ||
method: 'patch', | ||
url: BASE_URL + '/pets/42', | ||
@@ -101,3 +104,3 @@ headers: USER_2, | ||
Request({ | ||
method: 'put', | ||
method: 'patch', | ||
url: BASE_URL + '/pets/42', | ||
@@ -204,3 +207,3 @@ headers: USER_1, | ||
Request({ | ||
method: 'put', | ||
method: 'patch', | ||
url: BASE_URL + '/pets', | ||
@@ -243,2 +246,19 @@ headers: USER_1, | ||
}) | ||
it('should serve swagger docs', function(done) { | ||
Request.get({ | ||
url: BASE_URL + '/swagger.json', | ||
json: true | ||
}, function(err, res, body) { | ||
Expect(err).to.equal(null); | ||
if (process.env.WRITE_GOLDEN) { | ||
console.log("Writing new golden file!"); | ||
FS.writeFileSync(SWAGGER_GOLDEN_FILE, JSON.stringify(body, null, 2)); | ||
} else { | ||
var golden = JSON.parse(FS.readFileSync(SWAGGER_GOLDEN_FILE, 'utf8')); | ||
Expect(body).to.deep.equal(golden); | ||
} | ||
done(); | ||
}) | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
36413
10
1057
214
4
2
3
+ Addedasync@^0.9.0
+ Addedasync@0.9.2(transitive)