Merge branch 'master' into feature/socket.io-mvc

Conflicts:
	lib/app.js
	templates/base/development.js
	templates/base/environment.js
	templates/base/production.js
This commit is contained in:
Techwraith
2012-11-02 21:35:43 -07:00
15 changed files with 435 additions and 166 deletions

View File

@@ -135,6 +135,7 @@ if (cmds.length) {
if (!(cmds[0] == 'jake' ||
cmds[0] == 'secret' ||
cmds[0] == 'db:init' ||
cmds[0] == 'auth' ||
cmds[0] == 'console')
&& !cmds[1]) {
throw new Error(cmds[0] + ' command requires another argument.');
@@ -171,6 +172,10 @@ if (cmds.length) {
// Create DBs
cmd += 'console:start[' + (cmds[1] || 'development') + ']';
break;
case 'auth':
// Create DBs
cmd += 'auth:init';
break;
case 'db:init':
// Create DBs
cmd += 'db:init';

View File

View File

@@ -25,14 +25,17 @@ var fs = require('fs')
, cwd = process.cwd()
, errors = require('./response/errors')
, response = require('./response')
, utils = require('utilities')
, init = require('./init')
, helpers = require('./template/helpers')
, actionHelpers = require('./template/helpers/action')
, BaseController = require('./base_controller').BaseController
, BaseController = require('./controller/base_controller').BaseController
, ErrorController = require('./controller/error_controller').ErrorController
, StaticFileController =
require('./controller/static_file_controller').StaticFileController
, sessions = require('./sessions')
, CookieCollection = require('./cookies').CookieCollection
, Request = require('./request').Request
, Response = response.Response
, InFlight = require('./in_flight').InFlight
, i18n = utils.i18n
, usingCoffee; // Global variable for CoffeeScript
@@ -319,6 +322,7 @@ var App = function () {
geddy.server.addListener('request', function (req, resp) {
var reqUrl
, reqObj
, respObj
, params
, urlParams
, method
@@ -359,6 +363,8 @@ var App = function () {
// Buffered request-obj -- buffer the request data,
// and pass this proxy object to the controller
reqObj = new Request(req);
// Wrapped response-obj
respObj = new Response(resp);
finish = function (step) {
steps[step] = true;
@@ -396,18 +402,19 @@ var App = function () {
inFlightId = geddy.inFlight.addEntry({
request: req
, method: method
, response: resp
, response: respObj
, accessTime: accessTime
});
req._geddyId = inFlightId;
resp._geddyId = inFlightId;
reqObj._geddyId = inFlightId;
respObj._geddyId = inFlightId;
// FIXME: Holy shit, all inline here
resp.addListener('finish', function () {
var id = resp._geddyId
// Probably should proxy events for wrapped Response obj
respObj.resp.addListener('finish', function () {
var id = respObj._geddyId
, entry = geddy.inFlight.getEntry(id)
, req = entry.request
, stat = resp.statusCode
, stat = respObj.statusCode
, endTime = new Date()
, level = parseInt(stat, 10);
@@ -424,7 +431,7 @@ var App = function () {
'"' + entry.method + ' ' + reqUrl + ' ' +
req.httpVersion + '" ' +
stat + ' ' +
(resp._length || '-') + ' ' +
(respObj._length || '-') + ' ' +
'"' + (req.headers['referer'] || '-') + '" ' +
'"' + (req.headers['user-agent'] || '-') + '" ');
@@ -529,13 +536,10 @@ var App = function () {
controller.app = self;
controller.request = reqObj;
// Save a reference to the controller on the req to allow lookup
// when running filters in Connect-middleware compat-mode. Sometimes
// these functions are pre-bound, so "this" can't reference our
// controller
reqObj.controller = controller;
controller.response = respObj;
respObj.controller = controller;
controller.response = resp;
controller.method = method;
controller.params = params;
controller.name = params.controller;
@@ -543,7 +547,7 @@ var App = function () {
// We have a controller instance; register it with the
// in-flight data
geddy.inFlight.updateEntry(req._geddyId, {
geddy.inFlight.updateEntry(reqObj._geddyId, {
controller: controller
});
@@ -563,18 +567,16 @@ var App = function () {
else {
err = new errors.InternalServerError('No ' + params.action +
' action on ' + params.controller + ' controller.');
errResp = new response.Response(resp);
errResp.send(err.message, err.statusCode,
{'Content-Type': 'text/html'});
controller = new ErrorController(err, reqObj, respObj);
controller.respond();
}
}
// no controller, 500 error
else {
err = new errors.InternalServerError('controller ' +
params.controller + ' not found.');
errResp = new response.Response(resp);
errResp.send(err.message, err.statusCode,
{'Content-Type': 'text/html'});
controller = new ErrorController(err, reqObj, respObj);
controller.respond();
}
}
// Either static, 404, or 405
@@ -597,14 +599,16 @@ var App = function () {
// this point
if (fs.statSync(p).isDirectory()) {
// TODO: Make the name of any index file configurable
p = path.join(p, 'index.html');
if (utils.file.existsSync(p) && fs.statSync(p).isFile()) {
staticResp.sendFile(p);
staticPath = path.join(staticPath, 'index.html');
if (utils.file.existsSync(staticPath) && fs.statSync(staticPath).isFile()) {
controller = new StaticFileController(staticPath, reqObj, respObj);
controller.respond();
}
}
// Path to an actual file. Just serve it up
else if (fs.statSync(p).isFile()) {
staticResp.sendFile(p);
else if (fs.statSync(staticPath).isFile()) {
controller = new StaticFileController(staticPath, reqObj, respObj);
controller.respond();
}
};
@@ -616,9 +620,8 @@ var App = function () {
else if (staticUrl.match('partials/') && utils.file.existsSync(partialsPath)) {
sendStaticFile(partialsPath);
}
// Must be a 404 or 405
// 405?
else {
// 405?
nonMethodRoutes = router.all(reqUrl);
if (nonMethodRoutes.length) {
// build a unique list of acceptable methods for this resource
@@ -636,13 +639,12 @@ var App = function () {
errResp = new response.Response(resp);
errResp.send( err.message, err.statusCode, {'Content-Type': 'text/html'} );
// 404
}
else{
// 404
else {
err = new errors.NotFoundError(reqUrl + ' not found.');
errResp = new response.Response(resp);
errResp.send(err.message, err.statusCode,
{'Content-Type': 'text/html'});
controller = new ErrorController(err, reqObj, respObj);
controller.respond();
}
}
}

View File

@@ -9,6 +9,9 @@ config = {
, workers: 2
// Port to listen on
, port: 4000
// Default to no SSL and SPDY setup
, spdy: null
, ssl: null
// Set stdout to debug log-level
, debug: false
// Use worker-process rotation
@@ -44,11 +47,11 @@ config = {
defaultLocale: 'en-us'
, loadPaths: [path.normalize(cwd + '/config/locales')]
}
// Default to no SSL and SPDY setup
, spdy: null
, ssl: null
// Default to localhost `hostname`
, hostname: 'localhost'
// If not specifically set, will be constructed from
// protocol + '://' + hostname + ':' + port
, fullHostname: null
// Switch for making before/after filters compatible with
// Connect middleware
, connectCompatibility: false

View File

@@ -17,7 +17,9 @@ config = new (function () {
, fileBaseName
, useCoffee
, appBaseConfig
, appEnvConfig;
, appEnvConfig
, protocol
, fullHostname;
baseConfig = utils.mixin({}, require('./base_config'), true);
env = opts.environment || baseConfig.environment;
@@ -57,6 +59,19 @@ config = new (function () {
ret.rotateWorkers = false;
}
// Fix old null hostname configs
ret.hostname = ret.hostname || 'localhost';
// Construct fullHostname if not specifically set
if (!ret.fullHostname) {
protocol = ret.ssl ? 'https' : 'http';
fullHostname = protocol + '://' + ret.hostname;
if (ret.port != 80) {
fullHostname += ':' + ret.port;
}
ret.fullHostname = fullHostname;
}
return ret;
};

View File

@@ -16,8 +16,8 @@
*
*/
var crypto = require('crypto')
, response = require('./response')
, Templater = require('./template').Templater;
, response = require('../response')
, Templater = require('../template').Templater;
/**
@name controller
@@ -113,7 +113,6 @@ controller.BaseController.prototype = new (function () {
var _addFilter
, _execFilters
, _negotiateContent
, _doResponse
, _throwUndefinedFormatError
, _generateSameOriginToken
, _protectFromForgery;
@@ -285,48 +284,6 @@ controller.BaseController.prototype = new (function () {
};
};
_doResponse = function (stat, headers, content) {
// No repeatsies
if (this.completed) {
return;
}
this.completed = true;
var self = this
, r = new response.Response(this.response)
, action = this.action
, callback;
callback = function () {
// Set status and headers, can be overridded with after filters
if (self.cookies) {
headers['Set-Cookie'] = self.cookies.toArray();
}
r.setHeaders(stat, headers);
// Run after filters, then finish out the response
_execFilters.apply(self, [action, 'after', function () {
if (self.method == 'HEAD') {
r.finish();
}
else {
r.finalize(content);
}
}]);
};
if (this.session) {
// Save access time into session for expiry and
// - verifying sameOriginToken
this.session.set('accessTime', this.accessTime);
this.session.close(callback);
}
else {
callback();
}
};
_throwUndefinedFormatError = function () {
err = new geddy.errors.InternalServerError(
'Format not defined in response.formats.');
@@ -401,6 +358,48 @@ controller.BaseController.prototype = new (function () {
}
};
this._doResponse = function (stat, headers, content) {
// No repeatsies
if (this.completed) {
return;
}
this.completed = true;
var self = this
, r = this.response
, action = this.action
, callback;
callback = function () {
// Set status and headers, can be overridded with after filters
if (self.cookies) {
headers['Set-Cookie'] = self.cookies.toArray();
}
r.setHeaders(stat, headers);
// Run after filters, then finish out the response
_execFilters.apply(self, [action, 'after', function () {
if (self.method == 'HEAD') {
r.finish();
}
else {
r.finalize(content);
}
}]);
};
if (this.session) {
// Save access time into session for expiry and
// - verifying sameOriginToken
this.session.set('accessTime', this.accessTime);
this.session.close(callback);
}
else {
callback();
}
};
/*
*
* Public methods
@@ -502,7 +501,7 @@ controller.BaseController.prototype = new (function () {
}
}
_doResponse.apply(this, [302, { 'Location': url }, '']);
this._doResponse(302, { 'Location': url }, '');
};
this.error = function (err) {
@@ -555,7 +554,7 @@ controller.BaseController.prototype = new (function () {
, callback;
callback = function (formattedContent) {
_doResponse.apply(self, [200, {'Content-Type': contentType}, formattedContent]);
self._doResponse(200, {'Content-Type': contentType}, formattedContent);
};
// Error during content negotiation may result in an error response, so

View File

@@ -0,0 +1,24 @@
var errors = require('../response/errors')
, utils = require('utilities')
, BaseController = require('./base_controller.js').BaseController
, ErrorController;
ErrorController = function (err, req, resp) {
this.err = err;
this.request = req;
this.response = resp;
};
ErrorController.prototype = new BaseController();
ErrorController.prototype.constructor = ErrorController;
ErrorController.prototype.respond = function () {
var err = this.err;
this._doResponse(err.statusCode, {'Content-Type': 'text/html'},
err.message);
};
module.exports.ErrorController = ErrorController;

View File

@@ -0,0 +1,20 @@
var BaseController = require('./base_controller.js').BaseController
, StaticFileController;
StaticFileController = function (path, req, resp) {
this.path = path;
this.request = req;
this.response = resp;
};
StaticFileController.prototype = new BaseController();
StaticFileController.prototype.constructor = StaticFileController;
StaticFileController.prototype.respond = function () {
this.response.sendFile(this.path);
};
module.exports.StaticFileController = StaticFileController;

View File

@@ -1,23 +1,30 @@
var EventBuffer = require('utilities').EventBuffer
, EventEmitter = require('events').EventEmitter;
, EventEmitter = require('events').EventEmitter
, ServerRequest = require('http').IncomingMessage
, utils = require('utilities')
, parseQuery
, Request;
var Request = function (httpReq) {
var self = this
, reqProperties = [
'method'
, 'url'
, 'headers'
, 'trailers'
, 'httpVersion'
, 'connection'
];
this.buffer = new EventBuffer(httpReq);
reqProperties.forEach(function (prop) {
self[prop] = httpReq[prop];
});
parseQuery = function (url) {
var str = String(url);
var q = str.split('?')[1] || '';
return q ? utils.uri.objectify(q) : {};
};
Request.prototype = new EventEmitter();
Request = function (req) {
// Copy has-own props over from original
utils.mixin(this, req);
// Save original req obj
this.req = req;
// Set up buffering
this.buffer = new EventBuffer(req);
// Methods for so-called Connect-style middleware
this.query = parseQuery(req.url);
};
// Inherit from actual ServerRequest
Request.prototype = new ServerRequest();
Request.prototype.constructor = Request;
Request.prototype.sync = function () {

View File

@@ -17,8 +17,11 @@
*/
var fs = require('fs')
, ServerResponse = require('http').OutgoingMessage
, utils = require('utilities')
, errors = require('./errors')
, format = require('./format');
, format = require('./format')
, Response;
var response = new function () {
// From Paperboy, http://github.com/felixge/node-paperboy
@@ -58,11 +61,33 @@ var response = new function () {
}();
response.Response = function (resp) {
Response = function (resp) {
this.resp = resp;
// Copy has-own props over from original
utils.mixin(this, resp);
};
response.Response.prototype = new function () {
// Inherit from actual ServerResponse
Response.prototype = new ServerResponse();
Response.prototype.constructor = Response;
utils.mixin(Response.prototype, new (function () {
// Override, delegate
this.setHeaders = function (statusCode, headers) {
var contentType = headers['Content-Type'];
var charset = response.charsets[contentType];
if (charset) {
contentType += '; charset: ' + charset;
headers['Content-Type'] = contentType;
}
this.resp.statusCode = statusCode;
for (var p in headers) {
this.resp.setHeader(p, headers[p]);
}
};
// Custom methods
this.send = function (content, statusCode, headers) {
//var success = !errors.errorTypes[statusCode];
var s = statusCode || 200;
@@ -109,19 +134,6 @@ response.Response.prototype = new function () {
};
this.setHeaders = function (statusCode, headers) {
var contentType = headers['Content-Type'];
var charset = response.charsets[contentType];
if (charset) {
contentType += '; charset: ' + charset;
headers['Content-Type'] = contentType;
}
this.resp.statusCode = statusCode;
for (var p in headers) {
this.resp.setHeader(p, headers[p]);
}
};
this.writeBody = function (c) {
var content = c || ''
, resp = this.resp;
@@ -134,6 +146,22 @@ response.Response.prototype = new function () {
this.resp.end();
};
}();
// Methods for so-called Connect-style middleware
this.redirect = function () {
var stat, url;
// Express API, optional param comes first, WTF
if (arguments.length == 2) {
stat = arguments[0];
url = arguments[1];
}
else {
url = arguments[0];
}
this.controller.redirect(url);
};
})());
response.Response = Response;
module.exports = response;

View File

@@ -3,9 +3,56 @@ require('../lib/geddy')
// Dependencies
var fs = require('fs')
, path = require('path')
, fs = require('fs')
, path = require('path')
, utils = require('../lib/utils')
, Adapter = require('../lib/template/adapters');
, Adapter = require('../lib/template/adapters')
, getRouterPath
, addRoute;
getRouterPath = function () {
var beginPath
, jsRouter = path.normalize('config/router.js')
, coffeeRouter = path.normalize('config/router.coffee')
, routerPath;
// Check if the router file exists
beginPath = path.join(process.cwd(), 'config');
utils.file.searchParentPath(jsRouter, function (err) {
if (err) {
var jsErr = err;
// If jsEnvironment wasn't found, try finding coffee variant
utils.file.searchParentPath(coffeeRouter, beginPath, function (err) {
if (err) {
throw jsErr;
} else {
routerPath = coffeeRouter;
}
});
} else {
routerPath = jsRouter;
}
});
return routerPath;
};
addRoute = function (routerPath, newRoute) {
var text = fs.readFileSync(routerPath, 'utf8')
, routerArr;
// Don't add the same route over and over
if (text.indexOf(newRoute) == -1) {
// Add the new resource route just above the export
routerArr = text.split('exports.router');
routerArr[0] += newRoute + '\n';
text = routerArr.join('exports.router');
fs.writeFileSync(routerPath, text, 'utf8');
return true;
}
else {
return false;
}
};
namespace('env', function () {
task('init', function (environment) {
@@ -474,37 +521,11 @@ namespace('gen', function () {
options = options || {};
var names = utils.string.getInflections(name)
, routerPath = getRouterPath()
, routeType = options.bare ? 'Bare' : 'Resource'
, jsRouter = path.normalize('config/router.js')
, coffeeRouter = path.normalize('config/router.coffee')
, routerPath
, routerArr
, text
, splitText
, newRoute
, beginPath;
// Check if the router file exists
beginPath = path.join(process.cwd(), 'config');
utils.file.searchParentPath(jsRouter, function (err) {
if (err) {
var jsErr = err;
// If jsEnvironment wasn't found, try finding coffee variant
utils.file.searchParentPath(coffeeRouter, beginPath, function (err) {
if (err) {
throw jsErr;
} else {
routerPath = coffeeRouter;
}
});
} else {
routerPath = jsRouter;
}
});
, newRoute;
if (routerPath) {
text = fs.readFileSync(routerPath, 'utf8');
if (routerPath.match('.coffee')) {
if (options.bare) {
newRoute = 'router.match(\'/' + names.filename.plural +
@@ -523,15 +544,7 @@ namespace('gen', function () {
}
}
// Don't add the same route over and over
if (text.indexOf(newRoute) == -1) {
// Add the new resource route just above the export
routerArr = text.split('exports.router');
routerArr[0] += newRoute + '\n';
text = routerArr.join('exports.router');
fs.writeFileSync(routerPath, text, 'utf8');
if (addRoute(routerPath, newRoute)) {
console.log('[Added] ' + routeType + ' ' + names.filename.plural +
' route added to ' + routerPath);
}
@@ -539,10 +552,11 @@ namespace('gen', function () {
console.log(routeType + ' ' + names.filename.plural + ' route already defined in ' +
routerPath);
}
} else {
}
else {
console.log('There is no router file to add routes too');
}
});
task('views', function (name, options) {
@@ -745,3 +759,86 @@ namespace('gen', function () {
});
});
namespace('auth', function () {
task('init', {async: true}, function () {
var go = false
, packages = 'geddy-passport passport passport-local ' +
'passport-facebook passport-twitter'
, readline = require('readline')
, rl = readline.createInterface({
input: process.stdin
, output: process.stdout
})
, toBase = process.cwd()
, fromBase = path.join(toBase, 'node_modules', 'geddy-passport');
rl.setPrompt('This command will create/overwrite files in your app.\n' +
'Do you wish to continue? (yes|no)\n');
rl.prompt();
rl.addListener('line', function (line) {
if (line == 'yes') {
go = true;
}
rl.close();
});
rl.addListener('close', function () {
if (go) {
console.log('Installing ' + packages + '...');
jake.exec('npm uninstall ' + packages +
' && npm install ' + packages, function () {
var list = fs.readFileSync(path.join(fromBase, 'file_list.json'))
, routerPath = getRouterPath()
, newRoute;
list = JSON.parse(list.toString());
list.forEach(function (item) {
var from = path.join(fromBase, item)
, to = path.dirname(path.join(toBase, item));
jake.mkdirP(to);
console.log('Creating file: ' + item);
jake.cpR(from, to, {silent: true});
});
if (routerPath) {
if (routerPath.match('.coffee')) {
throw new Error(
'Geddy passport integration does not support CoffeeScript.');
}
else {
newRoute = "router.get('/login').to('Main.login');\n" +
"router.get('/logout').to('Main.logout');\n" +
"router.post('/auth/local').to('Auth.local');\n" +
"router.get('/auth/twitter').to('Auth.twitter');\n" +
"router.get('/auth/twitter/callback').to('Auth.twitterCallback');\n" +
"router.get('/auth/facebook').to('Auth.facebook');\n" +
"router.resource('users');";
if (addRoute(routerPath, newRoute)) {
console.log('Added authentication routes:\n' + newRoute);
}
else {
console.log('Authentication routes already defined in ' +
routerPath);
}
}
}
else {
console.log('There is no router file to add routes too');
}
console.log('Cleaning up...');
jake.exec('npm uninstall geddy-passport', function () {
console.log('Please set up your Passport options in config/environment.js');
complete();
});
}, {printStdout: true});
}
});
});
});

View File

@@ -23,7 +23,7 @@ var config = {
, port: 4000
, model: {
defaultAdapter: 'memory'
}
}
, sessions: {
store: 'memory'
, key: 'sid'

View File

@@ -1,4 +1,23 @@
var config = {
/*
metrics: {
port: 4001
}
*/
/* // For Passport auth via geddy-passport
, passport: {
successRedirect: '/'
, failureRedirect: '/login'
, twitter: {
consumerKey: 'XXXXX'
, consumerSecret: 'XXXXX'
}
, facebook: {
clientID: 'XXXXX'
, clientSecret: 'XXXXX'
}
}
*/
};
module.exports = config;

View File

@@ -20,21 +20,54 @@ var config = {
detailedErrors: false
, hostname: null
, port: 4000
/* TODO: Define a defaultAdapter. You can use postgresdb, memory, mongo or riak.
, model: {
defaultAdapter: 'mongo'
}
}
, db: {
mongo: {
dbname: 'local'
username: null
, dbname: 'production'
, prefix: null
, password: null
, host: 'localhost'
, port: 27017
}
}
*/
, sessions: {
store: 'cookie'
, key: 'sid'
, expiry: 14 * 24 * 60 * 60
/* // Using Postgres as the default, with only a Postgres DB
, model: {
defaultAdapter: 'postgres'
}
, db: {
postgres: {
user: process.env.USER
, database: process.env.USER
, password: null
, host: null
, port: 5432
}
}
*/
/* // Using Postgres as the default, with both Postgres and Riak
, model: {
defaultAdapter: 'postgres'
}
, db: {
postgres: {
user: process.env.USER
, database: process.env.USER
, password: null
, host: null
, port: 5432
}
, riak: {
protocol: 'http'
, host: 'localhost'
, port: 8098
}
}
*/
};
module.exports = config;

17
test/request.js Normal file
View File

@@ -0,0 +1,17 @@
var assert = require('assert')
, Request = require('../lib/request').Request
, ServerRequest = require('http').IncomingMessage
, tests;
tests = {
'create instance': function () {
var serverReq = new ServerRequest()
, req = new Request(serverReq);
assert.ok(req instanceof Request);
}
};
module.exports = tests;