Skip to content

Instantly share code, notes, and snippets.

@dopa
Created June 20, 2012 18:46
Show Gist options
  • Save dopa/2961510 to your computer and use it in GitHub Desktop.
Save dopa/2961510 to your computer and use it in GitHub Desktop.
memecube server.js
/*jshint forin:true, noarg:true, noempty:true, eqeqeq:true, bitwise:true, strict:false, undef:true, curly:true, node:true, indent:4, maxerr:50, white:false, laxcomma:true, supernew:false */
/*global mongoose */
/*global io */
/*global req */
/*global res */
/*global JSONRES */
var express = require('express')
, conf = require('./conf')
, url = require('url')
, moment = require('moment')
, _ = require('underscore')._
//, request = require('request')
, mongooseAuth = require('mongoose-auth')
, everyauth = require('everyauth') //https://github.com/bnoguchi/mongoose-auth/blob/master/example/server.js
, Promise = everyauth.Promise
, RedisStore = require('connect-redis')(express) // http://www.hacksparrow.com/use-redisstore-instead-of-memorystore-express-js-in-production.html
, log = require('book').default() // https://github.com/shtylman/node-logger
, NotFound = require('./error').NotFound // https://github.com/shtylman/expressjs-tips-tricks/blob/master/error_handler.js
, BadRequest = require('./error').BadRequest
, kProduction = false;
//global mongoose
mongoose = require('mongoose');
Schema = mongoose.Schema;
ObjectId = mongoose.SchemaTypes.ObjectId;
JSONRES = { successful: false, errors: [], warnings: [], messages: [], data: {} };
// move this elsewhere
getJSONRes = function(successful,data,errors,warnings,messages) {
var jr = JSON.parse(JSON.stringify(JSONRES));
if( typeof successful !== 'undefined' ) { jr.successful = successful; }
if( typeof data !== 'undefined' ) { jr.data = data; }
if( typeof errors !== 'undefined' ) { jr.errors = errors; }
if( typeof warnings !== 'undefined' ) { jr.warnings = warnings; }
if( typeof messages !== 'undefined' ) { jr.messages = messages; }
return jr; // return JSON.stringify(jr);
};
// Required Modules
require('coffee-script');
require('express-namespace');
// Configuration
process.env.NODE_ENV = process.env.NODE_ENV || 'development';
var env = process.env.NODE_ENV;
kProduction = env === 'production';
everyauth.debug = true;
require('./everyauth-loader.js');
/*console.log(env);
console.log(conf.hostname);
console.log(conf.hostname[env]);*/
/* mongoose-auth :: begin -- https://github.com/bnoguchi/mongoose-auth/blob/master/example/server.js */
var UserSchema = new Schema({
role: { type: String, default: 'default' }
, gameuser: Schema.Types.Mixed // {} works too
, gameplayer: Schema.Types.Mixed // {} works too
, gamePointsTotal: { type: Number, default: 0 }
, gamePointsToday: { type: Number, default: 0 }
, gamePointsDay1: { type: Number, default: 0 }
, gamePointsDay2: { type: Number, default: 0 }
});
UserSchema.plugin(mongooseAuth, {
everymodule: {
everyauth: {
User: function () {
return User;
}
}
}
, twitter: {
everyauth: {
myHostname: conf.hostname[env] // 'http://memecube-gsummit.jit.su'
, consumerKey: conf.twit.consumerKey
, consumerSecret: conf.twit.consumerSecret
, redirectPath: '/auth/success'
}
}
});
mongoose.model('Org', require('./models/org')); // ex: Gamification.co
Meeting = mongoose.model('Meeting', require('./models/meeting')); // ex: GSummit 2012
mongoose.model('User', UserSchema);
mongoose.model('Activity', require('./models/activity'));
mongoose.model('Reward', require('./models/reward'));
mongoose.model('Tweet', require('./models/tweet'));
mongoose.model('Retweet', require('./models/retweet'));
mongoose.model('Mention', require('./models/mention'));
Event = mongoose.model('Event', require('./models/event')); // Same as talks or sessions
mongoose.model('Speaker', require('./models/speaker'));
mongoose.model('Rating', require('./models/rating'));
mongoose.model('Settings', require('./models/settings'));
Checkin = mongoose.model('Checkin', require('./models/checkin'));
Metric = mongoose.model('Metric', require('./models/metric'));
mongoose.model('Leaderboard', require('./models/leaderboard'));
/*var db = mongoose.connect('mongodb://'+conf.mongo.hostname[env]+':'+conf.mongo.port[env]+'/' + conf.mongo.dbname[env], function(err) {
if (err) { throw err; }
});*/
var db = mongoose.connect(conf.mongo.url[env]+conf.mongo.dbname[env], function(err) { if (err) { throw err; } });
User = mongoose.model('User');
/* mongoose-auth :: end */
var app = module.exports = express.createServer()
, connections = {}
, memeactions = require('./memeactions')(app, connections, conf)
, game = require('./game')(app, connections)
, routes; // = require('./routes')(app);
//global socket.io
io = require('socket.io').listen(app);
/* ability-js :: begin -- https://github.com/scottkf/ability-js/blob/master/index.js */
abilities = {
admin: {
index: ['all']
, settings: ['all']
, orgs: ['all']
, meetings: ['all']
, tweets: ['all']
, retweets: ['all']
, ratings: ['all']
, events: ['all']
, checkins: ['all']
, activities: ['all']
, pages: ['all']
, private: ['all']
, protected: ['all']
, public: ['all']
},
default: {
index: ['all']
, orgs: ['read'] // temp!!
, meetings: ['read'] // temp!!
, retweets: ['all']
, tweets: ['read', 'write']
, ratings: ['read', 'write']
, events: ['read']
, checkins: ['all']
, activities: ['read', 'write']
, settings: ['all']
, pages: ['read']
, protected: ['all']
, public: ['all']
},
banned: {
index: ['all']
, public: ['all']
, events: ['read']
, pages: ['read']
}
};
ability = require('ability')();
ability.add(abilities);
ability.configure({
redirect: false,
redirect_to: '/',
role_name: 'role'
});
/* ability-js :: end */
/* access and auth middleware :: begin */
function runAbility (req, res, next) {
// console.log('running ability');
if(authorize()) {
next();
} else {
res.redirect('/');
}
}
function runAuth (req, res, next) {
// console.log(req.user);
if(!req.user) {
//console.log('not authenticated')
res.redirect('/');
} else {
//console.log('authenticated')
next();
}
}
// var access = [runAbility];
// var auth = [runAuth];
// var accessauth = [runAbility, runAuth];
/* access and auth middleware :: end */
app.configure(function(){ // app.configure will be deprecated in Express 3.0. Just use if statement
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser());
//app.use(express.csrf());
// moved // app.use(mongooseAuth.middleware());
// app.use(checkAccess); //ability breaks here
// app.use(checkAuth); //auth will need to be made smarter so that it atleast lets you see public area
//app.use(app.router); //@Justin CHECK - seems not recommended to have this statement - https://github.com/bnoguchi/mongoose-auth | Beyond Schema Decoration: Routing
app.use(express.favicon(__dirname + '/public/favicon.ico', { maxAge: 864000 }));
app.use(express.static(__dirname + '/public'));
});
app.configure('development', function(){
app.use(express.session({ store: new RedisStore, secret: conf.redis.secret[env] }));
app.set('port', 3000);
app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
});
app.configure('test', function(){
app.use(express.session({ store: new RedisStore, secret: conf.redis.secret[env] }));
app.set('port', 3001);
app.use(express.errorHandler());
});
app.configure('beta', function(){
var redisUrl = url.parse(conf.redis.url[env]);
var redisAuth = redisUrl.auth.split(':');
app.use(express.session({ store: new RedisStore({ host: redisUrl.hostname, port: redisUrl.port, db: redisAuth[0], pass: redisAuth[1] }) , secret: conf.redis.secret[env] }));
app.set('port', 8124);
app.use(express.errorHandler());
});
app.configure('production', function(){
app.use(express.session({ store: new RedisStore, secret: conf.redis.secret[env] }));
app.set('port', 8124);
app.use(express.errorHandler());
// Don't crash on uncaught exceptions. (move to prod section!)
process.on('uncaughtException', function (err) {
console.error(err);
console.log("Node NOT Exiting...");
});
});
app.configure(function(){
app.use(mongooseAuth.middleware());
});
// Helpers
//require('./helpers')(app);
app.helpers({
_:_
});
// Dynamic Helpers
app.dynamicHelpers({
token: function(req, res) {
return req.session._csrf;
}
});
// Routes
require('./routes')(app);
require('./routes/users')(app, everyauth);
require('./routes/settings')(app, runAbility);
require('./routes/orgs')(app);
require('./routes/meetings')(app);
require('./routes/events')(app, runAbility);
//require('./routes/checkins')(app, runAbility); // This works but all methods have been moved to memeactions.js (sockets)
require('./routes/pages')(app, runAbility);
require('./routes/tweets')(app, runAbility);
require('./routes/retweets')(app, runAbility);
require('./routes/checkins')(app, runAbility);
require('./routes/ratings')(app, runAbility);
app.get('/mu-b34a7fdd-7cf66407-e0bcc925-1dc061ed', function(req, res){ res.send('42'); }); //blitz.io
app.get('/public', runAbility, function(req, res, next){
res.render('public', {locals: {
title: "Public"}});
});
app.get('/protected', runAbility, function(req, res, next) {
res.render('protected', {locals: {
title: "Protected"}});
});
app.get('/private', runAbility, function(req, res, next) {
res.render('private', {locals: {
title: "Private"}});
});
app.get('/pauth', runAuth, function(req, res, next) {
res.render('pauth', {locals: {
title: "Authenticated"}});
});
app.get('/logout', function (req, res, next) {
req.logout();
res.redirect('/');
});
app.get('/404', function(req, res, next) {
next(new NotFound());
});
app.get('/500', function(req, res, next) {
next(new Error('server error'));
});
// error handler is last middleware (doesn't have to be)
// make sure to use 4 arguments
// this is how express detects this is an error handler
app.use(function(err, req, res, next) {
var status = err.status || 500;
// switch on status, could do instance of checks
switch (status) {
case 400:
return res.send(err.message, status);
case 404:
return res.send('not found!', status);
}
// if req.accepts('json') or html
// can also choose to render pretty pages
// you can capture the url from req.url
// this is useful for tracking down certain errors
log.error(err, { url: req.url });
if (!kProduction) {
return res.send(err.stack);
}
return res.send(status);
});
app.dynamicHelpers({ messages: require('express-messages') });
ability.addHelpers(app);
mongooseAuth.helpExpress(app);
//Sockets
sessions = [];
//how to get express session id for socket.io -- it also has tips to load the full session from RedisStore
//http://www.danielbaulig.de/socket-ioexpress/
var parseCookie = require('connect').utils.parseCookie;
io.set('authorization', function (data, accept) {
if (data.headers.cookie) {
data.cookie = parseCookie(data.headers.cookie);
data.sessionID = data.cookie['connect.sid'];
} else {
return accept('No cookie transmitted.', false);
}
accept(null, true);
});
io.enable('browser client minification'); // send minified client
io.enable('browser client etag'); // apply etag caching logic based on version number
io.enable('browser client gzip'); // gzip the file
io.set('log level', 1); // reduce logging
io.set('transports', [ // enable all transports (optional if you want flashsocket)
'websocket'
, 'flashsocket'
, 'htmlfile'
, 'xhr-polling'
, 'jsonp-polling'
]);
io.sockets.on('connection', function (socket) {
// these are events coming FROM the client, we're listening for client events
//server - connect, disconnect, authenticate, navigate, retweet
//client - connected, disconnected, tweeted, arrived, left, retweeted
//socket.io help
// socket.emit --> sends to self
// socket.broadcast.emit --> sends to everyone but self
// io.sockets.emit --> sends to everyone including self
var socketId = socket.id
, sessionId = socket.handshake.sessionID;
if(!(_.contains(sessions, sessionId))) {
sessions.push(sessionId);
/*console.log('CONNECTED to socketId:' + socketId);
console.log('sessionId: ' + sessionId);*/
socket.emit('connected', { socketId: socketId, sessionId: socket.handshake.sessionID });
socket.broadcast.emit('connected', { socketId: socketId, sessionId: socket.handshake.sessionID }); // tell everybody else
} else {
/*console.log('RE-CONNECTED to socketId:' + socketId);
console.log('sessionId: ' + sessionId);*/
//socket.emit('connected', { message: 'socket id:' + socketId + ' | session id:' + sessionId});
socket.emit('connected', { socketId: socketId, sessionId: socket.handshake.sessionID });
}
//connections[socket.handshake.sessionID] = socket;
// We might want to keep a separate hash keyed by sessionId for some reason, but connections
// is for finding a specific authenticated user's socket, therefore must be keyed by userId
socket.on('disconnect', function () {
/*console.log('DISCONNECTED connection from socketId:' + socketId);
console.log('sessionId: '+sessionId);*/
//delete connections[sessionId];
//delete connections[socket.handshake.sessionID];
delete connections[socket.userId];
socket.broadcast.emit('peerdisconnected', { message: 'client disconnected' });
});
// http://psitsmike.com/2011/10/node-js-and-socket-io-multiroom-chat-tutorial/
socket.on('authenticate', function(data) {
/*console.log('AUTHENTICATED connection from socketId:' + socketId);
console.log('sessionId: '+sessionId);
console.log(data);*/
//connections[sessionId] = socket; // NO - this should happen on connection, not authentication
//console.log(userId,"authenticated");
var room = '';
socket.userId = data.userId;
connections[socket.userId] = socket; // Must be keyed on userId - that's the whole point
socket.room = room;
socket.join(room);
});
socket.on('navigate', function(data) {
var rooms = io.sockets.manager.rooms
, oldroom = socket.room
, newroom = data.room
, oldroomcount = 0
, newroomcount = 0;
//console.log('navigated connection from socket:' + socketId + ' | session id:' + sessionId);
socket.leave(oldroom);
socket.join(newroom);
socket.room = newroom;
if(!_.isUndefined(oldroom) && !_.isNull(oldroom) && !_.isEmpty(oldroom)) {
var _oldroom = rooms['/'+oldroom];
oldroomcount = !(_.isUndefined(_oldroom)) ? _oldroom.length : 0;
}
newroomcount = rooms['/'+newroom].length;
socket.emit('arrived','SERVER',{message: 'You\'ve arrived.', room: newroom, count: newroomcount});
socket.broadcast.to(oldroom).emit('left','SERVER',{message: 'User left.', room: oldroom, count: oldroomcount});
socket.broadcast.to(newroom).emit('arrived','SERVER',{message: 'User arrived.', room: newroom, count: newroomcount});
});
/* *************************************** */
/* incoming socket events from the browser */
/* *************************************** */
socket.on('connect_gameplayer', function(securityData) {
//console.log('Received socket event connect_gameplayer');
if( !socket.hasOwnProperty('userId') ) { return; }
var sreq = {socket: socket, user: {_id: socket.userId}, sessionId: socket.handshake.sessionID, securityData: securityData };
game.connectGameUser(sreq);
});
socket.on('refresh_gamepoints', function(securityData) {
if( !socket.hasOwnProperty('userId') ) { return; }
//if( typeof req === 'undefined' || !req.hasOwnProperty('user') ) { return; }
var sreq = {socket: socket, user: {_id: socket.userId}, sessionId: socket.handshake.sessionID, securityData: securityData };
game.getPlayerInfo(sreq);
});
socket.on('register_gameactivity', function(securityData) { // used for some in-game actions (check-in?)
if( !socket.hasOwnProperty('userId') ) { return; }
//if( typeof req === 'undefined' || !req.hasOwnProperty('user') ) { return; }
var sreq = {socket: socket, user: {_id: socket.userId}, sessionId: socket.handshake.sessionID, securityData: securityData };
game.registerActivity(sreq);
});
/*socket.on('toggle_twitter_enabled', function() {
memeactions.toggleTwitterEnabled(socket);
});*/
socket.on('check_in', function(data) {
//console.log('Received socket event check_in');
if( !socket.hasOwnProperty('userId') ) { return; }
var sreq = {socket: socket, user: {_id: socket.userId}, sessionId: socket.handshake.sessionID, securityData: data.securityData };
memeactions.checkIn(sreq, {}, data.eventId, game.registerActivity_CheckIn, game);
});
socket.on('check_out', function(data) {
if( !socket.hasOwnProperty('userId') ) { return; }
var sreq = {socket: socket, user: {_id: socket.userId}, sessionId: socket.handshake.sessionID, securityData: data.securityData };
memeactions.checkOut(sreq, {}, data.eventId, game.registerActivity_CheckIn, game);
});
socket.on('create_tweet', function(data) {
//console.log('Socket Event Received: create_tweet');
if( !socket.hasOwnProperty('userId') ) { return; }
var sreq = {socket: socket, user: {_id: socket.userId}, sessionId: socket.handshake.sessionID, securityData: data.securityData };
memeactions.createTweet(sreq, {}, data.newTweet, game.registerActivity_Tweet, game); //registerActivity_ReceiveMention
});
socket.on('create_retweet', function(data) {
if( !socket.hasOwnProperty('userId') ) { return; }
var sreq = {socket: socket, user: {_id: socket.userId}, sessionId: socket.handshake.sessionID, securityData: data.securityData };
memeactions.createRetweet(sreq, {}, data.tweetId, game.registerActivity_Retweet, game);
});
socket.on('refresh_tweet_count', function(data) {
//console.log('refresh_tweet_count receieved for event ' + data.eventId + ' to ' + data.count);
Event.update({_id: data.eventId}, {tweetCount: data.count}, {multi: true}, function(err, ev){
// if(err) { throw err; }
//console.log("updated in db, now sending socket update");
io.sockets.in('pageshome').emit('refresh_tweet_count', 'SERVER', {eventId: data.eventId, count: data.count});
});
});
socket.on('refresh_retweet_count', function(data) {
//console.log('refresh_retweet_count receieved for event ' + data.eventId + ' to ' + data.count);
Event.update({_id: data.eventId}, {retweetCount: data.count}, {multi: true}, function(err, ev){
if(err) { throw err; }
//console.log("updated in db, now sending socket update");
io.sockets.in('pageshome').emit('refresh_retweet_count', 'SERVER', {eventId: data.eventId, count: data.count});
});
});
socket.on('refresh_checkin_count', function(data) {
//console.log('refresh_checkin_count receieved for event ' + data.eventId + ' to ' + data.count);
Event.update({_id: data.eventId}, {checkinCount: data.count}, {multi: true}, function(err, ev){
// if(err) { throw err; }
//console.log("updated in db, now sending socket update");
io.sockets.in('pageshome').emit('refresh_checkin_count', 'SERVER', {eventId: data.eventId, count: data.count});
});
});
socket.on('register_rating', function(data) {
//console.log('register_rating socket event received');
if( !socket.hasOwnProperty('userId') ) { return; }
var sreq = {socket: socket, user: {_id: socket.userId}, sessionId: socket.handshake.sessionID, securityData: data.securityData };
memeactions.registerRating(sreq, {}, data.eventId, data.rating, game.registerActivity_Rating, game);
});
socket.on('refresh_user_rating', function(data) {
//console.log('refresh_user_rating socket event received eventId' + data.eventId);
if( !socket.hasOwnProperty('userId') && !socket.hasOwnProperty('eventId') ) { return; }
var sreq = {socket: socket, user: {_id: socket.userId}, sessionId: socket.handshake.sessionID, securityData: data.securityData };
memeactions.refreshUserRating(sreq, {}, data.eventId, data.userId);
});
socket.on('refresh_game_activity', function(data) {
//console.log('refresh_game_activity socket event received');
if( !socket.hasOwnProperty('userId') ) { return; }
var sreq = {socket: socket, user: {_id: socket.userId}, sessionId: socket.handshake.sessionID, securityData: data.securityData };
game.refreshUserActivity(sreq, {});
});
// socket.on('retweet', function(data) {
// var room = socket.room
// , count = io.sockets.manager.rooms['/'+socket.room].length;
// socket.emit('retweeted','SERVER',{message: 'You retweeted.', room: room, count: count, rtcount: data});
// socket.broadcast.to(room).emit('retweeted','SERVER',{message: 'User retweeted.', room: room, count: count, rtcount: data});
// });
});
// start a timer to fire update to event rooms every 1 min
/*
var limit=10//config.limit,
interval=5//config.interval,
all_d=[]; // use all_d to hold config.limit number of data sets for initial connections
(function schedule() {
setTimeout( function () {
// var ts=(new Date()).getTime();
// var now = moment();
//note: all moment objets are mutable so we need to clone them before we do any manipulation
var displayTimeFormat = 'YYYY-MM-DD h:mm:ss a z'
var now = moment.utc();
var nowreal = moment.utc(now);
//[GA] HARDCODE - WE WILL HAVE TO FAKE THE # OF DAYS TO THE GSUMMIT - CAN GO IN SOME SORT OF CONF OR DB
var day1 = moment.utc(conf.app.day1)
, day2 = moment.utc(conf.app.day2);
now = moment.utc(day1).add('hours', 14).add('minutes',10);
// now.add('days', 10).subtract('minutes', 220);
var justnow = moment.utc(now).subtract('minutes', 1).seconds(0);
var justnowreal = moment.utc(nowreal).subtract('minutes', 1).seconds(0);
//console.log('---------------------------------------------------------------');
//console.log("hi, this is the scheduler. the time is:"+ now.format(displayTimeFormat) + " and it was:" + justnow.format(displayTimeFormat) + " just now");
//console.log("hi, this is the scheduler. the time is really:"+ nowreal.format(displayTimeFormat) + " and it was:" + justnowreal.format(displayTimeFormat) + " just now real");
Meeting
.findOne({name: 'GSummit 2012'}, function(err, meeting) { if(err) { throw err; }
Event.find({_meeting: meeting._id, startDateTime: {$lte: justnow}, endDateTime: {$gte: justnow} })
.asc('startDateTime')
.limit(100)
.run(function(err, events) { if(err) { throw err; }
//console.log("yayyyy! i found " + events.length + " events going on!");
for(var i=0;i<events.length;i++) {
var event = events[i];
console.log(event._id + " | " + event.name + " from " + moment.utc(event.startDateTime).format(displayTimeFormat) + " to " + moment.utc(event.endDateTime).format(displayTimeFormat));
Metric
.findOne({_event: event._id, date: {$lte: justnowreal}})
.desc('date')
.populate("_event")
.run(function(err, metric) { if(err) { throw err; }
if(metric) {
var metricDate = moment.utc(metric.date);
var justNowRealDate = moment.utc(justnowreal).seconds(0);
//[GA] HARDCODE - FOR 4 HOURS - ONLY TEMP AS I CANT UNDERTAND WHY THIS IS NOT WORKING - TIMEZONE ISSUE - IT WAS EARLEIR WITHOUT THIS
var minuteDiff = justNowRealDate.diff(metricDate, 'minutes') - (4*60);
console.log(metricDate, justNowRealDate, minuteDiff);
if(minuteDiff == 0) {
console.log("sending metric push notification to room "+ metric._event.name + " events"+metric._event._id+" | tc:"+ metric.tweetCount);
io.sockets.in('events'+metric._event._id).emit('metric_update', 'SERVER', {message: 'Metric Push Notification', room: 'events'+metric._event._id, date: metricDate, tweetCount: metric.tweetCount, retweetCount: metric.retweetCount, checkinCount: metric.checkinCount});
} else {
console.log("sending metric push notification to room "+ metric._event.name + " events"+metric._event._id+" | tc:"+ 0);
io.sockets.in('events'+metric._event._id).emit('metric_update', 'SERVER', {message: 'Metric Push Notification', room: 'events'+metric._event._id, date: metricDate, tweetCount: 0, retweetCount: 0, checkinCount: 0});
}
}
});
}
});
});
schedule();
}, interval*1000);
})();*/
app.listen(app.settings.port);
// console.log("Express server listening on port %d in %s mode", app.address().port, env);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment