6
\$\begingroup\$

Our company is planning on doing a fitness competition.
So as the hackathon project I threw together this team based tracking service.

It measure a walk from Seattle to New York
Player can log in via fitbit (moves/jawbone/fuel/etc.. to come later) once logged in you can select a team.

When you log in it automatically logs a callback with fitbit.com to receive update information from your fitbit device (it just pulls distance traveled from this information). Then plots the team position on a map (see link above).

Running Demo here. thorsanvil.com/tagfit2/

Here I have included the backend.
It is a nodejs server running express/passport to handle the bulk of the work.
Not being that experienced in javascript looking for feedback on style/error handling better ways to do things.

var express         = require('express');
var passport        = require('passport');
var FitbitStrategy  = require('passport-fitbit').Strategy;
var persistance     = require('./TagFitDB.js');
var oauthSignature  = require('oauth-signature');
var https           = require('https');
var http            = require('http');
var randomString    = require("randomstring");
var config          = require('../config.js');


passport.serializeUser(function(user, done) {
    done(null, user);
});
passport.deserializeUser(function(obj, done) {
    done(null, obj);
});
passport.use(new FitbitStrategy(
    config.passport.fitbit,
    function(token, tokenSecret, profile, done) {
        var connection  = persistance.getConnect();
        var queryStr    = "SELECT Id, Name FROM User WHERE Service=? and UserId=?";
        connection.query(queryStr, [profile.provider, profile.id], function(err, resp) {
            if (err) done(err);
            if (resp.length == 0)
            {
                var addUserQuery = "INSERT INTO User (Name, Service, UserId, Token, TokenSecret, TeamId) VALUES(?, ?, ?, ?, ?, NULL)";
                connection.query(addUserQuery, [profile.displayName, profile.provider, profile.id, token, tokenSecret], function(err, resp) {
                    if (err) done(err);
                    process.nextTick(function() {subscribeUser(profile.id, token, tokenSecret);});
                    done(null, {id:resp.insertId, display:profile.displayName, token:token, tokenSecret:tokenSecret});
                });
            }
            else
            {
                var updateUserQuery = "UPDATE User SET Token=?, TokenSecret=? WHERE Id=?";
                connection.query(updateUserQuery, [token, tokenSecret, resp[0].Id], function(err, updateDataResp) {
                    if (err) done(err);
                    done(null, {id: resp[0].Id, display: resp[0].Name, token:token, tokenSecret:tokenSecret});
                });
            }
        });
    }
));

var app     = express();
app.configure(function() {
    app.use(express.cookieParser());
    app.use(express.bodyParser());
    app.use(express.session({secret: 'SECRET1'}));
    app.use(passport.initialize());
    app.use(passport.session());
});



app.get('/tagfit2/rest/version', function(req, res) {
    res.json({version: '00.01.000', majorVer: 0, minorVer: 1, build: 0});
});

app.get('/tagfit2/rest/team', function(req, res) {
    var connection  = persistance.getConnect();
    //var queryStr    = 'SELECT * FROM Team ORDER BY Distance';
    var queryStr = 'SELECT T.Id, T.Name, TD.Distance '
                 + 'FROM Team T '
                 + 'LEFT JOIN '
                 +       '(SELECT U.TeamId, SUM(Distance) AS Distance '
                 +       ' FROM User U, UserInfo UI '
                 +       ' WHERE U.Id = UI.UserId '
                 +       ' GROUP by U.TeamId'
                 +       ') TD '
                 + 'ON   T.Id=TD.TeamId '
                 + 'ORDER BY TD.Distance';
    connection.query(queryStr, function(err, teams) {
        if (err) {console.log('/tagfit2/rest/team E1: ' + err);res.send(500);return;}
        for(var loop=0;loop < teams.length;loop++) {
            if (!teams[loop].Distance) { teams[loop].Distance = 0;}
        }
         res.json({Name: 'Team', items: teams});
    });
});
app.get('/tagfit2/rest/team/:team_id', function(req, res) {
    var connection  = persistance.getConnect();
    var queryStr    = 'SELECT (Year(UI.lastUpdate) + DAYOFYEAR(UI.lastUpdate)) AS Name, sum(UI.Distance) AS Distance '
                    + 'FROM     User U, UserInfo UI '
                    + 'WHERE    U.TeamId=? && UI.UserId=U.Id '
                    + 'GROUP BY UI.lastUpdate '
                    + 'ORDER BY UI.lastUpdate ';
    connection.query(queryStr, req.params.team_id, function(err, teams) {
        if (err) {console.log('/tagfit2/rest/team/:team_id $2: ' + err);res.send(500);return;}
        if (teams.length > 0)
        {
            var start   = teams[0].Name - 1;
            var dist    = 0;
            for(var loop=0;loop < teams.length; loop++) {
                dist    += teams[loop].Distance;
                teams[loop].Name = ('Day: ' + (teams[loop].Name - start));
                teams[loop].Distance = dist;
            }
        }

        res.json({Name: 'Progress', items: teams});
    });
});

app.get('/tagfit2/rest/curentUser', function(req, res) {
    if (req.user) {
        var connection  = persistance.getConnect();
        var queryStr    = "SELECT T.Name FROM User U, Team T WHERE U.Id=? && U.TeamId = T.Id";
        connection.query(queryStr, req.user.id, function(err, data) {
            if (err) {console.log('/tagfit2/rest/curentUser E3: ' + err);res.send(500);return;}
            if (data.length == 0) {
                var teamNames   = "SELECT Id, Name FROM Team";
                connection.query(teamNames, function(err, data) {
                    if (err) {console.log('/tagfit2/rest/curentUser E4: ' + err);res.send(500);return;}
                    res.json({loggedin: true, display: req.user.display, team: false, teams: data});
                });
            }
            else {
                res.json({loggedin: true, display: req.user.display + '(' + data[0].Name + ')', team: true});
            }
        });
    }
    else {
        res.json({loggedin: false});
    }
});
app.get('/tagfit2/rest/join/:team_id', function(req, res) {
    if (req.user) {
        var connection  = persistance.getConnect();
        var queryStr    = "SELECT T.Name FROM User U, Team T WHERE U.Id=? && U.TeamId = T.Id";
        connection.query(queryStr, req.user.id, function(err, userData) {
            if (err) {console.log('/tagfit2/rest/join/:team_id E5: ' + err);res.send(500);return;}
            if (userData.length == 0) {
                var validateStr = "SELECT Name FROM Team where Id=?";
                connection.query(validateStr, req.params.team_id, function(err, teamData) {
                    if (err) {console.log('/tagfit2/rest/join/:team_id E6: ' + err);send(500);return;}
                    if (teamData.length > 0) {
                        var joinStr = "UPDATE User set TeamId=? where Id=?";
                        connection.query(joinStr, [req.params.team_id, req.user.id], function(err, insert) {
                            if (err) {console.log('/tagfit2/rest/join/:team_id E7: ' + err);send(500);return;}
                            res.json({loggedin: true, display: req.user.display + '(' + teamData[0].Name + ')', team: true});
                        });
                    }
                    else {
                        res.json({loggedin: true, display: req.user.display, team: false});
                    }
                });
            }
            else {
                res.json({loggedin: true, display: req.user.display + '(' + data[0].Name + ')', team: true});
            }
        });
    }
    else {
        res.json({loggedin: false});
    }
});

app.get('/tagfit2/rest/logout',  function(req, res, next) {
    req.logout();
    res.redirect("/tagfit2/");
});
function makeHttpRequest(httpMethod, httpType, host, path, token, tokenSecret, actionCallback) {

    if(typeof(actionCallback) === 'undefined')  {actionCallback = function(err, body){if (err) {console.log('makeHttpRequest: ' + err);}};}

    var consumerSecret  = config.passport.fitbit.consumerSecret;
    var time        = Math.floor(new Date().getTime()/1000);
    var nonce       = randomString.generate(16);
    var parameters  = {
        'oauth_consumer_key':     config.passport.fitbit.consumerKey,
        'oauth_nonce':            nonce,
        'oauth_signature_method': 'HMAC-SHA1',
        'oauth_timestamp':        time,
        'oauth_token':            token,
        'oauth_version':          '1.0'
    };

    encodedSignature = oauthSignature.generate(httpMethod, httpType + '://' + host + path, parameters, consumerSecret, tokenSecret);

    var oauthString   = "OAuth ";
    Object.keys(parameters).forEach(function(key) {
        oauthString   += key + '="' + parameters[key] + '", ';
    });
    oauthString += 'oauth_signature="' + encodedSignature + '"';

    var action = httpType == "https" ? https : http;
    var sendRequest = https.request(
        {   method:   httpMethod,
            hostname: host,
            path:     path,
            headers:  {Authorization: oauthString}
        },
        function(sendResponse) {
            var data    = '';
            sendResponse.setEncoding('utf8');
            sendResponse.on('data', function(chunk) {
                data    += chunk;
            });
            sendResponse.on('end', function() {
                actionCallback(null, data);
            });
        }
    );
    sendRequest.on('error', function(error) {
        actionCallback(httpMethod + ' ' + httpType + '://' + host + path + ' ERROR => ' + error);
    });
    sendRequest.end();
}
function subscribeUser(userId, token, tokenSecret) {
    makeHttpRequest('POST', 'https', 'api.fitbit.com', '/1/user/-/activities/apiSubscriptions/' + userId + '.json', token, tokenSecret);
}

// Fitbit login callback point
app.get('/tagfit2/rest/callback',  function(req, res, next) {
    passport.authenticate('fitbit', function(err, user) {
        if (err)        { return next(err) }
        if (!user)      { return next("No User");}

        req.login(user, function(err) {
            if (err) { return next(err) };
            res.redirect('/tagfit2/');
        });

    })(req, res, next);
});
// Fitbit login request.
app.get('/tagfit2/rest/fitbit',
  function(req, res, next) {
    passport.authenticate('fitbit', function(err, user, info) {
      if (err)   { return res.send({'status':'err','message':err.message}); }
      if (!user) { return res.send({'status':'fail','message':info.message}); }
      req.logIn(user, function(err) {
        if (err) { return res.send({'status':'err','message':err.message}); }
        return res.send({'status':'ok'});
      });
    })(req, res, next);
  },
  function(err, req, res, next) {
    return res.send({'status':'err','message':err.message});
  }
);
// Fitbit sends update when user syncs device.
app.post('/tagfit2/rest/fitbitupdate',
  function(req, res, next) {

    for(var loop=0;loop < req.body.length; loop++)
    {
        var user = req.body[loop];
        process.nextTick(function() {updateUserInfo(user);});
    }
    res.status(204).send();
  }
);

function updateUserInfo(user) {

    // Body: [{"collectionType":"activities","date":"2014-12-15","ownerId":"XXXXX","ownerType":"user","subscriptionId":"XXXXX"}]

    // GET /1/user/-/activities/date/2010-02-21.json

    /* Body: {  "activities":[],
                "goals":{"activeMinutes":30,"caloriesOut":2184,"distance":8.05,"steps":10000},
                "summary":{
                    "activeScore":-1,
                    "activityCalories":405,
                    "caloriesBMR":1534,
                    "caloriesOut":1869,
                    "distances":[
                        {"activity":"total","distance":1.78},
                        {"activity":"tracker","distance":1.78},
                        {"activity":"loggedActivities","distance":0},
                        {"activity":"veryActive","distance":0},
                        {"activity":"moderatelyActive","distance":1.23},
                        {"activity":"lightlyActive","distance":0.54},
                        {"activity":"sedentaryActive","distance":0}
                    ],
                    "fairlyActiveMinutes":34,
                    "lightlyActiveMinutes":46,
                    "marginalCalories":211,
                    "sedentaryMinutes":875,
                    "steps":2256,
                    "veryActiveMinutes":0}
                }
    */

    var provider    = 'fitbit';
    var owner       = user.ownerId;
    var date        = user.date;
    var connection  = persistance.getConnect();
    var queryStr    = "SELECT Id, Token, TokenSecret, DATE_FORMAT(lastUpdate, '%Y-%m-%d') AS LastUpdate FROM User WHERE Service=? and UserId=?";
    connection.query(queryStr, [provider, owner], function(err, resp) {
        if (err)                {console.log('updateUserInfo: E8: ' + err);return;}
        if (resp.length != 1)   {console.log('updateUserInfo: E9: ' + provider + ' ' + owner);return;}

        makeHttpRequest('GET', 'https', 'api.fitbit.com', '/1/user/-/activities/date/'+date+'.json', resp[0].Token, resp[0].TokenSecret, function(err, res){
            if (err) {console.log('updateUserInfo: E10: ' + err);return;}
            var activity    = JSON.parse(res);
            var distance    = activity.summary.distances;
            for(var loop = 0;loop < distance.length; loop++) {
                if (distance[loop].activity == 'total') {
                    var distanceValue = distance[loop].distance * 1000;
                    updateUserInfoInDB(resp[loop].Id, resp[loop].LastUpdate, date, distanceValue);
                    break;
                }
            }
        });
    });
}


function updateUserInfoInDB(userId, lastUpdate, thisUpdate, distance) {

    var connection  = persistance.getConnect();
    if (lastUpdate != thisUpdate) {
        var insertStr   = "INSERT INTO UserInfo (UserId, lastUpdate, Distance) VALUES(?, ?, ?)";
        connection.query(insertStr, [userId, thisUpdate, distance], function(err, rep) {
            if (err) {console.log('updateUserInfoInDB: E11: ' + err);return;}
            var updateStr   = "UPDATE User Set lastUpdate=? where Id=?";
            connection.query(updateStr,[thisUpdate, userId], function(err, rep) {
                if (err) {console.log('updateUserInfoInDB: E12: ' + err);return;}
                console.log('User: ' + userId + ' NewData: ' + distance + ' For: ' + thisUpdate);
            });
        });
    }
    else {
        var updateStr   = "UPDATE UserInfo SET Distance=? WHERE UserId=? AND lastUpdate=?";
        connection.query(updateStr, [distance, userId, lastUpdate], function(err, rep) {
            if (err) {console.log('updateUserInfoInDB: E13: ' + err);return}
            console.log('User: ' + userId + ' Update: ' + distance + ' For: ' + thisUpdate);
        });
    }
}


app.listen(4002);
console.log('Listening on port: 4002');
\$\endgroup\$
1
\$\begingroup\$

I'll just comment on a few of the node/js stuff that I think will make your code simpler and point out some js coding standards.

  • Use === when comparing equality, avoid having to memorize this I normally only use == when I want to treat null and undefined as the same.

  • use forEach when iterating over array values. When iterating over array values I only use a traditional for loop if I have to break out of it. I prefer forEach because it is less code and less variables to declare.

    req.body.forEach(function(user){
    

    vs

    for(var loop=0;loop < req.body.length; loop++)
        {
            var user = req.body[loop];
    
  • Date.now() can be used over new Date().getTime()

  • console.log can use formatting console.log('User: %s Update: %s For: %s', user, distance, thisUpdate);

  • resp.length === 0 could be !resp.length (either one is fine but just pointing it out)

Not sure if all the process.nextTick's are needed but for something like

app.post('/tagfit2/rest/fitbitupdate',
  function(req, res, next) {

    for(var loop=0;loop < req.body.length; loop++)
    {
        var user = req.body[loop];
        process.nextTick(function() {updateUserInfo(user);});
    }
    res.status(204).send();
  }
);

You are essentially (it all depends on the current event loop) doing

app.post('/tagfit2/rest/fitbitupdate',
  function(req, res, next) {
    res.status(204).send();
    for(var loop=0;loop < req.body.length; loop++)
    {
        var user = req.body[loop];
        updateUserInfo(user);
    }

  }
);

Not sure exactly why the ternary operator is being used here but the || operator can be used to default values a little cleaner

var action = httpType == "https" ? https : http;

vs

var action = httpType || http;
\$\endgroup\$
1
  • \$\begingroup\$ Thanks. That helped a lot. Still getting used to the idioms of javascript. \$\endgroup\$ – Martin York Dec 19 '14 at 20:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.