Commit 85d680e8 by Fernando Martínez Santiago

Merge branch 'develop' of http://scm.ujaen.es/softuno/pictogram into fernando_branch

# Conflicts:
#	android/Pictogram/Pictogram.iml
#	android/Pictogram/app/src/main/res/raw/pcbdb_create.sql
parents 7fb517e2 2d613873
Showing with 1244 additions and 282 deletions
...@@ -22,15 +22,22 @@ sails/src/assets/app/modules ...@@ -22,15 +22,22 @@ sails/src/assets/app/modules
sails/src/assets/app/css sails/src/assets/app/css
sails/src/assets/app/js sails/src/assets/app/js
sails/src/assets/scripts/config.js sails/src/assets/scripts/config.js
# Other #
#########
sails/src/config/ssl/**/*.key sails/src/config/ssl/**/*.key
sails/src/config/ssl/**/*.crt sails/src/config/ssl/**/*.crt
sails/src/config/ssl/**/*.csr sails/src/config/ssl/**/*.csr
sails/src/config/local.js sails/src/config/local.js
sails/src/node_modules sails/src/node_modules
sails/.vagrant sails/.vagrant
sails/logs/
sails/npm-debug.log
sails/playbook.retry
sails/upload.zip
# NOT to be ignored #
# This files override sails-mysql files to support milliseconds timestamps #
############################################################################
!sails/src/node_modules/sails-mysql/lib/sql.js
!sails/src/node_modules/sails-mysql/lib/utils.js
# Android # # Android #
########### ###########
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.gradle" /> <excludeFolder url="file://$MODULE_DIR$/.gradle" />
</content> </content>
<orderEntry type="jdk" jdkName="1.8" jdkType="JavaSDK" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>
\ No newline at end of file
...@@ -31,7 +31,7 @@ android { ...@@ -31,7 +31,7 @@ android {
resValue "integer", "netservice_timing", "20" resValue "integer", "netservice_timing", "20"
} }
debug { debug {
resValue "string", "server", "https://dev.yottacode.com" //resValue "string", "server", "https://dev.yottacode.com"
resValue "bool", "force_db_create", "false" resValue "bool", "force_db_create", "false"
resValue "bool", "ssl_connect", "false" resValue "bool", "ssl_connect", "false"
resValue "bool", "force_img_download", "false" resValue "bool", "force_img_download", "false"
......
...@@ -43,11 +43,12 @@ public abstract class Action { ...@@ -43,11 +43,12 @@ public abstract class Action {
final String param_id_dev="id_dev"; final String param_id_dev="id_dev";
final String param_timestamp="timestamp"; final String param_timestamp="timestamp";
final Date currentTime = new Date(); final Date currentTime = new Date();
SimpleDateFormat datetime = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); SimpleDateFormat datetime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSSZ");
try { try {
JSONObject jsonObject = new JSONObject() JSONObject jsonObject = new JSONObject()
.put(param_id_stu, PCBcontext.getPcbdb().getCurrentUser().get_id_stu()) .put(param_id_stu, PCBcontext.getPcbdb().getCurrentUser().get_id_stu())
.put(param_timestamp, datetime.format(currentTime)); .put(param_timestamp, datetime.format(currentTime));
Log.d("TIMESTAMP-----------> ", datetime.format(currentTime));
if (PCBcontext.getPcbdb().getCurrentUser().has_supervisor()) if (PCBcontext.getPcbdb().getCurrentUser().has_supervisor())
jsonObject.put(param_id_sup,PCBcontext.getPcbdb().getCurrentUser().get_id_sup()); jsonObject.put(param_id_sup,PCBcontext.getPcbdb().getCurrentUser().get_id_sup());
//TODO Decidir qué almacenar con DEVICE //TODO Decidir qué almacenar con DEVICE
......
...@@ -27,11 +27,11 @@ SET time_zone = "+00:00"; ...@@ -27,11 +27,11 @@ SET time_zone = "+00:00";
CREATE TABLE IF NOT EXISTS `action` ( CREATE TABLE IF NOT EXISTS `action` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`type` varchar(20) COLLATE utf8_unicode_ci NOT NULL CHECK (type IN ('Add','Select','Delete','Show','Unshow','Pause','tryinit','tryend','initsession','endsession','pausesession','resumesession')), `type` varchar(20) COLLATE utf8_unicode_ci NOT NULL CHECK (type IN ('Add','Select','Delete','Show','Unshow','Pause','tryinit','tryend','initsession','endsession','pausesession','resumesession')),
`timestamp` timestamp DEFAULT CURRENT_TIMESTAMP, `timestamp` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3),
`id_sup` int(11) DEFAULT NULL, `id_sup` int(11) DEFAULT NULL,
`id_stu` int(11) NOT NULL, `id_stu` int(11) NOT NULL,
`id_try` int(11) DEFAULT NULL, `id_try` int(11) DEFAULT NULL,
`description` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'serialization of properties of the picto at the time of the action', `description` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'serialization of properties of the picto at the time of the action',
`gps_lat` float DEFAULT NULL, `gps_lat` float DEFAULT NULL,
`gps_lon` float DEFAULT NULL, `gps_lon` float DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
...@@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS `action` ( ...@@ -42,7 +42,7 @@ CREATE TABLE IF NOT EXISTS `action` (
KEY `fk_stu_act` (`id_stu`), KEY `fk_stu_act` (`id_stu`),
KEY `id_try` (`id_try`) KEY `id_try` (`id_try`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1
COMMENT="This table registers and action performed by a user at a given time, all information of the action is in JSON format in the 'description' column and the operation performed is one of the possible for the 'type' column"; COMMENT="This table registers and action performed by a user at a given time, all information of the action is in JSON format in the 'description' column and the operation performed is one of the possible for the 'type' column. NOTE: timestamps must support fractional seconds, so MySQL versions >= 5.6.4 are required.";
-- -------------------------------------------------------- -- --------------------------------------------------------
...@@ -70,10 +70,10 @@ COMMENT="Stores the expressions available in several languages for a given categ ...@@ -70,10 +70,10 @@ COMMENT="Stores the expressions available in several languages for a given categ
CREATE TABLE IF NOT EXISTS `method` ( CREATE TABLE IF NOT EXISTS `method` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(40) COLLATE utf8_unicode_ci NOT NULL, `name` varchar(40) COLLATE utf8_unicode_ci NOT NULL,
`description` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL, `description` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL,
`id_stu` int(11) NOT NULL, `id_stu` int(11) NOT NULL,
`registration` date DEFAULT NULL, `registration` date DEFAULT NULL,
`notes` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL, `notes` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `fk_stu_met` (`id_stu`) KEY `fk_stu_met` (`id_stu`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1
...@@ -91,8 +91,8 @@ CREATE TABLE IF NOT EXISTS `instruction` ( ...@@ -91,8 +91,8 @@ CREATE TABLE IF NOT EXISTS `instruction` (
`name` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, `name` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`objective` varchar(512) COLLATE utf8_unicode_ci DEFAULT NULL, `objective` varchar(512) COLLATE utf8_unicode_ci DEFAULT NULL,
`status` varchar(60) COLLATE utf8_unicode_ci DEFAULT NULL CHECK (status IN ('started','finished')), `status` varchar(60) COLLATE utf8_unicode_ci DEFAULT NULL CHECK (status IN ('started','finished')),
`begin` timestamp NULL, `begin` timestamp(3) NULL,
`end` timestamp NULL, `end` timestamp(3) NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1
COMMENT="An instruction is a 'phase' in a method for learning AAC"; COMMENT="An instruction is a 'phase' in a method for learning AAC";
...@@ -122,7 +122,7 @@ COMMENT="One in a set of instructions predefined or stored by users. They are re ...@@ -122,7 +122,7 @@ COMMENT="One in a set of instructions predefined or stored by users. They are re
CREATE TABLE IF NOT EXISTS `meta_method` ( CREATE TABLE IF NOT EXISTS `meta_method` (
`id` tinyint(4) NOT NULL AUTO_INCREMENT, `id` tinyint(4) NOT NULL AUTO_INCREMENT,
`name` varchar(40) COLLATE utf8_unicode_ci NOT NULL, `name` varchar(40) COLLATE utf8_unicode_ci NOT NULL,
`description` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL, `description` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL,
`id_sup` INT( 11 ) DEFAULT NULL, `id_sup` INT( 11 ) DEFAULT NULL,
UNIQUE(name,id_sup), UNIQUE(name,id_sup),
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
...@@ -195,7 +195,7 @@ CREATE TABLE IF NOT EXISTS `picto_acl` ( ...@@ -195,7 +195,7 @@ CREATE TABLE IF NOT EXISTS `picto_acl` (
`id_sup` int(11) NOT NULL DEFAULT '0', `id_sup` int(11) NOT NULL DEFAULT '0',
`id_pic` int(11) NOT NULL DEFAULT '0', `id_pic` int(11) NOT NULL DEFAULT '0',
`privilege` varchar(1) COLLATE utf8_unicode_ci NOT NULL, `privilege` varchar(1) COLLATE utf8_unicode_ci NOT NULL,
`timestamp` datetime, `timestamp` timestamp(3),
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `k_pic_aclp` (`id_pic`), KEY `k_pic_aclp` (`id_pic`),
KEY `k_sup_aclp` (`id_sup`) KEY `k_sup_aclp` (`id_sup`)
...@@ -235,7 +235,7 @@ CREATE TABLE IF NOT EXISTS `picto_exp` ( ...@@ -235,7 +235,7 @@ CREATE TABLE IF NOT EXISTS `picto_exp` (
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `id_pic` (`id_pic`) KEY `id_pic` (`id_pic`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=72565 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=72565
COMMENT="Expression (text label) for a picto in different languages. This is used by the TTS engine"; COMMENT="Expression (text label) for a picto (or a category) in different languages. This is used by the TTS engine";
-- -------------------------------------------------------- -- --------------------------------------------------------
...@@ -264,7 +264,7 @@ COMMENT="Labels assigned to pictos by default or by supervisors"; ...@@ -264,7 +264,7 @@ COMMENT="Labels assigned to pictos by default or by supervisors";
CREATE TABLE IF NOT EXISTS `source` ( CREATE TABLE IF NOT EXISTS `source` (
`id` tinyint(4) NOT NULL AUTO_INCREMENT, `id` tinyint(4) NOT NULL AUTO_INCREMENT,
`name` varchar(40) COLLATE utf8_unicode_ci NOT NULL, `name` varchar(40) COLLATE utf8_unicode_ci NOT NULL,
`description` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL, `description` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`) PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=2 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=2
COMMENT="Contains all possible sources of pictos"; COMMENT="Contains all possible sources of pictos";
...@@ -285,9 +285,9 @@ CREATE TABLE IF NOT EXISTS `student` ( ...@@ -285,9 +285,9 @@ CREATE TABLE IF NOT EXISTS `student` (
`gender` char(1) COLLATE utf8_unicode_ci NOT NULL, `gender` char(1) COLLATE utf8_unicode_ci NOT NULL,
`country` char(2) COLLATE utf8_unicode_ci NOT NULL, `country` char(2) COLLATE utf8_unicode_ci NOT NULL,
`pic` varchar(255) COLLATE utf8_unicode_ci DEFAULT 'defaultAvatar.jpg', `pic` varchar(255) COLLATE utf8_unicode_ci DEFAULT 'defaultAvatar.jpg',
`notes` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL, `notes` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL,
`lang` varchar(5) COLLATE utf8_unicode_ci NOT NULL, `lang` varchar(5) COLLATE utf8_unicode_ci NOT NULL,
`attributes` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Attributes describing student along with his/her configuration', `attributes` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT 'Attributes describing student along with his/her configuration',
`id_off` int(11) DEFAULT NULL, `id_off` int(11) DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`), UNIQUE KEY `username` (`username`),
...@@ -366,10 +366,10 @@ COMMENT="Supervisors information"; ...@@ -366,10 +366,10 @@ COMMENT="Supervisors information";
CREATE TABLE IF NOT EXISTS `try` ( CREATE TABLE IF NOT EXISTS `try` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`id_ws` int(11) NOT NULL, `id_ws` int(11) NOT NULL,
`begin` timestamp DEFAULT CURRENT_TIMESTAMP, `begin` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3),
`end` timestamp NULL, `end` timestamp(3) NULL,
`result` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL, `result` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`description` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL, `description` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `id_ws` (`id_ws`) KEY `id_ws` (`id_ws`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1 ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1
...@@ -388,10 +388,10 @@ CREATE TABLE IF NOT EXISTS `working_session` ( ...@@ -388,10 +388,10 @@ CREATE TABLE IF NOT EXISTS `working_session` (
`id` int(11) NOT NULL AUTO_INCREMENT, `id` int(11) NOT NULL AUTO_INCREMENT,
`id_sup` int(11) NOT NULL, `id_sup` int(11) NOT NULL,
`id_ins` int(11) NOT NULL, `id_ins` int(11) NOT NULL,
`begin` timestamp DEFAULT CURRENT_TIMESTAMP, `begin` timestamp(3) DEFAULT CURRENT_TIMESTAMP(3),
`end` timestamp NULL, `end` timestamp(3) NULL,
`current` boolean NULL DEFAULT 1, `current` boolean NULL DEFAULT 1,
`description` varchar(1024) COLLATE utf8_unicode_ci DEFAULT NULL, `description` varchar(4096) COLLATE utf8_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
KEY `id_sup` (`id_sup`), KEY `id_sup` (`id_sup`),
KEY `id_ins` (`id_ins`), KEY `id_ins` (`id_ins`),
......
...@@ -38,8 +38,8 @@ id_sup int(11) NOT NULL, ...@@ -38,8 +38,8 @@ id_sup int(11) NOT NULL,
id_ws int(11) NOT NULL, id_ws int(11) NOT NULL,
id_opentry int(11) , id_opentry int(11) ,
total_tries int DEFAULT 0, total_tries int DEFAULT 0,
begin timestamp NULL, begin timestamp(3) NULL,
end timestamp NULL, end timestamp(3) NULL,
PRIMARY KEY(id), PRIMARY KEY(id),
UNIQUE(id_stu), UNIQUE(id_stu),
UNIQUE(id_sup), UNIQUE(id_sup),
...@@ -142,9 +142,9 @@ thisTrigger: BEGIN ...@@ -142,9 +142,9 @@ thisTrigger: BEGIN
LEAVE thisTrigger; LEAVE thisTrigger;
END IF; END IF;
IF ((old.end IS NULL) and (new.end IS NOT NULL)) THEN IF ((old.end IS NULL) and (new.end IS NOT NULL)) THEN
CALL deleteOpenTry(new.id); CALL deleteOpenTry(new.id);
END IF; END IF;
END;; END;;
......
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
tags: tags:
- reset-test-database - reset-test-database
with_items: with_items:
- categories
- metadata - metadata
- categories
- name: Imports application essential data - name: Imports application essential data
mysql_db: mysql_db:
......
...@@ -11,6 +11,12 @@ ...@@ -11,6 +11,12 @@
- root_password_again - root_password_again
when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
- name: Install MySQL repos
apt:
deb: http://dev.mysql.com/get/mysql-apt-config_0.7.3-1_all.deb
state: present
when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
- name: Update apt-get cache - name: Update apt-get cache
apt: apt:
update-cache: yes update-cache: yes
...@@ -21,15 +27,17 @@ ...@@ -21,15 +27,17 @@
name: "{{ item }}" name: "{{ item }}"
state: present state: present
with_items: with_items:
- mysql-server - mysql-server-5.7
- mysql-client - mysql-client
- python-mysqldb # required by ansible - python-mysqldb # required by ansible
when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
- name: Add MySQL community repository - name: Add MySQL community repository
yum: yum:
name: "http://dev.mysql.com/get/mysql-community-release-el7-5.noarch.rpm" name: "http://dev.mysql.com/get/mysql57-community-release-el7-8.noarch.rpm"
state: present state: present
when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7' when: ansible_distribution == 'CentOS' and ansible_distribution_major_version == '7'
- name: Install mysql packages - name: Install mysql packages
yum: yum:
name: "{{ item }}" name: "{{ item }}"
......
...@@ -42,7 +42,7 @@ module.exports = { ...@@ -42,7 +42,7 @@ module.exports = {
if (!params.type) return res.json(500, {error: "No type of action defined"}); if (!params.type) return res.json(500, {error: "No type of action defined"});
// Optional params // Optional params
var timestamp = null; if(params.timestamp) timestamp = params.timestamp; var timestamp = params.timestamp ? params.timestamp : null;
var sup = null; if(params.supervisor) sup = params.supervisor; var sup = null; if(params.supervisor) sup = params.supervisor;
var _try = null; if(params._try) _try = params._try; var _try = null; if(params._try) _try = params._try;
var description = null; if(params.description) description = params.description; var description = null; if(params.description) description = params.description;
...@@ -59,18 +59,20 @@ module.exports = { ...@@ -59,18 +59,20 @@ module.exports = {
"description": description, "description": description,
"gpsLat": gpsLat, "gpsLat": gpsLat,
"gpsLon": gpsLon "gpsLon": gpsLon
}).exec(function(err, created){ })
if(err || !created) return res.json(500, { error: "Error saving an action "+ params.type +": " + err }); .then(function(created){
if(created) { if (!created)
StuOpenTry.findOne({id_stu : params.student}).exec(function(err, data){ throw new Error("Action.create returned NULL");
if(err || !data){
sails.log.error("Error finding try for student "+ params.student + ' when creating action '+ params.type); StuOpenTry.findOne({id_stu : params.student})
return res.json(500, {error: 'Action '+ params.type +' not created'}); .then(function(data){
} if(!data)
sails.log.debug("Try for student "+params.student+ ":"+JSON.stringify(data)); throw new Error("Error finding try for student "+ params.student + ' when creating action '+ params.type);
return res.json(200,{open_try: data.openTry }); return res.ok({open_try: data.openTry });
}) })
} })
.fail(function(err) {
res.serverError("Error saving an action "+ params.type +": " + err);
}); });
}, },
...@@ -100,7 +102,7 @@ module.exports = { ...@@ -100,7 +102,7 @@ module.exports = {
Action.create({ Action.create({
type: a.action, type: a.action,
timestamp: a.attributes.timestamp, timestamp: a.attributes.timestamp.toISOString(),
supervisor: sup, supervisor: sup,
student: params.student, student: params.student,
description: desc description: desc
......
...@@ -80,7 +80,10 @@ module.exports = { ...@@ -80,7 +80,10 @@ module.exports = {
if (supervisor) { if (supervisor) {
Picto.find({ category: req.params.id_cat }) Picto.find({ category: req.params.id_cat })
.populate('expressions', { lang: supervisor.lang }) .populate('expressions', { lang: supervisor.lang })
.then(pictos => res.ok(pictos)) .then(function (pictos) {
sails.log.debug(pictos.length + " pictos sent for category " + req.params.id_cat + " in language " + supervisor.lang);
res.ok(pictos);
})
.catch(() => res.badRequest()); .catch(() => res.badRequest());
} else { } else {
res.badRequest(); res.badRequest();
...@@ -271,18 +274,19 @@ module.exports = { ...@@ -271,18 +274,19 @@ module.exports = {
* { * {
* id: pictoId, * id: pictoId,
* source: 1, * source: 1,
* owner: supervisorId * owner: supervisorId,
* uri: 'https://path/to/picto/in/the/web'
* } * }
*/ */
upload: function (req, res) { upload: function (req, res) {
Supervisor.findOne({ id: req.body.owner }).then(function (supervisor) { Supervisor.findOne({ id: req.body.owner }).then(function (supervisor) {
var pictoFileName; var pictoFileName;
var pictoDirectory = sails.config.pictogram.paths.supervisorCustomPictoDirectory; var pictoDirectory = sails.config.pictogram.paths.supervisorCustomPictoDirectory;
if (!supervisor) { if (!supervisor) {
throw new Error(); throw new Error();
} }
pictoFileName = sails.config.pictogram.paths.getSupervisorCustomPictoFileName(supervisor.id); pictoFileName = sails.config.pictogram.paths.getSupervisorCustomPictoFileName(supervisor.id);
sails.log.debug("Uploading picto with FileName: " + pictoFileName);
req.file('file').upload({ req.file('file').upload({
maxBytes: 1000000, maxBytes: 1000000,
......
...@@ -133,6 +133,7 @@ module.exports = { ...@@ -133,6 +133,7 @@ module.exports = {
Student.create(params) Student.create(params)
.exec(function (err, created) { .exec(function (err, created) {
if (err) { if (err) {
console.log(err);
sails.log.debug(err); sails.log.debug(err);
return res.json(500, err); return res.json(500, err);
} }
...@@ -410,10 +411,16 @@ module.exports = { ...@@ -410,10 +411,16 @@ module.exports = {
supervisor: req.param('id_sup') supervisor: req.param('id_sup')
}) })
.then((stuSup) => { .then((stuSup) => {
if (!stuSup) { if (!stuSup)
res.badRequest(); res.badRequest();
throw new Error('stusup not found');
} stuSup.destroy({
student: req.param('id_stu'),
supervisor: req.param('id_sup')
}).then(function(err){
if (err)
throw err;
});
const socketToOmit = req.isSocket ? req.socket : undefined; const socketToOmit = req.isSocket ? req.socket : undefined;
const unlinkSupervisorFromStudentEvent = sails.hooks.events.unlinkSupervisorFromStudent( const unlinkSupervisorFromStudentEvent = sails.hooks.events.unlinkSupervisorFromStudent(
...@@ -1001,15 +1008,29 @@ module.exports = { ...@@ -1001,15 +1008,29 @@ module.exports = {
} }
}, },
// /**
// Logs a TRY action and broadcast to anyone subscribed to this student * Logs a TRY action and broadcast to anyone subscribed to this student
* @param {request} req
* {
* "action": <action> ("add", "delete", ...),
* "attributes": {
* "id_stu": <id_stu>,
* "timestamp": <timestamp_string_in_ISO_format> (e.g.: "2016-07-13 17:50:00.224+0200"),
* "picto": {...}
* }
* }
* @param {response} res {<action_created>}
*/
action: function (req, res) { action: function (req, res) {
var action = req.param('action'); var action = req.param('action');
var attributes = req.param('attributes'); var attributes = req.param('attributes');
sails.log.debug("Inside action. Student:" + attributes.id_stu); sails.log.debug("Inside action. Student:" + attributes.id_stu);
if (req.isSocket) { if (!req.isSocket) {
sails.log.debug("No socket request for action");
res.badRequest()
} else {
sails.log.debug("websockets - room " + sails.hooks.rooms.student(attributes.id_stu)); sails.log.debug("websockets - room " + sails.hooks.rooms.student(attributes.id_stu));
// BROADCAST to everyone subscribed to this student // BROADCAST to everyone subscribed to this student
const socketToOmit = (req.isSocket) ? req.socket : undefined; const socketToOmit = (req.isSocket) ? req.socket : undefined;
...@@ -1028,29 +1049,20 @@ module.exports = { ...@@ -1028,29 +1049,20 @@ module.exports = {
Action.create({ Action.create({
type: action, type: action,
timestamp: attributes.timestamp, timestamp: attributes.timestamp, // it comes already in ISO format
supervisor: sup, supervisor: sup,
student: attributes.id_stu, student: attributes.id_stu,
description: desc description: desc
}) })
.exec(function (err, created) { .then(function (created) {
if (err) { res.json({
sails.log.error(err.details); action: created
res.serverError(err.details); });
} })
else if (created) .fail(function(err) {
res.json({ sails.log.error(err.details);
action: created res.serverError(err.details);
});
});
/*
res.json({
msg: "Action "+ action +" action from student " + attributes.id_stu
}); });
*/
} else {
sails.log.debug("No socket request for action");
} }
}, },
......
...@@ -83,17 +83,17 @@ module.exports = { ...@@ -83,17 +83,17 @@ module.exports = {
res.serverError(); res.serverError();
}); });
} else { } else {
res.badRequest('Invalid user or password'); res.badRequest('Invalid password');
} }
} else { } else {
res.badRequest('Invalid user or password'); res.badRequest('Invalid user');
} }
}) })
.catch(function () { .catch(function () {
res.badRequest('Invalid user or password'); res.badRequest('Invalid user or password');
}); });
} else { } else {
res.badRequest('Invalid user or password'); res.badRequest('No email or or password');
} }
}, },
...@@ -374,10 +374,9 @@ module.exports = { ...@@ -374,10 +374,9 @@ module.exports = {
student.supervision = req.token.office ? 2 : 1; student.supervision = req.token.office ? 2 : 1;
return student; return student;
}); });
if (req.token.isSupAdmin && req.token.office && req.token.office.id) { if (req.token.isSupAdmin && req.token.office && req.token.office.id) {
Student.find({ office: req.token.office.id }).then(function (officeStudents) { Student.find({ office: req.token.office.id }).then(function (officeStudents) {
students.concat(officeStudents); students = students.concat(officeStudents);
students = students.map((student) => { students = students.map((student) => {
student.supervision = student.supervision || 0; student.supervision = student.supervision || 0;
return student; return student;
......
...@@ -7,148 +7,142 @@ ...@@ -7,148 +7,142 @@
module.exports = { module.exports = {
// /**
//close action * Closes a working session
// close a WS * @param req
// * {
* id_ws: <working session ID>,
* }
*/
close: function(req, res) { close: function(req, res) {
if (!req.params.id_ws) return res.json(500, {error: "No working session defined"}); if (!req.params.id_ws)
res.badRequest("No working session defined");
StuOpenTry.findOne( { id_ws : req.params.id_ws } ).exec(function(err, t) {
if (err) {
sails.log.error("Error Recovering from ws "+err);
return res.json(500, {error: 'recovering from ws:'+err});
}
if(t && t.openTry!=null) /*WS recovery*/ {
sails.log.debug("Recovering WS required for WS "+req.params.id_ws);
var ws_end;
if (t.end==null) ws_end = t.begin;
else ws_end = t.end;
WorkingSession.update({id: t.id_ws}, {end: ws_end.toISOString()}).exec(function(err,u) {
if (err) {
sails.log.error("Error updating no closed WS " + t.id_ws);
return res.json(500, {error: 'Working Session could not be closed'});
}});
return res.json(200, {end: ws_end.toISOString()});
}
else
return res.json(200, {result:'no recovery required'});
StuOpenTry.findOne( { id_ws : req.params.id_ws } )
.then(function(t) {
var ws_end = req.params.end ? req.params.end : new Date().toISOString();
if (t && t.openTry) /*WS recovery*/ {
ws_end = t.end ? t.end.toISOString() : t.begin.toISOString();
}
WorkingSession.update({id: req.params.id_ws}, {end: ws_end})
.then(function(t) {
res.ok({end: ws_end});
})
.fail(function(err) {
console.log("could not update " + req.params.id_ws);
throw err;
});
})
.fail(function(err) {
res.serverError('Error closing working session: ' + err);
}); });
}, },
// create action /**
// adds a working session and a InitSession action * Creates a new working session
// * @param {request} req
// @TODO 357 * {
id_sup: <supervisor ID>
id_stu: <student ID>
id_ins: <instruction ID>
* }
* @param {response} res
* {
* // TODO: specify return values
* }
*/
create: function(req, res) { create: function(req, res) {
var params = req.allParams(); var params = req.allParams();
if (!params.id_sup) return res.json(500, {error: "No supervisor defined"}); console.log(JSON.stringify(params));
if (!params.id_ins) return res.json(500, {error: "No instruction defined"});
if (!params.id_stu) return res.json(500, {error: "No student defined"});
if (!params.desc) params.desc = "";
var data = {}; if (!params.id_sup) res.badRequest("No supervisor defined");
data.supervisor = params.id_sup; if (!params.id_ins) res.badRequest("No instruction defined");
data.instruction = params.id_ins; if (!params.id_stu) res.badRequest("No student defined");
data.description = params.desc; if (!params.desc) params.desc = "";
var data = {};
data.supervisor = params.id_sup;
data.instruction = params.id_ins;
data.description = params.desc;
console.log("BEGIN " + params.begin);
data.begin = params.begin; // data comes in ISO format
StuOpenTry.findOne( {or: [ StuOpenTry.findOne({or: [ // pending open try?
{ id_sup : params.id_sup }, { id_sup : params.id_sup },
{ id_stu : params.id_stu } { id_stu : params.id_stu }
]} ).exec(function(err, t) { ]})
if (err) { .then(function(t) {
sails.log.error("Error Recovering from ws "+err); var ws = null;
return res.json(500, {error: 'recovering from ws:'+err}); if (t && t.openTry) { // YES, so WS not closed, we update to close it
} var ws_end = t.end ? t.end.toISOString() : t.begin.toISOString();
if(!t || t.openTry==null) //WS closed correctly ws = WorkingSession.update({id: t.id_ws}, {end: ws_end})
WorkingSession.create(data).exec(function(err, ws){ .then(function(ws) {
if(err || !ws){ return ws;
sails.log.error("Creating new Working Sesion error "+err); });
return res.json(500, {error: 'Working Session not created'});
}
// Create the InitSession Action
Action.create({
"type": "initsession",
"timestamp": ws.begin,
"supervisor": ws.supervisor,
"student": params.id_stu
}).exec(function(err, action){
if(err || !action) {
sails.log.error("Creating initial action for new Working Sesion. Error "+err);
return res.json(500, {error: 'Working Session not created'});
}
StuOpenTry.findOne( {id_stu : params.id_stu } ).exec(function(err, t) {
if (err) {
sails.log.error("Error Recovering from ws "+err);
return res.json(500, {error: 'recovering from ws:'+err});
}
// Return the working session and try created
sails.log.debug("Initial action for new Working Sesion "+JSON.stringify(ws)+". Action:"+JSON.stringify(action));
return res.json({
"id": ws.id,
"first_try_id":t.openTry,
"recovered_ws": null
})
})
});
});
else { //Not closed previous WS must be recovered and closed
sails.log.debug("Recovering WS required for student "+params.id_stu);
var ws_end;
if (t.end==null) ws_end = t.begin;
else ws_end = t.end;
WorkingSession.update({id: t.id_ws}, {end: ws_end.toISOString()}).exec(function(err,u) {
if (err) {
sails.log.error("Error updating no closed WS " + t.id_ws);
return res.json(500, {error: 'Working Session not created'});
}
WorkingSession.create(data).exec(function(err, ws){
if(err || !ws){
sails.log.error("Creating new Working Sesion error "+err);
return res.json(500, {error: 'Working Session not created'});
}
// Create the InitSession Action
Action.create({
"type": "initsession",
"timestamp": ws.begin,
"supervisor": ws.supervisor,
"student": params.id_stu
}).exec(function(err, action){
if(err || !action) {
sails.log.error("Creating initial action for new Working Sesion. Error "+err);
return res.json(500, {error: 'Working Session not created'});
}
StuOpenTry.findOne( {id_stu : params.id_stu} ).exec(function(err, t) {
if (err) {
sails.log.error("Error Recovering from ws "+err);
return res.json(500, {error: 'recovering from ws:'+err});
}
// Return the working session and try created
sails.log.debug("Initial action for new Working Sesion "+JSON.stringify(action));
return res.json({
"id": ws.id,
"first_try_id":t.openTry,
"recovered_ws": null
})
})
});
});
});
} }
return ws;
})
.then(function(oldws) { // now we can create new working session
return WorkingSession.create(data)
.then(function(ws) {
return ws;
});
})
.then(function(ws) { // now we have a new working session
if (!ws)
throw new Error("Error when calling WorkingSession.create");
// Create the InitSession Action
// This creates, also, a new try (see triggers-session-constraints.sql)
var action = Action.create({
"type": "initsession",
"timestamp": ws.begin.toISOString(),
"supervisor": ws.supervisor,
"student": params.id_stu
})
.then(function(action) {
if(!action)
throw new Error("Error when creating action 'initsession'");
return action;
});
return [action, ws];
}).spread(function(action, ws) {
StuOpenTry.findOne({id_stu : params.id_stu })
.then(function(opentry) {
if (!opentry)
throw new Error("Error when looking for open try");
// Return the working session and try created
sails.log.debug("Initial action for new Working Sesion "+JSON.stringify(ws)+". Action:"+JSON.stringify(action));
return res.ok({
"id": ws.id,
"first_try_id":opentry.id,
"recovered_ws": null
});
});
})
.fail(function(err) {
res.serverError("Session not created: " + err);
}); });
}, },
// update action /**
// ends a working session and a creates an EndSession action * Updates working session information and a creates an EndSession action if
// * update info is to end session
// @TODO 357 * @param {request} req
* {
* // TODO: specify parameters
* }
* @param {response} res
* {
* // TODO: specify return values
* }
*/
update: function(req, res) { update: function(req, res) {
var params = req.allParams(); var params = req.allParams();
console.log(JSON.stringify(params));
WorkingSession.update({id:params.id}, params).exec(function(err, ws){ WorkingSession.update({id:params.id}, params).exec(function(err, ws){
if(err || !ws){ if(err || !ws){
...@@ -162,7 +156,7 @@ module.exports = { ...@@ -162,7 +156,7 @@ module.exports = {
// Create the EndSession Action // Create the EndSession Action
Action.create({ Action.create({
"type": "endsession", "type": "endsession",
"timestamp": ws[0].end, "timestamp": ws[0].end.toISOString(),
"supervisor": ws[0].supervisor, "supervisor": ws[0].supervisor,
"student": params.id_stu "student": params.id_stu
}).exec(function(err, action){ }).exec(function(err, action){
......
...@@ -73,5 +73,10 @@ module.exports = { ...@@ -73,5 +73,10 @@ module.exports = {
columnName: "gps_lon", columnName: "gps_lon",
type: "float" type: "float"
} }
} },
beforeCreate: function (attrs, next) {
sails.log.debug("Inserting action with following attrs: " + JSON.stringify(attrs));
next();
},
}; };
...@@ -169,6 +169,8 @@ module.exports = { ...@@ -169,6 +169,8 @@ module.exports = {
tape_background: '#00ffff' tape_background: '#00ffff'
}; };
sails.log.verbose('Requested attributes for Student', attributes);
if (typeof attributes === 'object') { if (typeof attributes === 'object') {
Object.keys(defaultAttributes).forEach((attribute) => { Object.keys(defaultAttributes).forEach((attribute) => {
if (defaultAttributes.hasOwnProperty(attribute) && attributes.hasOwnProperty(attribute)) { if (defaultAttributes.hasOwnProperty(attribute) && attributes.hasOwnProperty(attribute)) {
......
...@@ -30,7 +30,7 @@ module.exports = { ...@@ -30,7 +30,7 @@ module.exports = {
columnName: "id_ins", columnName: "id_ins",
required: true, required: true,
type: "integer", type: "integer",
model: "Instruction" model: "Instruction"
}, },
begin: { begin: {
type: "datetime", type: "datetime",
...@@ -40,21 +40,50 @@ module.exports = { ...@@ -40,21 +40,50 @@ module.exports = {
type: "datetime" type: "datetime"
}, },
current: { current: {
type: "integer", type: "integer",
}, },
description: { description: {
type: "string", type: "string",
size: 1024 size: 1024
}, },
// Relación con Try. [1 WorkingSession to N Try] // Relación con Try. [1 WorkingSession to N Try]
tries: { tries: {
collection: 'Try', collection: 'Try',
via: "workingSession" via: "workingSession"
} }
}, },
/**
* Checks a previous session is not opened for that supervisor. If so,
* session is closed (current is set to 0)
* @param {Object} attrs All session properties to be stored
* @param {Function} next Function to be executed when the check process
* has been completed (an error object will be passed
* to the function if necesary)
*/
beforeCreate: function (attrs, next) {
WorkingSession.find({id_sup: attrs.supervisor, current: 1})
.then(function(wss) {
if (wss) {
async.each(wss, function(ws, cb) {
ws.current = 0;
WorkingSession.update(ws.id, ws, function(err, update) {
if (err) throw new Error("Error when udpating open sessions");
cb();
});
});
}
next();
})
.fail(function(err) {
next(err);
})
},
// //
// Returns the number of working sessions per year (REPORTS) // Returns the number of working sessions per year (REPORTS)
// //
per_year: function(id_stu, year, callback) { per_year: function(id_stu, year, callback) {
var l = []; var l = [];
...@@ -65,18 +94,18 @@ module.exports = { ...@@ -65,18 +94,18 @@ module.exports = {
// eachSeries // eachSeries
async.eachSeries( async.eachSeries(
// 1st array of items // 1st array of items
months, months,
// 2nd function to operate over one item // 2nd function to operate over one item
function(month, next) { function(month, next) {
var month_1 = year +'-'+ month +'-01'; // 2015-1-01 var month_1 = year +'-'+ month +'-01'; // 2015-1-01
var month_2 = (month<12) ? (year +'-'+ (month+1) +'-01') : ((year+1) +'-01-01'); // 2015-2-01 ó 2016-01-01 var month_2 = (month<12) ? (year +'-'+ (month+1) +'-01') : ((year+1) +'-01-01'); // 2015-2-01 ó 2016-01-01
WorkingSession.count({ WorkingSession.count({
'id_stu' : id_stu, 'id_stu' : id_stu,
'begin' : { 'begin' : {
'>=' : month_1, '>=' : month_1,
'<' : month_2 '<' : month_2
} }
}).exec(function(err, ws) { }).exec(function(err, ws) {
if (err) if (err)
...@@ -84,7 +113,7 @@ module.exports = { ...@@ -84,7 +113,7 @@ module.exports = {
if(!ws) if(!ws)
console.log("No ws in this month"); console.log("No ws in this month");
if(ws) if(ws)
console.log(JSON.stringify(ws)); console.log(JSON.stringify(ws));
...@@ -99,7 +128,7 @@ module.exports = { ...@@ -99,7 +128,7 @@ module.exports = {
}, },
// 3rd final function when all is ready // 3rd final function when all is ready
function (err){ function (err){
console.log("It's the final functiooonnnnnnnn....."); console.log("It's the final functiooonnnnnnnn.....");
console.log(JSON.stringify(l)); console.log(JSON.stringify(l));
return callback(err, l); return callback(err, l);
...@@ -111,7 +140,7 @@ module.exports = { ...@@ -111,7 +140,7 @@ module.exports = {
// //
// Returns the number of working sessions per month (REPORTS) // Returns the number of working sessions per month (REPORTS)
// //
per_month: function(id_stu, month, callback) { per_month: function(id_stu, month, callback) {
var l = []; var l = [];
...@@ -122,18 +151,18 @@ module.exports = { ...@@ -122,18 +151,18 @@ module.exports = {
// eachSeries // eachSeries
async.eachSeries( async.eachSeries(
// 1st array of items // 1st array of items
months, months,
// 2nd function to operate over one item // 2nd function to operate over one item
function(month, next) { function(month, next) {
var month_1 = year + '-'+ month +'-01'; // 2015-1-01 var month_1 = year + '-'+ month +'-01'; // 2015-1-01
var month_2 = (month<12) ? (year +'-'+ (month+1) +'-01') : ((year+1) +'-01-01'); // 2015-2-01 ó 2016-01-01 var month_2 = (month<12) ? (year +'-'+ (month+1) +'-01') : ((year+1) +'-01-01'); // 2015-2-01 ó 2016-01-01
WorkingSession.count({ WorkingSession.count({
'id_stu' : id_stu, 'id_stu' : id_stu,
'begin' : { 'begin' : {
'>=' : month_1, '>=' : month_1,
'<' : month_2 '<' : month_2
} }
}).exec(function(err, ws) { }).exec(function(err, ws) {
if (err) if (err)
...@@ -141,7 +170,7 @@ module.exports = { ...@@ -141,7 +170,7 @@ module.exports = {
if(!ws) if(!ws)
console.log("No ws in this month"); console.log("No ws in this month");
if(ws) if(ws)
console.log(JSON.stringify(ws)); console.log(JSON.stringify(ws));
...@@ -154,7 +183,7 @@ module.exports = { ...@@ -154,7 +183,7 @@ module.exports = {
}, },
// 3rd final function when all is ready // 3rd final function when all is ready
function (err){ function (err){
console.log("It's the final functiooonnnnnnnn....."); console.log("It's the final functiooonnnnnnnn.....");
console.log(JSON.stringify(l)); console.log(JSON.stringify(l));
return callback(err, l); return callback(err, l);
......
...@@ -80,9 +80,11 @@ ...@@ -80,9 +80,11 @@
"enlarge": "Enlarge", "enlarge": "Enlarge",
"enormous": "Enormous", "enormous": "Enormous",
"error_adding_picto": "Error adding picto", "error_adding_picto": "Error adding picto",
"error_creating_session": "Error when creating session",
"error_deleting_picto": "Error deleting picto", "error_deleting_picto": "Error deleting picto",
"error_downloading_supervisors": "Error downloading supervisors", "error_downloading_supervisors": "Error downloading supervisors",
"error_downloading_offices": "Error downloading offices", "error_downloading_offices": "Error downloading offices",
"error_fetching_students": "Error when loading students",
"error_only_support_images": "Only images are supported (JPG, PNG or GIF files)", "error_only_support_images": "Only images are supported (JPG, PNG or GIF files)",
"error_loading_pictos": "Error loading pictos information", "error_loading_pictos": "Error loading pictos information",
"expand_navigation": "Expand navigation", "expand_navigation": "Expand navigation",
...@@ -125,6 +127,7 @@ ...@@ -125,6 +127,7 @@
"loading_pictos": "Loading pictos", "loading_pictos": "Loading pictos",
"login": "Log In", "login": "Log In",
"login_fail": "Invalid user or password", "login_fail": "Invalid user or password",
"login_footer": "Pictogram is a product by <a href='http://www.yottacode.com'>Yottacode S.L.</a> for Augmentative and Alternative Communication, loaded with more than 20,000 images from the <a href=''>SymbolStix</a> collection, in several languages. From here, you will be able to manage devices with official <i>Pictogram Tablet</i> app installed.",
"login_success": "Login succeed. Welcome {{name}}", "login_success": "Login succeed. Welcome {{name}}",
"logout": "Log Out", "logout": "Log Out",
"man": "Man", "man": "Man",
......
...@@ -83,9 +83,11 @@ ...@@ -83,9 +83,11 @@
"expand_navigation": "Desplegar navegación", "expand_navigation": "Desplegar navegación",
"expression": "Expresión:", "expression": "Expresión:",
"error_adding_picto": "Error al añadir el picto", "error_adding_picto": "Error al añadir el picto",
"error_creating_session": "Error al crear sesión",
"error_deleting_picto": "Error borrando el picto", "error_deleting_picto": "Error borrando el picto",
"error_downloading_supervisors": "Error al descargar los supervisores", "error_downloading_supervisors": "Error al descargar los supervisores",
"error_downloading_offices": "Error al descargar las oficinas", "error_downloading_offices": "Error al descargar las oficinas",
"error_fetching_students": "Error al cargar estudiantes",
"error_only_support_images": "Sólo se soportan imágenes (ficheros JPG, PNG o GIF)", "error_only_support_images": "Sólo se soportan imágenes (ficheros JPG, PNG o GIF)",
"error_loading_pictos": "Error cargando información de los pictos", "error_loading_pictos": "Error cargando información de los pictos",
"February": "Febrero", "February": "Febrero",
...@@ -125,6 +127,7 @@ ...@@ -125,6 +127,7 @@
"loading_pictos": "Cargando pictos", "loading_pictos": "Cargando pictos",
"login": "Iniciar sesión", "login": "Iniciar sesión",
"login_fail": "Usuario o contraseña incorrectos", "login_fail": "Usuario o contraseña incorrectos",
"login_footer": "Pictogram es un producto de <a href='http://www.yottacode.com'>Yottacode S.L.</a> para Comunicación Aumentativa y Alternativa, cargado con más los más de 20,000 pictogramas de <a href=''>SymbolStix</a>, en varios idiomas. Desde aquí podrás gestionar los dispositivos que dispongan de la app oficial <i>Pictogram Tablet</i>.",
"login_success": "Login correcto. Bienvenido {{name}}", "login_success": "Login correcto. Bienvenido {{name}}",
"logout": "Salir", "logout": "Salir",
"man": "Hombre", "man": "Hombre",
......
...@@ -20,12 +20,12 @@ var dashboardControllers = angular.module('dashboardControllers', ['dashboardCon ...@@ -20,12 +20,12 @@ var dashboardControllers = angular.module('dashboardControllers', ['dashboardCon
// Main Controller // Main Controller
// //
// //
dashboardControllers.controller('MainCtrl', function MainCtrl($scope, $window, $location, $translate) { dashboardControllers.controller('MainCtrl', function MainCtrl($scope, $window, $location, $translate, $rootScope) {
// Always use objects if we want to modify this scope in the childs // Always use objects if we want to modify this scope in the childs
// Save name and surname in global scope // Save name and surname in global scope
$scope.user = { $scope.user = {
id: '', id: '',
name: '', name: '',
surname: '', surname: '',
...@@ -43,7 +43,7 @@ dashboardControllers.controller('MainCtrl', function MainCtrl($scope, $window, $ ...@@ -43,7 +43,7 @@ dashboardControllers.controller('MainCtrl', function MainCtrl($scope, $window, $
lang = 'es-es'; lang = 'es-es';
$translate.use(lang); $translate.use(lang);
//$translateProvider.preferredLanguage('es-es'); //$translateProvider.preferredLanguage('es-es');
//--------------------------------------------------------------- //---------------------------------------------------------------
...@@ -56,6 +56,14 @@ dashboardControllers.controller('MainCtrl', function MainCtrl($scope, $window, $ ...@@ -56,6 +56,14 @@ dashboardControllers.controller('MainCtrl', function MainCtrl($scope, $window, $
$scope.logout(); $scope.logout();
}); });
//-------------------------------------------
// For scrolling when using routig
//
$rootScope.$on('$routeChangeSuccess', function(newRoute, oldRoute) {
$location.hash($routeParams.scrollTo);
$anchorScroll();
});
//--------------------------------------------------------------- //---------------------------------------------------------------
// Controller's functions // Controller's functions
// //
...@@ -77,4 +85,4 @@ dashboardControllers.controller('MainCtrl', function MainCtrl($scope, $window, $ ...@@ -77,4 +85,4 @@ dashboardControllers.controller('MainCtrl', function MainCtrl($scope, $window, $
// Redirect to login // Redirect to login
$location.path('/login'); $location.path('/login');
}; };
}); });
\ No newline at end of file
...@@ -13,7 +13,9 @@ dashboardControllers.controller('StudentCollectionsCtrl', function StudentCollec ...@@ -13,7 +13,9 @@ dashboardControllers.controller('StudentCollectionsCtrl', function StudentCollec
$http, $http,
config, config,
$window, $window,
$location,
$filter, $filter,
$anchorScroll,
$modal) { $modal) {
$scope.emptyStudentPicto = { $scope.emptyStudentPicto = {
id: null, id: null,
...@@ -119,11 +121,8 @@ dashboardControllers.controller('StudentCollectionsCtrl', function StudentCollec ...@@ -119,11 +121,8 @@ dashboardControllers.controller('StudentCollectionsCtrl', function StudentCollec
$scope.show_category = function (studentPicto) { $scope.show_category = function (studentPicto) {
if ($scope.isCategory(studentPicto)) { if ($scope.isCategory(studentPicto)) {
$scope.selectedCategory = studentPicto; $scope.selectedCategory = studentPicto;
setTimeout(function () { $location.hash('picto-category-grid');
$(document.body).animate({ $anchorScroll();
scrollTop: $('#picto-category-grid').position().top
}, 500);
}, 0);
} }
}; };
......
...@@ -3,7 +3,14 @@ ...@@ -3,7 +3,14 @@
//----------------------- //-----------------------
// Student Session Controller // Student Session Controller
//----------------------- //-----------------------
dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtrl($scope, $stateParams, $http, config, $window) { dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtrl(
$scope,
$stateParams,
$http,
config,
$window,
$translate,
ngToast) {
...@@ -121,8 +128,10 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr ...@@ -121,8 +128,10 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr
//Modify the desc. of a try //Modify the desc. of a try
$scope.update_try = function(t){ $scope.update_try = function(t){
var update_data={}; var update_data={};
if (t.description!=null) update_data.description=t.description; if (t.description)
if (t.result!=null) update_data.result=t.result; update_data.description = t.description;
if (t.result)
update_data.result = t.result;
$http $http
.put(config.backend+'/try/' + t.id, update_data) .put(config.backend+'/try/' + t.id, update_data)
.success(function(data, status, headers, config) { .success(function(data, status, headers, config) {
...@@ -183,12 +192,10 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr ...@@ -183,12 +192,10 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr
.post(config.backend+ '/ws/'+$scope.wsessions[0].id+'/close') .post(config.backend+ '/ws/'+$scope.wsessions[0].id+'/close')
.then( .then(
function(data, status, headers, config) { function(data, status, headers, config) {
$scope.wsessions[0].end=data.data.end; $scope.wsessions[0].end=data.data.end;
$scope.ws_recover=false; $scope.ws_recover=false;
} }
, function(data, status, headers, config) { ,function(data, status, headers, config) {
} }
); );
} }
...@@ -206,7 +213,8 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr ...@@ -206,7 +213,8 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr
.post(config.backend+'/ws', { .post(config.backend+'/ws', {
"id_sup": $scope.user.id, "id_sup": $scope.user.id,
"id_stu": $scope.studentData.id, "id_stu": $scope.studentData.id,
"id_ins": $scope.selectedIns "id_ins": $scope.selectedIns.id,
"begin": new Date().toISOString()
}) })
.success(function(data, status, headers, config) { .success(function(data, status, headers, config) {
...@@ -229,20 +237,20 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr ...@@ -229,20 +237,20 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr
$scope.studentData.current_instruction=$scope.selectedIns.name; $scope.studentData.current_instruction=$scope.selectedIns.name;
}) })
.error(function(data, status, headers, config) { .error(function(data, status, headers, config) {
$translate('error_creating_session').then(function (translation) {
ngToast.danger({ content: translation });
});
}); });
}; }
// //
// update the description of a given working session // update the description of a given working session
// //
$scope.update_ws = function (ws){ $scope.update_ws = function (ws){
$http $http
.put(config.backend+'/ws/' + ws.id, { "description" : ws.description }) .put(config.backend+'/ws/' + ws.id, { "description" : ws.description })
.then(function(data, status, headers, config) { .then(function(data, status, headers, config) {
// TODO notify update?
}) })
,function(data, status, headers, config) { ,function(data, status, headers, config) {
...@@ -252,16 +260,21 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr ...@@ -252,16 +260,21 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr
// //
// Finish a working session updating its end time // Finish a working session updating its end time
// //
$scope.stop_ws = function (){ $scope.stop_ws = function () {
// For view // For view
$scope.sessionRunning = false; $scope.sessionRunning = false;
if ($scope.wsessions.length>0) $scope.wsessions[$scope.wsessions.length-1].tries.pop(); if ($scope.wsessions.length>0)
$scope.ws.end = new Date(); $scope.wsessions[$scope.wsessions.length-1].tries.pop();
$scope.ws.end = new Date().toISOString();
$http
.put(config.backend+'/ws/' + $scope.ws.id, { "end": $scope.ws.end, $http
"id_stu": $scope.studentData.id .post(
}) config.backend+'/ws/' + $scope.ws.id + '/close',
{
"end": $scope.ws.end,
"id_stu": $scope.studentData.id
}
)
.success(function(data, status, headers, config) { .success(function(data, status, headers, config) {
// Empty actual WS and actual try // Empty actual WS and actual try
...@@ -376,26 +389,22 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr ...@@ -376,26 +389,22 @@ dashboardControllers.controller('StudentSessionCtrl', function StudentSessionCtr
// what data has updated, and refresh what needs to be refreshed. // what data has updated, and refresh what needs to be refreshed.
if($scope.ws){ if($scope.ws){
console.log(data.action);
switch(data.action){ switch(data.action){
case 'Add': case 'Add':
$scope.actual_try.actions.push(data); $scope.actual_try.actions.push(data);
break; break;
case 'Delete': case 'Delete':
$scope.actual_try.actions.push(data); $scope.actual_try.actions.push(data);
break; break;
case 'Select': case 'Select':
$scope.actual_try.actions.push(data); $scope.actual_try.actions.push(data);
break; break;
case 'Show': case 'Show':
$scope.load_tries($scope.selectedIns);
$scope.load_tries($scope.selectedIns);
// Empty actual try and push the first action of next try // Empty actual try and push the first action of next try
$scope.actual_try.actions = []; $scope.actual_try.actions = [];
$scope.actual_try.actions.push({ action: 'tryinit' }); $scope.actual_try.actions.push({ action: 'tryinit' });
break; break;
} }
} }
......
...@@ -78,6 +78,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -78,6 +78,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
*/ */
$scope.updateStudent = function () { $scope.updateStudent = function () {
var password; var password;
var attrs;
if ($scope.formUser.password_confirm || $scope.formUser.password) { if ($scope.formUser.password_confirm || $scope.formUser.password) {
if ($scope.formUser.password_confirm === $scope.formUser.password) { if ($scope.formUser.password_confirm === $scope.formUser.password) {
...@@ -90,7 +91,8 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -90,7 +91,8 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
} }
} }
$http.put(config.backend + '/stu/' + $scope.studentData.id, {
attrs = {
birthdate: $scope.formUser.birthdate, birthdate: $scope.formUser.birthdate,
country: $scope.formUser.country, country: $scope.formUser.country,
gender: $scope.formUser.gender, gender: $scope.formUser.gender,
...@@ -99,8 +101,12 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -99,8 +101,12 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
notes: $scope.formUser.notes, notes: $scope.formUser.notes,
surname: $scope.formUser.surname, surname: $scope.formUser.surname,
username: $scope.formUser.username, username: $scope.formUser.username,
password: password };
})
if (password)
attrs.password = password;
$http.put(config.backend + '/stu/' + $scope.studentData.id, attrs)
.success(function (data) { .success(function (data) {
$translate('student_updated').then(function (translation) { $translate('student_updated').then(function (translation) {
ngToast.success({ content: translation }); ngToast.success({ content: translation });
......
...@@ -48,9 +48,10 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl( ...@@ -48,9 +48,10 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
// WebSockets communication // WebSockets communication
// //
io.socket.on('update_peers', function (data) { io.socket.on('update_peers', function (data) {
$translate('num_peers').then(function (translation) { // REMOVED (too invasive)
ngToast.success(translation + ': ' + data.count); // $translate('num_peers').then(function (translation) {
}); // ngToast.success(translation + ': ' + data.count);
//});
$scope.studentData.num_peers = data.count; $scope.studentData.num_peers = data.count;
}); });
......
...@@ -125,6 +125,7 @@ ...@@ -125,6 +125,7 @@
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
</div> </div>
<a name="picto-category-grid"/>
<div <div
id="picto-category-grid" id="picto-category-grid"
class="picto-grid picto-category-grid" class="picto-grid picto-category-grid"
......
...@@ -44,10 +44,10 @@ ...@@ -44,10 +44,10 @@
<img ng-src="{{ a.attributes.picto.picto.uri }}" /> <img ng-src="{{ a.attributes.picto.picto.uri }}" />
<div class="action-type"> <div class="action-type">
<span ng-if="a.action == 'add'" class="glyphicon glyphicon-plus color_green" aria-hidden="true"></span> <span ng-if="a.action == 'add'" class="glyphicon glyphicon-plus color_green" aria-hidden="true"></span>
<span ng-if="a.action == 'select'" class="glyphicon glyphicon-hand-up color_blue" aria-hidden="true"></span> <span ng-if="a.action == 'Select'" class="glyphicon glyphicon-hand-up color_blue" aria-hidden="true"></span>
<span ng-if="a.action == 'delete'" class="glyphicon glyphicon-remove color_red" aria-hidden="true"></span> <span ng-if="a.action == 'Delete'" class="glyphicon glyphicon-remove color_red" aria-hidden="true"></span>
<span ng-if="a.action == 'show'" class="glyphicon glyphicon-eye-open color_blue" aria-hidden="true"></span> <span ng-if="a.action == 'Show'" class="glyphicon glyphicon-eye-open color_blue" aria-hidden="true"></span>
<span ng-if="a.action == 'unshow'" class="glyphicon glyphicon-edit color_blue" aria-hidden="true"></span> <span ng-if="a.action == 'Unshow'" class="glyphicon glyphicon-edit color_blue" aria-hidden="true"></span>
<span ng-if="a.action == 'tryinit'" class="glyphicon glyphicon-log-in" aria-hidden="true"></span> <span ng-if="a.action == 'tryinit'" class="glyphicon glyphicon-log-in" aria-hidden="true"></span>
<span ng-if="a.action == 'tryend'" class="glyphicon glyphicon-log-out" aria-hidden="true"></span> <span ng-if="a.action == 'tryend'" class="glyphicon glyphicon-log-out" aria-hidden="true"></span>
<span ng-if="a.action == 'pausesession'" class="glyphicon glyphicon-pause" aria-hidden="true"></span> <span ng-if="a.action == 'pausesession'" class="glyphicon glyphicon-pause" aria-hidden="true"></span>
......
...@@ -49,7 +49,12 @@ ...@@ -49,7 +49,12 @@
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" id="student_country" placeholder="{{ 'country' | translate }}" ng-model="formUser.country" /> <select class="form-control" name="student_country" id="student_country" ng-model="formUser.country" required>
<option value="ES">España</option>
<option value="US">United States</option>
<option value="UK">United Kingdom</option>
<option value="IE">Ireland</option>
</select>
</div> </div>
</fieldset> </fieldset>
......
...@@ -53,7 +53,9 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl( ...@@ -53,7 +53,9 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
$scope.students = data; $scope.students = data;
}) })
.error(function () { .error(function () {
// TODO show error with ngToast $translate('error_fetching_students').then(function (translation) {
ngToast.danger({ content: translation });
});
}); });
// Reset form Student // Reset form Student
...@@ -70,7 +72,6 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl( ...@@ -70,7 +72,6 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
gender: 'F', gender: 'F',
lang: 'es-es', lang: 'es-es',
notes: '', notes: '',
pic: '/app/img/default.jpg',
office: $scope.user.office || { name: '' }, office: $scope.user.office || { name: '' },
current_method: 'no_method', current_method: 'no_method',
current_instruction: 'no_instruction' current_instruction: 'no_instruction'
......
<h3 class="color_green" translate>add_student</h3> <h3 class="color_green" translate>add_student</h3>
<form name="AddStudentForm" role="form" ng-submit="add_student()" ng-controller="StudentAddCtrl"> <form name="AddStudentForm" role="form" ng-submit="add_student()" ng-controller="StudentAddCtrl">
<div class="row"> <div class="row">
<div class="col-sm-6"> <div class="col-sm-6">
...@@ -45,7 +45,13 @@ ...@@ -45,7 +45,13 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" id="student_country" placeholder="{{ 'country' | translate }}" ng-model="formdatastudent.country" /> <legend translate>country</legend>
<select class="form-control" name="student_country" id="student_country" ng-model="formdatastudent.country" required>
<option value="ES" selected>España</option>
<option value="US">United States</option>
<option value="UK">United Kingdom</option>
<option value="IE">Ireland</option>
</select>
</div> </div>
</fieldset> </fieldset>
</div> </div>
...@@ -77,4 +83,4 @@ ...@@ -77,4 +83,4 @@
</div> </div>
</form> </form>
<hr /> <hr />
\ No newline at end of file
/**
* Local environment settings
*
* Use this file to specify configuration settings for use while developing
* the app on your personal system: for example, this would be a good place
* to store database or email passwords that apply only to you, and shouldn't
* be shared with others in your organization.
*
* These settings take precedence over all other config files, including those
* in the env/ subfolder.
*
* PLEASE NOTE:
* local.js is included in your .gitignore, so if you're using git
* as a version control solution for your Sails app, keep in mind that
* this file won't be committed to your repository!
*
* Good news is, that means you can specify configuration for your local
* machine in this file without inadvertently committing personal information
* (like database passwords) to the repo. Plus, this prevents other members
* of your team from commiting their local configuration changes on top of yours.
*
* In a production environment, you probably want to leave this file out
* entirely and leave all your settings in env/production.js
*
*
* For more information, check out:
* http://sailsjs.org/#!/documentation/anatomy/myApp/config/local.js.html
*/
const path = require('path');
const fs = require('fs');
module.exports = {
/**
* Your SSL certificate and key, if you want to be able to serve HTTP
* responses over https:// and/or use websockets over the wss:// protocol
* (recommended for HTTP, strongly encouraged for WebSockets)
*/
ssl: {
// ca: fs.readFileSync(path.join(__dirname, 'ssl', 'bundle.crt')),
key: fs.readFileSync(path.join(__dirname, 'ssl', 'key.key')),
cert: fs.readFileSync(path.join(__dirname, 'ssl', 'cert.crt')),
},
/**
* The `port` setting determines which TCP port your app will be
* deployed on.
*
* By default, if it's set, Sails uses the `PORT` environment variable.
* Otherwise it falls back to port 1337.
*
* In env/production.js, you'll probably want to change this setting
* to 80 (http://) or 443 (https://) if you have an SSL certificate
*/
port: process.env.PORT || 1337,
/*
* The runtime "environment" of your Sails app is either typically
* 'development' or 'production'.
*
* In development, your Sails app will go out of its way to help you
* (for instance you will receive more descriptive error and
* debugging output)
*
* In production, Sails configures itself (and its dependencies) to
* optimize performance. You should always put your app in production mode
* before you deploy it to a server. This helps ensure that your Sails
* app remains stable, performant, and scalable.
*
* By default, Sails sets its environment using the `NODE_ENV` environment
* variable. If NODE_ENV is not set, Sails will run in the
* 'development' environment.
*/
environment: process.env.NODE_ENV || 'development',
};
...@@ -75,7 +75,10 @@ module.exports.routes = { ...@@ -75,7 +75,10 @@ module.exports.routes = {
'POST /stu/:id_stu/picto/:id_picto': 'StudentController.add_picto', 'POST /stu/:id_stu/picto/:id_picto': 'StudentController.add_picto',
'POST /stu/subscribe': 'StudentController.subscribe', 'POST /stu/subscribe': 'StudentController.subscribe',
'POST /stu/unsubscribe': 'StudentController.unsubscribe', 'POST /stu/unsubscribe': 'StudentController.unsubscribe',
// Websocket request for propagating actions add, delete, modify...
'POST /stu/vocabulary': 'StudentController.vocabulary', 'POST /stu/vocabulary': 'StudentController.vocabulary',
'POST /stu/action': 'StudentController.action', 'POST /stu/action': 'StudentController.action',
'POST /stu/config': 'StudentController.config', 'POST /stu/config': 'StudentController.config',
'POST /stu/actions_batch': 'StudentController.actions_batch', 'POST /stu/actions_batch': 'StudentController.actions_batch',
......
-----BEGIN CERTIFICATE REQUEST-----
MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
AQEBBQADggEPADCCAQoCggEBAPkWZj6TTJQon1rupP1SUY8dQiSv3+1xByQAGYaf
J9vZWfe6ZAy0ndFH26m0lc2S76BkQRfhWBTvWcsFhACWefSlGxpIiwxWdQOcF6cf
WyFi9VBPUXvTPyd64hCd+9+XML/0hzU4BpzJsEbPyhOv7VZA2VCZ+GAP86b/MGCS
s2QgWi7ZW0bTX0tO1WDC8JdHeaUXspwfJaqsw25SiDsNfWQmAZOa5F9I0vl4GCku
ivy16Cn/1XnpBw9M9DH18H1ENDFkOnUJYa8NU/t9lJJqNapDZ3zTLIlNd28nl3LP
HfbPGhq7qMAYmtiYt6rxHRBazL1OEFf2xb9DIVVlIKQXf/cCAwEAAaAAMA0GCSqG
SIb3DQEBCwUAA4IBAQArBhbVjPadSIWK9yGO7oca2TT+ZMp2kw4OY7+NHj+4NUFk
CKe2ZOAdvKM9wqY5P4QHux1WnqzEtga78ykaJofHeB/EFFqMkun5ETFbd/wIGKMy
WdB/vNNbsv5Hf2/ezGiw1HUsCGp2P/JTDO9+a5O3ioFhp4tvfJ7TBfdzD5eq7Xlq
DAHXPimbtnqiGGBf6efefG/Rl4cvvLyH5k/mUj00/stx3rjVayvedzOL8W4oRoxJ
SQEaqlp/MtPLXBuG0RkZsJloon+aZfcR3WE4iG2BVQe7Dqtdto+mRa9e1ba0ZUQK
Fq1iIGvsp9YIHCS3NXeJBeIllYkFK2hI7o6hALPf
-----END CERTIFICATE REQUEST-----
/**
* Module Dependencies
*/
var mysql = require('mysql');
var _ = require('lodash');
var utils = require('./utils');
var sql = module.exports = {
// Convert mysql format to standard javascript object
normalizeSchema: function (schema) {
return _.reduce(schema, function(memo, field) {
// Marshal mysql DESCRIBE to waterline collection semantics
var attrName = field.Field;
var type = field.Type;
// Remove (n) column-size indicators
type = type.replace(/\([0-9]+\)$/,'');
memo[attrName] = {
type: type,
defaultsTo: field.Default,
autoIncrement: field.Extra === 'auto_increment'
};
if(field.primaryKey) {
memo[attrName].primaryKey = field.primaryKey;
}
if(field.unique) {
memo[attrName].unique = field.unique;
}
if(field.indexed) {
memo[attrName].indexed = field.indexed;
}
return memo;
}, {});
},
// @returns ALTER query for adding a column
addColumn: function (collectionName, attrName, attrDef) {
// Escape table name and attribute name
var tableName = mysql.escapeId(collectionName);
// sails.log.verbose("ADDING ",attrName, "with",attrDef);
// Build column definition
var columnDefinition = sql._schema(collectionName, attrDef, attrName);
return 'ALTER TABLE ' + tableName + ' ADD ' + columnDefinition;
},
// @returns ALTER query for dropping a column
removeColumn: function (collectionName, attrName) {
// Escape table name and attribute name
var tableName = mysql.escapeId(collectionName);
attrName = mysql.escapeId(attrName);
return 'ALTER TABLE ' + tableName + ' DROP COLUMN ' + attrName;
},
countQuery: function(collectionName, options, tableDefs){
var query = 'SELECT count(*) as count from `' + collectionName + '`';
return query += sql.serializeOptions(collectionName, options, tableDefs);
},
// Create a schema csv for a DDL query
schema: function(collectionName, attributes) {
return sql.build(collectionName, attributes, sql._schema);
},
_schema: function(collectionName, attribute, attrName) {
attrName = mysql.escapeId(attrName);
var type = sqlTypeCast(attribute);
// Process PK field
if(attribute.primaryKey) {
var columnDefinition = attrName + ' ' + type;
// If type is an integer, set auto increment
if(type === 'TINYINT' || type === 'SMALLINT' || type === 'INT' || type === 'BIGINT') {
return columnDefinition + ' UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY';
}
// Just set NOT NULL on other types
return columnDefinition + ' NOT NULL PRIMARY KEY';
}
// Process NOT NULL field.
// if notNull is true, set NOT NULL constraint
var nullPart = '';
if (attribute.notNull) {
nullPart = ' NOT NULL ';
}
// Process UNIQUE field
if(attribute.unique) {
return attrName + ' ' + type + nullPart + ' UNIQUE KEY';
}
// Process INDEX field (NON-UNIQUE KEY)
if(attribute.index) {
return attrName + ' ' + type + nullPart + ', INDEX(' + attrName + ')';
}
return attrName + ' ' + type + ' ' + nullPart;
},
// Create an attribute csv for a DQL query
attributes: function(collectionName, attributes) {
return sql.build(collectionName, attributes, sql.prepareAttribute);
},
// Create a value csv for a DQL query
// key => optional, overrides the keys in the dictionary
values: function(collectionName, values, key) {
return sql.build(collectionName, values, sql.prepareValue, ', ', key);
},
prepareCriterion: function(collectionName, value, key, parentKey) {
// Special sub-attr case
if (validSubAttrCriteria(value)) {
return sql.where(collectionName, value, null, key);
}
// Build escaped attr and value strings using either the key,
// or if one exists, the parent key
var attrStr, valueStr;
// Special comparator case
if (parentKey) {
attrStr = sql.prepareAttribute(collectionName, value, parentKey);
valueStr = sql.prepareValue(collectionName, value, parentKey);
// Why don't we strip you out of those bothersome apostrophes?
var nakedButClean = String(valueStr).replace(new RegExp('^\'+|\'+$', 'g'), '');
if (key === '<' || key === 'lessThan') {
return attrStr + '<' + valueStr;
}
else if (key === '<=' || key === 'lessThanOrEqual') {
return attrStr + '<=' + valueStr;
}
else if (key === '>' || key === 'greaterThan') {
return attrStr + '>' + valueStr;
}
else if (key === '>=' || key === 'greaterThanOrEqual') {
return attrStr + '>=' + valueStr;
}
else if (key === '!' || key === 'not') {
if (value === null) {
return attrStr + ' IS NOT NULL';
}
else if (_.isArray(value)) {
return attrStr + ' NOT IN(' + valueStr + ')';
}
else {
return attrStr + '<>' + valueStr;
}
}
else if (key === 'like') {
return attrStr + ' LIKE \'' + nakedButClean + '\'';
}
else if (key === 'contains') {
return attrStr + ' LIKE \'%' + nakedButClean + '%\'';
}
else if (key === 'startsWith') {
return attrStr + ' LIKE \'' + nakedButClean + '%\'';
}
else if (key === 'endsWith') {
return attrStr + ' LIKE \'%' + nakedButClean + '\'';
}
else {
throw new Error('Unknown comparator: ' + key);
}
} else {
attrStr = sql.prepareAttribute(collectionName, value, key);
valueStr = sql.prepareValue(collectionName, value, key);
// Special IS NULL case
if (_.isNull(value)) {
return attrStr + ' IS NULL';
} else {
return attrStr + '=' + valueStr;
}
}
},
prepareValue: function(collectionName, value, attrName) {
// Cast dates to SQL
if (_.isDate(value)) {
value = toSqlDate(value);
}
// Cast functions to strings
if (_.isFunction(value)) {
value = value.toString();
}
// Escape (also wraps in quotes)
return mysql.escape(value);
},
prepareAttribute: function(collectionName, value, attrName) {
return mysql.escapeId(collectionName) + '.' + mysql.escapeId(attrName);
},
// // Starting point for predicate evaluation
// // parentKey => if set, look for comparators and apply them to the parent key
where: function(collectionName, where, key, parentKey) {
return sql.build(collectionName, where, sql.predicate, ' AND ', undefined, parentKey);
},
// Recursively parse a predicate calculus and build a SQL query
predicate: function(collectionName, criterion, key, parentKey) {
var queryPart = '';
if (parentKey) {
return sql.prepareCriterion(collectionName, criterion, key, parentKey);
}
// OR
if (key.toLowerCase() === 'or') {
queryPart = sql.build(collectionName, criterion, sql.where, ' OR ');
return ' ( ' + queryPart + ' ) ';
}
// AND
else if (key.toLowerCase() === 'and') {
queryPart = sql.build(collectionName, criterion, sql.where, ' AND ');
return ' ( ' + queryPart + ' ) ';
}
// IN
else if (_.isArray(criterion)) {
queryPart = sql.prepareAttribute(collectionName, null, key) + ' IN (' + sql.values(collectionName, criterion, key) + ')';
return queryPart;
}
// LIKE
else if (key.toLowerCase() === 'like') {
return sql.build(collectionName, criterion, function(collectionName, value, attrName) {
var attrStr = sql.prepareAttribute(collectionName, value, attrName);
// TODO: Handle regexp criterias
if (_.isRegExp(value)) {
throw new Error('RegExp LIKE criterias not supported by the MySQLAdapter yet. Please contribute @ http://github.com/balderdashy/sails-mysql');
}
var valueStr = sql.prepareValue(collectionName, value, attrName);
// Handle escaped percent (%) signs [encoded as %%%]
valueStr = valueStr.replace(/%%%/g, '\\%');
return attrStr + ' LIKE ' + valueStr;
}, ' AND ');
}
// NOT
else if (key.toLowerCase() === 'not') {
throw new Error('NOT not supported yet!');
}
// Basic criteria item
else {
return sql.prepareCriterion(collectionName, criterion, key);
}
},
serializeOptions: function(collectionName, options, tableDefs) {
// Join clause
// allow the key to be named with join or joins
var joins = options.join || options.joins || [];
if (joins.length > 0) {
return this.buildJoinQuery(collectionName, joins, options, tableDefs);
}
return this.buildSingleQuery(collectionName, options, tableDefs);
},
/**
* Build Up a Select Statement Without Joins
*/
buildSingleQuery: function(collectionName, options, tableDefs) {
var queryPart = '';
if(options.where) {
queryPart += 'WHERE ' + sql.where(collectionName, options.where) + ' ';
}
if (options.groupBy) {
queryPart += 'GROUP BY ';
// Normalize to array
if(!_.isArray(options.groupBy)) {
options.groupBy = [options.groupBy];
}
_.each(options.groupBy, function(key) {
queryPart += key + ', ';
});
// Remove trailing comma
queryPart = queryPart.slice(0, -2) + ' ';
}
if (options.sort) {
queryPart += 'ORDER BY ';
// Sort through each sort attribute criteria
_.each(options.sort, function(direction, attrName) {
queryPart += sql.prepareAttribute(collectionName, null, attrName) + ' ';
// Basic MongoDB-style numeric sort direction
if (direction === 1) {
queryPart += 'ASC, ';
} else {
queryPart += 'DESC, ';
}
});
// Remove trailing comma
if(queryPart.slice(-2) === ', ') {
queryPart = queryPart.slice(0, -2) + ' ';
}
}
if (_.has(options, 'limit') && (options.limit !== null && options.limit !== undefined)) {
queryPart += 'LIMIT ' + options.limit + ' ';
}
if (_.has(options, 'skip') && (options.skip !== null && options.skip !== undefined)) {
// Some MySQL hackery here. For details, see:
// http://stackoverflow.com/questions/255517/mysql-offset-infinite-rows
if (!options.limit) {
queryPart += 'LIMIT 18446744073709551610 ';
}
queryPart += 'OFFSET ' + options.skip + ' ';
}
return queryPart;
},
// Put together the CSV aggregation
// separator => optional, defaults to ', '
// keyOverride => optional, overrides the keys in the dictionary
// (used for generating value lists in IN queries)
// parentKey => key of the parent to this object
build: function(collectionName, collection, fn, separator, keyOverride, parentKey) {
separator = separator || ', ';
var $sql = '';
_.each(collection, function(value, key) {
$sql += fn(collectionName, value, keyOverride || key, parentKey);
// (always append separator)
$sql += separator;
});
// (then remove final one)
return String($sql).replace(new RegExp(separator + '+$'), '');
}
};
// Cast waterline types into SQL data types
function sqlTypeCast(attr) {
var type;
var size;
var expandedType;
if(_.isObject(attr) && _.has(attr, 'type')) {
type = attr.type;
} else {
type = attr;
}
type = type && type.toLowerCase();
switch (type) {
case 'string': {
size = 255; // By default.
// If attr.size is positive integer, use it as size of varchar.
if(!Number.isNaN(attr.size) && (parseInt(attr.size) === parseFloat(attr.size)) && (parseInt(attr.size) > 0)) {
size = attr.size;
}
expandedType = 'VARCHAR(' + size + ')';
break;
}
case 'text':
case 'array':
case 'json':
expandedType = 'LONGTEXT';
break;
case 'mediumtext':
expandedType = 'mediumtext';
break;
case 'longtext':
expandedType = 'longtext';
break;
case 'boolean':
expandedType = 'BOOL';
break;
case 'int':
case 'integer': {
size = 32; // By default
if(!Number.isNaN(attr.size) && (parseInt(attr.size) === parseFloat(attr.size)) && (parseInt(size) > 0)) {
size = parseInt(attr.size);
}
// MEDIUMINT gets internally promoted to INT so there is no real benefit
// using it.
switch (size) {
case 8:
expandedType = 'TINYINT';
break;
case 16:
expandedType = 'SMALLINT';
break;
case 32:
expandedType = 'INT';
break;
case 64:
expandedType = 'BIGINT';
break;
default:
expandedType = 'INT';
break;
}
break;
}
case 'float':
case 'double':
expandedType = 'FLOAT';
break;
case 'decimal':
expandedType = 'DECIMAL';
break;
case 'date':
expandedType = 'DATE';
break;
case 'datetime':
expandedType = 'DATETIME(3)';
break;
case 'time':
expandedType = 'TIME';
break;
case 'binary':
expandedType = 'BLOB';
break;
default:
console.error('Unregistered type given: ' + type);
expandedType = 'LONGTEXT';
break;
}
return expandedType;
}
function wrapInQuotes(val) {
return '"' + val + '"';
}
function toSqlDate(date) {
date = date.getFullYear() + '-' +
('00' + (date.getMonth()+1)).slice(-2) + '-' +
('00' + date.getDate()).slice(-2) + ' ' +
('00' + date.getHours()).slice(-2) + ':' +
('00' + date.getMinutes()).slice(-2) + ':' +
('00' + date.getSeconds()).slice(-2) + '.' +
('00' + date.getMilliseconds()).slice(-3);
return date;
}
// Return whether this criteria is valid as an object inside of an attribute
function validSubAttrCriteria(c) {
return _.isObject(c) && (
!_.isUndefined(c.not) || !_.isUndefined(c.greaterThan) || !_.isUndefined(c.lessThan) ||
!_.isUndefined(c.greaterThanOrEqual) || !_.isUndefined(c.lessThanOrEqual) || !_.isUndefined(c['<']) ||
!_.isUndefined(c['<=']) || !_.isUndefined(c['!']) || !_.isUndefined(c['>']) || !_.isUndefined(c['>=']) ||
!_.isUndefined(c.startsWith) || !_.isUndefined(c.endsWith) || !_.isUndefined(c.contains) || !_.isUndefined(c.like));
}
/**
* Utility Functions
*/
// Dependencies
var mysql = require('mysql');
var _ = require('lodash');
var url = require('url');
// Module Exports
var utils = module.exports = {};
/**
* Parse URL string from config
*
* Parse URL string into connection config parameters
*/
utils.parseUrl = function (config) {
if(!_.isString(config.url)) {
return config;
}
var obj = url.parse(config.url);
config.host = obj.hostname || config.host;
config.port = obj.port || config.port;
if(_.isString(obj.pathname)) {
config.database = obj.pathname.split('/')[1] || config.database;
}
if(_.isString(obj.auth)) {
config.user = obj.auth.split(':')[0] || config.user;
config.password = obj.auth.split(':')[1] || config.password;
}
return config;
};
/**
* Prepare values
*
* Transform a JS date to SQL date and functions
* to strings.
*/
utils.prepareValue = function(value) {
if(_.isUndefined(value) || value === null) {
return value;
}
// Cast functions to strings
if (_.isFunction(value)) {
value = value.toString();
}
// Store Arrays and Objects as strings
if (_.isArray(value) || value.constructor && value.constructor.name === 'Object') {
try {
value = JSON.stringify(value);
} catch (e) {
// just keep the value and let the db handle an error
value = value;
}
}
// Cast dates to SQL
if (_.isDate(value)) {
value = utils.toSqlDate(value);
}
return mysql.escape(value);
};
/**
* Builds a Select statement determining if Aggeregate options are needed.
*/
utils.buildSelectStatement = function(criteria, table, schemaDefs) {
var query = '';
if(criteria.groupBy || criteria.sum || criteria.average || criteria.min || criteria.max) {
query = 'SELECT ';
// Append groupBy columns to select statement
if(criteria.groupBy) {
if(_.isArray(criteria.groupBy)) {
_.each(criteria.groupBy, function(opt){
query += opt + ', ';
});
} else {
query += criteria.groupBy + ', ';
}
}
// Handle SUM
if (criteria.sum) {
if(_.isArray(criteria.sum)) {
_.each(criteria.sum, function(opt){
query += 'SUM(' + opt + ') AS ' + opt + ', ';
});
} else {
query += 'SUM(' + criteria.sum + ') AS ' + criteria.sum + ', ';
}
}
// Handle AVG (casting to float to fix percision with trailing zeros)
if (criteria.average) {
if(_.isArray(criteria.average)) {
_.each(criteria.average, function(opt){
query += 'AVG(' + opt + ') AS ' + opt + ', ';
});
} else {
query += 'AVG(' + criteria.average + ') AS ' + criteria.average + ', ';
}
}
// Handle MAX
if (criteria.max) {
if(_.isArray(criteria.max)) {
_.each(criteria.max, function(opt){
query += 'MAX(' + opt + ') AS ' + opt + ', ';
});
} else {
query += 'MAX(' + criteria.max + ') AS ' + criteria.max + ', ';
}
}
// Handle MIN
if (criteria.min) {
if(_.isArray(criteria.min)) {
_.each(criteria.min, function(opt){
query += 'MIN(' + opt + ') AS ' + opt + ', ';
});
} else {
query += 'MIN(' + criteria.min + ') AS ' + criteria.min + ', ';
}
}
// trim trailing comma
query = query.slice(0, -2) + ' ';
// Add FROM clause
return query += 'FROM `' + table + '` ';
}
/**
* If no aggregate options lets just build a normal query
*/
// Add all keys to the select statement for this table
query += 'SELECT ';
var selectKeys = [],
joinSelectKeys = [];
if ( !schemaDefs[table] ) {
throw new Error('Schema definition missing for table: `'+table+'`');
}
_.each(schemaDefs[table], function(schemaDef, key) {
selectKeys.push({ table: table, key: key });
});
// Check for joins
if(criteria.joins || criteria.join) {
var joins = criteria.joins || criteria.join;
_.each(joins, function(join) {
if(!join.select) {
return;
}
_.each(_.keys(schemaDefs[join.child.toLowerCase()]), function(key) {
var _join = _.cloneDeep(join);
_join.key = key;
joinSelectKeys.push(_join);
});
// Remove the foreign key for this join from the selectKeys array
selectKeys = selectKeys.filter(function(select) {
var keep = true;
if(select.key === join.parentKey && join.removeParentKey) {
keep = false;
}
return keep;
});
});
}
// Add all the columns to be selected that are not joins
_.each(selectKeys, function(select) {
query += '`' + select.table + '`.`' + select.key + '`, ';
});
// Add all the columns from the joined tables
_.each(joinSelectKeys, function(select) {
// Create an alias by prepending the child table with the alias of the join
var alias = select.alias.toLowerCase() + '_' + select.child.toLowerCase();
// If this is a belongs_to relationship, keep the foreign key name from the AS part
// of the query. This will result in a selected column like: "user"."id" AS "user_id__id"
if(select.model) {
return query += mysql.escapeId(alias) + '.' + mysql.escapeId(select.key) + ' AS ' +
mysql.escapeId(select.parentKey + '__' + select.key) + ', ';
}
// If a junctionTable is used, the child value should be used in the AS part of the
// select query.
if(select.junctionTable) {
return query += mysql.escapeId(alias) + '.' + mysql.escapeId(select.key) + ' AS ' +
mysql.escapeId(select.alias + '__' + select.key) + ', ';
}
// Else if a hasMany attribute is being selected, use the alias plus the child
return query += mysql.escapeId(alias) + '.' + mysql.escapeId(select.key) + ' AS ' +
mysql.escapeId(select.alias + '__' + select.key) + ', ';
});
// Remove the last comma
query = query.slice(0, -2) + ' FROM `' + table + '` ';
return query;
};
utils.toSqlDate = function toSqlDate(date) {
date = date.getFullYear() + '-' +
('00' + (date.getMonth()+1)).slice(-2) + '-' +
('00' + date.getDate()).slice(-2) + ' ' +
('00' + date.getHours()).slice(-2) + ':' +
('00' + date.getMinutes()).slice(-2) + ':' +
('00' + date.getSeconds()).slice(-2) + '.' +
('00' + date.getMilliseconds()).slice(-2);
return date;
};
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
"sails": "^0.12.3", "sails": "^0.12.3",
"sails-disk": "~0.10.0", "sails-disk": "~0.10.0",
"sails-generate-auth": "^0.2.0", "sails-generate-auth": "^0.2.0",
"sails-mysql": "^0.10.12", "sails-mysql": "^0.12",
"sails-test-helper": "^0.3.5", "sails-test-helper": "^0.3.5",
"socket.io": "~1.3.2", "socket.io": "~1.3.2",
"socket.io-redis": "^0.1.4", "socket.io-redis": "^0.1.4",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment