From 5aceb9c897183fe1fa1f2811f522e388adc5cc56 Mon Sep 17 00:00:00 2001 From: Robert Kosara Date: Sun, 8 Jul 2012 23:35:25 -0700 Subject: [PATCH] First (pre-)release version, login works, only shows list of runs, no export yet. --- ReadMe.md | 7 ++ app.js | 20 ++--- package.json | 5 +- routes.js | 12 +-- static/css/style.css | 4 + user.js | 84 ++++++++++++++++++-- views/export.jade | 20 +++++ views/index.jade | 46 +++++------ views/layout.jade | 51 ++++++------ views/{login-redirect.jade => redirect.jade} | 5 +- 10 files changed, 177 insertions(+), 77 deletions(-) create mode 100644 ReadMe.md create mode 100644 views/export.jade rename views/{login-redirect.jade => redirect.jade} (68%) diff --git a/ReadMe.md b/ReadMe.md new file mode 100644 index 0000000..14ea57f --- /dev/null +++ b/ReadMe.md @@ -0,0 +1,7 @@ +# Dependencies + +* [express](http://expressjs.com/) +* [jade](http://jade-lang.com/) +* [connect-memcached](https://github.com/balor/connect-memcached) +* [queue-async](https://github.com/mbostock/queue) +* [mysql](https://github.com/felixge/node-mysql) \ No newline at end of file diff --git a/app.js b/app.js index 7e5c83d..60e936d 100644 --- a/app.js +++ b/app.js @@ -4,7 +4,7 @@ */ var express = require('express') - , jog = require('jog') + , queue = require('queue-async') , mysql = require('mysql') , user = require('./user.js') , run = require('./run.js') @@ -15,8 +15,6 @@ var MemcachedStore = require('connect-memcached')(express); var dbClient = mysql.createClient(dbConf); -var log = jog(new jog.FileStore('status.log')); - var app = module.exports = express.createServer(); var PORT = 5555; @@ -48,15 +46,19 @@ app.configure('production', function(){ app.get('/', routes.index); -app.get('/index-login', routes.index_login); - -app.get('/nike-login', function(req, res){ - req.session.userID = user.newUser(dbClient, req.session.userID || -1, - req.query.nuid, req.query.oauth_token, req.query.access_token).userID; -// res.end(JSON.stringify(req.query)+'\n'+JSON.stringify(req.session)); +app.get('/nike-login', function(req, res) { + req.session.userID = user.login(dbClient, req.query.nuid, req.query.oauth_token, req.query.access_token); routes.redirectLogin(req, res); }); +app.get('/export', function(req, res) { + if (req.session.userID == undefined) { + res.render('redirect', { title: 'Redirecting ...', redirectURL: 'http://eagerfeet.org/'}) + } else { + user.getActivities(req.session.userID, res); + } +}); + user.loadUsers(dbClient); app.listen(PORT, function(){ diff --git a/package.json b/package.json index 4c5fa89..aaca7a7 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,9 @@ , "private": true , "dependencies": { "express": ">= 2.5.9" - , "jade": ">= 0.0.1" + , "jade": ">= 0.26.0" , "mysql": ">= 0.9.6" - , "jog": ">= 0.3.0" , "connect-memcached": ">= 0.0.3" - , "node-runkeeper": ">= 0.1.4" + , "queue-async": ">= 0.0.2" } } \ No newline at end of file diff --git a/routes.js b/routes.js index 097eece..b2bcf2a 100644 --- a/routes.js +++ b/routes.js @@ -4,13 +4,13 @@ */ exports.index = function(req, res){ - res.render('index', { title: 'eagerfeet 2', login: false, userID: req.session.userID || 'nobody' }) -}; - -exports.index_login = function(req, res){ - res.render('index', { title: 'eagerfeet 2', login: true, userID: req.session.userID || 'nobody' }) + if (req.session.userID != undefined) { + res.render('redirect', { title: 'Redirecting ...', redirectURL: 'http://eagerfeet.org/export'}) + } else { + res.render('index', { title: 'eagerfeet'}) + } }; exports.redirectLogin = function(req, res) { - res.render('login-redirect', { title: 'Login successful', redirectURL: 'http://eagerfeet.org/index-login' }) + res.render('redirect', { title: 'Login successful!', redirectURL: 'http://eagerfeet.org/export'}) } \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index ca2fba3..74775af 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -9,4 +9,8 @@ div.logo { div.nike-login { padding-top: 10px; +} + +li.run { + margin-top: .5em; } \ No newline at end of file diff --git a/user.js b/user.js index 50a1435..758f295 100644 --- a/user.js +++ b/user.js @@ -3,6 +3,37 @@ var users = {}; var maxUserID = 1000; +var https = require('https'); + +function serverRequest(path, params, resultFunc) { + + var queryString = ''; + var numParams = 0; + + for (var key in params) { + if (params.hasOwnProperty(key)) { + queryString += ((numParams == 0) ? '?' : '&'); + queryString += key+'='+params[key]; + numParams += 1; + } + } + + var options = { + host: 'api.nike.com', + path: path + queryString + }; + + https.get(options, function(response) { + var body = ''; + response.on('data', function(chunk) { + body += chunk; + }); + response.on('end', function() { + resultFunc(body); + }); + }); +} + exports.loadUsers = function(dbClient) { dbClient.query('select * from Users', function(err, results, fields) { if (err) { @@ -17,14 +48,53 @@ exports.loadUsers = function(dbClient) { }); } -exports.newUser = function(dbClient, userID, nikeUID, nikeOAuthToken, nikeAccessToken) { - if (userID < 0) { - userID = maxUserID; +exports.login = function(dbClient, nikeUID, nikeOAuthToken, nikeAccessToken) { + var user = null; + for (var userID in users) { + if (users[userID].nikeUID == nikeUID) { + user = users[userID]; + } + } + if (user == null) { + userID = maxUserID+1; maxUserID += 1; + var user = {userID: userID, nikeAccessToken: nikeAccessToken, nikeOAuthToken: nikeOAuthToken, nikeUID: nikeUID}; + dbClient.query('insert into Users (userID, nikeUID, nikeOAuthToken, nikeAccessToken) values (?,?,?,?);', + [userID, nikeUID, nikeOAuthToken, nikeAccessToken]); + users[userID] = user; } - var user = {userID: userID}; - dbClient.query('replace into Users values (?,?,?,?);', [user.userID, nikeUID, nikeOAuthToken, nikeAccessToken]); - users[userID] = user; - return user; + return user.userID; } +function getUser(userID) { + return users[userID]; +} + +exports.getUser = getUser; + +function pad(num) { + return (num < 10) ? ('0' + num) : ('' + num); +} + +exports.getActivities = function(userID, res) { + var user = getUser(userID); + serverRequest('/partner/sport/run/activities', {access_token: user.nikeAccessToken, start: 0, end: 100}, function(data) { + var activities = JSON.parse(data).activities; + activities.reverse(); + activities.forEach(function(run) { + var seconds = run.activity.metrics.totalDuration/1000; + if (seconds < 60 * 60) { + var minutes = Math.floor(seconds/60); + seconds -= minutes*60; + run.time = pad(minutes)+':'+pad(seconds); + } else { + var hours = Math.floor(seconds/3600); + seconds -= hours*3600; + var minutes = Math.floor(seconds/60); + seconds -= minutes*60; + run.time = pad(hours)+':'+pad(minutes)+':'+pad(seconds); + } + }); + res.render('export', {activities: activities}); + }); +} diff --git a/views/export.jade b/views/export.jade new file mode 100644 index 0000000..e7a965a --- /dev/null +++ b/views/export.jade @@ -0,0 +1,20 @@ +extends layout + +block append main + div.row + div.span8 + h1 Export + + p A list of your runs on Nike+ (only shows the 100 most recent for now): + div#runs + ul + each run in activities + li.run #{run.activity.name}, + - var miles = (run.activity.metrics.totalDistance/1.609344).toFixed(2) + - var kms = run.activity.metrics.totalDistance.toFixed(2) + | #{miles} mi / #{kms} km in #{run.time}, #{run.activity.metrics.totalCalories} calories + if run.activity.metrics.averageHeartRate > 0 + i.icon-heart +// p Checking for new runs ... +// div#progressbar.progress.progress-striped.active +// div.bar(style="width: 100%;") diff --git a/views/index.jade b/views/index.jade index aecdcf7..f24049d 100644 --- a/views/index.jade +++ b/views/index.jade @@ -1,28 +1,22 @@ extends layout -block append content - div.container - div.row - div.logo.span12 - img(src='/img/header.png', width='820', height='110') - div.row - div.span8 - h1 Welcome to #{title}! - if (login) - if (userID != 'nobody') - p User #{userID} - div.nike-login - script(id="nikeoauth", - data-clientid="54dff1e61f4ac4a56c98ad57ef3ed377", - data-userid="#{userID}", - src="https://www.nike.com/profile/assets/oauth/js/nikeoauth.min.js") - div#runs - else - p This site is currently being rewritten from scratch to work with Nike's new authenticaton scheme and to make it easier to add more features later. Please - a(href='http://blog.eagerfeet.org/') - | check out the blog - | or - a(href='http://twitter.com/eagerfeet') - | follow me on twitter - | for updates. - p The login issues are largely solved, but I am struggling with some other parts of the site's backend right now (and lack of time). I will post on the blog and on Twitter when the new site is ready for you to test. Please bear with me a few more days, the site will be back soon. +block append main + div.row + div.logo.span12 + img(src='/img/header.png', width='820', height='110') + div.row + div.span8 + h1 Welcome to eagerfeet, Limited Preview Edition! + p Please use the button below to log in using your Nike+ credentials. Once logged in, you will see a list of your most recent runs. You will not be able to actually export those runs at this point, but you will be in a few days. + p For more details and updates, + a(href='http://blog.eagerfeet.org/') + | check out the blog + | or + a(href='http://twitter.com/eagerfeet') + | follow me on twitter + | . + div.nike-login + script(id="nikeoauth", + data-clientid="54dff1e61f4ac4a56c98ad57ef3ed377", + data-userid="nobody", + src="https://www.nike.com/profile/assets/oauth/js/nikeoauth.min.js") diff --git a/views/layout.jade b/views/layout.jade index 4f2d3b1..be7ab73 100644 --- a/views/layout.jade +++ b/views/layout.jade @@ -1,12 +1,12 @@ -!!! html +doctype html html(lang="en") head meta(charset='utf-8') - title= title - block head + title eagerfeet + link(rel='stylesheet', href='/css/bootstrap-2.0.4.min.css') + link(rel='stylesheet', href='/css/style.css') + block header meta(name='viewport', content='width=device-width, initial-scale=1.0') - link(rel='stylesheet', href='/css/bootstrap-2.0.4.min.css') - link(rel='stylesheet', href='/css/style.css') //- Le HTML5 shim, for IE6-8 support of HTML5 elements //if lt IE 9 script(src='http://html5shim.googlecode.com/svn/trunk/html5.js') @@ -28,22 +28,25 @@ html(lang="en") a(href='/') Home li a(href='http://blog.eagerfeet.org/') blog - //- Tracking code, please remove if you use this somewhere else! - script(src='http://static.getclicky.com/js', type='text/javascript') - script(type='text/javascript') try{ clicky.init(66409777); }catch(e){} - noscript - p - img(alt='Clicky', width='1', height='1', src="http://in.getclicky.com/66409777ns.gif') - script(type='text/javascript') - | var _gauges = _gauges || []; - | (function() { - | var t = document.createElement('script'); - | t.type = 'text/javascript'; - | t.async = true; - | t.id = 'gauges-tracker'; - | t.setAttribute('data-site-id', '4ef430c8613f5d6715000004'); - | t.src = '//secure.gaug.es/track.js'; - | var s = document.getElementsByTagName('script')[0]; - | s.parentNode.insertBefore(t, s); - | })(); - //- End of tracking code + div.container + block main + + //- Tracking code, please remove if you use this somewhere else! + script(src='http://static.getclicky.com/js', type='text/javascript') + script(type='text/javascript') try{ clicky.init(66409777); }catch(e){} + noscript + p + img(alt='Clicky', width='1', height='1', src="http://in.getclicky.com/66409777ns.gif') + script(type='text/javascript'). + var _gauges = _gauges || []; + (function() { + var t = document.createElement('script'); + t.type = 'text/javascript'; + t.async = true; + t.id = 'gauges-tracker'; + t.setAttribute('data-site-id', '4ef430c8613f5d6715000004'); + t.src = '//secure.gaug.es/track.js'; + var s = document.getElementsByTagName('script')[0]; + s.parentNode.insertBefore(t, s); + })(); + //- End of tracking code diff --git a/views/login-redirect.jade b/views/redirect.jade similarity index 68% rename from views/login-redirect.jade rename to views/redirect.jade index a40a1f2..8e75840 100644 --- a/views/login-redirect.jade +++ b/views/redirect.jade @@ -1,7 +1,8 @@ extends layout -block head +block header meta(http-equiv='Refresh', content='0; url=#{redirectURL}') block content - h1 Login successful, hooray! \ No newline at end of file + div.container + h1 #{title}