diff --git a/package.json b/package.json index 8d7d1f36d8..f1f5636c67 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,8 @@ "validator": "~1.5.1", "nodemailer": "~0.5.2", "grunt-cli": "~0.1.9", - "paypal-ipn": "~1.0.1" + "paypal-ipn": "~1.0.1", + "express-csv": "~0.6.0" }, "private": true, "subdomain": "habitrpg", diff --git a/public/js/app.js b/public/js/app.js index 359a76dfdc..1b4d1d5d18 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -31,6 +31,7 @@ window.habitrpg = angular.module('habitrpg', .when('/options/groups/guilds', '/options/groups/guilds/public') .when('/options/inventory', '/options/inventory/inventory') .when('/options/inventory/stable', '/options/inventory/stable/pets') + .when('/options/settings', '/options/settings/settings') // redirect states that don't match .otherwise("/tasks"); @@ -161,6 +162,18 @@ window.habitrpg = angular.module('habitrpg', controller: 'SettingsCtrl', templateUrl: "partials/options.settings.html" }) + .state('options.settings.settings', { + url: "/settings", + templateUrl: "partials/options.settings.settings.html" + }) + .state('options.settings.api', { + url: "/api", + templateUrl: "partials/options.settings.api.html" + }) + .state('options.settings.export', { + url: "/export", + templateUrl: "partials/options.settings.export.html" + }) // Options > Settings .state('options.admin', { diff --git a/src/controllers/auth.js b/src/controllers/auth.js index 7a7242e3f0..f0e065738b 100644 --- a/src/controllers/auth.js +++ b/src/controllers/auth.js @@ -13,6 +13,7 @@ var api = module.exports; var NO_TOKEN_OR_UID = { err: "You must include a token and uid (user id) in your request"}; var NO_USER_FOUND = {err: "No user found."}; +var NO_SESSION_FOUND = { err: "You must be logged in." }; /* beforeEach auth interceptor @@ -40,6 +41,19 @@ api.auth = function(req, res, next) { }); }; +api.authWithSession = function(req, res, next) { //[todo] there is probably a more elegant way of doing this... + var uid; + uid = req.session.userId; + if (!(req.session && req.session.userId)) { + return res.json(401, NO_SESSION_FOUND); + } + return User.findOne({_id: uid,}, function(err, user) { + if (err) return res.json(500, {err: err}); + if (_.isEmpty(user)) return res.json(401, NO_USER_FOUND); + res.locals.user = user; + return next(); + }); +}; api.registerUser = function(req, res, next) { var confirmPassword, e, email, password, username, _ref; @@ -263,4 +277,4 @@ api.setupPassport = function(router) { // if (req.isAuthenticated()) { return next(); } // res.redirect('/login') // } -}; \ No newline at end of file +}; diff --git a/src/controllers/dataexport.js b/src/controllers/dataexport.js new file mode 100644 index 0000000000..817e20791a --- /dev/null +++ b/src/controllers/dataexport.js @@ -0,0 +1,27 @@ +var _ = require('lodash'); +var csv = require('express-csv'); +var nconf = require('nconf'); +var moment = require('moment'); +var dataexport = module.exports; + + +/* + ------------------------------------------------------------------------ + Data export + ------------------------------------------------------------------------ +*/ + +dataexport.history = function(req, res) { + var user = res.locals.user; + var output = [ + ["Task Name", "Task ID", "Task Type", "Date", "Value"] + ]; + _.each(user.tasks, function(task) { + _.each(task.history, function(history) { + output.push( + [task.text, task.id, task.type, moment(history.date).format("MM-DD-YYYY HH:mm:ss"), history.value] + ); + }); + }); + return res.csv(output); +} diff --git a/src/routes/api.js b/src/routes/api.js index 7f0ef08b3f..4eaaa796c2 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -4,6 +4,7 @@ var user = require('../controllers/user'); var groups = require('../controllers/groups'); var auth = require('../controllers/auth'); var challenges = require('../controllers/challenges'); +var dataexport = require('../controllers/dataexport'); var nconf = require('nconf'); /* @@ -25,6 +26,9 @@ router.get('/status', function(req, res) { }); }); +/* Data export */ +router.get('/export/history',auth.auth,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes + /* Scoring*/ router.post('/user/task/:id/:direction', auth.auth, cron, user.scoreTask); router.post('/user/tasks/:id/:direction', auth.auth, cron, user.scoreTask); diff --git a/src/routes/dataexport.js b/src/routes/dataexport.js new file mode 100644 index 0000000000..090961fd99 --- /dev/null +++ b/src/routes/dataexport.js @@ -0,0 +1,10 @@ +var express = require('express'); +var router = new express.Router(); +var dataexport = require('../controllers/dataexport'); +var auth = require('../controllers/auth'); +var nconf = require('nconf'); + +/* Data export */ +router.get('/history.csv',auth.authWithSession,dataexport.history); //[todo] encode data output options in the data controller and use these to build routes + +module.exports = router; diff --git a/src/server.js b/src/server.js index 902669becd..d6b43abb95 100644 --- a/src/server.js +++ b/src/server.js @@ -114,6 +114,7 @@ if ("development" === app.get("env")) { app.use(require('./routes/pages').middleware); app.use(require('./routes/auth').middleware); app.use('/api/v1', require('./routes/api').middleware); +app.use('/export', require('./routes/dataexport').middleware); app.use(require('./controllers/deprecated').middleware); server = http.createServer(app).listen(app.get("port"), function() { return console.log("Express server listening on port " + app.get("port")); diff --git a/views/options/settings.jade b/views/options/settings.jade index 0b587fc567..e5dd0b2cee 100644 --- a/views/options/settings.jade +++ b/views/options/settings.jade @@ -1,6 +1,22 @@ -script(type='text/ng-template', id='partials/options.settings.html') +script(id='partials/options.settings.html', type="text/ng-template") + ul.nav.nav-tabs + li(ng-class="{ active: $state.includes('options.settings.settings') }") + a(ui-sref='options.settings.settings') + | Settings + li(ng-class="{ active: $state.includes('options.settings.api') }") + a(ui-sref='options.settings.api') + | API + li(ng-class="{ active: $state.includes('options.settings.export') }") + a(ui-sref='options.settings.export') + | Data Export + + .tab-content + .tab-pane.active + div(ui-view) + +script(type='text/ng-template', id='partials/options.settings.settings.html') .row-fluid - .personal-options.span6.border-right + .personal-options.span6 h2 Settings h4 Custom Day Start .option-group.option-short @@ -36,6 +52,9 @@ script(type='text/ng-template', id='partials/options.settings.html') a.btn.btn-danger(ng-click='modals.reset = true', tooltip='Resets your entire account (dangerous).') Reset a.btn.btn-danger(ng-click='modals.restore = true', tooltip='Restores attributes to your character.') Restore a.btn.btn-danger(ng-click='modals.delete = true', tooltip='Delete your account.') Delete + +script(type='text/ng-template', id='partials/options.settings.api.html') + .row.fluid .span6 h2 API small Copy these for use in third party applications. @@ -45,3 +64,11 @@ script(type='text/ng-template', id='partials/options.settings.html') pre.prettyprint {{user.apiToken}} h6 QR Code img(src='https://chart.googleapis.com/chart?cht=qr&chs=200x200&chl=%7Baddress%3A%22https%3A%2F%2Fhabitrpg.com%22%2Cuser%3A%22{{user.id}}%22%2Ckey%3A%22{{user.apiToken}}%22%7D,&choe=UTF-8&chld=L', alt='qrcode') + +script(id='partials/options.settings.export.html', type="text/ng-template") + .row.fluid + .span6 + h2 Data Export + small Here are a few options for saving your Habit data. + h4 Habit History + a(href="/export/history.csv") Export History (CSV)