diff --git a/analysis/analyse.js b/analysis/analyse.js
index a73e684..4b77408 100755
--- a/analysis/analyse.js
+++ b/analysis/analyse.js
@@ -7,10 +7,16 @@ function main(path) {
var groups = lib.makeGroups(db);
// Erase groups that are not usable
+ var invalid = 0;
groups = groups.filter(function(elt) {
// An elt is valid if it contains at least 2 exp, BaseRecommendation included
- return elt.length > 1 && elt.find(function(e) { return e.recommendation_style[4] === 'B'; }) !== undefined;
+ if (elt.length > 1 && elt.find(function(e) { return e.recommendation_style[4] === 'B'; }) !== undefined) {
+ return true
+ } else {
+ invalid++;
+ return false;
+ }
});
@@ -18,7 +24,13 @@ function main(path) {
elt.sort(lib.compareRecommendationStyle);
});
- console.log(`There were ${db.users.length} users for ${db.experiments.length} experiments`);
+ var nbExp = 0;
+
+ groups.forEach(function(elt) {
+ nbExp += elt.length;
+ });
+
+ console.log(`There were ${db.users.length} users for ${nbExp} experiments (${invalid} invalid)`);
console.log(`There were ${groups.length} groups that were made.`);
groups.forEach(function(elt) {
diff --git a/analysis/dbrequests.js b/analysis/dbrequests.js
new file mode 100644
index 0000000..0f41b1c
--- /dev/null
+++ b/analysis/dbrequests.js
@@ -0,0 +1,1629 @@
+// Polyfill of find array
+if (!Array.prototype.find) {
+ Array.prototype.find = function(predicate) {
+ if (this === null || this === undefined) {
+ throw new TypeError('Array.prototype.find a été appelé sur null ou undefined');
+ }
+ if (typeof predicate !== 'function') {
+ throw new TypeError('predicate doit être une fonction');
+ }
+ var list = Object(this);
+ var length = list.length >>> 0;
+ var thisArg = arguments[1];
+ var value;
+
+ for (var i = 0; i < length; i++) {
+ value = list[i];
+ if (predicate.call(thisArg, value, i, list)) {
+ return value;
+ }
+ }
+ return undefined;
+ };
+}
+
+// Production steps of ECMA-262, Edition 5, 15.4.4.21
+// Reference: http://es5.github.io/#x15.4.4.21
+if (!Array.prototype.reduce) {
+ Array.prototype.reduce = function(callback /*, initialValue*/) {
+ 'use strict';
+ if (this === null || this === undefined) {
+ throw new TypeError('Array.prototype.reduce called on null or undefined');
+ }
+ if (typeof callback !== 'function') {
+ throw new TypeError(callback + ' is not a function');
+ }
+ var t = Object(this), len = t.length >>> 0, k = 0, value;
+ if (arguments.length == 2) {
+ value = arguments[1];
+ } else {
+ while (k < len && !(k in t)) {
+ k++;
+ }
+ if (k >= len) {
+ throw new TypeError('Reduce of empty array with no initial value');
+ }
+ value = t[k++];
+ }
+ for (; k < len; k++) {
+ if (k in t) {
+ value = callback(value, t[k], k, t);
+ }
+ }
+ return value;
+ };
+}
+
+var pg = require('pg');
+var pgc = require('../../private.js');
+var Log = require('../../lib/NodeLog.js');
+var async = require('async');
+
+/**
+ *
+ * @namespace
+ */
+var DBReq = {};
+
+/**
+ * A class that loads every information from an experiment
+ * @param id {Number} id of the experiment to load
+ * @param finishAction {function} callback on the result when loading is
+ * finished
+ * @memberof DBReq
+ * @constructor
+ * @private
+ */
+DBReq.Info = function(id, finishAction) {
+
+ /**
+ * Id of the experiment to log
+ * @type {Number}
+ */
+ this.id = id;
+
+ /**
+ * Map between each element to load and a boolean saying if the element has
+ * been already loaded
+ * @type {Object}
+ */
+ this.ready = {
+ cameras: false,
+ coins: false,
+ arrows: false,
+ resets : false,
+ previousNext: false,
+ hovered: false,
+ pointerLocked: false,
+ switchedLockOption: false,
+ redCoins: false
+ };
+
+ /**
+ * List of the ids of the coins involved in the experiment
+ * @type {Number[]}
+ */
+ this.redCoins = [];
+
+ /**
+ * Container of the result
+ * @type {Object}
+ */
+ this.results = {};
+
+ /**
+ * Callback to call on finalResult when the loading is complete
+ * @type {function}
+ */
+ this.finishAction = finishAction;
+
+ /**
+ * Client of connection to database
+ */
+ this.client = null;
+
+ /**
+ * Function that releases the client to database
+ * @type {function}
+ */
+ this.release = null;
+
+ // Connect to db
+ var self = this;
+ self.client = new pg.Client(pgc.url);
+
+ self.client.connect(function(err) {
+ if (err !== null) {
+ // Retry later
+ setTimeout(function() {
+ new DBReq.Info(id, finishAction);
+ }, 1000);
+ return;
+ }
+ self.execute();
+ });
+};
+
+/**
+ * Loads everything async
+ */
+DBReq.Info.prototype.execute = function() {
+ this.loadCameras();
+ this.loadCoins();
+ this.loadArrows();
+ this.loadResets();
+ this.loadPreviousNext();
+ this.loadHovered();
+ this.loadSwitchedLockOption();
+ this.loadPointerLocked();
+ this.loadRedCoins();
+};
+
+/**
+ * Checks if everything is loaded, and if so merges the results and call the
+ * final callback
+ */
+DBReq.Info.prototype.tryMerge = function() {
+ // If not ready, do nothing
+ for (var i in this.ready) {
+ if (!this.ready[i]) {
+ return;
+ }
+ }
+
+ // Release db connection
+ // this.release();
+ // this.release = null;
+ this.client.end();
+ this.client = null;
+
+ this.merge();
+ this.finishAction(this.finalResult);
+};
+
+/**
+ * Merges the results of the different SQL requests and prepare final result.
+ */
+DBReq.Info.prototype.merge = function() {
+
+ var i = 0;
+
+ this.finalResult = {redCoins : [], events : []};
+
+ for (;;) {
+ // Find next element
+ var nextIndex = null;
+
+ for (i in this.results) {
+ // The next element is placed at the index 0 (since the elements
+ // gotten from the database are sorted)
+ if (this.results[i].length !== 0 &&
+ (nextIndex === null || this.results[i][0].time < this.results[nextIndex][0].time)) {
+ nextIndex = i;
+ }
+ }
+
+ // If there is no next element, we're done
+ if (nextIndex === null) {
+ break;
+ }
+
+ // Add the next element in results and shift its table
+ this.finalResult.events.push(this.results[nextIndex].shift());
+ }
+
+ // Set red coins
+ for (i = 0; i < this.redCoins.length; i++) {
+ this.finalResult.redCoins.push(this.redCoins[i]);
+ }
+};
+
+/**
+ * Launches the SQL request to load the camera information from the DB and
+ * tries to merge when finished
+ */
+DBReq.Info.prototype.loadCameras = function() {
+ var self = this;
+ this.client.query(
+ "SELECT ((camera).position).x AS px, " +
+ "((camera).position).y AS py, " +
+ "((camera).position).z AS pz, " +
+ "((camera).target).x AS tx, " +
+ "((camera).target).y AS ty, " +
+ "((camera).target).z AS tz, " +
+ "time AS time " +
+ "FROM keyboardevent WHERE exp_id = $1 ORDER BY time;",
+ [self.id],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in loadCameras');
+ } else {
+ self.results.cameras = [];
+ for (var i in result.rows) {
+ self.results.cameras.push(
+ {
+ type: 'camera',
+ position : {
+ x: result.rows[i].px,
+ y: result.rows[i].py,
+ z: result.rows[i].pz
+ },
+ target : {
+ x: result.rows[i].tx,
+ y: result.rows[i].ty,
+ z: result.rows[i].tz
+ },
+ time: result.rows[i].time
+ }
+ );
+ }
+ }
+ self.ready.cameras = true;
+ self.tryMerge();
+ }
+ );
+};
+
+/**
+ * Launches the SQL request to load the coin information from the DB and
+ * tries to merge when finished
+ */
+DBReq.Info.prototype.loadCoins = function() {
+ var self = this;
+ this.client.query(
+ "SELECT coin_id AS \"coinId\", time FROM coinclicked WHERE exp_id = $1 ORDER BY time;",
+ [self.id],
+ function(err,result) {
+ if (err !== null) {
+ Log.dberror(err + ' in loadCoins');
+ } else {
+ self.results.coins = [];
+ var i = 0;
+ for (; i < result.rows.length; i++) {
+ self.results.coins.push(
+ {
+ type: 'coin',
+ time: result.rows[i].time,
+ id: result.rows[i].coinId
+ }
+ );
+ }
+ }
+ self.ready.coins = true;
+ self.tryMerge();
+ }
+ );
+};
+
+/**
+ * Launches the SQL request to load the recommendation information from the DB and
+ * tries to merge when finished
+ */
+DBReq.Info.prototype.loadArrows = function() {
+ var self = this;
+ this.client.query(
+ "SELECT arrow_id AS \"arrowId\", time FROM arrowclicked WHERE exp_id = $1 ORDER BY time;",
+ [self.id],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in loadArrows');
+ } else {
+ self.results.arrows = [];
+ var i = 0;
+ for (; i < result.rows.length; i++) {
+ self.results.arrows.push(
+ {
+ type: 'arrow',
+ time: result.rows[i].time,
+ id: result.rows[i].arrowId
+ }
+ );
+ }
+ }
+ self.ready.arrows = true;
+ self.tryMerge();
+ }
+ );
+};
+
+/**
+ * Launches the SQL request to load the reset information from the DB and
+ * tries to merge when finished
+ */
+DBReq.Info.prototype.loadResets = function() {
+ var self = this;
+ this.client.query(
+ "SELECT time FROM resetclicked WHERE exp_id = $1 ORDER BY time;",
+ [self.id],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in loadResets');
+ } else {
+ self.results.resets = [];
+ var i = 0;
+ for (; i < result.rows.length; i++) {
+ self.results.resets.push(
+ {
+ type: 'reset',
+ time: result.rows[i].time
+ }
+ );
+ }
+ }
+ self.ready.resets = true;
+ self.tryMerge();
+ }
+ );
+};
+
+/**
+ * Launches the SQL request to load the previous / next information from the DB and
+ * tries to merge when finished
+ */
+DBReq.Info.prototype.loadPreviousNext = function () {
+ var self = this;
+ this.client.query(
+ "SELECT ((camera).position).x AS px, " +
+ "((camera).position).y AS py, " +
+ "((camera).position).z AS pz, " +
+ "((camera).target).x AS tx, " +
+ "((camera).target).y AS ty, " +
+ "((camera).target).z AS tz, " +
+ "time AS time " +
+ "FROM previousnextclicked WHERE exp_id = $1 ORDER BY time;",
+ [self.id],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in loadPreviousNext');
+ } else {
+ self.results.previousNext = [];
+ var i = 0;
+ for (; i < result.rows.length; i++) {
+ self.results.previousNext.push(
+ {
+ type: 'previousnext',
+ time: result.rows[i].time,
+ previous: result.rows[i].previousnext == 'p',
+ position : {
+ x: result.rows[i].px,
+ y: result.rows[i].py,
+ z: result.rows[i].pz
+ },
+ target : {
+ x: result.rows[i].tx,
+ y: result.rows[i].ty,
+ z: result.rows[i].tz
+ }
+ }
+ );
+ }
+ }
+ self.ready.previousNext = true;
+ self.tryMerge();
+ }
+ );
+};
+
+/**
+ * Launches the SQL request to load the hovered information from the DB and
+ * tries to merge when finished
+ */
+DBReq.Info.prototype.loadHovered = function() {
+ var self = this;
+ this.client.query(
+ "SELECT start, time, arrow_id AS \"arrowId\" FROM hovered WHERE exp_id = $1 ORDER BY time;",
+ [self.id],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in loadHovered');
+ } else {
+ self.results.hovered = [];
+ var i = 0;
+ for (; i < result.rows.length; i++) {
+ self.results.hovered.push(
+ {
+ type: "hovered",
+ time: result.rows[i].time,
+ start: result.rows[i].start,
+ id: result.rows[i].arrowId
+ }
+ );
+ }
+ }
+ self.ready.hovered = true;
+ self.tryMerge();
+ }
+ );
+};
+
+/**
+ * Launches the SQL request to load the pointer lock information from the DB and
+ * tries to merge when finished
+ */
+DBReq.Info.prototype.loadPointerLocked = function() {
+ var self = this;
+ this.client.query(
+ "SELECT time, locked FROM pointerlocked WHERE exp_id = $1 ORDER BY time;",
+ [self.id],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in loadPointerLocked');
+ } else {
+ self.results.pointerlocked = [];
+ var i = 0;
+ for (; i < result.rows.length; i++) {
+ self.results.pointerlocked.push(
+ {
+ type: "pointerlocked",
+ locked: result.rows[i].locked,
+ time: result.rows[i].time
+ }
+ );
+ }
+ }
+ self.ready.pointerLocked = true;
+ self.tryMerge();
+ }
+ );
+};
+
+/**
+ * Launches the SQL request to load the switch pointer lock option information
+ * from the DB and tries to merge when finished
+ */
+DBReq.Info.prototype.loadSwitchedLockOption = function() {
+ var self = this;
+ this.client.query(
+ "SELECT time, locked FROM switchedlockoption WHERE exp_id = $1 ORDER BY time;",
+ [self.id],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in loadSwitchedLockOption');
+ } else {
+ self.results.switchedlockoption = [];
+ var i = 0;
+ for (; i < result.rows.length; i++) {
+ self.results.switchedlockoption.push(
+ {
+ type: "switchedlockoption",
+ locked: result.rows[i].locked,
+ time: result.rows[i].time
+ }
+ );
+ }
+ }
+ self.ready.switchedLockOption = true;
+ self.tryMerge();
+ }
+ );
+};
+
+/**
+ * Launches the SQL request to load the coins used in the expermient from the
+ * DB and tries to merge when finished
+ */
+DBReq.Info.prototype.loadRedCoins = function() {
+ var self = this;
+ this.client.query(
+ "SELECT coin_1 AS coin1, \n" +
+ " coin_2 AS coin2, \n" +
+ " coin_3 AS coin3, \n" +
+ " coin_4 AS coin4, \n" +
+ " coin_5 AS coin5, \n" +
+ " coin_6 AS coin6, \n" +
+ " coin_7 AS coin7, \n" +
+ " coin_8 AS coin8 \n" +
+ "FROM CoinCombination, Experiment \n" +
+ "WHERE CoinCombination.id = Experiment.coin_combination_id AND \n" +
+ " Experiment.id = $1;",
+ [self.id],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in loadRedCoins');
+ } else {
+ self.redCoins.push(result.rows[0].coin1);
+ self.redCoins.push(result.rows[0].coin2);
+ self.redCoins.push(result.rows[0].coin3);
+ self.redCoins.push(result.rows[0].coin4);
+ self.redCoins.push(result.rows[0].coin5);
+ self.redCoins.push(result.rows[0].coin6);
+ self.redCoins.push(result.rows[0].coin7);
+ self.redCoins.push(result.rows[0].coin8);
+ }
+ self.ready.redCoins = true;
+ self.tryMerge();
+ }
+ );
+};
+
+/**
+ * Class that creates a user
+ * @param workerId {string} the name of the person doing the experiment
+ * @param age {string} a string representing an age range
+ * @param male {boolean} indicates if the user is a man or a woman
+ * @param rating {Number} between 1 and 5, describes the level of the user
+ * @param lastTime {Number} between 0 and 3 such that
+ *
+ * - never played
+ * - this year
+ * - this month
+ * - this week
+ *
+ * @param finishAction {function} callback that has as a parameter the id of
+ * the new user
+ * @memberof DBReq
+ * @constructor
+ * @private
+ */
+DBReq.UserCreator = function(workerId, age, male, rating, lastTime, finishAction) {
+ /**
+ * Callback to call on the id when the user is created
+ * @type {function}
+ */
+ this.finishAction = finishAction;
+
+ this.workerId = workerId;
+ this.age = age;
+ this.male = male;
+ this.rating = rating;
+ this.lastTime = lastTime;
+
+ // Connect to db
+ var self = this;
+ pg.connect(pgc.url, function(err, client, release) {
+ self.client = client;
+ self.release = release;
+ self.execute();
+ });
+};
+
+/**
+ * Executes the SQL request and calls the callback
+ */
+DBReq.UserCreator.prototype.execute = function() {
+ var self = this;
+ this.client.query("BEGIN; LOCK Users IN SHARE ROW EXCLUSIVE MODE;", [], function() {
+ self.client.query(
+ "INSERT INTO users(worker_id, age, male, rating, lasttime) VALUES($1, $2, $3, $4, $5);",
+ [
+ self.workerId,
+ self.age,
+ self.male,
+ self.rating,
+ self.lastTime
+ ],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in UserCreator INSERT INTO');
+ }
+ self.client.query(
+ "SELECT max(id) FROM Users;",
+ [],
+ function(err, result) {
+ self.finalResult = result.rows[0].max;
+ self.finish();
+ }
+ );
+ }
+ );
+ });
+};
+
+/**
+ * Release the DB connection and call the callback
+ */
+DBReq.UserCreator.prototype.finish = function() {
+
+ var self = this;
+
+ this.client.query("COMMIT;", [], function() {
+
+ self.release();
+ self.client = null;
+ self.release = null;
+
+ self.finishAction(self.finalResult);
+
+ });
+};
+
+/**
+ * Class that creates an experiment
+ * @param userId {Number} id of the user that does the experiment
+ * @param experiments {Object[]} array of objects representing the experiments
+ * that the user has already done {sceneId: Number, recommendationStyle: string, coins Number[]}
+ * @param finishAction {function} callback that has as parameters
+ *
+ * - the id of the experiment (Number)
+ * - the id of the coin combination (Number)
+ * - the id of the scene (Number)
+ * - the recommendation style (string)
+ * - the coins (Number[])
+ *
+ * @memberof DBReq
+ * @constructor
+ * @private
+ */
+DBReq.ExpCreator = function(userId, experiments, finishAction) {
+ this.finishAction = finishAction;
+ this.userId = userId;
+ this.experiments = experiments;
+ this.finalResult = {};
+
+ // Connect to db
+ var self = this;
+ pg.connect(pgc.url, function(err, client, release) {
+
+ self.client = client;
+ self.release = release;
+
+ self.startTime = Date.now();
+
+ // Start transaction and lock table
+ self.client.query("BEGIN; LOCK CoinCombination IN SHARE ROW EXCLUSIVE MODE; LOCK Experiment IN SHARE ROW EXCLUSIVE MODE;", [], function() {
+ self.execute();
+ });
+ });
+};
+
+function predicate(line) {
+
+ return function(elt, idx, arr) {
+
+ return (
+ (elt.recommendationStyle !== null && (elt.recommendationStyle.trim() === line.recommendationStyle.trim())) ||
+ line.sceneId === elt.sceneId
+ );
+
+ };
+
+}
+
+/**
+ * Executes the SQL request and calls the callback
+ */
+// Abandon all hope ye who enter here
+DBReq.ExpCreator.prototype.execute = function() {
+ var self = this;
+ this.client.query(
+ "SELECT DISTINCT \n" +
+ " RecommendationStyle.name AS \"recommendationStyle\", \n" +
+ " CoinCombination.scene_id AS \"sceneId\", \n" +
+ " CoinCombination.id, \n" +
+ " coin_1 AS coin1, \n" +
+ " coin_2 AS coin2, \n" +
+ " coin_3 AS coin3, \n" +
+ " coin_4 AS coin4, \n" +
+ " coin_5 AS coin5, \n" +
+ " coin_6 AS coin6, \n" +
+ " coin_7 AS coin7, \n" +
+ " coin_8 AS coin8\n" +
+ "FROM CoinCombination, Experiment, Users U, Users Other, RecommendationStyle, Scene\n" +
+ "WHERE\n" +
+ " Scene.id = CoinCombination.scene_id AND\n" +
+ " Scene.name != 'peachcastle' AND\n" +
+ " CoinCombination.id = Experiment.coin_combination_id AND\n" +
+ " Other.id = Experiment.user_id AND\n" +
+ " Experiment.finished = true AND\n" +
+ " Other.rating = U.rating AND\n" +
+ " Other.id != U.id AND\n" +
+ " U.id = $1 \n" +
+ "EXCEPT \n" +
+ "SELECT DISTINCT \n" +
+ " Experiment.recommendation_style AS \"recommendationStyle\", \n" +
+ " CoinCombination.scene_id AS \"sceneId\", \n" +
+ " CoinCombination.id, \n" +
+ " coin_1 AS coin1, \n" +
+ " coin_2 AS coin2, \n" +
+ " coin_3 AS coin3, \n" +
+ " coin_4 AS coin4, \n" +
+ " coin_5 AS coin5, \n" +
+ " coin_6 AS coin6, \n" +
+ " coin_7 AS coin7, \n" +
+ " coin_8 AS coin8\n" +
+ "FROM CoinCombination, Experiment\n" +
+ "WHERE\n" +
+ " CoinCombination.id = Experiment.coin_combination_id;",
+ [self.userId],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in ExpCreator first request');
+ }
+
+ var line, found = false;
+
+ while ((line = result.rows.shift()) !== undefined) {
+
+ if (line === undefined) {
+
+ break;
+
+ }
+
+ // Look for an experiment impossible
+ var elt = self.experiments.find(predicate(line));
+
+ // Line is a valid experiment
+ if (elt === undefined) {
+
+ found = true;
+ break;
+
+ }
+
+ }
+
+ if (found) {
+ // Set the result
+ self.finalResult.coinCombinationId = line.id;
+ self.finalResult.sceneId = line.sceneId;
+ self.finalResult.recommendationStyle = line.recommendationStyle;
+ self.finalResult.coins = [
+ line.coin1,
+ line.coin2,
+ line.coin3,
+ line.coin4,
+ line.coin5,
+ line.coin6,
+ line.coin7,
+ line.coin8
+ ];
+
+ // There is a suggested experiment : create it
+ self.client.query(
+ "INSERT INTO Experiment(user_id, coin_combination_id, recommendation_style)\n" +
+ "VALUES($1,$2,$3)\n" +
+ "RETURNING id",
+ [self.userId, line.id, line.recommendationStyle],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in ExpCreator second request (with suggested experiment)');
+ }
+ self.finalResult.expId = result.rows[0].id;
+ self.finish();
+ }
+ );
+
+
+ } else {
+ // Find the scene / recommendation style for the new experiment
+ self.client.query(
+ "SELECT RecommendationStyle.name, Scene.id AS \"sceneId\"\n" +
+ "FROM RecommendationStyle, Scene\n" +
+ "WHERE\n" +
+ " Scene.name != 'peachcastle' AND \n" +
+ " RecommendationStyle.name NOT IN(\n" +
+ " SELECT DISTINCT Experiment.recommendation_style AS name\n" +
+ " FROM Experiment\n" +
+ " WHERE Experiment.user_id = $1\n" +
+ " AND Experiment.recommendation_style != ''\n" +
+ " ) AND\n" +
+ " Scene.id NOT IN(\n" +
+ " SELECT DISTINCT CoinCombination.scene_id AS id\n" +
+ " FROM Experiment, CoinCombination\n" +
+ " WHERE\n" +
+ " Experiment.coin_combination_id = CoinCombination.id AND\n" +
+ " user_id = $1\n" +
+ " ) \n" +
+ "\n" +
+ "ORDER BY RANDOM()\n" +
+ "LIMIT 1;",
+ [self.userId],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in ExpCreator second request (without suggested experiment');
+ }
+ if (result.rows.length > 0) {
+ self.finalResult.sceneId = result.rows[0].sceneId;
+ self.finalResult.recommendationStyle = result.rows[0].name;
+
+ // Generate random coins
+ self.client.query(
+ "SELECT generate_series AS id\n" +
+ "FROM Scene, generate_series(0,Scene.coin_number-1)\n" +
+ "WHERE Scene.id = $1\n" +
+ "ORDER BY RANDOM()\n" +
+ "LIMIT 8;",
+ [self.finalResult.sceneId],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in ExpCreator third request (without suggested experiment');
+ }
+ self.finalResult.coins = [];
+ for (var i = 0; i < 8; i++) {
+ self.finalResult.coins.push(result.rows[i].id);
+ }
+
+ // And then, create the CoinCombination
+ self.client.query(
+ "INSERT INTO CoinCombination(scene_id, coin_1, coin_2, coin_3, coin_4, coin_5, coin_6, coin_7, coin_8)\n" +
+ "VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9)\n" +
+ "RETURNING id;",
+ [
+ self.finalResult.sceneId,
+ self.finalResult.coins[0],
+ self.finalResult.coins[1],
+ self.finalResult.coins[2],
+ self.finalResult.coins[3],
+ self.finalResult.coins[4],
+ self.finalResult.coins[5],
+ self.finalResult.coins[6],
+ self.finalResult.coins[7],
+ ],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in ExpCreator fourth request (without suggested experiment');
+ }
+ self.finalResult.coinCombinationId = result.rows[0].id;
+
+ // And create the experiment
+ self.client.query(
+ "INSERT INTO Experiment(user_id, coin_combination_id, recommendation_style)\n" +
+ "VALUES($1,$2,$3)\n" +
+ "RETURNING id;",
+ [self.userId, self.finalResult.coinCombinationId, self.finalResult.recommendationStyle],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in ExpCreator fifth request (without suggested experiment');
+ }
+ self.finalResult.expId = result.rows[0].id;
+ self.finish();
+ }
+ );
+ }
+ );
+ }
+ );
+ } else {
+ self.finish();
+ }
+ }
+ );
+ }
+ }
+ );
+};
+
+/**
+ * Release the DB connection and call the callback
+ */
+DBReq.ExpCreator.prototype.finish = function() {
+ var self = this;
+
+ // self.finishAction(
+ // self.finalResult.expId,
+ // self.finalResult.coinCombinationId,
+ // self.finalResult.sceneId,
+ // self.finalResult.recommendationStyle,
+ // self.finalResult.coins
+ // );
+
+ // Commit, and then release
+ this.client.query("COMMIT;", function() {
+
+ Log.debug('Exp creation took ' + (Date.now() - self.startTime) + ' ms', true);
+
+ self.release();
+ self.client = null;
+ self.release = null;
+
+ self.finishAction(
+ self.finalResult.expId,
+ self.finalResult.sceneId,
+ self.finalResult.coinCombinationId,
+ self.finalResult.recommendationStyle,
+ self.finalResult.coins
+ );
+ });
+};
+
+/**
+ * Class that checks if an user id exists
+ * @param id {Number} id of the user to check
+ * @param finishAction {function} callback that has as a parameter which is a
+ * boolean indicating wether the user id exists or not
+ * @memberof DBReq
+ * @constructor
+ * @private
+ */
+DBReq.UserIdChecker = function(id, finishAction) {
+ this.id = id;
+ this.finishAction = finishAction;
+
+ var self = this;
+ pg.connect(pgc.url, function(err, client, release) {
+ self.client = client;
+ self.release = release;
+ self.execute();
+ });
+};
+
+/**
+ * Executes the SQL request and calls the callback
+ */
+DBReq.UserIdChecker.prototype.execute = function() {
+ var self = this;
+ this.client.query(
+ "SELECT count(id) > 0 AS answer FROM users WHERE id = $1;",
+ [self.id],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in UserIdChecker');
+ return;
+ }
+ self.finalResult = result.rows[0].answer;
+ self.finish();
+ }
+ );
+};
+
+/**
+ * Release the DB connection and call the callback
+ */
+DBReq.UserIdChecker.prototype.finish = function() {
+ this.release();
+ this.client = null;
+ this.release = null;
+
+ this.finishAction(this.finalResult);
+};
+
+/**
+ * Class that checks if a workerId exists
+ * @param id {string} workerId of to test
+ * @param finishAction {function} callback that has as a parameter which is a
+ * boolean indicating wether the user id exists or not
+ * @memberof DBReq
+ * @constructor
+ * @private
+ */
+DBReq.UserNameChecker = function(name, finishAction) {
+ this.name = name;
+ this.finishAction = finishAction;
+ var self = this;
+ pg.connect(pgc.url, function(err, client, release) {
+ if (err) {
+ Log.dberror(err + ' in UserNameChcker connection');
+ return;
+ }
+ self.client = client;
+ self.release = release;
+ self.execute();
+ });
+};
+
+/**
+ * Executes the SQL request and calls the callback
+ */
+DBReq.UserNameChecker.prototype.execute = function() {
+ var self = this;
+ this.client.query(
+ "SELECT count(id) > 0 AS answer FROM users WHERE worker_id = $1",
+ [self.name],
+ function(err, result) {
+ if (err !== null) {
+ Log.dberror(err + ' in UserNameChecker');
+ return;
+ }
+ self.finalResult = result.rows[0].answer;
+ self.finish();
+ }
+ );
+
+};
+
+/**
+ * Release the DB connection and call the callback
+ */
+DBReq.UserNameChecker.prototype.finish = function() {
+ this.release();
+ this.client = null;
+ this.release = null;
+
+ this.finishAction(this.finalResult);
+};
+
+/**
+ * Class that checks if an experiment exists
+ * @param id {Number} id of the experiment to check
+ * @param finishAction {function} callback that has as a parameter which is the
+ * id of the scene if the experiment exists, or null otherwise
+ * @memberof DBReq
+ * @constructor
+ * @private
+ */
+DBReq.ExpIdChecker = function(id, finishAction) {
+ this.id = id;
+ this.finishAction = finishAction;
+
+ var self = this;
+ pg.connect(pgc.url, function(err, client, release) {
+ self.client = client;
+ self.release = release;
+ self.execute();
+ });
+};
+
+/**
+ * Executes the SQL request and calls the callback
+ */
+DBReq.ExpIdChecker.prototype.execute = function() {
+ var self = this;
+ this.client.query(
+ "SELECT scene_id AS \"sceneId\" FROM experiment, CoinCombination WHERE CoinCombination.id = Experiment.coin_combination_id AND Experiment.id = $1;",
+ [self.id],
+ function(err, result) {
+ if (result === undefined || result.rows.length === 0) {
+ self.finalResult = null;
+ } else {
+ self.finalResult = result.rows[0].sceneId;
+ }
+ self.finish();
+ }
+ );
+};
+
+/**
+ * Release the DB connection and call the callback
+ */
+DBReq.ExpIdChecker.prototype.finish = function() {
+ this.release();
+ this.client = null;
+ this.release = null;
+
+ this.finishAction(this.finalResult);
+};
+
+/**
+ * Class that gives access to the last not finished experiment
+ * @param id {Number} id of the user of who you want the last experiment
+ * @param finishAction {function} callback that has as parameters
+ *
+ * - the id of the experiment (Number)
+ * - the id of the coin combination (Number)
+ * - the id of the scene (Number)
+ * - the recommendation style (string)
+ * - the coins (Number[])
+ *
xperiment exists, or null otherwise
+ * @memberof DBReq
+ * @private
+ * @constructor
+ */
+DBReq.LastExpGetter = function(userId, finishAction) {
+
+ var self = this;
+ this.userId = userId;
+ this.finishAction = finishAction;
+ this.finalResult = {};
+
+ if (this.userId === undefined) {
+ return;
+ }
+
+ pg.connect(pgc.url, function(err, client, release) {
+ self.client = client;
+ self.release = release;
+ self.execute();
+ });
+};
+
+/**
+ * Executes the SQL request and calls the callback
+ */
+DBReq.LastExpGetter.prototype.execute = function() {
+ var self = this;
+ this.client.query(
+ 'SELECT scene_id AS "sceneId", \n' +
+ ' coin_1, \n' +
+ ' coin_2, \n' +
+ ' coin_3, \n' +
+ ' coin_4, \n' +
+ ' coin_5, \n' +
+ ' coin_6, \n' +
+ ' coin_7, \n' +
+ ' coin_8, \n' +
+ ' Experiment.recommendation_style AS "recommendationStyle", \n' +
+ ' Experiment.id AS "expId", \n' +
+ ' CoinCombination.id as "coinCombinationId" \n' +
+ 'FROM Experiment, CoinCombination \n' +
+ 'WHERE Experiment.coin_combination_id = CoinCombination.id \n' +
+ ' AND Experiment.user_id = $1 \n' +
+ ' AND NOT Experiment.finished \n' +
+ 'ORDER BY Experiment.id DESC \n' +
+ 'LIMIT 1;',
+ [self.userId],
+ function (err, result) {
+
+ if (err !== null) {
+ Log.dberror(err + ' in LastExpGetter (DBReq)');
+ }
+
+ if (result.rows.length === 0) {
+ Log.debug('Timeout', true);
+ setTimeout(function() {
+ self.execute();
+ }, 1000);
+ return;
+ }
+
+ self.client.query(
+ 'UPDATE Experiment SET finished = true WHERE id = $1',
+ [result.rows[0].expId],
+ function() {
+ self.finalResult.sceneId = result.rows[0].sceneId;
+ self.finalResult.recommendationStyle = result.rows[0].recommendationStyle;
+ self.finalResult.coins = [
+ result.rows[0].coin_1,
+ result.rows[0].coin_2,
+ result.rows[0].coin_3,
+ result.rows[0].coin_4,
+ result.rows[0].coin_5,
+ result.rows[0].coin_6,
+ result.rows[0].coin_7,
+ result.rows[0].coin_8
+ ];
+ self.finalResult.coinCombinationId = result.rows[0].coinCombinationId;
+ self.finalResult.expId = result.rows[0].expId;
+ self.finish();
+
+ }
+ );
+ }
+ );
+};
+
+/**
+ * Release the DB connection and call the callback
+ */
+DBReq.LastExpGetter.prototype.finish = function() {
+ this.release();
+ this.client = null;
+ this.release = null;
+
+ this.finishAction(
+ this.finalResult.expId,
+ this.finalResult.sceneId,
+ this.finalResult.coinCombinationId,
+ this.finalResult.recommendationStyle,
+ this.finalResult.coins
+ );
+};
+
+/**
+ * Class that gets the info from all experiment
+ * @param finishAction {function} callback that has as a parameter which is an
+ * array of objects containing the id, the username, the name of the scene and
+ * the id of the user.
+ * @memberof DBReq
+ * @constructor
+ * @private
+ */
+DBReq.ExpGetter = function(finishAction) {
+ this.finishAction = finishAction;
+
+ var self = this;
+ pg.connect(pgc.url, function(err, client, release) {
+ self.client = client;
+ self.release = release;
+ self.execute();
+ });
+};
+
+/**
+ * Executes the SQL request and calls the callback
+ */
+DBReq.ExpGetter.prototype.execute = function() {
+ var self = this;
+ this.client.query(
+ "SELECT " +
+ "experiment.id AS \"expId\", " +
+ "users.worker_id AS username, " +
+ "scene.name AS scenename, " +
+ "users.id AS \"userId\" " +
+ "FROM experiment, users, scene, CoinCombination " +
+ "WHERE experiment.user_id = users.id and scene.id = CoinCombination.scene_id AND " +
+ " Experiment.coin_combination_id = CoinCombination.id " +
+ "ORDER BY experiment.id;",
+ [],
+ function(err, result) {
+ self.finalResult = result.rows;
+ self.finish();
+ }
+ );
+};
+
+/**
+ * Release the DB connection and call the callback
+ */
+DBReq.ExpGetter.prototype.finish = function() {
+ this.release();
+ this.client = null;
+ this.release = null;
+
+ this.finishAction(this.finalResult);
+};
+
+/**
+ * Class that creates a tutorial
+ * @param id {Number} id of the user doing the tutorial
+ * @param finishAction {function} callback that has as parameters
+ *
+ * - the id of the experiment (Number)
+ * - the id of the generated coins (Number[])
+ *
+ * @memberof DBReq
+ * @constructor
+ * @private
+ */
+DBReq.TutorialCreator = function(id, finishAction) {
+ this.id = id;
+ this.finishAction = finishAction;
+ this.finalResult = {};
+
+ var self = this;
+ pg.connect(pgc.url, function(err, client, release) {
+ self.client = client;
+ self.release = release;
+ self.execute();
+ });
+};
+
+/**
+ * Executes the SQL request and calls the callback
+ */
+DBReq.TutorialCreator.prototype.execute = function() {
+ var self = this;
+ this.client.query(
+ // Generate random coins
+ "SELECT Scene.id AS \"sceneId\", generate_series AS id\n" +
+ "FROM Scene, generate_series(0,Scene.coin_number-1)\n" +
+ "WHERE Scene.name = 'peachcastle'\n" +
+ "ORDER BY RANDOM()\n" +
+ "LIMIT 8;",
+ [],
+ function(err, result) {
+ self.finalResult.coins = [];
+ for (var i = 0; i < 8; i++) {
+ self.finalResult.coins.push(result.rows[i].id);
+ }
+ // Create CoinCombination
+ self.client.query(
+ "INSERT INTO CoinCombination(scene_id, coin_1, coin_2, coin_3, coin_4, coin_5, coin_6, coin_7, coin_8)\n" +
+ "VALUES($1,$2,$3,$4,$5,$6,$7,$8,$9)\n" +
+ "RETURNING id;",
+ [
+ result.rows[0].sceneId,
+ result.rows[0].id,
+ result.rows[1].id,
+ result.rows[2].id,
+ result.rows[3].id,
+ result.rows[4].id,
+ result.rows[5].id,
+ result.rows[6].id,
+ result.rows[7].id
+ ],
+ function(err, result) {
+ // Create experiment
+ self.client.query(
+ "INSERT INTO Experiment(user_id, coin_combination_id, finished)\n" +
+ "VALUES($1,$2, true)\n" +
+ "RETURNING id;",
+ [self.id, result.rows[0].id],
+ function(err, result) {
+ self.finalResult.expId = result.rows[0].id;
+ self.finish();
+ }
+ );
+ }
+ );
+ }
+ );
+};
+
+/**
+ * Release the DB connection and call the callback
+ */
+DBReq.TutorialCreator.prototype.finish = function() {
+ this.release();
+ this.release = null;
+ this.client = null;
+
+ this.finishAction(this.finalResult.expId, this.finalResult.coins);
+};
+
+/**
+ * Class that verifies that a user has correctly done all the experiments
+ * @param userId {Number} id of the user to verify
+ * @param finishAction {function} callback that has as parameter a boolean
+ * which is true is the verification was a success
+ * @memberof DBReq
+ * @constructor
+ * @private
+ */
+DBReq.UserVerifier = function(userId, finishAction) {
+ this.userId = userId;
+ this.finishAction = finishAction;
+ var self = this;
+
+ pg.connect(pgc.url, function(err, client, release) {
+ self.client = client;
+ self.release = release;
+
+ self.execute();
+ });
+};
+
+/**
+ * Executes the SQL request and calls the callback
+ */
+DBReq.UserVerifier.prototype.execute = function() {
+
+ var self = this;
+ this.client.query(
+ "SELECT id as \"expId\" FROM Experiment WHERE user_id = $1",
+ [self.userId],
+ function(err, result) {
+ if (result.rows.length !== 4) {
+ self.finish(false);
+ }
+
+ async.map(
+ result.rows,
+ function(elt, callback) {
+ self.client.query(
+ "SELECT count(*) > 5 AS ok FROM CoinClicked WHERE exp_id = $1",
+ [elt.expId],
+ function(err, result) {
+ callback(null, result.rows[0].ok === true);
+ }
+ );
+ },
+ function(err, result) {
+ var ok = result.reduce(function(prev, next) { return prev && next; });
+ self.client.query(
+ "UPDATE Users SET valid = $1 WHERE id = $2",
+ [ok, self.userId],
+ function(err, result) {
+ self.finish(ok);
+ }
+ );
+ }
+ );
+ }
+ );
+
+};
+
+/**
+ * Release the DB connection and call the callback
+ */
+DBReq.UserVerifier.prototype.finish = function(finalResult) {
+ this.release();
+
+ this.client = null;
+ this.release = null;
+ this.finishAction(finalResult);
+};
+
+/**
+ * Class that gets the "valid" attribute of a user in the databse
+ * @param userId {Number} id of the user
+ * @param finishAction {function} callback that has as parameters :
+ *
+ * - the workerId of the user (string)
+ * - the "valid" attribute of the database (boolean)
+ *
+ * @memberof DBReq
+ * @constructor
+ * @private
+ */
+DBReq.UserGetter = function(userId, finishAction) {
+ this.userId = userId;
+ this.finishAction = finishAction;
+
+ var self = this;
+ pg.connect(pgc.url, function(err, client, release) {
+ self.client = client;
+ self.release = release;
+
+ self.execute();
+ });
+};
+
+/**
+ * Executes the SQL request and calls the callback
+ */
+DBReq.UserGetter.prototype.execute = function() {
+ var self = this;
+
+ this.client.query(
+ 'SELECT worker_id AS "workerId", valid FROM Users WHERE id = $1',
+ [self.userId],
+ function(err, result) {
+ self.finish(result.rows[0].workerId, result.rows[0].valid);
+ }
+ );
+};
+
+/**
+ * Release the DB connection and call the callback
+ */
+DBReq.UserGetter.prototype.finish = function(workerId, valid) {
+
+ this.release();
+ this.release = null;
+ this.client = null;
+
+ this.finishAction(workerId, valid);
+
+};
+
+/**
+ * Try to get a user by id, and creates it if it doesn't exists
+ * @param id {Number} id to test
+ * @param callback {function} callback to call on the id
+ * @memberof DBReq
+ */
+DBReq.tryUser = function(id, callback) {
+ if (id !== undefined && id !== null) {
+ new DBReq.UserIdChecker(id, function(clear) {
+ if (clear) {
+ callback(id);
+ } else {
+ new DBReq.UserCreator(callback);
+ }
+ });
+ } else {
+ new DBReq.UserCreator(callback);
+ }
+};
+
+// Helper for the rest
+// http://stackoverflow.com/questions/9830359/apply-an-array-to-a-constructor-function-in-javascript
+function construct(constr, args) {
+ var instance = Object.create(constr.prototype);
+ var result = constr.apply(instance, args);
+ return typeof result === 'object' ? result : instance; // yes, this is what 'new' does
+}
+
+/**
+ * Loads every information from an experiment
+ * (wraps the {@link DBReq.Info} constructor)
+ * @param id {Number} id of the experiment to load
+ * @param finishAction {function} callback on the result when loading is
+ * finished
+ * @memberof DBReq
+ */
+DBReq.getInfo = function() { construct(DBReq.Info, arguments); };
+
+/**
+ * Creates an user
+ * (wraps the {@link DBReq.UserCreator} constructor)
+ * @param workerId {string} the name of the person doing the experiment
+ * @param age {string} a string representing an age range
+ * @param male {boolean} indicates if the user is a man or a woman
+ * @param rating {Number} between 1 and 5, describes the level of the user
+ * @param lastTime {Number} between 0 and 3 such that
+ *
+ * - never played
+ * - this year
+ * - this month
+ * - this week
+ *
+ * @param finishAction {function} callback that has as a parameter the id of
+ * the new user
+ * @memberof DBReq
+ */
+DBReq.createUser = function() { construct(DBReq.UserCreator, arguments); };
+
+/**
+ * Creates an experiment
+ * (wraps the {@link DBReq.ExpCreator} constructor)
+ * @param userId {Number} id of the user that does the experiment
+ * @param experiments {Object[]} array of objects representing the experiments
+ * that the user has already done {sceneId: Number, recommendationStyle: string, coins Number[]}
+ * @param finishAction {function} callback that has as parameters
+ *
+ * - the id of the experiment (Number)
+ * - the id of the coin combination (Number)
+ * - the id of the scene (Number)
+ * - the recommendation style (string)
+ * - the coins (Number[])
+ *
+ * @memberof DBReq
+ */
+DBReq.createExp = function() { construct(DBReq.ExpCreator, arguments); };
+
+/**
+ * Creates a tutorial
+ * (wraps the {@link DBReq.TurorialCreator} constructor)
+ * @param id {Number} id of the user doing the tutorial
+ * @param finishAction {function} callback that has as parameters
+ *
+ * - the id of the experiment (Number)
+ * - the id of the generated coins (Number[])
+ *
+ * @memberof DBReq
+ */
+DBReq.createTutorial = function() { construct(DBReq.TutorialCreator, arguments); };
+
+/**
+ * Checks if an user id exists
+ * (wraps the {@link DBReq.UserIdChecker} constructor)
+ * @param id {Number} id of the user to check
+ * @param finishAction {function} callback that has as a parameter which is a
+ * boolean indicating wether the user id exists or not
+ * @memberof DBReq
+ */
+DBReq.checkUserId = function() { construct(DBReq.UserIdChecker, arguments); };
+
+/**
+ * Checks if a workerId exists
+ * (wraps the {@link DBReq.UserNameChecker} constructor)
+ * @param id {string} workerId of to test
+ * @param finishAction {function} callback that has as a parameter which is a
+ * boolean indicating wether the user id exists or not
+ * @memberof DBReq
+ */
+DBReq.checkUserName = function() { construct(DBReq.UserNameChecker, arguments); };
+
+/**
+ * Checks if an experiment exists
+ * (wraps the {@link DBReq.ExpIdChecker} constructor)
+ * @param id {Number} id of the experiment to check
+ * @param finishAction {function} callback that has as a parameter which is the
+ * id of the scene if the experiment exists, or null otherwise
+ * @memberof DBReq
+ */
+DBReq.checkExpId = function() { construct(DBReq.ExpIdChecker, arguments); };
+
+/**
+ * Gets the info from all experiment
+ * (wraps the {@link DBReq.ExpGetter} constructor)
+ * @param finishAction {function} callback that has as a parameter which is an
+ * array of objects containing the id, the username, the name of the scene and
+ * the id of the user.
+ * @memberof DBReq
+ */
+DBReq.getAllExps = function() { construct(DBReq.ExpGetter, arguments); };
+
+/**
+ * Gives access to the last not finished experiment
+ * (wraps the {@link DBReq.LastExpGetter} constructor)
+ * @param id {Number} id of the user of who you want the last experiment
+ * @param finishAction {function} callback that has as parameters
+ *
+ * - the id of the experiment (Number)
+ * - the id of the coin combination (Number)
+ * - the id of the scene (Number)
+ * - the recommendation style (string)
+ * - the coins (Number[])
+ *
xperiment exists, or null otherwise
+ * @memberof DBReq
+ */
+DBReq.getLastExp = function() { construct(DBReq.LastExpGetter, arguments); };
+
+/**
+ * Verifies that a user has correctly done all the experiments
+ * (wraps the {@link DBReq.UserVerifier} constructor)
+ * @param userId {Number} id of the user to verify
+ * @param finishAction {function} callback that has as parameter a boolean
+ * which is true is the verification was a success
+ * @memberof DBReq
+ */
+DBReq.verifyUser = function() { construct(DBReq.UserVerifier, arguments); };
+
+/**
+ * Gets the "valid" attribute of a user in the databse
+ * (wraps the {@link DBReq.UserGetter} constructor)
+ * @param userId {Number} id of the user
+ * @param finishAction {function} callback that has as parameters :
+ *
+ * - the workerId of the user (string)
+ * - the "valid" attribute of the database (boolean)
+ *
+ * @memberof DBReq
+ */
+DBReq.getUser = function() { construct(DBReq.UserGetter, arguments); };
+
+module.exports = DBReq;
diff --git a/analysis/loadTables.js b/analysis/loadTables.js
index 4e6db3b..a497ea0 100644
--- a/analysis/loadTables.js
+++ b/analysis/loadTables.js
@@ -1,6 +1,6 @@
var pg = require('pg');
var async = require('async');
-var DBReq = require('../controllers/prototype/dbrequests.js');
+var DBReq = require('./dbrequests.js');
var users, client, release, scenes, coinCombinations, experiments, callback, url, db = {};
@@ -63,7 +63,7 @@ function main() {
function(callback) {
client.query(
- 'SELECT * FROM Experiment;',
+ 'SELECT * FROM Experiment WHERE finished;',
function(err, result) {
experiments = result.rows;
callback();
@@ -86,7 +86,7 @@ function main() {
users,
function(user, callback) {
client.query(
- 'SELECT * FROM Experiment WHERE user_id = $1',
+ 'SELECT * FROM Experiment WHERE user_id = $1 AND finished',
[user.id],
function(err, result) {
user.experiments = result.rows;
@@ -134,8 +134,8 @@ function main() {
experiments,
function(exp, callback) {
DBReq.getInfo(exp.id, function(result) {
- exp.elements = result;
write('.');
+ exp.elements = result;
callback();
});
},