mirror of
https://github.com/typicode/json-server.git
synced 2025-07-30 21:54:11 +08:00
Use restify
This commit is contained in:
82
bin/cli.js
82
bin/cli.js
@ -1,82 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
var program = require('commander'),
|
|
||||||
request = require('superagent'),
|
|
||||||
server = require('../server'),
|
|
||||||
logger = require('../utils/logger'),
|
|
||||||
moment = require('moment'),
|
|
||||||
fs = require('fs'),
|
|
||||||
pkg = require('../package.json'),
|
|
||||||
options = {};
|
|
||||||
|
|
||||||
function loadFile(file, cb) {
|
|
||||||
var path = process.cwd() + '/' + file,
|
|
||||||
db;
|
|
||||||
|
|
||||||
if (/\.json$/.test(file)) db = require(path);
|
|
||||||
if (/\.js$/.test(file)) db = require(path).run();
|
|
||||||
|
|
||||||
cb(db);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadURL(url, cb) {
|
|
||||||
logger.info('Fetching ' + url + '...')
|
|
||||||
request
|
|
||||||
.get(url)
|
|
||||||
.end(function(error, res) {
|
|
||||||
if (error) {
|
|
||||||
logger.error(error);
|
|
||||||
} else {
|
|
||||||
cb(JSON.parse(res.text));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveDbOnCommand(app) {
|
|
||||||
console.assert(app, 'expected app object');
|
|
||||||
|
|
||||||
process.stdin.resume();
|
|
||||||
process.stdin.setEncoding('utf8');
|
|
||||||
logger.notice('To save live database at any moment, enter `s`');
|
|
||||||
|
|
||||||
process.stdin.on('data', function (userInput) {
|
|
||||||
if (userInput.trim().toLowerCase() == 's') {
|
|
||||||
var liveDB = app.db();
|
|
||||||
var now = moment().format('YYYY-MM-DD-HH:mm:ss')
|
|
||||||
var filename = 'json-server.' + now + '.json';
|
|
||||||
console.assert(liveDB, 'expected live db object');
|
|
||||||
fs.writeFileSync(filename,
|
|
||||||
JSON.stringify(liveDB, null, 2),
|
|
||||||
'utf-8');
|
|
||||||
console.log('saved db to', filename);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onDatabaseLoaded(db) {
|
|
||||||
var app = server.run(db, options);
|
|
||||||
saveDbOnCommand(app);
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
|
|
||||||
program
|
|
||||||
.version(pkg.version)
|
|
||||||
.option('-f --file <file>', 'load db from a js or json file')
|
|
||||||
.option('-u --url <url>', 'load db from a URL')
|
|
||||||
.option('-p --port [port]', 'server port')
|
|
||||||
.option('--read-only', 'read only mode');
|
|
||||||
|
|
||||||
program.on('--help', function () {
|
|
||||||
var examples =
|
|
||||||
' Examples:\n\n' +
|
|
||||||
' json-server --file db.json\n' +
|
|
||||||
' json-server --file seed.js\n' +
|
|
||||||
' json-server --url http://example.com/db.json\n'
|
|
||||||
console.log(examples);
|
|
||||||
});
|
|
||||||
|
|
||||||
program.parse(process.argv);
|
|
||||||
|
|
||||||
if (program.port) options.port = program.port;
|
|
||||||
if (program.readOnly) options.readOnly = true;
|
|
||||||
if (program.file) loadFile(program.file, onDatabaseLoaded);
|
|
||||||
if (program.url) loadURL(program.url, onDatabaseLoaded);
|
|
@ -15,7 +15,9 @@
|
|||||||
"superagent": "~0.15.7",
|
"superagent": "~0.15.7",
|
||||||
"underscore": "~1.5.2",
|
"underscore": "~1.5.2",
|
||||||
"underscore.inflections": "~0.2.1",
|
"underscore.inflections": "~0.2.1",
|
||||||
"moment": "~2.4.0"
|
"moment": "~2.4.0",
|
||||||
|
"low": "^0.4.2",
|
||||||
|
"restify": "^2.6.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"supertest": "~0.8.1",
|
"supertest": "~0.8.1",
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
var _ = require('underscore'),
|
|
||||||
utils = require('../utils/utils'),
|
|
||||||
db = {};
|
|
||||||
|
|
||||||
_.mixin(require('../utils/db-mixins'));
|
|
||||||
|
|
||||||
exports.setDatabase = function(object) {
|
|
||||||
db = object;
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.database = function(req, res) {
|
|
||||||
res.jsonp(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /:resource?attr=&attr=
|
|
||||||
exports.list = function(req, res) {
|
|
||||||
var collection = db[req.params.resource],
|
|
||||||
properties = {},
|
|
||||||
result;
|
|
||||||
|
|
||||||
Object.keys(req.query).forEach(function (key) {
|
|
||||||
var value = req.query[key];
|
|
||||||
properties[key] = utils.toNative(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_(properties).isEmpty()) {
|
|
||||||
result = collection;
|
|
||||||
} else {
|
|
||||||
result = _(collection).where(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.jsonp(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /:parent/:parentId/:resource
|
|
||||||
exports.nestedList = function(req, res) {
|
|
||||||
var properties = {},
|
|
||||||
resource;
|
|
||||||
|
|
||||||
// Set parentID
|
|
||||||
properties[req.params.parent.slice(0, - 1) + 'Id'] = +req.params.parentId;
|
|
||||||
|
|
||||||
// Filter using parentID
|
|
||||||
resource = _.where(db[req.params.resource], properties);
|
|
||||||
|
|
||||||
res.jsonp(resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /:resource/:id
|
|
||||||
exports.show = function(req, res) {
|
|
||||||
var resource = _.get(db, req.params.resource, +req.params.id);
|
|
||||||
|
|
||||||
res.jsonp(resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.create = function(req, res) {
|
|
||||||
req.body.id = Math.round(new Date().getTime() / 1000);
|
|
||||||
res.jsonp(req.body);
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.update = function(req, res) {
|
|
||||||
var resource = _.get(db, req.params.resource, +req.params.id),
|
|
||||||
clonedResource = _.clone(resource),
|
|
||||||
result = _.extend(clonedResource, req.body);
|
|
||||||
|
|
||||||
res.jsonp(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.destroy = function(req, res) {
|
|
||||||
res.send(204)
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
var _ = require('underscore'),
|
|
||||||
utils = require('../utils/utils'),
|
|
||||||
db = {};
|
|
||||||
|
|
||||||
_.mixin(require('../utils/db-mixins'));
|
|
||||||
|
|
||||||
exports.setDatabase = function(object) {
|
|
||||||
db = object;
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.database = function(req, res) {
|
|
||||||
res.jsonp(db)
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.db = function() {
|
|
||||||
return db;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /:resource?attr=&attr=
|
|
||||||
exports.list = function(req, res) {
|
|
||||||
var collection = db[req.params.resource],
|
|
||||||
properties = {},
|
|
||||||
result;
|
|
||||||
|
|
||||||
Object.keys(req.query).forEach(function (key) {
|
|
||||||
var value = req.query[key];
|
|
||||||
properties[key] = utils.toNative(value);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_(properties).isEmpty()) {
|
|
||||||
result = collection;
|
|
||||||
} else {
|
|
||||||
result = _(collection).where(properties);
|
|
||||||
}
|
|
||||||
|
|
||||||
res.jsonp(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /:parent/:parentId/:resource
|
|
||||||
exports.nestedList = function(req, res) {
|
|
||||||
var properties = {},
|
|
||||||
resource;
|
|
||||||
|
|
||||||
// Set parentID
|
|
||||||
properties[req.params.parent.slice(0, - 1) + 'Id'] = +req.params.parentId;
|
|
||||||
|
|
||||||
// Filter using parentID
|
|
||||||
resource = _.where(db[req.params.resource], properties);
|
|
||||||
|
|
||||||
res.jsonp(resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
// GET /:resource/:id
|
|
||||||
exports.show = function(req, res) {
|
|
||||||
var resource = _.get(db, req.params.resource, +req.params.id);
|
|
||||||
|
|
||||||
res.jsonp(resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST /:resource
|
|
||||||
exports.create = function(req, res) {
|
|
||||||
var resource = _.create(db, req.params.resource, req.body);
|
|
||||||
|
|
||||||
res.jsonp(resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUT /:resource/:id
|
|
||||||
// PATCH /:resource/:id
|
|
||||||
exports.update = function(req, res) {
|
|
||||||
_.update(db, req.params.resource, +req.params.id, req.body);
|
|
||||||
var resource = _.get(db, req.params.resource, +req.params.id);
|
|
||||||
|
|
||||||
res.jsonp(resource);
|
|
||||||
}
|
|
||||||
|
|
||||||
// DELETE /:resource/:id
|
|
||||||
exports.destroy = function(req, res) {
|
|
||||||
_.remove(db, req.params.resource, +req.params.id);
|
|
||||||
_.clean(db);
|
|
||||||
|
|
||||||
res.send(204);
|
|
||||||
}
|
|
||||||
|
|
96
server.js
96
server.js
@ -1,96 +0,0 @@
|
|||||||
var express = require('express'),
|
|
||||||
cors = require('cors'),
|
|
||||||
http = require('http'),
|
|
||||||
path = require('path'),
|
|
||||||
fs = require('fs'),
|
|
||||||
_ = require('underscore'),
|
|
||||||
logger = require('./utils/logger');
|
|
||||||
|
|
||||||
|
|
||||||
var defaultOptions = {
|
|
||||||
port: process.env.PORT || 3000,
|
|
||||||
readOnly: false
|
|
||||||
}
|
|
||||||
|
|
||||||
function createApp(db, options) {
|
|
||||||
// Create app
|
|
||||||
var app = express(),
|
|
||||||
options = options || {},
|
|
||||||
routes;
|
|
||||||
|
|
||||||
// Configure all environments
|
|
||||||
app.use(express.favicon());
|
|
||||||
app.use(express.logger('dev'));
|
|
||||||
app.use(express.json());
|
|
||||||
app.use(express.urlencoded());
|
|
||||||
app.use(express.methodOverride());
|
|
||||||
|
|
||||||
|
|
||||||
// Configure development
|
|
||||||
if ('development' == app.get('env')) {
|
|
||||||
app.use(express.errorHandler());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure using options provided
|
|
||||||
app.set('port', options.port);
|
|
||||||
routes = options.readOnly ? './routes/read-only' : './routes/read-write';
|
|
||||||
routes = require(routes);
|
|
||||||
|
|
||||||
// Use default or user public directory
|
|
||||||
// Note: should be done before CORS and app.router setting
|
|
||||||
if (fs.existsSync(process.cwd() + '/public')) {
|
|
||||||
app.use(express.static(process.cwd() + '/public'));
|
|
||||||
} else {
|
|
||||||
app.use(express.static(path.join(__dirname, './public')));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable CORS for everything
|
|
||||||
app.use(cors());
|
|
||||||
app.options('*', cors());
|
|
||||||
|
|
||||||
// Set app.router
|
|
||||||
app.use(app.router);
|
|
||||||
|
|
||||||
// Set API entry points
|
|
||||||
app.get('/db', routes.database)
|
|
||||||
app.get('/:resource', routes.list);
|
|
||||||
app.get('/:parent/:parentId/:resource', routes.nestedList);
|
|
||||||
app.get('/:resource/:id', routes.show);
|
|
||||||
app.post('/:resource', routes.create);
|
|
||||||
app.put('/:resource/:id', routes.update);
|
|
||||||
app.patch('/:resource/:id', routes.update);
|
|
||||||
app.del('/:resource/:id', routes.destroy);
|
|
||||||
|
|
||||||
// Set database
|
|
||||||
routes.setDatabase(db);
|
|
||||||
app.db = routes.db;
|
|
||||||
|
|
||||||
// And done! Ready to serve JSON!
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
|
|
||||||
function run(db, options) {
|
|
||||||
options = _.defaults(options || {}, defaultOptions);
|
|
||||||
|
|
||||||
var app = createApp(db, options);
|
|
||||||
|
|
||||||
if (_.isEmpty(db)) {
|
|
||||||
logger.error('No resources found!');
|
|
||||||
} else {
|
|
||||||
logger.success('Available resources');
|
|
||||||
for (var prop in db) {
|
|
||||||
logger.url(options.port, prop);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
http
|
|
||||||
.createServer(app)
|
|
||||||
.listen((options.port), function(){
|
|
||||||
logger.success('Express server listening on port ' + options.port);
|
|
||||||
logger.success('Congrats! Open http://localhost:' + options.port);
|
|
||||||
});
|
|
||||||
return app;
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.createApp = createApp;
|
|
||||||
exports.run = run;
|
|
127
src/server.js
Normal file
127
src/server.js
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
var _ = require('underscore')
|
||||||
|
var low = require('low')
|
||||||
|
var restify = require('restify')
|
||||||
|
var utils = require('./utils')
|
||||||
|
|
||||||
|
low._.createId = utils.createId
|
||||||
|
|
||||||
|
var server = restify.createServer()
|
||||||
|
|
||||||
|
server.use(restify.acceptParser(server.acceptable))
|
||||||
|
server.use(restify.queryParser())
|
||||||
|
server.use(restify.bodyParser())
|
||||||
|
server.use(restify.CORS())
|
||||||
|
server.use(restify.jsonp())
|
||||||
|
server.use(restify.gzipResponse())
|
||||||
|
|
||||||
|
routes = {}
|
||||||
|
|
||||||
|
// GET /db
|
||||||
|
routes.db = function(req, res, next) {
|
||||||
|
res.send(low.db)
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /:resource?attr=&attr=
|
||||||
|
routes.list = function(req, res, next) {
|
||||||
|
var properties = {}
|
||||||
|
var query
|
||||||
|
|
||||||
|
Object.keys(req.query).forEach(function (key) {
|
||||||
|
var value = req.query[key]
|
||||||
|
properties[key] = utils.toNative(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
if (_(properties).isEmpty()) {
|
||||||
|
query = low(req.params.resource)
|
||||||
|
} else {
|
||||||
|
query = low(req.params.resource).where(properties)
|
||||||
|
}
|
||||||
|
|
||||||
|
res.send(query.value())
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /:parent/:parentId/:resource
|
||||||
|
routes.nestedList = function(req, res, next) {
|
||||||
|
var properties = {}
|
||||||
|
var resource
|
||||||
|
|
||||||
|
// Set parentID
|
||||||
|
properties[req.params.parent.slice(0, - 1) + 'Id'] = +req.params.parentId
|
||||||
|
|
||||||
|
// Filter using parentID
|
||||||
|
resource = low(req.params.resource)
|
||||||
|
.where(properties)
|
||||||
|
.value()
|
||||||
|
|
||||||
|
res.send(resource)
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GET /:resource/:id
|
||||||
|
routes.show = function(req, res, next) {
|
||||||
|
var resource = low(req.params.resource)
|
||||||
|
.get(+req.params.id)
|
||||||
|
.value()
|
||||||
|
|
||||||
|
res.send(resource)
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// POST /:resource
|
||||||
|
routes.create = function(req, res, next) {
|
||||||
|
var resource = low(req.params.resource)
|
||||||
|
.insert(req.body)
|
||||||
|
.value()
|
||||||
|
|
||||||
|
res.send(resource)
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PUT /:resource/:id
|
||||||
|
// PATCH /:resource/:id
|
||||||
|
routes.update = function(req, res, next) {
|
||||||
|
var resource = low(req.params.resource)
|
||||||
|
.update(+req.params.id, req.body)
|
||||||
|
.value()
|
||||||
|
|
||||||
|
res.send(resource)
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE /:resource/:id
|
||||||
|
routes.destroy = function(req, res, next) {
|
||||||
|
try {
|
||||||
|
low(req.params.resource).remove(+req.params.id)
|
||||||
|
utils.clean()
|
||||||
|
|
||||||
|
res.send(204)
|
||||||
|
next()
|
||||||
|
} catch(e) {
|
||||||
|
console.trace(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
server.get('/db', routes.db)
|
||||||
|
server.get('/:resource', routes.list)
|
||||||
|
server.get('/:parent/:parentId/:resource', routes.nestedList)
|
||||||
|
server.get('/:resource/:id', routes.show)
|
||||||
|
server.post('/:resource', routes.create)
|
||||||
|
server.put('/:resource/:id', routes.update)
|
||||||
|
server.patch('/:resource/:id', routes.update)
|
||||||
|
server.del('/:resource/:id', routes.destroy)
|
||||||
|
|
||||||
|
server.get('/', restify.serveStatic({
|
||||||
|
directory: './public',
|
||||||
|
default: 'index.html'
|
||||||
|
}));
|
||||||
|
|
||||||
|
server.on('after', function (req, res, route, err) {
|
||||||
|
var latency = Date.now() - req.time()
|
||||||
|
console.log('%s %s %s - %sms',
|
||||||
|
req.method, req.url, res.statusCode, latency
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = server
|
60
src/utils.js
Normal file
60
src/utils.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
var low = require('low')
|
||||||
|
var _ = require('underscore')
|
||||||
|
_.mixin(require('underscore.inflections'))
|
||||||
|
|
||||||
|
|
||||||
|
// Turns string to native.
|
||||||
|
// Example:
|
||||||
|
// 'true' -> true
|
||||||
|
// '1' -> 1
|
||||||
|
function toNative(value) {
|
||||||
|
if (value === 'true' || value === 'false') {
|
||||||
|
return value === 'true'
|
||||||
|
} else if (!isNaN(+value)) {
|
||||||
|
return +value
|
||||||
|
} else {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates incremental id.
|
||||||
|
function createId(coll) {
|
||||||
|
if (_.isEmpty(coll)) {
|
||||||
|
return 1
|
||||||
|
} else {
|
||||||
|
return _.max(coll, function(doc) {
|
||||||
|
return doc.id
|
||||||
|
}).id + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Removes empty relations
|
||||||
|
function clean() {
|
||||||
|
var toBeRemoved = []
|
||||||
|
|
||||||
|
_(low.db).each(function(coll, collName) {
|
||||||
|
_(coll).each(function(doc) {
|
||||||
|
_(doc).each(function(value, key) {
|
||||||
|
if (/Id$/.test(key)) {
|
||||||
|
var reference = _.pluralize(key.slice(0, - 2))
|
||||||
|
if (!_.isUndefined(low(reference).get(doc[key]).value())) {
|
||||||
|
toBeRemoved.push({
|
||||||
|
collName: collName,
|
||||||
|
id: doc.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
_(toBeRemoved).each(function(item) {
|
||||||
|
low(item.collName).remove(item.id);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
toNative: toNative,
|
||||||
|
createId: createId,
|
||||||
|
clean: clean
|
||||||
|
}
|
@ -1,138 +0,0 @@
|
|||||||
var request = require('supertest'),
|
|
||||||
assert = require('assert'),
|
|
||||||
server = require('../server'),
|
|
||||||
fixture = require('./fixture'),
|
|
||||||
db,
|
|
||||||
app;
|
|
||||||
|
|
||||||
describe('Read only routes', function() {
|
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
db = fixture();
|
|
||||||
app = server.createApp(db, { readOnly: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /:resource', function() {
|
|
||||||
it('should respond with json and resources and corresponding resources', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/posts')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(db.posts)
|
|
||||||
.expect(200, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /:resource?attr=&attr=', function() {
|
|
||||||
it('should respond with json and filter resources', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/comments?postId=1&published=true')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect([db.comments[0]])
|
|
||||||
.expect(200, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /:parent/:parentId/:resource', function() {
|
|
||||||
it('should respond with json and corresponding nested resources', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/posts/1/comments')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect([db.comments[0], db.comments[1]])
|
|
||||||
.expect(200, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
describe('GET /:resource/:id', function() {
|
|
||||||
it('should respond with json and corresponding resource', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/posts/1')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(db.posts[0])
|
|
||||||
.expect(200, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /db', function() {
|
|
||||||
it('should respond with json and full database', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/db')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(db)
|
|
||||||
.expect(200, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('POST /:resource', function() {
|
|
||||||
it('should respond with fake json and not create a resource', function(done) {
|
|
||||||
request(app)
|
|
||||||
.post('/posts')
|
|
||||||
.send({body: '...'})
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err, res){
|
|
||||||
if (err) return done(err);
|
|
||||||
assert(res.body.hasOwnProperty('id'));
|
|
||||||
assert.equal(res.body.body, '...');
|
|
||||||
assert.equal(db.posts.length, 2);
|
|
||||||
done()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PUT /:resource/:id', function() {
|
|
||||||
it('should respond with fake json and not update resource', function(done) {
|
|
||||||
request(app)
|
|
||||||
.put('/posts/1')
|
|
||||||
.send({id: 999, body: '...'})
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect({id: 999, body: '...'})
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err, res){
|
|
||||||
if (err) return done(err);
|
|
||||||
// Checking that first post wasn't updated
|
|
||||||
assert.deepEqual(db.posts[0], {id: 1, body: 'foo'});
|
|
||||||
done()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('PATCH /:resource/:id', function() {
|
|
||||||
it('should respond with fake json and not update resource', function(done) {
|
|
||||||
request(app)
|
|
||||||
.patch('/posts/1')
|
|
||||||
.send({body: '...'})
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect({id: 1, body: '...'})
|
|
||||||
.expect(200)
|
|
||||||
.end(function(err, res){
|
|
||||||
if (err) return done(err);
|
|
||||||
// Checking that first post wasn't updated
|
|
||||||
assert.deepEqual(db.posts[0], {id: 1, body: 'foo'});
|
|
||||||
done()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('DELETE /:resource/:id', function() {
|
|
||||||
it('should respond with empty data and not destroy resource', function(done) {
|
|
||||||
request(app)
|
|
||||||
.del('/posts/1')
|
|
||||||
.expect(204)
|
|
||||||
.end(function(err, res){
|
|
||||||
if (err) return done(err);
|
|
||||||
assert.equal(db.posts.length, 2);
|
|
||||||
assert.equal(db.comments.length, 4);
|
|
||||||
done()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('OPTIONS /:resource/:id', function() {
|
|
||||||
it('should respond with empty data and not destroy resource', function(done) {
|
|
||||||
request(app)
|
|
||||||
.options('/posts/1')
|
|
||||||
.expect(204, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,130 +1,129 @@
|
|||||||
var request = require('supertest'),
|
var request = require('supertest')
|
||||||
assert = require('assert'),
|
var assert = require('assert')
|
||||||
server = require('../server'),
|
var low = require('low')
|
||||||
routes = require('../routes/read-write'),
|
var server = require('../src/server')
|
||||||
fixture = require('./fixture'),
|
var fixture = require('./fixture')
|
||||||
db,
|
var db
|
||||||
app;
|
|
||||||
|
|
||||||
describe('Read write routes', function() {
|
describe('Server', function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
db = fixture();
|
low.db = fixture()
|
||||||
app = server.createApp(db);
|
server
|
||||||
});
|
})
|
||||||
|
|
||||||
describe('GET /:resource', function() {
|
|
||||||
it('should respond with json and corresponding resources', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/posts')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(db.posts)
|
|
||||||
.expect(200, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /:resource?attr=&attr=', function() {
|
|
||||||
it('should respond with json and filter resources', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/comments?postId=1&published=true')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect([db.comments[0]])
|
|
||||||
.expect(200, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /:parent/:parentId/:resource', function() {
|
|
||||||
it('should respond with json and corresponding nested resources', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/posts/1/comments')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect([
|
|
||||||
db.comments[0],
|
|
||||||
db.comments[1]
|
|
||||||
])
|
|
||||||
.expect(200, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /:resource/:id', function() {
|
|
||||||
it('should respond with json and corresponding resource', function(done) {
|
|
||||||
request(app)
|
|
||||||
.get('/posts/1')
|
|
||||||
.expect('Content-Type', /json/)
|
|
||||||
.expect(db.posts[0])
|
|
||||||
.expect(200, done);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /db', function() {
|
describe('GET /db', function() {
|
||||||
it('should respond with json and full database', function(done) {
|
it('should respond with json and full database', function(done) {
|
||||||
request(app)
|
request(server)
|
||||||
.get('/db')
|
.get('/db')
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect(db)
|
.expect(low.db)
|
||||||
.expect(200, done);
|
.expect(200, done)
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
|
describe('GET /:resource', function() {
|
||||||
|
it('should respond with json and corresponding resources', function(done) {
|
||||||
|
request(server)
|
||||||
|
.get('/posts')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(low.db.posts)
|
||||||
|
.expect(200, done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GET /:resource?attr=&attr=', function() {
|
||||||
|
it('should respond with json and filter resources', function(done) {
|
||||||
|
request(server)
|
||||||
|
.get('/comments?postId=1&published=true')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect([low.db.comments[0]])
|
||||||
|
.expect(200, done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GET /:parent/:parentId/:resource', function() {
|
||||||
|
it('should respond with json and corresponding nested resources', function(done) {
|
||||||
|
request(server)
|
||||||
|
.get('/posts/1/comments')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect([
|
||||||
|
low.db.comments[0],
|
||||||
|
low.db.comments[1]
|
||||||
|
])
|
||||||
|
.expect(200, done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('GET /:resource/:id', function() {
|
||||||
|
it('should respond with json and corresponding resource', function(done) {
|
||||||
|
request(server)
|
||||||
|
.get('/posts/1')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(low.db.posts[0])
|
||||||
|
.expect(200, done)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('POST /:resource', function() {
|
describe('POST /:resource', function() {
|
||||||
it('should respond with json and create a resource', function(done) {
|
it('should respond with json and create a resource', function(done) {
|
||||||
request(app)
|
request(server)
|
||||||
.post('/posts')
|
.post('/posts')
|
||||||
.send({body: 'foo'})
|
.send({body: 'foo'})
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect({id: 3, body: 'foo'})
|
.expect({id: 3, body: 'foo'})
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(function(err, res){
|
.end(function(err, res){
|
||||||
if (err) return done(err);
|
if (err) return done(err)
|
||||||
assert.equal(db.posts.length, 3);
|
assert.equal(low.db.posts.length, 3)
|
||||||
done();
|
done()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
describe('PUT /:resource/:id', function() {
|
describe('PUT /:resource/:id', function() {
|
||||||
it('should respond with json and update resource', function(done) {
|
it('should respond with json and update resource', function(done) {
|
||||||
request(app)
|
request(server)
|
||||||
.put('/posts/1')
|
.put('/posts/1')
|
||||||
.send({id: 1, body: 'foo'})
|
.send({id: 1, body: 'foo'})
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect({id: 1, body: 'foo'})
|
.expect({id: 1, body: 'foo'})
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(function(err, res){
|
.end(function(err, res){
|
||||||
if (err) return done(err);
|
if (err) return done(err)
|
||||||
assert.deepEqual(db.posts[0], {id: 1, body: 'foo'});
|
assert.deepEqual(low.db.posts[0], {id: 1, body: 'foo'})
|
||||||
done();
|
done()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
describe('PATCH /:resource/:id', function() {
|
describe('PATCH /:resource/:id', function() {
|
||||||
it('should respond with json and update resource', function(done) {
|
it('should respond with json and update resource', function(done) {
|
||||||
request(app)
|
request(server)
|
||||||
.patch('/posts/1')
|
.patch('/posts/1')
|
||||||
.send({body: 'bar'})
|
.send({body: 'bar'})
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect({id: 1, body: 'bar'})
|
.expect({id: 1, body: 'bar'})
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(function(err, res){
|
.end(function(err, res){
|
||||||
if (err) return done(err);
|
if (err) return done(err)
|
||||||
assert.deepEqual(db.posts[0], {id: 1, body: 'bar'});
|
assert.deepEqual(low.db.posts[0], {id: 1, body: 'bar'})
|
||||||
done();
|
done()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
describe('DELETE /:resource/:id', function() {
|
describe('DELETE /:resource/:id', function() {
|
||||||
it('should respond with empty data, destroy resource and dependent resources', function(done) {
|
it('should respond with empty data, destroy resource and dependent resources', function(done) {
|
||||||
request(app)
|
request(server)
|
||||||
.del('/posts/1')
|
.del('/posts/1')
|
||||||
.expect(204)
|
.expect(204)
|
||||||
.end(function(err, res){
|
.end(function(err, res){
|
||||||
if (err) return done(err);
|
if (err) return done(err)
|
||||||
assert.equal(db.posts.length, 1);
|
assert.equal(low.db.posts.length, 1)
|
||||||
assert.equal(db.comments.length, 2);
|
assert.equal(low.db.comments.length, 2)
|
||||||
done();
|
done()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
});
|
})
|
@ -1,18 +1,12 @@
|
|||||||
var request = require('supertest'),
|
var request = require('supertest')
|
||||||
assert = require('assert'),
|
var assert = require('assert')
|
||||||
server = require('../server'),
|
var server = require('../src/server')
|
||||||
routes = require('../routes/read-write'),
|
|
||||||
app;
|
|
||||||
|
|
||||||
describe('Static routes', function() {
|
describe('Static routes', function() {
|
||||||
|
|
||||||
beforeEach(function() {
|
|
||||||
app = server.createApp({}, routes);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('GET /', function() {
|
describe('GET /', function() {
|
||||||
it('should respond with html', function(done) {
|
it('should respond with html', function(done) {
|
||||||
request(app)
|
request(server)
|
||||||
.get('/')
|
.get('/')
|
||||||
.expect('Content-Type', /html/)
|
.expect('Content-Type', /html/)
|
||||||
.expect(200, done);
|
.expect(200, done);
|
||||||
@ -21,7 +15,7 @@ describe('Static routes', function() {
|
|||||||
|
|
||||||
describe('GET /stylesheets/style.css', function() {
|
describe('GET /stylesheets/style.css', function() {
|
||||||
it('should respond with css', function(done) {
|
it('should respond with css', function(done) {
|
||||||
request(app)
|
request(server)
|
||||||
.get('/stylesheets/style.css')
|
.get('/stylesheets/style.css')
|
||||||
.expect('Content-Type', /css/)
|
.expect('Content-Type', /css/)
|
||||||
.expect(200, done);
|
.expect(200, done);
|
||||||
|
@ -1,86 +0,0 @@
|
|||||||
(function(root) {
|
|
||||||
|
|
||||||
var _ = root._ || require('underscore');
|
|
||||||
|
|
||||||
if (!root._) {
|
|
||||||
_.mixin(require('underscore.inflections'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function get(db, table, id) {
|
|
||||||
return _.find(db[table], function (row) {
|
|
||||||
return row.id === id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function exist(db, table, id) {
|
|
||||||
return !_.isUndefined(_.get(db, table, id));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createId(db, table) {
|
|
||||||
if (_.isEmpty(db[table])) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return _.max(db[table], function(row) {
|
|
||||||
return row.id;
|
|
||||||
}).id + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function create(db, table, obj) {
|
|
||||||
var clone = _.clone(obj);
|
|
||||||
|
|
||||||
if (_.isUndefined(clone.id)) clone.id = _.createId(db, table);
|
|
||||||
|
|
||||||
db[table].push(clone);
|
|
||||||
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
function update(db, table, id, attrs) {
|
|
||||||
var row = get(db, table, id),
|
|
||||||
updatedRow = _.extend(row, attrs),
|
|
||||||
index = _.indexOf(db[table], row);
|
|
||||||
|
|
||||||
db[table][index] = updatedRow;
|
|
||||||
}
|
|
||||||
|
|
||||||
function clean(db) {
|
|
||||||
var toBeRemoved = [];
|
|
||||||
|
|
||||||
_(db).each(function(table, tableName) {
|
|
||||||
_(table).each(function(row) {
|
|
||||||
_(row).each(function(value, key) {
|
|
||||||
if (/Id$/.test(key)) {
|
|
||||||
var reference = _.pluralize(key.slice(0, - 2));
|
|
||||||
if (!_.exist(db, reference, row[key])) {
|
|
||||||
toBeRemoved.push({
|
|
||||||
tableName: tableName,
|
|
||||||
id: row.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
_(toBeRemoved).each(function(row) {
|
|
||||||
_.remove(db, row.tableName, row.id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function remove(db, table, id) {
|
|
||||||
var newTable = _.reject(db[table], function(row) {
|
|
||||||
return row.id === id;
|
|
||||||
});
|
|
||||||
|
|
||||||
db[table] = newTable;
|
|
||||||
}
|
|
||||||
|
|
||||||
_.get = get;
|
|
||||||
_.exist = exist;
|
|
||||||
_.createId = createId;
|
|
||||||
_.create = create;
|
|
||||||
_.update = update;
|
|
||||||
_.clean = clean;
|
|
||||||
_.remove = remove;
|
|
||||||
})(this);
|
|
@ -1,11 +0,0 @@
|
|||||||
var logan = require('logan');
|
|
||||||
|
|
||||||
logan.set({
|
|
||||||
error: ['%', 'red'],
|
|
||||||
success: ['%', 'green'],
|
|
||||||
info: ['%', 'grey'],
|
|
||||||
notice: ['%', 'yellow'],
|
|
||||||
url: [' http://localhost:%/'.grey + '%'.cyan, '.']
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = logan
|
|
@ -1,11 +0,0 @@
|
|||||||
function toNative(value) {
|
|
||||||
if (value === 'true' || value === 'false') {
|
|
||||||
return value === 'true';
|
|
||||||
} else if (!isNaN(+value)) {
|
|
||||||
return +value;
|
|
||||||
} else {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.toNative = toNative;
|
|
Reference in New Issue
Block a user