app.get('/', site.index);
app.get('/artworks', site.artworks);
-app.get('/signup', site.signup);
-app.post('/signup', user.validateSignUp, user.userExists, user.emailExists, user.createAccount);
+app.get('/leaderboard', user.leaderboard);
app.get('/login', site.login);
app.post('/login', user.validateLogin, user.checkUser, user.authenticate);
app.get('/logout', user.logout);
+app.get('/signup', site.signup);
+app.post('/signup', user.validateSignUp, user.userExists, user.emailExists, user.createAccount);
app.get('/:room', site.room);
app.get('/user/*', user.profile);
// Extract a random track from the database and send the load event
var sendLoadTrack = function() {
songsdb.srandmember(roomname, function(err, res) {
- songsdb.hmget(res, 'artistName', 'trackName', 'collectionName', 'previewUrl',
+ // Check if extracted track is in the list of already played tracks
+ if (playedtracks.indexOf(res) !== -1) {
+ return sendLoadTrack();
+ }
+ playedtracks.push(res);
+ songsdb.hmget('song:'+res, 'artistName', 'trackName', 'collectionName', 'previewUrl',
'artworkUrl60', 'trackViewUrl', function(e, replies) {
- // Check if extracted track is in the list of already played tracks
- if (playedtracks.indexOf(res) !== -1) {
- return sendLoadTrack();
- }
- playedtracks.push(res);
artistName = replies[0];
artistlcase = artistName.toLowerCase();
trackName = replies[1];
if (stats.points) {
// Update total points
db.hincrby(key, 'totpoints', stats.points);
+ // Update the score of the member in the sorted set
+ db.zincrby('users', stats.points, username);
}
if (stats.userscore) {
// Set personal best
"engines": {
"node": "0.6.x"
},
- "version": "0.3.1-2"
+ "version": "0.3.1-3"
}
\ No newline at end of file
height: 34px;
padding-left: 68px;
line-height: 40px;
- background: url('/static/images/logo.png') no-repeat 0px -54px;
+ background: url('/static/images/sprites.png') no-repeat 0px -182px;
font-size: 25px;
}
.page-header .logo {
height: 54px;
padding-left: 109px;
line-height: 68px;
- background: url('/static/images/logo.png') no-repeat 0 0;
+ background: url('/static/images/sprites.png') no-repeat 0px -128px;
font-size: 34px;
color: #0088CC;
}
.navbar .navbar-text {
line-height:19px;
- padding: 10px 10px 11px;
+ padding: 9px 10px 11px;
}
.form-horizontal .control-group {
margin-bottom: 10px;
width: 70px;
height: 70px;
}
+.highscores, .profile {
+ font-weight: bold;
+}
.profile {
font-size: 24px;
- font-weight: bold;
line-height: 32px;
}
+.highscores {
+ font-size: 20px;
+ line-height: 45px;
+}
.profile .img {
width: 32px;
height: 32px;
- float: left;
margin-right: 5px;
background: url('/static/images/sprites.png') no-repeat 0px -56px;
}
-.stats {
+.highscores .img {
+ width: 80px;
+ height: 45px;
+ margin-right:5px;
+ background: url('/static/images/sprites.png') no-repeat 0px -216px;
+}
+.leaderboard-wrapper, .stats {
border: 1px solid #ccc;
border-left: 0;
margin-top:8px;
}
-.stats td {
+.leaderboard-wrapper {
+ height: 349px;
+ overflow: auto;
+ margin-bottom:18px;
+ -webkit-border-radius: 4px;
+ -moz-border-radius: 4px;
+ border-radius: 4px;
+}
+.leaderboard {
+ margin:0;
+ border:0;
+}
+.leaderboard td, .stats td {
border-left: 1px solid #ccc;
border-top: 1px solid #ccc;
vertical-align: middle;
}
-.stats tbody tr:nth-child(odd) td {
+.leaderboard td [class^="icon-"] {
+ vertical-align: top;
+ margin-top: 2px;
+}
+.leaderboard tr:first-child td {
+ border-top: 0;
+}
+.leaderboard tbody tr:nth-child(odd) td, .stats tbody tr:nth-child(odd) td {
background-color: #ddd;
}
-.stats tbody tr:hover td {
+.leaderboard tbody tr:hover td, .stats tbody tr:hover td {
background-color: #dadada;
}
.room {
background: -ms-linear-gradient(center top , #FBFBFB, #F5F5F5);
background: linear-gradient(center top , #FBFBFB, #F5F5F5);
}
-.registered, #users .name, #users .points, .round-rank, .round-points, #users .guess-time, #tracks img.artwork, #tracks .info, #copy, #facebook-button, #twitter-button, #github-button {
+.registered, #users .name, #users .points, .round-rank, .round-points, #users .guess-time, #tracks img.artwork, #tracks .info, .highscores .img, .profile .img, #copy, #facebook-button, #twitter-button, #github-button {
float:left;
}
#tracks img.artwork {
var task = function(genre) {
return function(callback) {
db.srandmember(genre, function(err, res) {
- db.hget(res, "artworkUrl100", callback);
+ db.hget('song:'+res, 'artworkUrl100', callback);
});
};
};
if (req.session.user) {
res.local('loggedin', req.session.user);
}
- res.render("index", {rooms:rooms});
-};
-
-exports.signup = function(req, res) {
- var captcha = new Captcha();
- req.session.captchacode = captcha.getCode();
- res.render("signup", {captchaurl:captcha.toDataURL()});
-};
-
-exports.login = function(req, res) {
- res.render("login");
+ res.render('index', {rooms:rooms});
};
/**
});
};
+exports.login = function(req, res) {
+ res.render('login');
+};
+
+exports.signup = function(req, res) {
+ var captcha = new Captcha();
+ req.session.captchacode = captcha.getCode();
+ res.render('signup', {captchaurl:captcha.toDataURL()});
+};
+
+
exports.room = function(req, res) {
if (rooms.indexOf(req.params.room) !== -1) {
if (req.session.user) {
res.local('loggedin', req.session.user);
}
- res.render("room", {roomname:req.params.room,rooms:rooms});
+ res.render('room', {roomname:req.params.room,rooms:rooms});
}
else {
res.send(404);
return this.match(/^(?:[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+\.)*[\w\!\#\$\%\&\'\*\+\-\/\=\?\^\`\{\|\}\~]+@(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!\.)){0,61}[a-zA-Z0-9]?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9\-](?!$)){0,61}[a-zA-Z0-9]?)|(?:\[(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\]))$/);
};
+/**
+ * Parameters to get users ordered by best guess time.
+ */
+
+var sortparams = [
+ 'users'
+ , 'by'
+ , 'user:*->bestguesstime'
+ , 'get'
+ , '#'
+ , 'get'
+ , 'user:*->bestguesstime'
+ , 'limit'
+ , '0'
+ , '30'
+];
+
+/**
+ * Leaderboard helper function.
+ * Rearrange database results in an object.
+ */
+
+var buildLeaderboard = function(pointsresults, timesresults) {
+ var obj = {
+ pointsleaderboard: [],
+ timesleaderboard: []
+ }
+ for (var i=0; i<pointsresults.length; i+=2) {
+ obj.pointsleaderboard.push({
+ username: pointsresults[i],
+ totpoints: pointsresults[i+1]
+ });
+ obj.timesleaderboard.push({
+ username: timesresults[i],
+ bestguesstime: (timesresults[i+1] / 1000).toFixed(2)
+ });
+ }
+ return obj;
+};
+
/**
* Initialize dependencies.
*/
db = options.db;
};
+/**
+ * Show a list of users ordered by points and best guess time (limit set to 30).
+ */
+
+exports.leaderboard = function(req, res) {
+ db.zrevrange('users', 0, 29, 'withscores', function(err, pointsresults) {
+ db.sort(sortparams, function (e, timesresults) {
+ var leaderboard = buildLeaderboard(pointsresults, timesresults);
+ res.render('leaderboard', leaderboard);
+ });
+ });
+};
+
+/**
+ * Login middlewares.
+ */
+
+exports.validateLogin = function(req, res, next) {
+ var errors = {};
+
+ req.body.username = req.body.username.trim(); // Username sanitization
+ req.body.password = req.body.password.trim(); // Password sanitization
+ if (req.body.username === '') {
+ errors.username = "can't be empty";
+ }
+ if (req.body.password === '') {
+ errors.password = "can't be empty";
+ }
+
+ req.session.oldvalues = {username: req.body.username};
+ if (errors.username || errors.password) {
+ req.session.errors = errors;
+ return res.redirect('/login');
+ }
+
+ next();
+};
+
+exports.checkUser = function(req, res, next) {
+ var key = 'user:'+req.body.username;
+ db.exists(key, function(err, data) {
+ if (data === 1) {
+ // User exists, proceed with authentication
+ return next();
+ }
+ req.session.errors = {alert: 'The username you specified does not exists.'};
+ res.redirect('/login');
+ });
+};
+
+exports.authenticate = function(req, res) {
+ var key = 'user:'+req.body.username;
+ db.hmget(key, 'salt', 'password', function(err, data) {
+ var hash = crypto.createHash('sha256').update(data[0]+req.body.password).digest('hex');
+ if (hash === data[1]) {
+ // Authentication succeeded, regenerate the session
+ req.session.regenerate(function() {
+ req.session.cookie.maxAge = 604800000; // One week
+ req.session.user = req.body.username;
+ res.redirect('/');
+ });
+ return;
+ }
+ req.session.errors = {alert: 'The password you specified is not correct.'};
+ res.redirect('/login');
+ });
+};
+
+/**
+ * Logout the user.
+ */
+
+exports.logout = function(req, res) {
+ // Destroy the session
+ req.session.destroy(function() {
+ res.redirect('/');
+ });
+};
+
/**
* Sign up middlewares.
*/
// Add new user in the db
db.hmset(userkey, user);
db.set(mailkey, userkey);
- db.sadd('users', userkey);
- db.sadd('emails', mailkey);
+ db.zadd('users', 0, req.body.username);
+ db.sadd('emails', req.body.email);
// Delete old fields values (we don't want these to be available in login view)
delete req.session.oldvalues;
var msg = 'You successfully created your account. You are now ready to login.';
res.render('login', {success:msg});
};
-/**
- * Login middlewares.
- */
-
-exports.validateLogin = function(req, res, next) {
- var errors = {};
-
- req.body.username = req.body.username.trim(); // Username sanitization
- req.body.password = req.body.password.trim(); // Password sanitization
- if (req.body.username === '') {
- errors.username = "can't be empty";
- }
- if (req.body.password === '') {
- errors.password = "can't be empty";
- }
-
- req.session.oldvalues = {username: req.body.username};
- if (errors.username || errors.password) {
- req.session.errors = errors;
- return res.redirect('/login');
- }
-
- next();
-};
-
-exports.checkUser = function(req, res, next) {
- var key = 'user:'+req.body.username;
- db.exists(key, function(err, data) {
- if (data === 1) {
- // User exists, proceed with authentication
- return next();
- }
- req.session.errors = {alert: 'The username you specified does not exists.'};
- res.redirect('/login');
- });
-};
-
-exports.authenticate = function(req, res) {
- var key = 'user:'+req.body.username;
- db.hmget(key, 'salt', 'password', function(err, data) {
- var hash = crypto.createHash('sha256').update(data[0]+req.body.password).digest('hex');
- if (hash === data[1]) {
- // Authentication succeeded, regenerate the session
- req.session.regenerate(function() {
- req.session.cookie.maxAge = 604800000; // One week
- req.session.user = req.body.username;
- res.redirect('/');
- });
- return;
- }
- req.session.errors = {alert: 'The password you specified is not correct.'};
- res.redirect('/login');
- });
-};
-
-/**
- * Logout the user.
- */
-
-exports.logout = function(req, res) {
- // Destroy the session
- req.session.destroy(function() {
- res.redirect('/');
- });
-};
-
/**
* Show user profile.
*/
ul.nav.pull-right
li.active
a(href="/") Home
+ li
+ a(target="_blank", href="/leaderboard")
+ i.icon-list-alt.icon-white
+ | Leaderboard
if (typeof(loggedin) !== "undefined")
li
p.navbar-text Logged in as
--- /dev/null
+doctype html
+html
+ include header
+ title binb :: Leaderboard
+ body
+ include uv.jade
+ .navbar.navbar-fixed-top
+ .navbar-inner
+ .container
+ a.brand(href="#")
+ .logo #{motto}
+ .container
+ section
+ .row
+ .span7.offset1
+ .highscores High Scores
+ .img
+ section
+ .row
+ .span7.offset1
+ h4 Points
+ div.leaderboard-wrapper
+ table.table.table-striped.table-bordered.leaderboard
+ tbody
+ each user, i in pointsleaderboard
+ tr
+ td #{i+1}
+ td
+ a(href="/user/#{encodeURIComponent(user.username)}")
+ | #{user.username.replace(/&/g, '&')}
+ td #{user.totpoints}
+ .span7
+ h4 Times
+ div.leaderboard-wrapper
+ table.table.table-striped.table-bordered.leaderboard
+ tbody
+ each user, i in timesleaderboard
+ tr
+ td #{i+1}
+ td
+ a(href="/user/#{encodeURIComponent(user.username)}")
+ | #{user.username.replace(/&/g, '&')}
+ td
+ i.icon-time
+ | #{user.bestguesstime} sec
+ include footer
ul.nav.pull-right
li
a(href="/") Home
+ li
+ a(target="_blank", href="/leaderboard")
+ i.icon-list-alt.icon-white
+ | Leaderboard
li.active.dropdown
a.dropdown-toggle(data-toggle="dropdown",href="#") #{roomname}
b.caret