var express = require("express") , passport = require("passport") , passportLocal = require("passport-local") , fs = require("fs") , path = require("path") , proxy = require("./util/simple-http-proxy") , CSV = require("ya-csv") , CouchDB = require('./util/couch').CouchDB , _ = require("underscore") ; function assertSetting(name, settings, validate) { if ( typeof settings[name] === 'undefined' ) { throw new Error("Required setting '"+name+"' undefined."); } if ( _.isFunction(validate) && !validate(settings[name]) ) { throw new Error("Setting '"+name+"' with value '"+settings[name]+"' is invalid."); } } exports.App = function(settings) { assertSetting("couchDbURL", settings, _.isString); var couch = new CouchDB(settings.couchDbURL); function clientPath(relativePath) { return path.resolve(__dirname+'/../client/'+relativePath); } passport.use(new passportLocal.Strategy(function(username, password, done){ if ( username === "igor" && password === "mayer" ) { done(null,{ username: "igor" }); } else { done(null,false,{ message: 'Invalid credentials.' }); } })); passport.serializeUser(function(user, done) { done(null, user.username); }); passport.deserializeUser(function(id, done) { done(null, {username: id}); }); var app = express(); app.use(express.logger()); app.use(express.compress()); app.use(express.favicon()); // cookies and session app.use('/api',express.bodyParser()); app.use(express.cookieParser()); app.use(express.session({ secret: "quasi experimental design" })); // passport app.use(passport.initialize()); app.use(passport.session()); function ensureAuthenticated(req,res,next){ if (!req.user) { return res.send(401,{error:"Login before accessing API."}); } else { return next(); } } function returnUser(req,res) { res.send(200, req.user); } // static resources app.get('/', function(request, response){ response.sendfile(clientPath('index.html')); }); app.get('/*.html', function(request, response) { response.sendfile(clientPath(request.path)); }); _.each(['/dojo', '/dijit', '/dojox', '/qed', '/qed-client'], function(dir){ app.use(dir, express.static(clientPath(dir))); }); // post to this url to login app.post( '/api/login', passport.authenticate('local'), returnUser); // return the info for the current logged in user app.get( '/api/login', ensureAuthenticated, returnUser); // explicitly logout this user app.post( '/api/logout', ensureAuthenticated, function(req,res){ req.logout(); res.send(200,{}); }); // expose everything needed to fill in responses // FIXME : this needs more security, now you can guess ids // and get actual survey and response data! app.get( '/live', function(req,res){ res.redirect('/response.html#!/55704d6a84194c2d7ba9bec93c000e75'); }); app.get( '/api/surveyRuns/:id', function(req,res){ var id = req.params.id; couch.get(id).then(function(result){ if ( result.type === 'SurveyRun') { res.send(200, result); } else { res.send(404, {error: "Cannot find survey run "+id}); } }, function(error){ res.send(404, {error: "Cannot find survey run "+id}); }); }); app.get( '/api/responses/:id', function(req,res){ var id = req.params.id; couch.get(id).then(function(result){ if ( result.type === 'Response') { res.send(200, result); } else { res.send(404, {error: "Cannot find response "+id}); } }, function(error){ res.send(404, {error: "Cannot find response "+id}); }); }); app.post( '/api/responses', function(req,res){ console.log(req.body); couch.post(req.body).then(function(result){ res.send(200, result); }, function(error){ res.send(500, error); }); }); app.put( '/api/responses/:id', function(req,res){ var id = req.params.id; couch.put(id,req.body).then(function(result){ res.send(200, result); }, function(error){ res.send(500, error); }); }); // data is proxied to couch app.use('/api/data', ensureAuthenticated); app.use('/api/data', proxy(settings.couchDbURL)); // generate CSV download of responses app.get( '/api/surveyRuns/:id/responses.csv', ensureAuthenticated, function(req, res) { var id = req.params.id; var url = '_design/responses/_view/by_surveyrun?key='+JSON.stringify(id); couch.get(url).then(function(result){ var responses = _.map(result.rows, function(item) { return item.value; }); var flatResponses = responsesToVariables(responses); res.set({ 'Content-Type': 'text/csv', 'Content-Disposition': 'attachment; filename=surveyRun-'+id+'-responses.csv' }); res.status(200); writeObjectsToCSVStream(flatResponses, res); res.end(); }, function(error){ res.send(404, {error: "Cannot find responses for survey run "+id}); }); }); return app; }; function responsesToVariables(responses) { return _.map(responses, responseToVariables); } function responseToVariables(response) { var result = flattenObject(response.answers); return result; } function flattenObject(value) { var result = {}; (function agg(val,key,res){ if ( _.isObject(val) ) { var keys = _.keys(val); // FIXME : dirty hack for questions with only one input if ( keys.length === 1 ) { agg(val[keys[0]],key,res); } else { _.forEach(val, function(v,k){ agg(v,(key ? key+'.' : '')+k,res); }); } } else if ( _.isArray(val) ) { // FIXME : dirty hack for questions with only one input if ( val.length === 1 ) { agg(val[0],key,res); } else { _.forEach(val, function(v,i){ agg(v,(key ? key+'.' : '')+i,res); }); } } else { res[key] = val; } })(value,null,result); return result; } function writeObjectsToCSVStream(objects, stream) { var keys = _.chain(objects) .map(_.keys) .flatten() .uniq() .value(); var idxs = {}; _.forEach(keys, function(key,idx){ idxs[key] = idx; }); var writer = new CSV.CsvWriter(stream); writer.writeRecord(keys); _.forEach(objects, function(obj){ var row = []; _.forEach(obj, function(val,key){ row[idxs[key]] = val; }); writer.writeRecord(row); }); }