Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

baucis

Package Overview
Dependencies
Maintainers
1
Versions
202
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

baucis - npm Package Compare versions

Comparing version 0.3.5 to 0.4.0

middleware/configure.js

531

index.js

@@ -8,50 +8,18 @@ // Dependencies

// Private Members
// ---------------
var app = express();
var exec = require('./middleware/exec');
var headers = require('./middleware/headers');
var configure = require('./middleware/configure');
var query = require('./middleware/query');
var send = require('./middleware/send');
var validation = require('./middleware/validation');
function applyOptions(options, callback) {
applyControllerOptions(options, function (error) {
if (error) return next(error);
applyQueryOptions(options, callback);
});
}
// Apply various options based on controller parameters
function applyControllerOptions (options, callback) {
if (options.controller.select) query.select(options.controller.select);
if (options.controller.restrict) {
options.controller.restrict(options.query, options.request);
}
callback();
}
// Apply various options based on request query parameters
function applyQueryOptions (options, callback) {
var populate;
if (options.request.query.skip) options.query.skip(options.request.query.skip);
if (options.request.query.limit) options.query.limit(options.request.query.limit);
if (options.request.query.populate) {
populate = JSON.parse(options.request.query.populate);
if (!Array.isArray(populate)) populate = [ populate ];
populate.forEach(function (field) {
// Don't allow selecting +field from client
if (field.select && field.select.indexOf('+') !== -1) {
callback(new Error('Including fields excluded at schema level (using +) is not permitted'));
return false;
}
options.query.populate(field)
});
}
callback();
}
// Module Definition
// -----------------
var app = express();
var baucis = module.exports = function (options) {
options = options || {};
options || (options = {});
//if (options.prefixUrl) app.set('urlPrefix', options.urlPrefix);
Object.keys(options).forEach(function (key) {
app.set(key, options[key]);
});

@@ -61,435 +29,53 @@ return app;

// Default Settings
// ----------------
//app.set('urlPrefix', '/api');
// Middleware
// ----------
// Functions to return middleware for HTTP verbs
// Retrieve header for the addressed document
function head (options) {
var f = function (request, response, next) {
var id = request.params.id;
var query = mongoose.model(options.singular).findById(id);
var userOptions = {
query: query,
controller: options,
request: request
};
applyOptions(userOptions, function (error) {
if (error) return next(error);
query.count(function (error, count) {
if (error) return next(error);
if (count === 0) return response.send(404);
response.send(200);
});
});
};
return f;
}
// Retrieve the addressed document
function get (options) {
var f = function (request, response, next) {
var id = request.params.id;
var query = mongoose.model(options.singular).findById(id);
var userOptions = {
query: query,
controller: options,
request: request
};
applyOptions(userOptions, function (error) {
if (error) return next(error);
query.exec(function (error, doc) {
if (error) return next(error);
if (!doc) return response.send(404);
response.json(doc);
});
});
};
return f;
}
// Treat the addressed document as a collection, and push
// the addressed object to it
function post (options) {
var f = function (request, response, next) {
response.send(405); // method not allowed (as of yet unimplemented)
};
return f;
}
// Replace the addressed document, or create it if it doesn't exist
function put (options) {
var f = function (request, response, next) {
// Can't send id for update, even if unchanged
delete request.body._id;
var id = request.params.id || null;
var create = (id === null);
var query = mongoose.model(options.singular).findByIdAndUpdate(id, request.body, {upsert: true});
var userOptions = {
query: query,
controller: options,
request: request
};
applyControllerOptions(userOptions, function (error) {
if (error) return next(error);
query.exec(function (error, doc) {
if (error) return next(error);
if (create) response.status(201);
else response.status(200);
response.set('Location', path.join(options.basePath, doc.id));
response.json(doc);
});
});
};
return f;
}
// Delete the addressed object
function del (options) {
var f = function (request, response, next) {
var id = request.params.id;
var query = mongoose.model(options.singular).remove({ _id: id });
var userOptions = {
query: query,
controller: options,
request: request
};
applyControllerOptions(userOptions, function (error) {
if (error) return next(error);
query.exec(function (error, count) {
if (error) return next(error);
response.json(count);
});
});
};
return f;
}
// Retrieve documents matching conditions
function headCollection (options) {
var f = function (request, response, next) {
var conditions;
var query;
var userOptions = {
query: query,
controller: options,
request: request
};
if (request.query && request.query.conditions) {
conditions = JSON.parse(request.query.conditions);
}
query = mongoose.model(options.singular).find(conditions);
applyOptions(userOptions, function (error) {
if (error) return next(error);
query.count(function (error, count) {
if (error) return next(error);
response.send(200);
});
});
};
return f;
}
// retrieve documents matching conditions
function getCollection (options) {
var f = function (request, response, next) {
var firstWasProcessed = false;
var conditions;
if (request.query && request.query.conditions) {
conditions = JSON.parse(request.query.conditions);
}
var query = mongoose.model(options.singular).find(conditions);
var userOptions = {
query: query,
controller: options,
request: request
};
applyOptions(userOptions, function (error) {
if (error) return next(error);
// If `count` is set, return the number of documents the query matches
if (request.query.count === true) {
query.count(function (error, count) {
if (error) return next(error);
response.json(count);
});
return;
}
// Otherwise, stream the array to the client
response.set('Content-Type', 'application/json');
response.write('[');
query.stream()
.on('data', function (doc) {
if (firstWasProcessed) response.write(', ');
response.write(JSON.stringify(doc.toJSON()));
firstWasProcessed = true;
})
.on('error', next)
.on('close', function () {
response.write(']');
response.send();
});
});
};
return f;
}
// Create a new document and return its ID
function postCollection (options) {
var f = function (request, response, next) {
var body = request.body;
// Must be object or array
if (!body || typeof body !== 'object') {
return next(new Error('Must supply a document or array to POST'));
}
response.status(201);
// If just one object short circuit
if (!Array.isArray(body)) {
return mongoose.model(options.singular).create(body, function (error, doc) {
if (error) return next(error);
response.set('Location', path.join(options.basePath, doc.id));
response.json(doc);
});
}
// No empty arrays
if (body.length === 0) return next(new Error('Array was empty.'));
// Create and save given documents
var promises = body.map(mongoose.model(options.singular).create);
var ids = [];
var processedCount = 0;
var location;
// Stream the response JSON array
response.set('Content-Type', 'application/json');
response.write('[');
promises.forEach(function (promise) {
promise.then(function (doc) {
response.write(JSON.stringify(doc.toJSON()));
ids.push(doc.id);
// Still more to process?
if (processedCount < body.length) return response.write(', ');
// Last one was processed
response.write(']');
location = options.basePath + '?conditions={ _id: { $in: [' + ids.join() + '] } }';
response.set('Location', location);
response.send();
});
promise.error(function (error) {
next(error);
});
});
};
return f;
}
// Replace all docs with given docs ...
function putCollection (options) {
var f = function (request, response, next) {
response.send(405); // method not allowed (as of yet unimplemented)
};
return f;
}
// Delete all documents matching conditions
function delCollection (options) {
var f = function (request, response, next) {
var conditions = request.body || {};
var query = mongoose.model(options.singular).remove(conditions);
var userOptions = {
query: query,
controller: options,
request: request
};
applyOptions(userOptions, function (error) {
if (error) return next(error);
query.exec(function (error, count) {
if (error) return next(error);
response.json(count);
});
});
};
return f;
}
// Add "Link" header field, with some basic defaults
function addLinkRelations (options) {
var f = function (request, response, next) {
response.links({
collection: options.basePath,
search: options.basePath,
edit: path.join(options.basePath, request.params.id),
self: path.join(options.basePath, request.params.id),
'latest-version': path.join(options.basePath, request.params.id)
});
next();
};
return f;
}
// Add "Link" header field, with some basic defaults (for collection routes)
function addLinkRelationsCollection (options) {
var f = function (request, response, next) {
response.links({
search: options.basePath,
self: options.basePath,
'latest-version': options.basePath
});
next();
};
return f;
}
// Build the "Allow" response header
function addAllowResponseHeader (options) {
var f = function (request, response, next) {
var allowed = [];
if (options.head !== false) allowed.push('HEAD');
if (options.get !== false) allowed.push('GET');
if (options.post !== false) allowed.push('POST');
if (options.put !== false) allowed.push('PUT');
if (options.del !== false) allowed.push('DELETE');
response.set('Allow', allowed.join());
next();
};
return f;
}
// Build the "Accept" response header
function addAcceptResponseHeader (options) {
var f = function (request, response, next) {
response.set('Accept', 'application/json');
next();
};
return f;
}
// Validation
// ----------
// var validation = function (options) {
// var validators = {};
// var f = function (request, response, next) {
// response.json(validators);
// };
// Object.keys(s.paths).forEach(function (path) {
// var pathValidators = [];
// if (path.enumValues.length > 0) {
// // TODO
// pathValidators.push( );
// }
// if (path.regExp !== null) {
// // TODO
// pathValidators.push( );
// }
// // test path.instance TODO or path.options.type
// // TODO use any path.validators?
// // TODO other path.options?
// validators[path.path] = pathValidators;
// });
// return f;
// };
// Public Methods
// --------------
baucis.rest = function (options) {
options || (options = {}); // TODO clone, defaults
if (!options.singular) throw new Error('Must provide the Mongoose schema name');
if (!options.plural) options.plural = lingo.en.pluralize(options.singular);
if (!options.basePath) options.basePath = '/';
var basePath = options.basePath = path.join('/', options.basePath);
var basePathWithId = options.basePathWithId = path.join(basePath, ':id');
var basePathWithOptionalId = options.basePathWithOptionalId = path.join(basePath, ':id?');
var controller = express();
var basePath = path.join('/', options.basePath || '/');
var basePathWithId = path.join(basePath, ':id');
var basePathWithOptionalId = path.join(basePath, ':id?');
controller.set('model', mongoose.model(options.singular));
controller.set('plural', options.plural || lingo.en.pluralize(options.singular));
controller.set('basePath', basePath);
controller.set('basePathWithId', basePathWithId);
controller.set('basePathWithOptionalId', basePathWithOptionalId);
Object.keys(options).forEach(function (key) {
controller.set(key, options[key]);
});
controller.use(express.json());
controller.all(basePathWithId, addAllowResponseHeader(options));
controller.all(basePathWithId, addAcceptResponseHeader(options));
controller.all(basePath, addAllowResponseHeader(options));
controller.all(basePath, addAcceptResponseHeader(options));
// Initialize baucis state
controller.all(basePathWithOptionalId, function (request, response, next) {
request.baucis = {};
next();
});
// Allow/Accept headers
controller.all(basePathWithId, headers.allow);
controller.all(basePathWithId, headers.accept);
controller.all(basePath, headers.allow);
controller.all(basePath, headers.accept);
if (options.configure) options.configure(controller);
// Set Link header if desired
if (options.relations === true) {
controller.head(basePathWithId, addLinkRelations(options));
controller.get(basePathWithId, addLinkRelations(options));
controller.post(basePathWithId, addLinkRelations(options));
controller.put(basePathWithId, addLinkRelations(options));
controller.head(basePathWithId, headers.link);
controller.get(basePathWithId, headers.link);
controller.post(basePathWithId, headers.link);
controller.put(basePathWithId, headers.link);
controller.head(basePath, addLinkRelationsCollection(options));
controller.get(basePath, addLinkRelationsCollection(options));
controller.post(basePath, addLinkRelationsCollection(options));
controller.put(basePath, addLinkRelationsCollection(options));
controller.head(basePath, headers.linkCollection);
controller.get(basePath, headers.linkCollection);
controller.post(basePath, headers.linkCollection);
controller.put(basePath, headers.linkCollection);
}
// Add all pre-query middleware
if (options.all) controller.all(basePathWithOptionalId, options.all);

@@ -502,17 +88,20 @@ if (options.head) controller.head(basePathWithOptionalId, options.head);

if (options.head !== false) controller.head(basePathWithId, head(options));
if (options.get !== false) controller.get(basePathWithId, get(options));
if (options.post !== false) controller.post(basePathWithId, post(options));
if (options.put !== false) controller.put(basePathWithId, put(options));
if (options.del !== false) controller.del(basePathWithId, del(options));
// Add routes for singular documents
if (options.head !== false) controller.head(basePathWithId, query.head, configure.controller, configure.query, exec.count, send.count);
if (options.get !== false) controller.get(basePathWithId, query.get, configure.controller, configure.query, exec.exec, send.exec);
if (options.post !== false) controller.post(basePathWithId, query.post, configure.controller, configure.query, exec.exec, send.exec);
if (options.put !== false) controller.put(basePathWithId, query.put, configure.controller, configure.query, exec.exec, send.exec);
if (options.del !== false) controller.del(basePathWithId, query.del, configure.controller, configure.query, exec.exec, send.exec);
if (options.head !== false) controller.head(basePath, headCollection(options));
if (options.get !== false) controller.get(basePath, getCollection(options));
if (options.post !== false) controller.post(basePath, postCollection(options));
if (options.put !== false) controller.put(basePath, putCollection(options));
if (options.del !== false) controller.del(basePath, delCollection(options));
// Add routes for collections of documents
if (options.head !== false) controller.head(basePath, configure.conditions, query.headCollection, configure.controller, configure.query, exec.count, send.count);
if (options.get !== false) controller.get(basePath, configure.conditions, query.getCollection, configure.controller, configure.query, exec.stream, send.stream);
if (options.post !== false) controller.post(basePath, query.postCollection /*, exec.promises, send.promises */);
if (options.put !== false) controller.put(basePath, query.putCollection, configure.controller, configure.query, exec.exec, send.exec);
if (options.del !== false) controller.del(basePath, configure.conditions, query.delCollection, configure.controller, configure.query, exec.exec, send.exec);
if (options.publish !== false) app.use(path.join('/', options.plural), controller);
// Publish unless told not to
if (options.publish !== false) app.use(path.join('/', controller.get('plural')), controller);
return controller;
};
{
"name": "baucis",
"version": "0.3.5",
"version": "0.4.0",
"main": "index.js",
"scripts": {
"test": "mocha --globals vegetables"
"test": "mocha --bail --globals vegetables"
},

@@ -8,0 +8,0 @@ "repository": {

@@ -1,2 +0,2 @@

baucis v0.3.5
baucis v0.4.0
=============

@@ -3,0 +3,0 @@

@@ -19,3 +19,3 @@ var expect = require('expect.js');

request.get(options, function (error, response, body) {
request.del(options, function (error, response, body) {
if (error) return done(error);

@@ -29,22 +29,10 @@

expect(response).to.have.property('statusCode', 200);
expect(body).to.have.property('name', 'Shitake');
expect(body).to.be(1); // count of deleted objects
request.del(options, function (error, response, body) {
if (error) return done(error);
request.del(options, function (error, response, body) {
if (error) return done(error);
var options = {
url: 'http://localhost:8012/api/v1/vegetables/' + shitake._id,
json: true
};
expect(response).to.have.property('statusCode', 200);
expect(body).to.be(1); // count of deleted objects
request.get(options, function (error, response, body) {
if (error) return done(error);
expect(response).to.have.property('statusCode', 404);
done();
});
});
expect(response).to.have.property('statusCode', 404);
done();
});
});

@@ -51,0 +39,0 @@

@@ -14,14 +14,12 @@ var expect = require('expect.js');

url: 'http://localhost:8012/api/v1/vegetables/',
json: {
name: 'Tomato'
}
json: { name: 'Tomato' }
};
request.post(options, function (error, response, body) {
if (error) return done(error);
var id = body._id;
expect(response.statusCode).to.equal(201);
expect(id).not.to.be.empty(); // TODO check it's an ObjectID
expect(body._id).not.to.be.empty(); // TODO check it's an ObjectID
var options = {
url: 'http://localhost:8012/api/v1/vegetables/' + id,
url: 'http://localhost:8012/api/v1/vegetables/' + body._id,
json: true

@@ -28,0 +26,0 @@ };

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc