From df983ec5fe7fd0a568817c0913b038d3c02e100d Mon Sep 17 00:00:00 2001 From: ciberch Date: Tue, 5 Jun 2012 10:27:44 -0700 Subject: [PATCH 1/2] Part 2 - Step 1 dependencies --- package.json | 3 + server.js | 213 +++++++++++++++++++++++++++++++++++++++++++++++--- siteConfig.js | 17 +++- 3 files changed, 219 insertions(+), 14 deletions(-) diff --git a/package.json b/package.json index f848be5..1c3a7ab 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,9 @@ "engines" : ["node"], "repository" : { "type":"git", "url":"http://github.com/mape/node-express-boilerplate" }, "dependencies" : { + "activity-streams-mongoose": ">=0.0.9", + "mongoose": "", + "underscore": "", "cloudfoundry": ">=0.1.0", "connect" : ">=1.6.0", "connect-assetmanager" : ">=0.0.21", diff --git a/server.js b/server.js index 9685ac2..7aa368a 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,7 @@ // Fetch the site configuration var siteConf = require('./lib/getConfig'); +var cf = require('cloudfoundry'); +var _ = require('underscore')._; process.title = siteConf.uri.replace(/http:\/\/(www)?/, ''); @@ -21,12 +23,50 @@ var assetHandler = require('connect-assetmanager-handlers'); var notifoMiddleware = require('connect-notifo'); var DummyHelper = require('./lib/dummy-helper'); +var mongoose = require('mongoose'); +mongoose.connect(siteConf.mongoUrl); + // Session store var RedisStore = require('connect-redis')(express); var sessionStore = new RedisStore(siteConf.redisOptions); +var asmsDB = require('activity-streams-mongoose')(mongoose, {full: false, redis: siteConf.redisOptions, defaultActor: '/img/default.png'}); + +var thisApp = new asmsDB.ActivityObject({displayName: 'Activity Streams App', url: siteConf.uri, image:{url: '/img/as-logo-sm.png'}}); +var thisInstance = {displayName: "Instance 0 -- Local"}; +if (cf.app) { + thisInstance.image = {url: '/img/cf-process.jpg'}; + thisInstance.url = "http://" + cf.host + ":" + cf.port; + thisInstance.displayName = "App Instance " + cf.app['instance_index'] + " at " + thisInstance.url; + thisInstance.content = cf.app['instance_id'] + //temp + console.log("Instance JSON is *******"); + console.dir(app); +} + +thisApp.save(function (err) { + if (err === null) { + var startAct = new asmsDB.Activity( + { + actor: {displayName: siteConf.user_email, image:{url: "img/me.jpg"}}, + verb: 'start', + object: thisInstance, + target: thisApp._id, + title: "started" + }); + + asmsDB.publish('firehose', startAct); + } +}); + var app = module.exports = express.createServer(); app.listen(siteConf.internal_port, null); +app.asmsDB = asmsDB; +app.siteConf = siteConf; +app.thisApp = thisApp; +app.thisInstance = thisInstance; +app.cookieName = "jsessionid"; //Use this name to get sticky sessions. Default connect name is 'connect.sid'; +// Cookie name must be lowercase // Setup socket.io server var socketIo = new require('./lib/socket-io-server.js')(app, sessionStore); @@ -97,7 +137,8 @@ app.configure(function() { app.use(express.cookieParser()); app.use(assetsMiddleware); app.use(express.session({ - 'store': sessionStore + 'key': app.cookieName + , 'store': sessionStore , 'secret': siteConf.sessionSecret })); app.use(express.logger({format: ':response-time ms - :date - :req[x-real-ip] - :method :url :user-agent / :referrer'})); @@ -169,16 +210,168 @@ function NotFound(msg){ Error.captureStackTrace(this, arguments.callee); } -// Routing -app.all('/', function(req, res) { - // Set example session uid for use with socket.io. +function getMetaData(req, res, next) { + req.objectTypes = ['person', 'group', 'stream']; + req.verbs = ['post', 'join']; + next(); +}; + +function loadUser(req, res, next) { + console.log("Request Session is"); + console.dir(req.session); + if (!req.session.uid) { req.session.uid = (0 | Math.random()*1000000); - } - res.locals({ - 'key': 'value' - }); - res.render('index'); + } else if (req.session.auth){ + if (req.session.auth.github) + req.providerFavicon = '//github.com/favicon.ico'; + else if (req.session.auth.twitter) + req.providerFavicon = '//twitter.com/favicon.ico'; + else if (req.session.auth.facebook) + req.providerFavicon = '//facebook.com/favicon.ico'; + } + var displayName = req.session.user ? req.session.user.name : 'UID: '+(req.session.uid || 'has no UID'); + var avatarUrl = ((req.session.auth && req.session.user.image) ? req.session.user.image : '/img/codercat-sm.jpg'); + req.user = {displayName: displayName, image: {url: avatarUrl}}; + next(); +} + +function getDistinctVerbs(req, res, next){ + req.usedVerbs = [] + asmsDB.Activity.distinct('verb', {streams: req.session.desiredStream}, function(err, docs) { + if (!err && docs) { + _.each(docs, function(verb){ + req.usedVerbs.push(verb); + }); + next(); + } else { + next(new Error('Failed to fetch verbs')); + } + }); +}; + +function getDistinctActors(req, res, next){ + req.usedActors = [] + asmsDB.Activity.distinct('actor', {streams: req.session.desiredStream}, function(err, docs) { + if (!err && docs) { + _.each(docs, function(obj){ + req.usedActors.push(obj); + }); + next(); + } else { + next(new Error('Failed to fetch actors')); + } + }); +}; + +function getDistinctObjects(req, res, next){ + req.usedObjects = [] + asmsDB.Activity.distinct('object', {streams: req.session.desiredStream}, function(err, docs) { + if (!err && docs) { + _.each(docs, function(obj){ + req.usedObjects.push(obj); + }); + next(); + } else { + next(new Error('Failed to fetch objects')); + } + }); +}; + +function getDistinctObjectTypes(req, res, next){ + req.usedObjectTypes = ['none'] + asmsDB.Activity.distinct('object.objectType', {streams: req.session.desiredStream}, function(err, docs) { + if (!err && docs) { + _.each(docs, function(objType){ + req.usedObjectTypes.push(objType); + }); + next(); + } else { + next(new Error('Failed to fetch objTypes')); + } + }); +}; + +function getDistinctActorObjectTypes(req, res, next){ + req.usedActorObjectTypes = ['none'] + asmsDB.Activity.distinct('actor.objectType', {streams: req.session.desiredStream}, function(err, docs) { + if (!err && docs) { + _.each(docs, function(objType){ + req.usedActorObjectTypes.push(objType); + }); + next(); + } else { + next(new Error('Failed to fetch actorobjTypes')); + } + }); +}; + +function getDistinctStreams(req, res, next){ + req.session.desiredStream = req.params.streamName ? req.params.streamName : "firehose"; + req.streams = {} + asmsDB.Activity.distinct('streams', {}, function(err, docs) { + if (!err && docs) { + _.each(docs, function(stream){ + req.streams[stream] = {name: stream, items: []}; + }); + next(); + } else { + next(new Error('Failed to fetch streams')); + } + }); +} + +// Routing +app.get('/', loadUser, getDistinctStreams, getDistinctVerbs, getDistinctActorObjectTypes, getDistinctObjects, + getDistinctActors, getDistinctObjectTypes, getMetaData, function(req, res) { + + asmsDB.getActivityStreamFirehose(20, function (err, docs) { + var activities = []; + if (!err && docs) { + activities = docs; + } + req.streams.firehose.items = activities; + res.render('index', { + currentUser: req.user, + providerFavicon: req.providerFavicon, + streams : req.streams, + desiredStream : req.session.desiredStream, + objectTypes : req.objectTypes, + verbs: req.verbs, + usedVerbs: req.usedVerbs, + usedObjects: req.usedObjects, + usedObjectTypes: req.usedObjectTypes, + usedActorObjectTypes: req.usedActorObjectTypes, + usedActors: req.usedActors + }); + }); + +}); + +app.get('/streams/:streamName', loadUser, getDistinctStreams, getDistinctVerbs, getDistinctObjects, getDistinctActors, + getDistinctObjectTypes, getDistinctActorObjectTypes, getDistinctVerbs, getMetaData, function(req, res) { + + asmsDB.getActivityStream(req.params.streamName, 20, function (err, docs) { + var activities = []; + if (!err && docs) { + activities = docs; + } + req.streams[req.params.streamName].items = activities; + res.render('index', { + currentUser: req.user, + providerFavicon: req.providerFavicon, + streams : req.streams, + desiredStream : req.session.desiredStream, + objectTypes : req.objectTypes, + verbs: req.verbs, + usedVerbs: req.usedVerbs, + usedObjects: req.usedObjects, + usedObjectTypes: req.usedObjectTypes, + usedActorObjectTypes: req.usedActorObjectTypes, + usedActors: req.usedActors + }); + }); + }); // Initiate this after all other routing is done, otherwise wildcard will go crazy. @@ -189,4 +382,4 @@ app.all('*', function(req, res){ throw new NotFound; }); -console.log('Running in '+(process.env.NODE_ENV || 'development')+' mode @ '+siteConf.uri); \ No newline at end of file +console.log('Running in '+(process.env.NODE_ENV || 'development')+' mode @ '+siteConf.uri); diff --git a/siteConfig.js b/siteConfig.js index cec0aab..15df1fb 100644 --- a/siteConfig.js +++ b/siteConfig.js @@ -1,11 +1,14 @@ var cf = require('cloudfoundry'); var settings = { - 'sessionSecret': 'sessionSecret-238273283abs' - , 'internal_host' : '127.0.0.1' - , 'internal_port' : 8080 + 'user_email' : 'mwilkinson@vmware.com', + 'sessionSecret': 'sessionSecret' + , 'internal_host' : '127.0.0.1' + , 'internal_port' : 8080 , 'port': 8080 , 'uri': 'http://moni-air.local:8080' // Without trailing / - , 'redisOptions': {host: '127.0.0.1', port: 6379} + , 'redisOptions': {host: '127.0.0.1', port: 6379} + , 'mongoUrl': 'mongodb://localhost/mongodb-asms' + // You can add multiple recipients for notifo notifications , 'notifoAuth': null /*[ { 'username': '' @@ -47,5 +50,11 @@ if (cf.cloud) { settings.redisOptions.host = redisConfig.hostname; settings.redisOptions.pass = redisConfig.password; } + + if (cf.mongodb['mongo-asms']) { + var cfg = cf.mongodb['mongo-asms'].credentials; + settings.mongoUrl = ["mongodb://", cfg.username, ":", cfg.password, "@", cfg.hostname, ":", cfg.port,"/" + cfg.db].join(''); + } + settings.user_email = cf.app['users'][0]; } module.exports = settings; \ No newline at end of file From 225ce53c74d8c3d8e4460853da73d8323e011416 Mon Sep 17 00:00:00 2001 From: ciberch Date: Tue, 5 Jun 2012 12:04:55 -0700 Subject: [PATCH 2/2] Part 2 -Getting activities floating via Redis and Mongoose --- lib/socket-io-server.js | 115 ++++++++++++++++++++++++++-- package.json | 2 +- public/css/client.css | 14 ++-- public/img/as-logo-sm.png | Bin 0 -> 6947 bytes public/img/cf-process.jpg | Bin 0 -> 12615 bytes public/img/codercat-sm.jpg | Bin 0 -> 5585 bytes public/js/jquery.client.js | 55 ++++++++----- server.js | 153 ++----------------------------------- views/index.ejs | 8 +- 9 files changed, 164 insertions(+), 183 deletions(-) create mode 100644 public/img/as-logo-sm.png create mode 100644 public/img/cf-process.jpg create mode 100644 public/img/codercat-sm.jpg diff --git a/lib/socket-io-server.js b/lib/socket-io-server.js index f3a0d18..0794658 100644 --- a/lib/socket-io-server.js +++ b/lib/socket-io-server.js @@ -1,6 +1,9 @@ module.exports = function Server(expressInstance, sessionStore) { var parseCookie = require('connect').utils.parseCookie; var io = require('socket.io').listen(expressInstance); + var asmsServer = expressInstance.asmsDB; + var thisApp = expressInstance.thisApp; + var thisInstance = expressInstance.thisInstance; io.configure(function () { io.set('log level', 0); @@ -8,9 +11,9 @@ module.exports = function Server(expressInstance, sessionStore) { io.set('authorization', function(handshakeData, ack) { var cookies = parseCookie(handshakeData.headers.cookie); - sessionStore.get(cookies['connect.sid'], function(err, sessionData) { + sessionStore.get(cookies[expressInstance.cookieName], function(err, sessionData) { handshakeData.session = sessionData || {}; - handshakeData.sid = cookies['connect.sid']|| null; + handshakeData.sid = cookies[expressInstance.cookieName]|| null; ack(err, err ? false : true); }); }); @@ -18,20 +21,116 @@ module.exports = function Server(expressInstance, sessionStore) { io.sockets.on('connection', function(client) { var user = client.handshake.session.user ? client.handshake.session.user.name : 'UID: '+(client.handshake.session.uid || 'has no UID'); + var desiredStream = "firehose"; + + if (client.handshake.session && client.handshake.session.desiredStream) { + desiredStream = client.handshake.session.desiredStream; + } + // Join user specific channel, this is good so content is send across user tabs. client.join(client.handshake.sid); - client.send('welcome: '+user); - client.on('message', function(msg) { - // Send back the message to the users room. - io.sockets.in(client.handshake.sid).send('socket.io relay message "'+msg+'" from: '+ user +' @ '+new Date().toString().match(/[0-9]+:[0-9]+:[0-9]+/)); + var avatarUrl = (client.handshake.session.auth && client.handshake.session.user && client.handshake.session.user.image) ? client.handshake.session.user.image : '/img/codercat-sm.jpg'; + var currentUser = {displayName: user, image: {url: avatarUrl}}; + + + console.log("Subscribing " + user); + + asmsServer.subscribe(desiredStream, function(channel, json) { + client.send(json); + }); + + var cf_provider; + var provider = new asmsServer.ActivityObject({'displayName': 'The Internet', icon: {url: ''}}); + if (client.handshake.session.auth) { + if (client.handshake.session.auth.github) { + provider.displayName = 'GitHub'; + provider.icon.url = 'http://github.com/favicon.ico'; + } else if (client.handshake.session.auth.facebook) { + provider.displayName = 'Facebook'; + provider.icon = {url: 'http://facebook.com/favicon.ico'}; + } else if (client.handshake.session.auth.twitter) { + provider.displayName = 'Twitter'; + provider.icon = {url: 'http://twitter.com/favicon.ico'}; + } + } + provider.save(function(err) { + if (err == null) { + var cf_provider = new asmsServer.ActivityObject({'displayName': 'Cloud Foundry', icon:{url: 'http://www.cloudfoundry.com/images/favicon.ico'}}); + cf_provider.save(function(err) { + if (err == null) { + if (client.handshake.session && client.handshake.session.auth && client.handshake.session.user) { + var act = new asmsServer.Activity({ + id: 1, + actor: currentUser, + verb: 'connect', + object: thisInstance, + target: thisApp, + title: "connected to", + provider: provider, + generator: cf_provider + }); + asmsServer.publish(desiredStream, act); + } else { + console.log("We don't have a user name so don't raise an activity"); + console.dir(client.handshake.session.user); + } + + } else { + console.log("Got error publishing welcome message") + } + }); + } + }); + + + + client.on('message', function(message) { + var actHash = { + actor: currentUser, + verb: 'post', + object: {objectType: "note", content: message, displayName: ""}, + target: thisApp, + provider: provider, + generator: cf_provider + } + + if (actHash.verb == "post") { + actHash.title = "posted a " + actHash.object.objectType; + + } + + var act = new asmsServer.Activity(actHash); + // Send back the message to the users room. + asmsServer.publish(desiredStream, act); }); - client.on('disconnect', function() { console.log('disconnect'); }); + client.on('disconnect', function() { + console.log('********* disconnect'); + asmsServer.unsubscribe(desiredStream); + console.log("unsubscribed from firehose"); + + if (client.handshake.session.user && client.handshake.session.user.name) { + asmsServer.publish(desiredStream, new asmsServer.Activity({ + actor: currentUser, + verb: 'disconnect', + object: thisInstance, + target: thisApp, + title: "disconnected from", + provider: provider, + generator: cf_provider + })); + + + } else { + console.log("User disconnected"); + console.dir(client.handshake); + } + }); }); io.sockets.on('error', function(){ console.log(arguments); }); return io; -}; \ No newline at end of file +}; diff --git a/package.json b/package.json index 1c3a7ab..64ccea2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "engines" : ["node"], "repository" : { "type":"git", "url":"http://github.com/mape/node-express-boilerplate" }, "dependencies" : { - "activity-streams-mongoose": ">=0.0.9", + "activity-streams-mongoose": ">=0.0.11", "mongoose": "", "underscore": "", "cloudfoundry": ">=0.1.0", diff --git a/public/css/client.css b/public/css/client.css index 310dcbc..8cae1df 100644 --- a/public/css/client.css +++ b/public/css/client.css @@ -50,7 +50,7 @@ strong { height: 100%; box-shadow: 0 0 5px rgba(0,0,0,0.6) inset; } -#bubble { +#stream { position: absolute; top: 20px; left: 155px; @@ -58,10 +58,10 @@ strong { overflow: hidden; padding: 0 0 0 65px; } -#bubble ul { +#stream ul { height: 2000px; } -#bubble li { +#stream li { position:relative; padding:15px; margin:1em 0 3em 100px; @@ -75,13 +75,13 @@ strong { opacity: 0; -vendor-transition: opacity 4s linear; } -#bubble .avatar { +#stream .avatar { position: absolute; right: 100%; top: 10px; margin-right: 45px; } -#bubble .service { +#stream .service { position: absolute; right: 100%; top: -0px; @@ -91,10 +91,10 @@ strong { padding: 2px; box-shadow: 0 0 5px #000; } -#bubble li:first-child { +#stream li:first-child { opacity: 1; } -#bubble li:after { +#stream li:after { content: ""; position: absolute; bottom: -20px; diff --git a/public/img/as-logo-sm.png b/public/img/as-logo-sm.png new file mode 100644 index 0000000000000000000000000000000000000000..8d970aab9254f7daabf39743128484d4ef4a24d1 GIT binary patch literal 6947 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 literal 0 HcmV?d00001 diff --git a/public/img/cf-process.jpg b/public/img/cf-process.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8810401e5a3e3e32fd48c4ceb31d6c60bf35d621 GIT binary patch literal 12615 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 Sdd^|$!utiGZN2}~-2VV$_*)SG literal 0 HcmV?d00001 diff --git a/public/img/codercat-sm.jpg b/public/img/codercat-sm.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0d7fdfe33569bb4cea6be899b348cf2531fe5771 GIT binary patch literal 5585 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').text(msg).append($('').attr('src', image)); - if (service) { - $li.append($('').attr('src', service)); - } - $$('#bubble ul').prepend($li); - $$('#bubble').scrollTop(98).stop().animate({ - 'scrollTop': '0' - }, 500); - setTimeout(function() { - $li.remove(); - }, 5000); - - setTimeout(function() { - socketIoClient.send('pong'); - }, 1000); + socketIoClient.on('message', function(json) { + var doc = JSON.parse(json); + if (doc) { + var msg = doc.actor.displayName + " " + doc.title + " " + doc.object.displayName; + if (doc.target) { + msg+= " in " + doc.target.displayName; + } + if (doc.generator) { + msg+= " via " + doc.generator.displayName; + } + + if (doc.object && doc.object.content) { + msg+= ": " + doc.object.content; + } + + var $li = $('
  • ').text(msg).append($('').attr('src', doc.actor.image.url)); + if (doc.provider && doc.provider.icon && doc.provider.icon.url) { + $li.append($('').attr('src', doc.provider.icon.url)); + } + $$('#stream ul').prepend($li); + $$('#bubble').scrollTop(98).stop().animate({ + 'scrollTop': '0' + }, 5000); + setTimeout(function() { + $li.remove(); + }, 5000); + + if (doc.verb == "connect") { + setTimeout(function() { + socketIoClient.send('Ok great news !'); + }, 1000); + } else { + setTimeout(function() { + socketIoClient.send('I am still here'); + }, 10000); + } + } }); socketIoClient.on('disconnect', function() { diff --git a/server.js b/server.js index 7aa368a..c34d53e 100644 --- a/server.js +++ b/server.js @@ -39,9 +39,6 @@ if (cf.app) { thisInstance.url = "http://" + cf.host + ":" + cf.port; thisInstance.displayName = "App Instance " + cf.app['instance_index'] + " at " + thisInstance.url; thisInstance.content = cf.app['instance_id'] - //temp - console.log("Instance JSON is *******"); - console.dir(app); } thisApp.save(function (err) { @@ -51,7 +48,7 @@ thisApp.save(function (err) { actor: {displayName: siteConf.user_email, image:{url: "img/me.jpg"}}, verb: 'start', object: thisInstance, - target: thisApp._id, + target: thisApp, title: "started" }); @@ -210,16 +207,7 @@ function NotFound(msg){ Error.captureStackTrace(this, arguments.callee); } -function getMetaData(req, res, next) { - req.objectTypes = ['person', 'group', 'stream']; - req.verbs = ['post', 'join']; - next(); -}; - function loadUser(req, res, next) { - console.log("Request Session is"); - console.dir(req.session); - if (!req.session.uid) { req.session.uid = (0 | Math.random()*1000000); } else if (req.session.auth){ @@ -236,143 +224,14 @@ function loadUser(req, res, next) { next(); } -function getDistinctVerbs(req, res, next){ - req.usedVerbs = [] - asmsDB.Activity.distinct('verb', {streams: req.session.desiredStream}, function(err, docs) { - if (!err && docs) { - _.each(docs, function(verb){ - req.usedVerbs.push(verb); - }); - next(); - } else { - next(new Error('Failed to fetch verbs')); - } - }); -}; - -function getDistinctActors(req, res, next){ - req.usedActors = [] - asmsDB.Activity.distinct('actor', {streams: req.session.desiredStream}, function(err, docs) { - if (!err && docs) { - _.each(docs, function(obj){ - req.usedActors.push(obj); - }); - next(); - } else { - next(new Error('Failed to fetch actors')); - } - }); -}; - -function getDistinctObjects(req, res, next){ - req.usedObjects = [] - asmsDB.Activity.distinct('object', {streams: req.session.desiredStream}, function(err, docs) { - if (!err && docs) { - _.each(docs, function(obj){ - req.usedObjects.push(obj); - }); - next(); - } else { - next(new Error('Failed to fetch objects')); - } - }); -}; - -function getDistinctObjectTypes(req, res, next){ - req.usedObjectTypes = ['none'] - asmsDB.Activity.distinct('object.objectType', {streams: req.session.desiredStream}, function(err, docs) { - if (!err && docs) { - _.each(docs, function(objType){ - req.usedObjectTypes.push(objType); - }); - next(); - } else { - next(new Error('Failed to fetch objTypes')); - } - }); -}; - -function getDistinctActorObjectTypes(req, res, next){ - req.usedActorObjectTypes = ['none'] - asmsDB.Activity.distinct('actor.objectType', {streams: req.session.desiredStream}, function(err, docs) { - if (!err && docs) { - _.each(docs, function(objType){ - req.usedActorObjectTypes.push(objType); - }); - next(); - } else { - next(new Error('Failed to fetch actorobjTypes')); - } - }); -}; - -function getDistinctStreams(req, res, next){ - req.session.desiredStream = req.params.streamName ? req.params.streamName : "firehose"; - req.streams = {} - asmsDB.Activity.distinct('streams', {}, function(err, docs) { - if (!err && docs) { - _.each(docs, function(stream){ - req.streams[stream] = {name: stream, items: []}; - }); - next(); - } else { - next(new Error('Failed to fetch streams')); - } - }); -} - // Routing -app.get('/', loadUser, getDistinctStreams, getDistinctVerbs, getDistinctActorObjectTypes, getDistinctObjects, - getDistinctActors, getDistinctObjectTypes, getMetaData, function(req, res) { - - asmsDB.getActivityStreamFirehose(20, function (err, docs) { - var activities = []; - if (!err && docs) { - activities = docs; - } - req.streams.firehose.items = activities; - res.render('index', { - currentUser: req.user, - providerFavicon: req.providerFavicon, - streams : req.streams, - desiredStream : req.session.desiredStream, - objectTypes : req.objectTypes, - verbs: req.verbs, - usedVerbs: req.usedVerbs, - usedObjects: req.usedObjects, - usedObjectTypes: req.usedObjectTypes, - usedActorObjectTypes: req.usedActorObjectTypes, - usedActors: req.usedActors - }); - }); - +app.get('/', loadUser, function(req, res) { + res.render('index', { + currentUser: req.user, + providerFavicon: req.providerFavicon, + }); }); -app.get('/streams/:streamName', loadUser, getDistinctStreams, getDistinctVerbs, getDistinctObjects, getDistinctActors, - getDistinctObjectTypes, getDistinctActorObjectTypes, getDistinctVerbs, getMetaData, function(req, res) { - - asmsDB.getActivityStream(req.params.streamName, 20, function (err, docs) { - var activities = []; - if (!err && docs) { - activities = docs; - } - req.streams[req.params.streamName].items = activities; - res.render('index', { - currentUser: req.user, - providerFavicon: req.providerFavicon, - streams : req.streams, - desiredStream : req.session.desiredStream, - objectTypes : req.objectTypes, - verbs: req.verbs, - usedVerbs: req.usedVerbs, - usedObjects: req.usedObjects, - usedObjectTypes: req.usedObjectTypes, - usedActorObjectTypes: req.usedActorObjectTypes, - usedActors: req.usedActors - }); - }); - -}); // Initiate this after all other routing is done, otherwise wildcard will go crazy. var dummyHelpers = new DummyHelper(app); diff --git a/views/index.ejs b/views/index.ejs index 6c394d3..c941332 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -4,8 +4,12 @@ <%= (session.auth && session.auth.twitter) ? '//twitter.com/favicon.ico' : '' %> <%= (session.auth && session.auth.facebook) ? '//facebook.com/favicon.ico' : '' %> "> -
    -
      +
      +
        + <% if(!session.auth) {%> +
      • Log In to Send Messages
      • + <% } %> +
      Online