jsbin/lib/app.js
Remy Sharp 365e01e9bf Make cacheBust (and cachebust) global template var
Because they kept being included in the templates, but not being passed in, so this sorts out our fuck ups.
2014-08-19 11:54:05 +01:00

320 lines
10 KiB
JavaScript

'use strict';
var nodemailer = require('nodemailer'),
express = require('express'),
flatten = require('flatten.js').flatten,
path = require('path'),
app = express(),
hbs = require('./hbs'),
options = require('./config'),
store = require('./store')(options.store),
undefsafe = require('undefsafe'),
models = require('./models').createModels(store),
routes = require('./routes'),
handlers = require('./handlers'),
middleware = require('./middleware'),
helpers = require('./helpers'),
metrics = require('./metrics'),
github = require('./github')(options), // if used, contains github.id
dropbox = require('./dropbox')(options),
_ = require('underscore'),
crypto = require('crypto'),
filteredCookieSession = require('express-cookie-blacklist'),
sessionVersion = require('./session-version'),
stripeRoutes = require('./stripe')(options),
flattened;
function generateSessionSecret() {
console.log('Warning: Generating a session key - please see http://jsbin.com/help/session-secret');
return crypto.createHash('md5').update(Math.random() + '').digest('hex');
}
/**
* JS Bin configuration
*/
app.store = store;
app.sessionVersion = sessionVersion;
// Create model singletons
// models.createModels(store);
app.mailer = (function (mail) {
var mailTransport = null,
method = mail && mail.adapter,
settings = mail && mail[method];
if (method && options) {
mailTransport = nodemailer.createTransport(method, settings);
}
return new handlers.MailHandler(mailTransport, app.render.bind(app));
})(options.mail);
app.PRODUCTION = 'production';
app.DEVELOPMENT = 'development';
// Set the NODE_ENV variable as this is used by Express, we want the
// environment to take precedence but allow it to be set using the config file
// too.
if (process.env.NODE_ENV) {
options.env = process.env.NODE_ENV;
}
process.env.NODE_ENV = options.env;
// Need to set the node environment to run in the same timezone as the database
// This will ideally be UTC in both cases but if not this can be set either
// using the TZ environment variable or the "timezone" option.
// This is because the mysql library coerces MySQL dates (without timezones)
// into JavaScript date objects.
if (!process.env.TZ && options.timezone) {
process.env.TZ = options.timezone;
}
// Sort out the port.
(function () {
var port = process.env.PORT;
// if we're running from the bin/jsbin file, then we modify
// both the listen port AND the options.url
if (process.env.JSBIN_PORT) {
if (options.url.host.indexOf(':') === -1) {
options.url.host += ':' + process.env.JSBIN_PORT;
} else {
options.url.host = options.url.host.replace(/\:\d+$/, function () {
return ':' + process.env.JSBIN_PORT;
});
}
}
if (!port) {
options.url.host.replace(/\:(\d+)$/, function (m, p) {
if (p.length) {
port = p;
}
});
}
if (!port) {
port = 80;
}
// NOTE: this is *only* used for running the server.listen()
// it's not used in the urls to access assets, etc.
options.port = port;
})();
// Strip trailing slash from the prefix
options.url.prefix = options.url.prefix.replace(/\/$/, '');
// Apply the keys from the config file. All nested properties are
// space delimited to match the express style.
//
// For example, app.set('url prefix'); //=> '/'
flattened = flatten(options, ' ');
Object.getOwnPropertyNames(flattened).forEach(function (key) {
app.set(key, flattened[key]);
});
// in live, we run behind a proxy - so this will give us our IPs again:
// http://expressjs.com/guide.html#proxies
if (process.env.NODE_ENV === app.PRODUCTION || process.env.JSBIN_PROXY) {
app.enable('trust proxy');
}
app.set('root', path.resolve(path.join(__dirname, '..')));
// ensure jsbin is running from the expected root
process.chdir(app.get('root'));
app.set('version', require('../package').version);
app.set('view engine', 'html');
app.set('views', path.join(app.get('root'), 'views'));
app.set('url prefix', options.url.prefix);
app.set('url ssl', options.url.ssl);
app.set('url full', 'http://' + app.get('url host') + app.set('url prefix'));
app.set('basepath', app.get('url prefix'));
app.set('is_production', app.get('env') === app.PRODUCTION);
if (options.url.static) {
app.set('static url', 'http://' + app.get('url static'));
} else {
app.set('static url', app.get('url full'));
}
if (options.url.runner) {
// strip trailing slash, just in case
options.url.runner = options.url.runner.replace(/\/$/, '');
app.set('url runner', options.url.runner);
} else {
app.set('url runner', app.get('url full'));
}
app.set('views', 'views');
app.set('view engine', 'html');
app.engine('html', hbs.__express);
app.engine('txt', hbs.__express); // used in email
// Define some global template variables.
var helpers = helpers.createHelpers(app);
app.locals.version = app.get('version');
app.locals.client = app.get('client');
Object.keys(helpers).forEach(function (key) {
app.locals[key] = helpers[key];
});
// set these as global template variables since they keep getting missed
app.locals.is_production = app.get('is_production');
app.locals.cachebust = app.locals.cacheBust = app.get('is_production') ? '?' + app.get('version') : '';
app.connect = function (callback) {
app.emit('before:connect', app);
// Register all the middleware.
app.configure(function () {
var mount = app.set('url prefix') || '/',
logger;
logger = process.env.JSBIN_LOGGER || app.get('server logger') || 'tiny';
if (logger !== 'none') {
app.use(express.logger(logger));
}
// favicon
app.use(express.favicon(path.join(__dirname, '../', 'public','images','favicon.png')));
app.use(function (req, res, next) {
// used for timings
req.start = Date.now();
next();
});
app.emit('before:middleware');
app.use(middleware.flash());
// this middleware says:
// a) if we have a static url in our config, i.e. static.jsbin.com
// b) only serve static assets IF AND ONLY IF, the full url is requested,
// i.e. static.jsbin.com/css/style.css
// c) otherwise, skip the static handler, and go straight to routes.js
//
// If the request hits routes.js and doesn't match a defined route (or doesn't
// look like a bin url) then it gets 404'd - i.e. you can't request /css/style.css
// because it'll return a 404.
app.use(mount, (function () {
var staticRouter = express.static(path.join(app.get('root'), 'public'));
return function (req, res, next) {
// construct the full request url
var url = req.protocol + '://' + req.get('host') + req.url;
// *if* the config has a static url path (i.e. different from jsbin.com),
// i.e. static.jsbin.com
if (options.url.static) {
// and the url requested is NOT static, then continue with node
if (url.indexOf(options.url.static) === -1) {
return next();
}
}
// otherwise serve it as a static asset...
return staticRouter(req, res, next);
};
})());
app.use(middleware.limitContentLength({limit: app.get('max-request-size')}));
app.emit('before:cookies', { app: app });
app.use(express.cookieParser(app.get('session secret') || generateSessionSecret()));
app.use(filteredCookieSession(['user.settings', 'passport', 'user.email', 'user.github_id', 'user.github_token', 'user.dropbox_token', 'user.dropbox_id', 'user.flagged', 'user.bincount', 'user.api_key', 'user.key', 'user.id']));
app.use(express.cookieSession({
key: 'jsbin',
cookie: {
maxAge: 365 * 24 * 60 * 60 * 1000,
// the domain must contain a dot and should not have a port
domain: app.get('url host').indexOf('.') === -1 ? undefined : '.' + app.get('url host').replace(/:\d+$/, '')
}
}));
// memcached sessions
if (options.session.memcached && options.session.memcached.connection) {
require('./addons/memcached')(app, options.session.memcached.connection);
}
app.emit('after:session', {app: app});
app.use(function (req, res, next) {
// Deletes cookies using the old domain (without the leading '.')
// and we know this because they don't have a version in their cookie.
if (undefsafe(req, 'session.version') === undefined) {
var oldSess = _.extend({ version: app.get('version') }, req.session);
res.clearCookie('jsbin');
req.session = oldSess;
}
next();
});
// Passport
if (options.github && options.github.id) {
github.initialize(app);
}
if (options.dropbox && options.dropbox.id) {
dropbox.initialize();
}
app.use(express.urlencoded());
app.use(express.json());
app.use(middleware.csrf({ ignore: ['/', /^\/api\//, /^\/hooks\//, /^\/subscribe\//] }));
app.use(middleware.api({ app: app }));
app.use(middleware.subdomain(app));
app.use(middleware.noslashes());
app.use(middleware.ajax());
// app.use(middleware.cors());
app.use(middleware.jsonp());
app.use(middleware.protocolCheck);
app.use(middleware.decompressBody);
app.use(middleware.notices());
app.use(mount, app.router);
// All routes must be registered after middleware
// Register stripe webhooks
if (options.payment && options.payment.stripe && options.store.adapter === 'mysql') {
stripeRoutes(app);
}
// Register all routes
routes(app);
});
app.emit('after', app);
store.connect(function (err) {
if (err) {
metrics.increment('error.store.connect');
throw err;
}
var port = app.set('port');
app.listen(port);
if (typeof callback === 'function') {
callback();
}
app.emit('connected');
process.stdout.write('JS Bin v' + app.get('version') + ' is up and running on port ' + app.get('port') + '. Now point your browser at ' + app.get('url full') + '\n');
});
};
// Export the application to allow it to be included.
module.exports = { app: app };
// Run a local development server if this file is called directly.
if (require.main === module) {
app.connect();
}