mirror of
https://github.com/jsbin/jsbin.git
synced 2026-01-18 15:18:04 +00:00
Move state onto Request instances via helper middleware
This seems a little hairy but essentially avoids creating modules with factory functions which seems very clunky. We now provide various helper functions that are bound to the application state such as url, routing and models via the request object. This is massively overloading this object but seems to be the common way in Express apps to pass state into handlers.
This commit is contained in:
parent
84d4292d99
commit
fe5eae899c
15
lib/app.js
15
lib/app.js
@ -7,25 +7,17 @@ var express = require('express'),
|
||||
app = express(),
|
||||
options = require('./config'),
|
||||
store = require('./store')(options.store),
|
||||
models = require('./models'),
|
||||
handlers = require('./handlers'),
|
||||
middleware = require('./middleware'),
|
||||
flattened;
|
||||
|
||||
app.store = store;
|
||||
app.store = store;
|
||||
app.models = models.createModels(store);
|
||||
app.templates = {};
|
||||
|
||||
app.PRODUCTION = 'production';
|
||||
app.DEVELOPMENT = 'development';
|
||||
Object.defineProperties(app, {
|
||||
production: {
|
||||
get: function () {
|
||||
return this.set('env') === this.PRODUCTION;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: Refactor this!
|
||||
handlers = handlers(app);
|
||||
|
||||
// Apply the keys from the config file. All nested properties are
|
||||
// space delimited to match the express style.
|
||||
@ -48,6 +40,7 @@ app.use(express.errorHandler({showStack: true, dumpExceptions: true}));
|
||||
app.use(middleware.noslashes());
|
||||
app.use(middleware.ajax());
|
||||
app.use(middleware.jsonp());
|
||||
app.use(middleware.helpers(app));
|
||||
|
||||
// Create a Hogan/Mustache handler for templates.
|
||||
app.engine('html', function (path, options, fn) {
|
||||
|
||||
450
lib/handlers.js
450
lib/handlers.js
@ -1,7 +1,7 @@
|
||||
var async = require('asyncjs'),
|
||||
path = require('path'),
|
||||
utils = require('./utils'),
|
||||
createBinModel = require('./models/bin');
|
||||
handlers;
|
||||
|
||||
// Create a not found error object.
|
||||
function NotFound() {
|
||||
@ -9,259 +9,245 @@ function NotFound() {
|
||||
}
|
||||
NotFound.prototype = Object.create(Error.prototype);
|
||||
|
||||
module.exports = function (app) {
|
||||
var binModel = createBinModel(app.store), handlers;
|
||||
|
||||
handlers = {
|
||||
getDefault: function (req, res) {
|
||||
handlers.renderFiles(req, res);
|
||||
},
|
||||
getBin: function (req, res, next) {
|
||||
handlers.render(req, res, req.bin);
|
||||
},
|
||||
getBinPreview: function (req, res, next) {
|
||||
var options = {edit: !req.param('quiet')};
|
||||
handlers.formatPreview(req.bin, options, function (err, formatted) {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
|
||||
if (formatted) {
|
||||
res.send(formatted);
|
||||
} else {
|
||||
res.contentType('js');
|
||||
res.send(req.bin.javascript);
|
||||
}
|
||||
});
|
||||
},
|
||||
getBinSource: function (req, res) {
|
||||
res.contentType('json');
|
||||
var output = JSON.stringify(handlers.templateFromBin(req.bin));
|
||||
if (!req.ajax) {
|
||||
res.contentType('js');
|
||||
output = 'var template = ' + output;
|
||||
module.exports = handlers = {
|
||||
getDefault: function (req, res) {
|
||||
handlers.renderFiles(req, res);
|
||||
},
|
||||
getBin: function (req, res, next) {
|
||||
handlers.render(req, res, req.bin);
|
||||
},
|
||||
getBinPreview: function (req, res, next) {
|
||||
var options = {edit: !req.param('quiet')};
|
||||
handlers.formatPreview(req.bin, req.helpers, options, function (err, formatted) {
|
||||
if (err) {
|
||||
next(err);
|
||||
}
|
||||
res.send(output);
|
||||
},
|
||||
getBinSourceFile: function (req, res) {
|
||||
var format = req.params.format;
|
||||
|
||||
res.contentType(format);
|
||||
if (format !== 'html') {
|
||||
if (format === 'js' || format === 'json') {
|
||||
format = 'javascript';
|
||||
}
|
||||
res.send(req.bin[format]);
|
||||
if (formatted) {
|
||||
res.send(formatted);
|
||||
} else {
|
||||
handlers.getBinPreview(req, res);
|
||||
res.contentType('js');
|
||||
res.send(req.bin.javascript);
|
||||
}
|
||||
},
|
||||
redirectToLatest: function (req, res) {
|
||||
var path = req.path.replace('latest', req.bin.revision);
|
||||
res.redirect(303, path);
|
||||
},
|
||||
createBin: function (req, res, next) {
|
||||
var data = utils.extract(req.body, 'html', 'css', 'javascript');
|
||||
});
|
||||
},
|
||||
getBinSource: function (req, res) {
|
||||
res.contentType('json');
|
||||
var output = JSON.stringify(handlers.templateFromBin(req.bin));
|
||||
if (!req.ajax) {
|
||||
res.contentType('js');
|
||||
output = 'var template = ' + output;
|
||||
}
|
||||
res.send(output);
|
||||
},
|
||||
getBinSourceFile: function (req, res) {
|
||||
var format = req.params.format;
|
||||
|
||||
binModel.create(data, function (err, result) {
|
||||
res.contentType(format);
|
||||
if (format !== 'html') {
|
||||
if (format === 'js' || format === 'json') {
|
||||
format = 'javascript';
|
||||
}
|
||||
res.send(req.bin[format]);
|
||||
} else {
|
||||
handlers.getBinPreview(req, res);
|
||||
}
|
||||
},
|
||||
redirectToLatest: function (req, res) {
|
||||
var path = req.path.replace('latest', req.bin.revision);
|
||||
res.redirect(303, path);
|
||||
},
|
||||
createBin: function (req, res, next) {
|
||||
var data = utils.extract(req.body, 'html', 'css', 'javascript');
|
||||
|
||||
req.models.bin.create(data, function (err, result) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
handlers.renderCreated(req, res, result);
|
||||
});
|
||||
},
|
||||
createRevision: function (req, res, next) {
|
||||
var panel = req.param('panel'),
|
||||
params = {};
|
||||
|
||||
if (req.param('method') === 'save') {
|
||||
params = utils.extract(req.body, 'html', 'css', 'javascript');
|
||||
params.url = req.bin.url;
|
||||
params.revision = req.bin.revision + 1;
|
||||
req.models.bin.createRevision(params, function (err, result) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
handlers.renderCreated(req, res, result);
|
||||
});
|
||||
},
|
||||
createRevision: function (req, res, next) {
|
||||
var panel = req.param('panel'),
|
||||
params = {};
|
||||
} else if (req.param('method') === 'update') {
|
||||
params[panel] = req.param('content');
|
||||
params.streamingKey = req.param('checksum');
|
||||
params.revision = req.param('revision');
|
||||
params.url = req.param('code');
|
||||
|
||||
if (req.param('method') === 'save') {
|
||||
params = utils.extract(req.body, 'html', 'css', 'javascript');
|
||||
params.url = req.bin.url;
|
||||
params.revision = req.bin.revision + 1;
|
||||
binModel.createRevision(params, function (err, result) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
handlers.renderCreated(req, res, result);
|
||||
});
|
||||
} else if (req.param('method') === 'update') {
|
||||
params[panel] = req.param('content');
|
||||
params.streamingKey = req.param('checksum');
|
||||
params.revision = req.param('revision');
|
||||
params.url = req.param('code');
|
||||
req.models.bin.updatePanel(panel, params, function (err, result) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.json({ok: true, error: false});
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
notFound: function (req, res) {
|
||||
var files = handlers.defaultFiles();
|
||||
files[2] = 'not_found.js';
|
||||
handlers.renderFiles(req, res, files);
|
||||
},
|
||||
loadBin: function (req, res, next) {
|
||||
var rev = parseInt(req.params.rev, 10) || 1,
|
||||
query = {id: req.params.bin, revision: rev};
|
||||
|
||||
binModel.updatePanel(panel, params, function (err, result) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
res.json({ok: true, error: false});
|
||||
});
|
||||
function complete(err, result) {
|
||||
if (err) {
|
||||
return next(new NotFound('Could not find bin: ' + req.params.bin));
|
||||
} else {
|
||||
req.bin = result;
|
||||
next();
|
||||
}
|
||||
},
|
||||
notFound: function (req, res) {
|
||||
var files = handlers.defaultFiles();
|
||||
files[2] = 'not_found.js';
|
||||
handlers.renderFiles(req, res, files);
|
||||
},
|
||||
loadBin: function (req, res, next) {
|
||||
var rev = parseInt(req.params.rev, 10) || 1,
|
||||
query = {id: req.params.bin, revision: rev};
|
||||
}
|
||||
|
||||
function complete(err, result) {
|
||||
// TODO: Re-factor this logic.
|
||||
if ((req.params.rev || req.path.indexOf('latest') === -1) && req.path.indexOf('save') === -1) {
|
||||
req.models.bin.load(query, complete);
|
||||
} else {
|
||||
req.models.bin.latest(query, complete);
|
||||
}
|
||||
},
|
||||
render: function (req, res, bin) {
|
||||
var template = handlers.templateFromBin(bin),
|
||||
jsbin = handlers.jsbin(bin, req.helpers.production ? req.helpers.set('version') : 'debug');
|
||||
|
||||
req.helpers.analytics(function (err, analytics) {
|
||||
res.render('index', {
|
||||
tips: '{}',
|
||||
revision: bin.revision || 1,
|
||||
jsbin: JSON.stringify(jsbin),
|
||||
json_template: JSON.stringify(template),
|
||||
version: jsbin.version,
|
||||
analytics: analytics,
|
||||
'production?': req.helpers.production
|
||||
});
|
||||
});
|
||||
},
|
||||
renderFiles: function (req, res, files) {
|
||||
files = files || handlers.defaultFiles();
|
||||
async.files(files, req.helpers.set('views')).readFile("utf8").toArray(function (err, results) {
|
||||
if (!err) {
|
||||
handlers.render(req, res, {
|
||||
html: results[0].data,
|
||||
css: results[1].data,
|
||||
javascript: results[2].data
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
renderCreated: function (req, res, bin) {
|
||||
var permalink = req.helpers.urlForBin(bin),
|
||||
editPermalink = req.helpers.editUrlForBin(bin);
|
||||
|
||||
if (req.ajax) {
|
||||
res.json({
|
||||
code: bin.url,
|
||||
root: req.helpers.set('url full'),
|
||||
created: (new Date()).toISOString(), // Should be part of bin.
|
||||
revision: bin.revision,
|
||||
url: permalink,
|
||||
edit: editPermalink,
|
||||
html: editPermalink,
|
||||
js: editPermalink,
|
||||
title: utils.titleForBin(bin),
|
||||
allowUpdate: false,
|
||||
checksum: req.streamingKey
|
||||
});
|
||||
} else {
|
||||
res.redirect(303, '/' + bin.url + '/' + bin.revision + '/edit');
|
||||
}
|
||||
},
|
||||
jsbin: function (bin, version) {
|
||||
return {
|
||||
root: '',
|
||||
version: version,
|
||||
state: {
|
||||
stream: false,
|
||||
code: bin.url || null,
|
||||
revision: bin.revision || 1
|
||||
}
|
||||
};
|
||||
},
|
||||
templateFromBin: function (bin) {
|
||||
return utils.extract(bin, 'html', 'css', 'javascript');
|
||||
},
|
||||
defaultFiles: function () {
|
||||
return ['html', 'css', 'js'].map(function (ext) {
|
||||
return 'default.' + ext;
|
||||
});
|
||||
},
|
||||
formatPreview: function (bin, helpers, options, fn) {
|
||||
var formatted = bin.html || '',
|
||||
insert = [], parts, last, context;
|
||||
|
||||
// TODO: Re implement this entire block with an HTML parser.
|
||||
if (formatted) {
|
||||
helpers.analytics(function (err, analytics) {
|
||||
if (err) {
|
||||
return next(new NotFound('Could not find bin: ' + req.params.bin));
|
||||
return fn(err);
|
||||
}
|
||||
|
||||
if (formatted.indexOf('%code%') > -1) {
|
||||
formatted = formatted.replace(/%code%/g, bin.javascript);
|
||||
} else {
|
||||
req.bin = result;
|
||||
next();
|
||||
insert.push('<script>', bin.javascript.trim(), '</script>');
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Re-factor this logic.
|
||||
if ((req.params.rev || req.path.indexOf('latest') === -1) && req.path.indexOf('save') === -1) {
|
||||
binModel.load(query, complete);
|
||||
} else {
|
||||
binModel.latest(query, complete);
|
||||
}
|
||||
},
|
||||
render: function (req, res, bin) {
|
||||
var template = handlers.templateFromBin(bin),
|
||||
jsbin = handlers.jsbin(bin);
|
||||
if (!options || options.edit !== false) {
|
||||
insert.push('<script src="/js/render/edit.js"></script>');
|
||||
}
|
||||
|
||||
handlers.analytics(function (err, analytics) {
|
||||
res.render('index', {
|
||||
tips: '{}',
|
||||
revision: bin.revision || 1,
|
||||
jsbin: JSON.stringify(jsbin),
|
||||
json_template: JSON.stringify(template),
|
||||
version: jsbin.version,
|
||||
analytics: analytics,
|
||||
'production?': app.production
|
||||
});
|
||||
});
|
||||
},
|
||||
renderFiles: function (req, res, files) {
|
||||
files = files || handlers.defaultFiles();
|
||||
async.files(files, app.set('views')).readFile("utf8").toArray(function (err, results) {
|
||||
if (!err) {
|
||||
handlers.render(req, res, {
|
||||
html: results[0].data,
|
||||
css: results[1].data,
|
||||
javascript: results[2].data
|
||||
if (helpers.production) {
|
||||
insert.push(analytics);
|
||||
}
|
||||
|
||||
// Append scripts to the bottom of the page.
|
||||
if (insert.length) {
|
||||
parts = formatted.split('</body>');
|
||||
last = parts.pop();
|
||||
formatted = parts.join('</body>') + insert.join('\n') + '\n</body>' + last;
|
||||
}
|
||||
|
||||
if (formatted.indexOf('%css%') > -1) {
|
||||
formatted = formatted.replace(/%css%/g, bin.css);
|
||||
} else {
|
||||
insert = '<style>' + bin.css + '</style>';
|
||||
parts = formatted.split('</head>');
|
||||
last = parts.pop();
|
||||
formatted = parts.join('</head>') + insert + '</head>' + last;
|
||||
}
|
||||
|
||||
context = {
|
||||
domain: helpers.set('url host'),
|
||||
permalink: helpers.editUrlForBin(bin, true)
|
||||
};
|
||||
|
||||
// Append attribution comment to header.
|
||||
helpers.render('comment', context, function (err, comment) {
|
||||
formatted = formatted.replace(/<html[^>]*>/, function ($0) {
|
||||
return $0 + '\n' + comment.trim();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
renderCreated: function (req, res, bin) {
|
||||
var permalink = handlers.urlForBin(bin),
|
||||
editPermalink = handlers.editUrlForBin(bin);
|
||||
|
||||
if (req.ajax) {
|
||||
res.json({
|
||||
code: bin.url,
|
||||
root: app.set('url full'),
|
||||
created: (new Date()).toISOString(), // Should be part of bin.
|
||||
revision: bin.revision,
|
||||
url: permalink,
|
||||
edit: editPermalink,
|
||||
html: editPermalink,
|
||||
js: editPermalink,
|
||||
title: utils.titleForBin(bin),
|
||||
allowUpdate: false,
|
||||
checksum: req.streamingKey
|
||||
return fn(err || null, err ? undefined : formatted);
|
||||
});
|
||||
} else {
|
||||
res.redirect(303, '/' + bin.url + '/' + bin.revision + '/edit');
|
||||
}
|
||||
},
|
||||
jsbin: function (bin) {
|
||||
return {
|
||||
root: '',
|
||||
version: app.set('environment') === 'production' ? app.set('version') : 'debug',
|
||||
state: {
|
||||
stream: false,
|
||||
code: bin.url || null,
|
||||
revision: bin.revision || 1
|
||||
}
|
||||
};
|
||||
},
|
||||
urlForBin: function (bin, full) {
|
||||
return app.set(full ? 'url full' : 'url prefix') + bin.url + '/' + bin.revision;
|
||||
},
|
||||
editUrlForBin: function (bin, full) {
|
||||
return handlers.urlForBin(bin, full) + '/edit';
|
||||
},
|
||||
templateFromBin: function (bin) {
|
||||
return utils.extract(bin, 'html', 'css', 'javascript');
|
||||
},
|
||||
defaultFiles: function () {
|
||||
return ['html', 'css', 'js'].map(function (ext) {
|
||||
return 'default.' + ext;
|
||||
});
|
||||
},
|
||||
analytics: function (fn) {
|
||||
app.render('analytics', {id: app.set('analytics id')}, fn);
|
||||
},
|
||||
formatPreview: function (bin, options, fn) {
|
||||
var formatted = bin.html || '',
|
||||
insert = [], parts, last, context;
|
||||
|
||||
// TODO: Re implement this entire block with an HTML parser.
|
||||
if (formatted) {
|
||||
handlers.analytics(function (err, analytics) {
|
||||
if (err) {
|
||||
return fn(err);
|
||||
}
|
||||
|
||||
if (formatted.indexOf('%code%') > -1) {
|
||||
formatted = formatted.replace(/%code%/g, bin.javascript);
|
||||
} else {
|
||||
insert.push('<script>', bin.javascript.trim(), '</script>');
|
||||
}
|
||||
|
||||
if (!options || options.edit !== false) {
|
||||
insert.push('<script src="/js/render/edit.js"></script>');
|
||||
}
|
||||
|
||||
if (app.production) {
|
||||
insert.push(analytics);
|
||||
}
|
||||
|
||||
// Append scripts to the bottom of the page.
|
||||
if (insert.length) {
|
||||
parts = formatted.split('</body>');
|
||||
last = parts.pop();
|
||||
formatted = parts.join('</body>') + insert.join('\n') + '\n</body>' + last;
|
||||
}
|
||||
|
||||
if (formatted.indexOf('%css%') > -1) {
|
||||
formatted = formatted.replace(/%css%/g, bin.css);
|
||||
} else {
|
||||
insert = '<style>' + bin.css + '</style>';
|
||||
parts = formatted.split('</head>');
|
||||
last = parts.pop();
|
||||
formatted = parts.join('</head>') + insert + '</head>' + last;
|
||||
}
|
||||
|
||||
context = {
|
||||
domain: app.get('url host'),
|
||||
permalink: handlers.editUrlForBin(bin, true)
|
||||
};
|
||||
|
||||
// Append attribution comment to header.
|
||||
app.render('comment', context, function (err, comment) {
|
||||
formatted = formatted.replace(/<html[^>]*>/, function ($0) {
|
||||
return $0 + '\n' + comment.trim();
|
||||
});
|
||||
return fn(err || null, err ? undefined : formatted);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
fn(null, formatted);
|
||||
}
|
||||
},
|
||||
NotFound: NotFound
|
||||
};
|
||||
return handlers;
|
||||
} else {
|
||||
fn(null, formatted);
|
||||
}
|
||||
},
|
||||
NotFound: NotFound
|
||||
};
|
||||
|
||||
@ -37,6 +37,36 @@ module.exports = {
|
||||
if (req.path !== '/' && req.path.slice(-1) === '/') {
|
||||
res.redirect(301, req.path.slice(0, -1));
|
||||
}
|
||||
next();
|
||||
};
|
||||
},
|
||||
helpers: function (app) {
|
||||
return function (req, res, next) {
|
||||
req.store = app.store;
|
||||
req.models = app.models;
|
||||
|
||||
req.helpers = {
|
||||
set: app.set.bind(app),
|
||||
render: app.render.bind(app),
|
||||
analytics: function (fn) {
|
||||
app.render('analytics', {id: req.helpers.set('analytics id')}, fn);
|
||||
},
|
||||
urlForBin: function (bin, full) {
|
||||
return app.set(full ? 'url full' : 'url prefix') + bin.url + '/' + bin.revision;
|
||||
},
|
||||
editUrlForBin: function (bin, full) {
|
||||
return req.helpers.urlForBin(bin, full) + '/edit';
|
||||
}
|
||||
};
|
||||
|
||||
Object.defineProperties(req.helpers, {
|
||||
production: {
|
||||
get: function () {
|
||||
return this.set('env') === this.PRODUCTION;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user