Permalink
Browse files

It's a new dawn. It's a new day. And a ton of changes.

  • Loading branch information...
1 parent a4b8185 commit 2710ff86e51296b792da9688587946d29ffc93dd @mape committed Aug 14, 2011
View
@@ -1,4 +1,4 @@
-Copyright (c) 2009 Mathias Pettersson, mape@mape.me
+Copyright (c) 2011 Mathias Pettersson, mape@mape.me
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
View
@@ -1,24 +1,45 @@
# node-express-boilerplate
-## Short video showing the features
-[![Youtube](http://mape.me/node-boilerplate.png "Youtube video")](http://www.youtube.com/watch?v=esKYgej26dw)
+node-express-boilerplate gives the developer a clean slate to start with while bundling enough useful features so as to remove all those redundant tasks that can derail a project before it even really gets started.
-## What does node-express-boilerplate do?
+## So what does node-express-boilerplate do
-node-express-boilerplate is a collection of neat things that makes prototyping dummy frontend fast and streamlined. No more tab+refreshing to see css, javascript eller markup.
+<img src="http://s3.amazonaws.com/files.posterous.com/temp-2011-07-27/aCjCnipkcvcbdcpEcBopytIkxnGHlyiFkcHuwfoknhkJxGkeDJorbcitmcnt/express-boiler.png.scaled500.png?AWSAccessKeyId=AKIAJFZAE65UYRT34AOQ&Expires=1313339089&Signature=L%2F5TL5evSP4KQqWkeZgGVlA9Xs4%3D">
-* Bundled with socket.io server and socket.io client files (with flashsocket fallback).
-* Auto updates the CSS without pageload when CSS files change.
-* Auto refreshes the page when markup or javascript files change.
-* Auto matches urls to templates without server changes. Visiting /file-name/ tries to serve file-name.ejs and has fallback to index.ejs.
-* Use the Sync button to redirect all browsers to the current page. This is handy when you want to test the design on multiple browsers and don't want to navigate to the page with each browser.
-* The Validate button shows the current validation status of the current page. If it is green the page validates, if it is red it shows how many errors there are and if you click it you get a list of what errors are present and where they are.
-* Overlay images to have a design reference.
+First of all, it is very easy to understand, allowing you to start using it right away. There is minimal need to dig around in files just to get a good idea of how things work. And if you don't like how the boiler plate is set up, just fork it and change it according to your own personal preferences.
-## Requires
- npm install connect
- npm install connect-assetmanager
- npm install connect-assetmanager-handlers
- npm install express
- npm install ejs
- npm install socket.io
+### Features include:
+
+* Bundling [socket.io](http://socket.io/) and integrating with the [express](https://github.com/visionmedia/express) session store so data can be shared
+* Providing premade hooks to [authenticate](https://github.com/bnoguchi/everyauth) users via facebook/twitter/github
+* An [assetmanager](https://github.com/mape/connect-assetmanager/) that concatenates/mangles/compresses your CSS/JS assets to be as small and fast to deliver as possible, as well as cache busting using MD5 hashes
+* Auto updates of the browser (inline/refresh) as soon as CSS/JS/template-files are changed in order to remove all those annoying “save, tab, refresh” repetitions
+* [Notifications](http://notifo.com/) to your computer/mobile phone on certain user actions (This is something I relied heavily on last year when he was involved in NKO; as soon as a new game was started I knew about it and could jump in and interact - nobody enjoys something social if they are stuck there alone.)
+* Sane defaults in regards to productions/development environments
+* Logs errors to [Airbrakeapp.com](http://airbrakeapp.com/pages/home) in order to track any errors users are encountering
+* Auto matching of urls to templates without having to define a specific route (such as, visiting /file-name/ tries to serve file-name.ejs and fallbacks to index.ejs - this is helpful for quick static info pages)
+
+## Install on dev machine
+* git clone https://github.com/mape/node-express-boilerplate myproject
+* cd myproject
+* npm install
+* cp siteConfig.sample.js siteConfig.js
+* mate siteConfig.js # update config for your use case
+* nodemon server.js
+
+## Install on no.de
+* First on node machine
+ * ssh node@yourname.no.de
+ * pkgin update; pkgin install redis
+ * svccfg import /opt/local/share/smf/manifest/redis.xml
+ * svcadm enable redis
+
+* Then on local machine
+ * git clone [http://github.com/mape/node-express-boilerplate](http://github.com/mape/node-express-boilerplate) myproject
+ * cd myproject
+ * cp siteConfig.sample.js siteConfig.js
+ * edit siteConfig.js settings
+ * scp siteConfig.js node@yourname.no.de:~
+ * git remote add joyent node@yourname.no.de:repo
+ * git push joyent master
+ * open http://yourname.no.de
View
@@ -0,0 +1,91 @@
+var everyauth = require('everyauth');
+var https = require('https');
+module.exports = function Server(expressInstance, siteConf) {
+ everyauth.debug = siteConf.debug;
+
+ everyauth.everymodule.handleLogout( function (req, res) {
+ delete req.session.user;
+ req.logout();
+ res.writeHead(303, { 'Location': this.logoutRedirectPath() });
+ res.end();
+ });
+
+ // Facebook
+ if (siteConf.external.facebook) {
+ everyauth.facebook
+ .appId(siteConf.external.facebook.appId)
+ .appSecret(siteConf.external.facebook.appSecret)
+ .findOrCreateUser(function (session, accessToken, accessTokenExtra, facebookUserMetaData) {return true;})
+ .redirectPath('/');
+ }
+
+ // Twitter
+ if (siteConf.external.twitter) {
+ everyauth.twitter
+ .myHostname(siteConf.uri)
+ .consumerKey(siteConf.external.twitter.consumerKey)
+ .consumerSecret(siteConf.external.twitter.consumerSecret)
+ .findOrCreateUser(function (session, accessToken, accessSecret, twitterUser) {return true;})
+ .redirectPath('/');
+ }
+
+ // Github
+ if (siteConf.external.github) {
+ everyauth.github
+ .myHostname(siteConf.uri)
+ .appId(siteConf.external.github.appId)
+ .appSecret(siteConf.external.github.appSecret)
+ .findOrCreateUser(function (session, accessToken, accessTokenExtra, githubUser) {return true;})
+ .redirectPath('/');
+ }
+
+ everyauth.helpExpress(expressInstance);
+
+ // Fetch and format data so we have an easy object with user data to work with.
+ function normalizeUserData() {
+ function handler(req, res, next) {
+ if (req.session && !req.session.user && req.session.auth && req.session.auth.loggedIn) {
+ var user = {};
+ if (req.session.auth.github) {
+ user.image = 'http://1.gravatar.com/avatar/'+req.session.auth.github.user.gravatar_id+'?s=48';
+ user.name = req.session.auth.github.user.name;
+ user.id = 'github-'+req.session.auth.github.user.id;
+ }
+ if (req.session.auth.twitter) {
+ user.image = req.session.auth.twitter.user.profile_image_url;
+ user.name = req.session.auth.twitter.user.name;
+ user.id = 'twitter-'+req.session.auth.twitter.user.id_str;
+ }
+ if (req.session.auth.facebook) {
+ user.image = req.session.auth.facebook.user.picture;
+ user.name = req.session.auth.facebook.user.name;
+ user.id = 'facebook-'+req.session.auth.facebook.user.id;
+
+ // Need to fetch the users image...
+ https.get({
+ 'host': 'graph.facebook.com'
+ , 'path': '/me/picture?access_token='+req.session.auth.facebook.accessToken
+ }, function(response) {
+ user.image = response.headers.location;
+ req.session.user = user;
+ next();
+ }).on('error', function(e) {
+ req.session.user = user;
+ next();
+ });
+ return;
+ }
+ req.session.user = user;
+ }
+ next();
+ }
+ return handler;
+ }
+
+ return {
+ 'middleware': {
+ 'auth': everyauth.middleware
+ , 'normalizeUserData': normalizeUserData
+ }
+ };
+};
View
@@ -1,120 +1,37 @@
var fs = require('fs');
module.exports = function(app, timestamps) {
- var timestamps = {
- 'content': 0
- , 'css': 0
- };
-
- var path = false;
- app.get('/reload-content/', function(req, res) {
- var timeoutCss;
- var timeoutContent;
- var timeoutPath;
- var reloadCss = timestamps.css;
- var reloadContent = timestamps.content;
-
- (function reload () {
- timeoutContent = setTimeout(function () {
- if (reloadContent < timestamps.content) {
- reloadContent = timestamps.content;
- res.send('content');
- reset();
- } else {
- reload();
- }
- }, 100);
- })();
-
- (function reload () {
- timeoutCss = setTimeout(function () {
- if (reloadCss < timestamps.css) {
- reloadCss = timestamps.css;
- res.send('css');
- reset();
- } else {
- reload();
- }
- }, 100);
- })();
-
- (function reload () {
- timeoutPath = setTimeout(function () {
- if (path) {
- res.send(path);
- reset();
- } else {
- reload();
- }
- }, 100);
- })();
-
- function reset() {
- if (timeoutCss) {
- clearTimeout(timeoutCss);
- }
- if (timeoutCss) {
- clearTimeout(timeoutPath);
- }
- if (timeoutContent) {
- clearTimeout(timeoutContent);
- }
- }
- });
-
- app.post('/reload-content/', function(req, res) {
- if (req.body.path) {
- path = req.body.path;
- setTimeout(function() {
- path = null;
- }, 1000);
- }
- res.send('');
- });
-
- var exec = require('child_process').exec;
- var crypto = require('crypto');
- var validateCache = {};
- function getValidationSource(filePath, source, res, cacheKey) {
- fs.writeFile(filePath, source, function (err) {
- exec('curl -F "content=<'+filePath+';type=text/html" -F "showsource=no" http://validator.mape.me', function (error, stdout, stderr) {
- if (stdout.indexOf('There were errors') === -1) {
- validateCache[cacheKey] = 'ok';
- res.send('ok');
- } else {
- validateCache[cacheKey] = stdout;
- res.send(stdout);
- }
- fs.unlink(filePath, function() {});
+ var self = this;
+ app.configure('development', function(){
+ // Only look for changes is development mode
+ fs.readdir(app.settings.views, function(err, files) {
+ files.forEach(function(file) {
+ fs.watchFile(app.settings.views+'/'+file, function (old, newFile) {
+ if (old.mtime.toString() != newFile.mtime.toString()) {
+ updatedContent();
+ }
+ });
});
});
- }
- app.post('/validate-content/', function(req, res) {
- if (req.body.source) {
- var cacheKey = crypto.createHash('md5').update(req.body.source).digest('hex');
- if (validateCache[cacheKey] === undefined) {
- var filePath = '/tmp/'+Date.now()+'.tmp';
- getValidationSource(filePath, req.body.source, res, cacheKey);
- } else {
- res.send(validateCache[cacheKey]);
- }
- } else {
- res.send('nope');
- }
- });
+ // Keep track of who wants updates
+ app.get('/reload-content/', function(req, res) {
+ queue.push(res);
+ });
- fs.readdir(app.settings.views, function(err, files) {
- files.forEach(function(file) {
- fs.watchFile(app.settings.views+'/'+file, function (old, newFile) {
- if (old.mtime.toString() != newFile.mtime.toString()) {
- timestamps.content = Date.now();
- }
- });
+ app.post('/reload-content/', function(req, res) {
+ if (req.body.path) {
+ queue.forEach(function(res) {
+ res.send(req.body.path);
+ });
+ }
+ res.send('');
});
});
- app.get(/.*/, function(req, res) {
+ // Catch all that fell through and serve a template that matches if possible,
+ // otherwise fallback to index template.
+ app.all(/.*/, function(req, res) {
var file = 'index';
var urlMatch = req.url.match(/\/([^\/]+)/);
@@ -124,11 +41,12 @@ module.exports = function(app, timestamps) {
} else {
urlMatch = '';
}
-
- fs.stat(app.settings.views+'/'+file+'.ejs', function (err, stats) {
+ var templateFile = app.settings.views+'/'+file+'.'+app.settings['view engine'];
+ fs.stat(templateFile, function (err, stats) {
if (err) {
file = 'index';
}
+ // Check to see if there is a image overlay coupled with the template.
var overlayPath = 'public/img/overlays/'+file+'.png';
var overlayCenteredPath = 'public/img/overlays/'+file+'-center.png';
fs.stat(overlayPath, function (err, stats) {
@@ -150,11 +68,37 @@ module.exports = function(app, timestamps) {
locals: {
'page': (urlMatch) ? '/'+urlMatch+'/' : '/'
, 'date': new Date().toString()
- , 'dummyHelperHtml': overlay ? '<div id="dummy-overlay" style="background-image: url(/img/overlays/'+overlay+');"></div>' : ''
+ , 'dummyHelperHtml': overlay ? '<div id="dummy-overlay-container"><div id="dummy-overlay" style="background-image: url(/img/overlays/'+overlay+');"></div></div>' : ''
}
});
}
});
- return timestamps;
+ var queue = [];
+ // Clean out the queue every now and then
+ setInterval(function() {
+ queue = queue.filter(function(res) {
+ if (!res.finished) {
+ return res;
+ }
+ });
+ }, 3000000);
+
+ function updatedContent(date) {
+ queue.forEach(function(res) {
+ res.send('content');
+ });
+ queue = [];
+ }
+ function updatedCss(date) {
+ queue.forEach(function(res) {
+ res.send('css');
+ });
+ queue = [];
+ }
+
+ return {
+ 'updatedContent': updatedContent
+ , 'updatedCss': updatedCss
+ };
};
View
@@ -0,0 +1,14 @@
+var siteConfig;
+try {
+ // Usually we check for siteConfig.js in project root.
+ siteConfig = require('../siteConfig');
+} catch(e) {
+ try {
+ // Looks for siteConfig in home dir, used for no.de
+ siteConfig = require(process.env.HOME+'/siteConfig');
+ } catch(e) {
+ throw new Error('Could not load site config.')
+ }
+}
+
+module.exports = siteConfig;
Oops, something went wrong.

0 comments on commit 2710ff8

Please sign in to comment.