Permalink
Browse files

Implement live results using socket.io

  • Loading branch information...
lauweijie committed May 30, 2014
1 parent d0b4c2d commit 3959b5c09550685742d90b35dd36fe165377e9f1
View
@@ -8,7 +8,8 @@ angular.module('feedbakerApp', [
'ui.bootstrap',
'angularMoment',
'googlechart',
'ngFitText'
'ngFitText',
'btford.socket-io'
])
.config(function ($routeProvider, $locationProvider, $httpProvider) {
$routeProvider
@@ -1,21 +1,31 @@
'use strict';
angular.module('feedbakerApp')
.controller('AppPollsResultsCtrl', function ($scope, $location, $routeParams, Poll) {
.controller('AppPollsResultsCtrl', function ($scope, $location, $routeParams, Poll, socket) {
$scope.colors = ['#3366cc', '#dc3912', '#ff9900', '#109618', '#990099', '#0099c6', '#dd4477', '#66aa00'];
$scope.toAlpha = function(index) {
return String.fromCharCode(index + 65);
};
Poll.getResults({'id': $routeParams.id}, function(res) {
$scope.poll = res.poll;
$scope.updateAnswerCounts = function(answerCount) {
$scope.answerCount = [];
for(var i = 0; i < res.answerCount.length; i++) {
$scope.answerCount[res.answerCount[i]._id] = res.answerCount[i].count;
for(var i = 0; i < answerCount.length; i++) {
$scope.answerCount[answerCount[i]._id] = answerCount[i].count;
}
$scope.chartData = [['Choice', 'Responses', { role: 'style' }]];
for(i = 0; i < res.poll.choices.length; i++) {
for(i = 0; i < $scope.poll.choices.length; i++) {
$scope.chartData[i+1] = [$scope.toAlpha(i), $scope.answerCount[i] || 0, $scope.colors[i]];
}
$scope.chartObject.data = $scope.chartData;
};
Poll.getResults({'id': $routeParams.id}, function(res) {
socket.emit('subscribe', 'results/' + res.poll._id);
$scope.$on('$destroy', function() {
socket.emit('unsubscribe', 'results/' + res.poll._id);
});
socket.on('update', function (data) {
$scope.updateAnswerCounts(data.answerCount);
});
$scope.poll = res.poll;
$scope.chartObject = {
'type': 'ColumnChart',
'data': $scope.chartData,
@@ -32,6 +42,7 @@ angular.module('feedbakerApp')
}
}
};
$scope.updateAnswerCounts(res.answerCount);
}, function() {
$location.path('/app/polls');
});
@@ -0,0 +1,6 @@
'use strict';
angular.module('feedbakerApp')
.factory('socket', function (socketFactory) {
return socketFactory();
});
View
@@ -68,7 +68,9 @@
<script src="bower_components/angular-moment/angular-moment.js"></script>
<script src="bower_components/angular-google-chart/ng-google-chart.js"></script>
<script src="bower_components/ngFitText/ng-FitText.js"></script>
<script src="bower_components/angular-socket-io/socket.js"></script>
<!-- endbower -->
<script src="bower_components/socket.io-client/socket.io.js"></script>
<!-- endbuild -->
<!-- build:js({.tmp,app}) scripts/scripts.js -->
@@ -87,6 +89,7 @@
<script src="scripts/services/user.js"></script>
<script src="scripts/services/poll.js"></script>
<script src="scripts/services/poll_answer.js"></script>
<script src="scripts/services/socket.js"></script>
<script src="scripts/directives/mongooseError.js"></script>
<!-- endbuild -->
</body>
View
@@ -14,7 +14,9 @@
"angular-bootstrap": "~0.11.0",
"angular-moment": "~0.7.1",
"angular-google-chart": "~0.0.8",
"ngFitText": "~2.4.0"
"ngFitText": "~2.4.0",
"angular-socket-io": "~0.6.0",
"socket.io-client": "~1.0.2"
},
"devDependencies": {
"angular-mocks": ">=1.2.*",
View
@@ -12,12 +12,14 @@ var express = require('express'),
path = require('path'),
config = require('./config'),
passport = require('passport'),
mongoStore = require('connect-mongo')(session);
mongoStore = require('connect-mongo')(session),
passportSocketIo = require("passport.socketio");
/**
* Express configuration
*/
module.exports = function(app) {
module.exports = function(app, io) {
var appSecret = 'feedbaker secret';
var env = app.get('env');
if ('development' === env) {
@@ -53,14 +55,15 @@ module.exports = function(app) {
app.use(cookieParser());
// Persist sessions with mongoStore
app.use(session({
secret: 'feedbaker secret',
store: new mongoStore({
var sessionStore = new mongoStore({
url: config.mongo.uri,
collection: 'sessions'
}, function () {
console.log('db connection open');
})
});
app.use(session({
secret: appSecret,
store: sessionStore
}));
// Use passport session
@@ -71,4 +74,28 @@ module.exports = function(app) {
if ('development' === app.get('env')) {
app.use(errorHandler());
}
function onAuthorizeSuccess(data, accept){
accept(null, true);
}
function onAuthorizeFail(data, message, error, accept){
if(error)
throw new Error(message);
console.log('failed connection to socket.io:', message);
// We use this callback to log all of our failed connections.
accept(null, false);
}
// Set authorization for socket.io
io.set('authorization', passportSocketIo.authorize({
cookieParser: cookieParser,
key: 'connect.sid', // the name of the cookie where express/connect stores its session_id
secret: appSecret, // session_secret to parse the cookie
store: sessionStore,
success: onAuthorizeSuccess, // callback on success
fail: onAuthorizeFail, // callback on fail/error
}));
};
View
@@ -22,32 +22,43 @@ exports.get = function (req, res, next) {
/**
* Set answer for current user
*/
exports.set = function (req, res, next) {
Poll.findOne({
'_id': req.params.id,
'active': true
},
{
choices: 1
},
function(err, poll) {
if(!poll) {
return res.json(400, "Invalid poll");
}
if(parseInt(req.body.answer) >= poll.choices.length) {
return res.json(400, "Invalid answer");
}
Answer.findOneAndUpdate({
'user_id': req.user._id,
'poll_id': req.params.id,
}, {
'answer': req.body.answer
}, {
upsert: true
exports.set = function (io) {
return function (req, res, next) {
Poll.findOne({
'_id': req.params.id,
'active': true
},
function(err, answer) {
if (err) return res.json(400, err);
return res.json(answer);
{
choices: 1
},
function(err, poll) {
if(!poll) {
return res.json(400, "Invalid poll");
}
if(parseInt(req.body.answer) >= poll.choices.length) {
return res.json(400, "Invalid answer");
}
Answer.findOneAndUpdate({
'user_id': req.user._id,
'poll_id': req.params.id,
}, {
'answer': req.body.answer
}, {
upsert: true
},
function(err, answer) {
if (err) return res.json(400, err);
// Emit update on socket
Answer.aggregate()
.match({ 'poll_id': poll._id })
.group({ '_id': '$answer', count: { $sum: 1 } })
.exec(function(err, answerCount) {
io.to('results/' + poll._id).emit('update', {'answerCount': answerCount});
});
return res.json(answer);
});
});
});
};
};
View
@@ -12,7 +12,7 @@ var index = require('./controllers'),
/**
* Application routes
*/
module.exports = function(app) {
module.exports = function(app, io) {
// Param Checking
app.param(function(name, fn){
@@ -64,7 +64,7 @@ module.exports = function(app) {
app.route('/api/polls/:id/answer')
.get(middleware.auth, answers.get)
.put(middleware.auth, answers.set);
.put(middleware.auth, answers.set(io));
// All undefined api routes should return a 404
app.route('/api/*')
View
@@ -0,0 +1,22 @@
'use strict';
module.exports = function(app, io) {
io.sockets.on('connection', function (socket) {
socket.on('subscribe', function(room) {
var roomPath = room.split('/');
switch(roomPath[0]) {
case 'results':
// TODO verify socket.request.user is authorized to view results for poll
socket.join(room);
break;
}
});
socket.on('unsubscribe', function(room) {
socket.leave(room);
});
});
};
View
@@ -30,7 +30,9 @@
"passport": "~0.2.0",
"passport-local": "~0.1.6",
"passport-nus-openid": "~0.0.3",
"ejs": "~0.8.4"
"ejs": "~0.8.4",
"socket.io": "~1.0.2",
"passport.socketio": "~3.0.1"
},
"devDependencies": {
"grunt": "~0.4.1",
@@ -88,8 +90,8 @@
"test": "grunt test"
},
"repository": {
"type": "git",
"url": "https://github.com/lauweijie/feedbaker.git"
"type": "git",
"url": "https://github.com/lauweijie/feedbaker.git"
},
"homepage": "http://feedbaker.com/"
}
}
View
@@ -26,13 +26,16 @@ fs.readdirSync(modelsPath).forEach(function (file) {
// Passport Configuration
var passport = require('./lib/config/passport');
// Setup Express
// Setup Express and Socket.io
var app = express();
require('./lib/config/express')(app);
require('./lib/routes')(app);
var server = require('http').Server(app);
var io = require('socket.io')(server);
require('./lib/config/express')(app, io);
require('./lib/routes')(app, io);
require('./lib/sockets')(app, io);
// Start server
app.listen(config.port, config.ip, function () {
server.listen(config.port, config.ip, function () {
console.log('Express server listening on %s:%d, in %s mode', config.ip, config.port, app.get('env'));
});

0 comments on commit 3959b5c

Please sign in to comment.