diff --git a/README.md b/README.md index a094244..1c97783 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,8 @@ derby-starter ============= A generic server for use with Derby apps + +This project exists purely to DRY [derby-examples](https://github.com/codeparty/derby-examples). +It is not intended to be used with all Derby apps. + +Do not use this in a production environment. Redis support is disabled. diff --git a/config/development.json b/config/development.json new file mode 100644 index 0000000..4dfeef4 --- /dev/null +++ b/config/development.json @@ -0,0 +1,8 @@ +{ + "mongo": { + "db": "derby-starter-development" + }, + "session": { + "secret": "USE A REAL SECRET WHEN YOU DEPLOY" + } +} diff --git a/config/index.js b/config/index.js new file mode 100644 index 0000000..f17bcb2 --- /dev/null +++ b/config/index.js @@ -0,0 +1,137 @@ +var convict = require('convict'); +var path = require('path'); +var url = require('url'); +var check = require('convict/node_modules/validator').check + +module.exports = function config(options, cb) { + envAlias('MONGOHQ_URL', 'MONGO_URL'); + envAlias('MONGOLAB_URL', 'MONGO_URL'); + + envAlias('OPENREDIS_URL', 'REDIS_URL'); + envAlias('REDISTOGO_URL', 'REDIS_URL'); + envAlias('REDISCLOUD_URL', 'REDIS_URL'); + + var config = convict({ + env: { + doc: 'The application environment', + format: [ + 'development', + 'test', + 'production' + ], + default: 'development', + env: 'NODE_ENV' + }, + port: { + doc: 'The ipv4 address to bind.', + format: 'port', + default: 3000, + env: 'PORT' + }, + ip: { + doc: 'The ipv4 address to bind.', + format: 'ipaddress', + default: '0.0.0.0', + env: 'IP' + }, + static: { + doc: 'The folders express.static should serve.', + format: mountPoints, + default: path.resolve('public') + }, + mongo: { + url: { + format: mongoUrl, + default: undefined, + env: 'MONGO_URL', + arg: 'mongo-url' // eg. $ npm start --mongo-url "mongodb://127.0.0.1:27017/whatever" + }, + host: { + format: String, + default: 'localhost', + env: 'MONGO_HOST' + }, + port: { + format: 'port', + default: 27017, + env: 'MONGO_PORT' + }, + db: { + format: String, + default: undefined, + env: 'MONGO_DB' + }, + opts: { + safe: { + format: Boolean, + default: true, + env: 'MONGO_SAFE' + } + } + }, + session: { + secret: { + doc: 'Secret used to sign the session cookie', + format: secret(16), + default: undefined, + env: 'SESSION_SECRET' + } + } + }); + + config.load(options); + config.loadFile(__dirname + '/' + config.get('env') + '.json'); + + if (config.has('mongo.url') === false) setMongoUrl(config); + if (cb) cb(null, config); + + config.validate(); + return config; +} + +function setMongoUrl(config) { + var m = config.get('mongo'); + config.set('mongo.url', 'mongodb://' + m.host + ':' + m.port + '/' + m.db); +} + +function secret(length) { + return function (val) { + check(val).notEmpty().len(length); + } +} + +function optionalSecret(length) { + return function (val) { + if (!!val) secret(length)(val); + } +} + +function checkUrl(protocol, attributes) { + return function (val) { + var u = url.parse(val); + check(u.protocol, 'Wrong protocol.').equals(protocol) + attributes.forEach(function (attr) { + check(u[attr]).notEmpty() + }) + } +} + +function mongoUrl(val) { + checkUrl('mongodb:', ['hostname','port','path'])(val) +} + +function envAlias(source, target) { + if (process.env[source]) process.env[target] = process.env[source]; +} + +function mountPoints(val) { + if (Array.isArray(val)) { + val.forEach(function (el) { + check(el.route).contains('/'); + check(el.dir).notEmpty(); + }) + } else { + check(val).notEmpty(); + } +} + diff --git a/config/test.json b/config/test.json new file mode 100644 index 0000000..4063ebc --- /dev/null +++ b/config/test.json @@ -0,0 +1,8 @@ +{ + "mongo": { + "url": "mongodb://localhost:27017/derby-starter-test" + }, + "session": { + "secret": "USE A REAL SECRET WHEN YOU DEPLOY" + } +} diff --git a/lib/index.js b/lib/index.js index 88a4b65..72b00f3 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,7 +4,9 @@ exports.run = run; function run(app, options, cb) { options || (options = {}); - var port = options.port || process.env.PORT || 3000; + var config = require('../config')(options); + var port = config.get('port'); + var ip = config.get('ip'); function listenCallback(err) { console.log('%d listening. Go to: http://localhost:%d/', process.pid, port); @@ -12,10 +14,10 @@ function run(app, options, cb) { } function createServer() { if (typeof app === 'string') app = require(app); - require('./server').setup(app, options, function(err, expressApp) { + require('./server').setup(app, config, function(err, expressApp) { if (err) throw err; var server = require('http').createServer(expressApp); - server.listen(port, listenCallback); + server.listen(port, ip, listenCallback); }); } derby.run(createServer); diff --git a/lib/server.js b/lib/server.js index eb6d15f..53d246e 100644 --- a/lib/server.js +++ b/lib/server.js @@ -1,52 +1,16 @@ -var coffeeify = require('coffeeify'); var derby = require('derby'); var express = require('express'); -var redis = require('redis'); -var RedisStore = require('connect-redis')(express); var racerBrowserChannel = require('racer-browserchannel'); -var liveDbMongo = require('livedb-mongo'); var parseUrl = require('url').parse; +var MongoStore = require('connect-mongo')(express); derby.use(require('racer-bundle')); exports.setup = setup; -function setup(app, options, cb) { - var redisClient; - if (process.env.REDIS_HOST) { - redisClient = redis.createClient(process.env.REDIS_PORT, process.env.REDIS_HOST); - redisClient.auth(process.env.REDIS_PASSWORD); - } else if (process.env.OPENREDIS_URL) { - var redisUrl = parseUrl(process.env.OPENREDIS_URL); - redisClient = redis.createClient(redisUrl.port, redisUrl.hostname); - redisClient.auth(redisUrl.auth.split(":")[1]); - } else { - redisClient = redis.createClient(); - } - // redisClient.select(1); +function setup(app, config, cb) { - var mongoUrl = process.env.MONGO_URL || process.env.MONGOHQ_URL || 'mongodb://localhost:27017/derby-app'; // The store creates models and syncs data - var store = derby.createStore({ - db: liveDbMongo(mongoUrl + '?auto_reconnect', {safe: true}) - , redis: redisClient - }); - - store.on('bundle', function(browserify) { - // Add support for directly requiring coffeescript in browserify bundles - browserify.transform({global: true}, coffeeify); - - // HACK: In order to use non-complied coffee node modules, we register it - // as a global transform. However, the coffeeify transform needs to happen - // before the include-globals transform that browserify hard adds as the - // first trasform. This moves the first transform to the end as a total - // hack to get around this - var pack = browserify.pack; - browserify.pack = function(opts) { - var detectTransform = opts.globalTransform.shift(); - opts.globalTransform.push(detectTransform); - return pack.apply(this, arguments); - }; - }); + var store = require('./store')(derby, config); var publicDir = __dirname + '/../public'; @@ -65,20 +29,19 @@ function setup(app, options, cb) { .use(express.cookieParser()) .use(express.session({ - secret: process.env.SESSION_SECRET || 'YOUR SECRET HERE' - , store: new RedisStore() + secret: config.get('session.secret'), + store: new MongoStore({url: config.get('mongo.url')}) })) .use(createUserId) - if (options && options.static) { - if(Array.isArray(options.static)) { - for(var i = 0; i < options.static.length; i++) { - var o = options.static[i]; - expressApp.use(o.route, express.static(o.dir)); - } - } else { - expressApp.use(express.static(options.static)); + var serveStatic = config.get('static') + if (Array.isArray(serveStatic)) { + for(var i = 0; i < serveStatic.length; i++) { + var s = serveStatic[i]; + expressApp.use(s.route, express.static(s.dir)); } + } else { + expressApp.use(express.static(serveStatic)); } expressApp diff --git a/lib/store.js b/lib/store.js new file mode 100644 index 0000000..3cf7dc1 --- /dev/null +++ b/lib/store.js @@ -0,0 +1,47 @@ +var liveDbMongo = require('livedb-mongo'); +var coffeeify = require('coffeeify'); + +module.exports = store; + +function store(derby, config) { + var mongo = config.get('mongo'); + + // A real app should use redis to allow running multiple server processes. + // + // var db = liveDbMongo(mongo.url + '?auto_reconnect', mongo.opts); + // + // var oplogDb = db; // you can store the oplog in a different database + // var redisClient = require('redis').createClient(); + // var redisObserver = require('redis').createClient(); + // + // var driver = require('livedb').redisDriver(oplogDb, redisClient, redisObserver) + // + // var opts = { + // backend: { + // db: db, + // driver: driver + // } + // }; + + var opts = { db: liveDbMongo(mongo.url + '?auto_reconnect', mongo.opts) }; + var store = derby.createStore(opts); + + store.on('bundle', function(browserify) { + // Add support for directly requiring coffeescript in browserify bundles + browserify.transform({global: true}, coffeeify); + + // HACK: In order to use non-complied coffee node modules, we register it + // as a global transform. However, the coffeeify transform needs to happen + // before the include-globals transform that browserify hard adds as the + // first trasform. This moves the first transform to the end as a total + // hack to get around this + var pack = browserify.pack; + browserify.pack = function(opts) { + var detectTransform = opts.globalTransform.shift(); + opts.globalTransform.push(detectTransform); + return pack.apply(this, arguments); + }; + }); + + return store; +} diff --git a/package.json b/package.json index 36fcf18..6680496 100644 --- a/package.json +++ b/package.json @@ -16,17 +16,15 @@ "url": "https://github.com/codeparty/derby-starter/issues" }, "dependencies": { - "derby": "~0.6", + "derby": "git://github.com/codeparty/derby.git#375a2da411df1e9fdefcef43c71fa3205603bf2b", + "racer": "git://github.com/codeparty/racer.git#b3048e712caf9d47b5bd7a5d2e5421aaf8b11c0b", "express": "~3.4.8", + "connect-mongo": "~0.4.1", + "convict": "~0.4.2", "coffee-script": "~1.7.1", "coffeeify": "~0.6.0", - "redis": "~0.10.1", - "connect-redis": "~1.4.7", - "racer-browserchannel": "^0.2.0", + "racer-browserchannel": "~0.2.0", "racer-bundle": "~0.1.1", "livedb-mongo": "~0.3.0" - }, - "optionalDependencies": { - "hiredis": "~0.1.16" } } diff --git a/public/empty b/public/.gitkeep similarity index 100% rename from public/empty rename to public/.gitkeep