Commit 8a840026 by Arturo Montejo Ráez

Merge branch 'issue611' into develop

parents b7139260 7b224042
...@@ -140,6 +140,7 @@ CREATE TABLE IF NOT EXISTS `office` ( ...@@ -140,6 +140,7 @@ CREATE TABLE IF NOT EXISTS `office` (
`name` varchar(80) COLLATE utf8_unicode_ci NOT NULL, `name` varchar(80) COLLATE utf8_unicode_ci NOT NULL,
`logo_url` varchar(240) COLLATE utf8_unicode_ci NOT NULL, `logo_url` varchar(240) COLLATE utf8_unicode_ci NOT NULL,
`address` varchar(180) COLLATE utf8_unicode_ci NOT NULL, `address` varchar(180) COLLATE utf8_unicode_ci NOT NULL,
`postal_code` char(10) COLLATE utf8_unicode_ci NOT NULL,
`country` varchar(2) COLLATE utf8_unicode_ci NOT NULL, `country` varchar(2) COLLATE utf8_unicode_ci NOT NULL,
`lang` varchar(5) COLLATE utf8_unicode_ci NOT NULL, `lang` varchar(5) COLLATE utf8_unicode_ci NOT NULL,
`contact_person` varchar(80) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Contact person, main responsible', `contact_person` varchar(80) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Contact person, main responsible',
...@@ -308,6 +309,7 @@ CREATE TABLE IF NOT EXISTS `license` ( ...@@ -308,6 +309,7 @@ CREATE TABLE IF NOT EXISTS `license` (
`number` varchar(16) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, `number` varchar(16) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
`creation_ts` datetime DEFAULT CURRENT_TIMESTAMP, `creation_ts` datetime DEFAULT CURRENT_TIMESTAMP,
`activation_ts` datetime NULL, `activation_ts` datetime NULL,
`expiration_ts` datetime NULL,
`duration` int(11) DEFAULT 0, `duration` int(11) DEFAULT 0,
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `number` (`number`), UNIQUE KEY `number` (`number`),
...@@ -532,7 +534,7 @@ ALTER TABLE `student` ...@@ -532,7 +534,7 @@ ALTER TABLE `student`
-- Filtros para la tabla `license` -- Filtros para la tabla `license`
-- --
ALTER TABLE `license` ALTER TABLE `license`
ADD CONSTRAINT `license_fk_1` FOREIGN KEY (`id_stu`) REFERENCES `student` (`id`); ADD CONSTRAINT `license_fk_1` FOREIGN KEY (`id_stu`) REFERENCES `student` (`id`) ON DELETE CASCADE;
-- --
-- Filtros para la tabla `supervisor` -- Filtros para la tabla `supervisor`
-- --
......
...@@ -13,7 +13,8 @@ INSERT IGNORE INTO `office` ( ...@@ -13,7 +13,8 @@ INSERT IGNORE INTO `office` (
`contact_person`, `contact_person`,
`email`, `email`,
`phone1`, `phone1`,
`lang` `lang`,
`postal_code`
) VALUES ( ) VALUES (
'Asociación Provincial de Autismo de Jaén', 'Asociación Provincial de Autismo de Jaén',
'Avd. de Andalucía, 92 - bajo, 23006 Jaén', 'Avd. de Andalucía, 92 - bajo, 23006 Jaén',
...@@ -21,7 +22,8 @@ INSERT IGNORE INTO `office` ( ...@@ -21,7 +22,8 @@ INSERT IGNORE INTO `office` (
'Belén Pérez Vílchez', 'Belén Pérez Vílchez',
'belen.perez@autismojaen.es', 'belen.perez@autismojaen.es',
'+34 953 236 158', '+34 953 236 158',
'es-es' 'es-es',
'23006'
); );
-- --
......
...@@ -14,7 +14,8 @@ INSERT INTO `office` ( ...@@ -14,7 +14,8 @@ INSERT INTO `office` (
`contact_person`, `contact_person`,
`email`, `email`,
`phone1`, `phone1`,
`lang` `lang`,
`postal_code`
) VALUES ( ) VALUES (
'Comunicación Aumentativa JAén (CAJA)', 'Comunicación Aumentativa JAén (CAJA)',
'Paraje Las Lagunillas, Ed A3, primera plata, 23071. Jaén', 'Paraje Las Lagunillas, Ed A3, primera plata, 23071. Jaén',
...@@ -22,7 +23,8 @@ INSERT INTO `office` ( ...@@ -22,7 +23,8 @@ INSERT INTO `office` (
'Fernando Martínez Santiago', 'Fernando Martínez Santiago',
'dofer@ujaen.es', 'dofer@ujaen.es',
'+34 953 21 28 88', '+34 953 21 28 88',
'es-es' 'es-es',
'23071'
); );
-- --
......
...@@ -5,7 +5,8 @@ INSERT IGNORE INTO `office` ( ...@@ -5,7 +5,8 @@ INSERT IGNORE INTO `office` (
`contact_person`, `contact_person`,
`email`, `email`,
`phone1`, `phone1`,
`lang` `lang`,
`postal_code`
) VALUES ( ) VALUES (
'Centro Destrezas', 'Centro Destrezas',
'Avd. Eduardo García Maroto, 22, 1º Centro - Jaén', 'Avd. Eduardo García Maroto, 22, 1º Centro - Jaén',
...@@ -13,7 +14,8 @@ INSERT IGNORE INTO `office` ( ...@@ -13,7 +14,8 @@ INSERT IGNORE INTO `office` (
'Centro Destrezas', 'Centro Destrezas',
'centrodestrezas@gmail.com', 'centrodestrezas@gmail.com',
'+34 953 043 508', '+34 953 043 508',
'es-es' 'es-es',
'23007'
); );
INSERT IGNORE INTO `supervisor` ( INSERT IGNORE INTO `supervisor` (
......
/* global Instruction, Method */
/**
* LicenseController manages the requests related to the License model.
* Read it's documentation for further information.
* @type {Object}
*/
module.exports = {
/**
* Create a new Instruction, which is associated to the given method
* @param {request} req
* {
* duration (number of months)
* }
* @param {response} res
* {
* code (instructionId)
* method
* name
* objective
* }
*/
create: function (req, res) {
var params = req.allParams();
function get_new_random_license (callback) {
function random_license () {
var length = 16;
var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
var result = '';
for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
return result;
}
var license;
var found = true;
var maxtries = 10;
async.doWhilst(
function (cb) {
license = random_license();
License.findOne({number: license})
.then((l) => {
if (!l)
found = false;
cb();
})
.catch((err) => {
found = false;
cb();
});
},
function () {
return found;
},
function () {
callback(license);
}
);
}
if (!params.duration || params.duration < 0)
return res.badRequest();
get_new_random_license(function (license) {
License.create({
number: license,
duration: params.duration
})
.then((l) => {
if (l) {
delete l.id;
return res.ok(l);
}
return res.badRequest();
})
.catch((err) => {
return res.serverError(err);
});
});
},
/**
* Activate an existing License
* @param {request} req (with license number as parameter ID)
* {
* id_stu: id of the student
* }
* @param {response} res
*/
activate: function (req, res) {
var params = req.allParams();
if (!params.number)
return res.badRequest();
License.activate(req.license, req.body.id_stu, function(err, license) {
if (err)
return res.badRequest(err);
return res.ok(license);
});
}
};
/* global Student, PictoCore, VStuLastInstruction, StuPicto, StuSup, sailsTokenAuth, sails, /* global Student, PictoCore, VStuLastInstruction, StuPicto, StuSup, sailsTokenAuth, sails,
Picto */ Picto */
/** /**
/* StudentController /* StudentController
* *
* @description :: Server-side logic for managing students * @description :: Server-side logic for managing students
* @help :: See http://links.sailsjs.org/docs/controllers * @help :: See http://links.sailsjs.org/docs/controllers
*/ */
module.exports = { module.exports = {
// dummy function to test timeouts // dummy function to test timeouts
eternal: function (req, res) { eternal: function (req, res) {
setTimeout(function(){return;}, 1000*60*6); setTimeout(function(){return;}, 1000*60*6);
}, },
/** /**
* Login in the server as student, getting a toker for interacting with the platform * Login in the server as student, getting a toker for interacting with the platform
* @param {request} req * @param {request} req
* { * {
* username: 'johnydoe', * username: 'johnydoe',
* password: '12312' * password: '12312'
* } * }
* @param {response} res * @param {response} res
* { * {
* user: { * user: {
* id: 12, * id: 12,
* office: 1234, * office: 1234,
* username: 'johnydoe', * username: 'johnydoe',
* name: 'Johny', * name: 'Johny',
* surname: 'Doe', * surname: 'Doe',
* birthdate: '2009-12-10T00:00:00.000Z', * birthdate: '2009-12-10T00:00:00.000Z',
* gender: 'F', * gender: 'F',
* country: 'ES', * country: 'ES',
* pic: 'avatar/nice/url.jpg', * pic: 'avatar/nice/url.jpg',
* notes: null, * notes: null,
* lang: 'en-en', * lang: 'en-en',
* iat: 123512, * iat: 123512,
* exp: 1231292, * exp: 1231292,
* attributes: { @see Student.getValidAttributes() } * attributes: { @see Student.getValidAttributes() }
* }, * },
* token: '... asd90jkas ...', * token: '... asd90jkas ...',
* server_time: 123912932312 * server_time: 123912932312
* } * }
*/ */
login: function (req, res) { login: function (req, res) {
var bcrypt = require('bcrypt-nodejs'); var bcrypt = require('bcrypt-nodejs');
Student.findOne({ Student.findOne({
username: req.body.username username: req.body.username
}) })
.then(function (student) { .populate('license')
if (student) { .then(function (student) {
if (bcrypt.compareSync(req.body.password, student.password)) { if (student) {
student.isStudent = true; if (bcrypt.compareSync(req.body.password, student.password)) {
res.ok({ student.isStudent = true;
user: student, student.license = student.license[0];
token: sailsTokenAuth.issueToken(student, sails.config.jwt.expiresInMinutes), if (!student.license || student.license.hasExpired()) {
server_time: (new Date()) sails.log.error(`Tried to login with non valid license ${req.body.username}`);
.getTime() return res.badRequest("Student has an invalid license");
}); } else
} else { return res.ok({
sails.log.error(`Invalid student login: user ${student.username}, password\ user: student,
"${req.body.password}"`); token: sailsTokenAuth.issueToken(student, sails.config.jwt.expiresInMinutes),
res.badRequest(); server_time: (new Date()).getTime()
} });
} else { } else {
sails.log.error(`Tried to login as non-existing student ${req.body.username}`); sails.log.error(`Invalid student login: user ${student.username}, password\
res.badRequest(); "${req.body.password}"`);
} res.badRequest();
}) }
.catch(function () { } else {
sails.log.error(`Error getting student ${req.body.username} for login`); sails.log.error(`Tried to login as non-existing student ${req.body.username}`);
res.serverError(); res.badRequest();
}); }
}, })
.catch(function () {
/** sails.log.error(`Error getting student ${req.body.username} for login`);
* Get a student by id res.serverError();
* @param {request} req {} (with studentId specified as url parameters) });
* @param {response} res },
* {
* id: 12, /**
* office: 1234, * Get a student by id
* username: 'johnydoe', * @param {request} req {} (with studentId specified as url parameters)
* name: 'Johny', * @param {response} res
* surname: 'Doe', * {
* birthdate: '2009-12-10T00:00:00.000Z', * id: 12,
* gender: 'F', * office: 1234,
* country: 'ES', * username: 'johnydoe',
* pic: 'avatar/nice/url.jpg', * name: 'Johny',
* notes: null, * surname: 'Doe',
* lang: 'en-en', * birthdate: '2009-12-10T00:00:00.000Z',
* iat: 123512, * gender: 'F',
* exp: 1231292, * country: 'ES',
* attributes: { @see Student.getValidAttributes() }, * pic: 'avatar/nice/url.jpg',
* current_method: 'Do Things', // May be null * notes: null,
* current_instruction: 'Do Stuff', // May be null * lang: 'en-en',
* supervision: 0|1|2, // supervision level according to requester 0 -> office admin, 1 -> tutor, 2 -> therapist * iat: 123512,
* } * exp: 1231292,
*/ * attributes: { @see Student.getValidAttributes() },
getInfo: function (req, res) { * current_method: 'Do Things', // May be null
Student.findOne({id: req.params.id_stu}).populate('lastInstruction') * current_instruction: 'Do Stuff', // May be null
.then(function (student) { * supervision: 0|1|2, // supervision level according to requester 0 -> office admin, 1 -> tutor, 2 -> therapist
if (!student) * }
throw new Error("student not found"); */
getInfo: function (req, res) {
student.current_method = student.lastInstruction[0] ? student.lastInstruction[0].met_name : "no_method"; Student.findOne({id: req.params.id_stu})
student.current_instruction = student.lastInstruction[0] ? student.lastInstruction[0].ins_name : "no_instruction"; .populate('lastInstruction')
.populate('license')
// recover last instruction to complete student info .then(function (student) {
var stu_last_inst = VStuLastInstruction.findOne({student: student.id}) if (!student)
.then(function (stu_last_inst) { throw new Error("student not found");
return stu_last_inst;
}) student.current_method = student.lastInstruction[0] ? student.lastInstruction[0].met_name : "no_method";
.error(err => {throw err}); student.current_instruction = student.lastInstruction[0] ? student.lastInstruction[0].ins_name : "no_instruction";
// determine supervision level of the requester on the student // determine supervision level of the requester on the student
var stu_sup = StuSup.findOne({id_stu: student.id, id_sup: req.token.id}) var stu_sup = StuSup.findOne({id_stu: student.id, id_sup: req.token.id})
.then(function (stu_sup) { .then(function (stu_sup) {
return stu_sup; return stu_sup;
}) })
.error(err => {throw err}); .error(err => {throw err});
return [student, stu_last_inst, stu_sup]; return [student, stu_sup];
}) })
.spread(function (student, stu_last_inst, stu_sup) { .spread(function (student, stu_sup) {
if (stu_last_inst) {
student.current_method = stu_last_inst.met_name; // requester has no relation
student.current_instruction = stu_last_inst.ins_name; student.supervision = -1;
}
if (!stu_sup && req.token.office && student.office == req.token.office.id && req.token.isSupAdmin)
// requester has no relation student.supervision = 0; // requester is admin of the office
student.supervision = -1; else if (stu_sup && !req.token.office)
student.supervision = 1; // requester is tutor of the studend
if (!stu_sup && req.token.office && student.office == req.token.office.id) else if (stu_sup && req.token.office && student.office == req.token.office.id)
student.supervision = 0; // requester is admin of the office student.supervision = 2; // requester is supervisor of student
else if (stu_sup && !req.token.office)
student.supervision = 1; // requester is tutor of the studend if (student.supervision == -1) // should not hace access!!!
else if (stu_sup && req.token.office && student.office == req.token.office.id) return res.forbidden("Access to this student should not be granted to you");
student.supervision = 2; // requester is supervisor of student
return res.ok(student);
if (student.supervision == -1) // should not hace access!!! })
return res.forbidden("Access to this student should not be granted to you"); .catch(function (err) {
return res.notFound(err);
return res.ok(student); });
}) },
.catch(function (err) {
return res.notFound(err); //
}); // Adds a new student into the database
}, //
create: function (req, res) {
// var params = req.params.all();
// Adds a new student into the database License.isActivable(params.license_number, function(err) {
// if (err)
create: function (req, res) { return res.serverError(err);
var params = req.params.all();
Student.create(params) Student.create(params)
.then(function(created) { .then(function(created) {
sails.log.debug('Student ' + created.id + ' created: ' + JSON.stringify(created)); sails.log.debug('Student ' + created.id + ' created: ' + JSON.stringify(created));
return res.ok(created); License.activate(params.license_number, created.id, function(err, license) {
}) if (err)
.error(function(err) { return res.serverError(err);
if (err.message.search("Maximum number of enrolments reached") > 0) { created = created.toObject();
// This is a MySQL error triggered by TRG_NEW_STUDENT_MAXENROLMENTS trigger created.license = license.toObject();
// (see triggers-enroments-integrity-constraints.sql) return res.ok(created);
// As the format is not that of a normal error, we just get message });
sails.log.debug(err.message); })
return res.serverError(err.message); .error(function(err) {
} if (err.message.search("Maximum number of enrolments reached") > 0) {
else { // This is a MySQL error triggered by TRG_NEW_STUDENT_MAXENROLMENTS trigger
sails.log.debug(err.message); // (see triggers-enroments-integrity-constraints.sql)
return res.serverError(err.message); // As the format is not that of a normal error, we just get message
} sails.log.debug(err.message);
}); return res.serverError(err.message);
}, }
else {
/** sails.log.debug(err.message);
* Deletes an existing student by removing him/her from his/her office return res.serverError(err.message);
* and all his supervisors. }
* @param {request} req {} (with id_stu as url parameter) });
* @param {response} res {} });
*/ },
delete: function (req, res) {
if (!req.params.id_stu) /**
return res.json(500, { * Deletes an existing student by removing him/her from his/her office
error: 'No student defined' * and all his supervisors.
}); * @param {request} req {} (with id_stu as url parameter)
Student.logical_delete(req.params.id_stu, function (err) { * @param {response} res {}
if (err) { */
return res.json(500, { delete: function (req, res) {
error: err if (!req.params.id_stu)
}); return res.badRequest('No student defined');
} Student.logical_delete(req.params.id_stu, function (err) {
return res.json({ if (err)
result: 'Deleted' return res.serverError(err);
}); return res.ok({result: 'deleted'});
}); });
}, },
// //
// Updates student information // Updates student information
// //
update: function (req, res) { update: function (req, res) {
if (!req.params.id_stu) { if (!req.params.id_stu) {
res.badRequest(); res.badRequest();
} }
Student.findOne(req.params.id_stu).then(function(stu) { Student.findOne(req.params.id_stu)
var k; .populate('license')
.then(function(stu) {
// copy attributes var k;
for (k in req.body) stu[k] = req.body[k];
if (!stu)
if (!req.body.password) // to avoid change password when no one is provided throw new Error("Student not found");
delete stu.password;
// Update license
stu.save().then(function (saved) { if (req.body.license_number)
res.ok(stu); if (!stu.license[0] || req.body.license_number != stu.license[0].number) {
License.activate(req.body.license_number, stu.id, function(err, license) {
// Send websocket message if (err)
sails.hooks.events.broadcastEvent( return res.badRequest(err);
sails.hooks.rooms.student(stu.id), });
sails.hooks.events.updateStudent(stu), }
(req.isSocket) ? req.socket : undefined
); // copy attributes
}) for (k in req.body) stu[k] = req.body[k];
.catch(function(err) {
res.severError(); if (!req.body.password) // to avoid change password when no one is provided
}); delete stu.password;
})
.catch(function (err) { // delete license attribute as this has been already handled
res.notFound(); delete stu.license;
});
}, // delete username, as this should never be updated from requests
delete stu.username;
/** stu.save(function (err) {
* Return all existing supervisor and therapist from a given student if (err)
* @param {request} req {} (with studentId as url parameter) throw err;
* @param {response} res
* [ res.ok(stu);
* {
* id: supervisorId, // Send websocket message
* name: 'John', sails.hooks.events.broadcastEvent(
* surname: 'Doe', sails.hooks.rooms.student(stu.id),
* gender: 'M/F', sails.hooks.events.updateStudent(stu),
* pic: 'supervisor/avatar.jpg', (req.isSocket) ? req.socket : undefined
* address: 'My Adress, n1', );
* country: 'ES', });
* email: 'john@doe.es', })
* phone: '+123123123', .catch(function (err) {
* lang: 'es-es', res.notFound();
* active: true, });
* ttsEngine: 'IVONA-Text', },
* office: officeId
* },
* ... /**
* ] * Return all existing supervisor and therapist from a given student
*/ * @param {request} req {} (with studentId as url parameter)
supervisors: function (req, res) { * @param {response} res
if (!req.params.id_stu) { * [
return res.json(500, { * {
error: 'No student defined' * id: supervisorId,
}); * name: 'John',
} * surname: 'Doe',
Student.supervisors(req.params.id_stu, function (err, sups) { * gender: 'M/F',
if (err) throw err; * pic: 'supervisor/avatar.jpg',
return res.json(sups); * address: 'My Adress, n1',
}); * country: 'ES',
}, * email: 'john@doe.es',
* phone: '+123123123',
/** * lang: 'es-es',
* Return all existing therapists from a given student * active: true,
* @param {request} req {} (with studentId as url parameter) * ttsEngine: 'IVONA-Text',
* @param {response} res * office: officeId
* [ * },
* { * ...
* id: therapistId, * ]
* name: 'John', */
* surname: 'Doe', supervisors: function (req, res) {
* gender: 'M/F', if (!req.params.id_stu) {
* pic: 'supervisor/avatar.jpg', return res.json(500, {
* address: 'My Address, n1', error: 'No student defined'
* country: 'ES', });
* email: 'john@doe.es', }
* phone: '+123123123', Student.supervisors(req.params.id_stu, function (err, sups) {
* lang: 'es-es', if (err) throw err;
* active: true, return res.json(sups);
* ttsEngine: 'IVONA-Text', });
* office: officeId },
* },
* ... /**
* ] * Return all existing therapists from a given student
*/ * @param {request} req {} (with studentId as url parameter)
therapists: function (req, res) { * @param {response} res
if (!req.params.id_stu) { * [
return res.json(500, { * {
error: 'No student defined' * id: therapistId,
}); * name: 'John',
} * surname: 'Doe',
Student.therapists(req.params.id_stu, function (err, sups) { * gender: 'M/F',
if (err) throw err; * pic: 'supervisor/avatar.jpg',
return res.json(sups); * address: 'My Address, n1',
}); * country: 'ES',
}, * email: 'john@doe.es',
* phone: '+123123123',
/** * lang: 'es-es',
* Return all existing tutors from a given student * active: true,
* @param {request} req {} (with studentId as url parameter) * ttsEngine: 'IVONA-Text',
* @param {response} res * office: officeId
* [ * },
* { * ...
* id: tutorId, * ]
* name: 'John', */
* surname: 'Doe', therapists: function (req, res) {
* gender: 'M/F', if (!req.params.id_stu) {
* pic: 'supervisor/avatar.jpg', return res.json(500, {
* address: 'My Address, n1', error: 'No student defined'
* country: 'ES', });
* email: 'john@doe.es', }
* phone: '+123123123', Student.therapists(req.params.id_stu, function (err, sups) {
* lang: 'es-es', if (err) throw err;
* active: true, return res.json(sups);
* ttsEngine: 'IVONA-Text', });
* office: officeId },
* },
* ... /**
* ] * Return all existing tutors from a given student
*/ * @param {request} req {} (with studentId as url parameter)
tutors: function (req, res) { * @param {response} res
if (!req.params.id_stu) { * [
return res.json(500, { * {
error: 'No student defined' * id: tutorId,
}); * name: 'John',
} * surname: 'Doe',
Student.tutors(req.params.id_stu, function (err, sups) { * gender: 'M/F',
if (err) throw err; * pic: 'supervisor/avatar.jpg',
return res.json(sups); * address: 'My Address, n1',
}); * country: 'ES',
}, * email: 'john@doe.es',
* phone: '+123123123',
/** * lang: 'es-es',
* Creates a relation between the student and a given supervisor. * active: true,
* It broadcasts the event linkSupervisorToStudent to both the student room * ttsEngine: 'IVONA-Text',
* and the supervisor room. * office: officeId
* @param {request} { (with id_stu and id_sup as url parameters) * },
* asTherapist: true/false (optional) // assigns supervisor to student's office is true, set id_off to null otherwise * ...
* } * ]
* @param {response} {} */
*/ tutors: function (req, res) {
link_supervisor: function (req, res) { if (!req.params.id_stu) {
StuSup.create({ return res.json(500, {
student: req.param('id_stu'), error: 'No student defined'
supervisor: req.param('id_sup') });
}) }
.then(function (stuSup) { Student.tutors(req.params.id_stu, function (err, sups) {
if (!stuSup) if (err) throw err;
throw new Error('stusup not created'); return res.json(sups);
});
const socketToOmit = (req.isSocket) ? req.socket : undefined; },
const linkSupervisorToStudentEvent = sails.hooks.events.linkSupervisorToStudent(
stuSup.supervisor, /**
stuSup.student * Creates a relation between the student and a given supervisor.
); * It broadcasts the event linkSupervisorToStudent to both the student room
* and the supervisor room.
sails.hooks.events.broadcastEvent( * @param {request} { (with id_stu and id_sup as url parameters)
sails.hooks.rooms.supervisor(stuSup.supervisor), * asTherapist: true/false (optional) // assigns supervisor to student's office is true, set id_off to null otherwise
linkSupervisorToStudentEvent, * }
socketToOmit * @param {response} {}
); */
sails.hooks.events.broadcastEvent( link_supervisor: function (req, res) {
sails.hooks.rooms.student(stuSup.student), StuSup.create({
linkSupervisorToStudentEvent, student: req.param('id_stu'),
socketToOmit supervisor: req.param('id_sup')
); })
.then(function (stuSup) {
return stuSup; if (!stuSup)
}) throw new Error('stusup not created');
.catch((err) => {
StuSup.findOne({ student: req.param('id_stu'), supervisor: req.param('id_sup') }) const socketToOmit = (req.isSocket) ? req.socket : undefined;
.then((stuSup) => { const linkSupervisorToStudentEvent = sails.hooks.events.linkSupervisorToStudent(
// It was already there! stuSup.supervisor,
if (stuSup) stuSup.student
return stuSup; );
else
throw err; sails.hooks.events.broadcastEvent(
}); sails.hooks.rooms.supervisor(stuSup.supervisor),
}) linkSupervisorToStudentEvent,
.then((stuSup) => { socketToOmit
// update supervisor office if it is linked as therapist );
Supervisor.findOne({id: req.param('id_sup')}) sails.hooks.events.broadcastEvent(
.then((sup) => { sails.hooks.rooms.student(stuSup.student),
if (sup) { linkSupervisorToStudentEvent,
Student.findOne({id: req.param('id_stu')}) socketToOmit
.then((stu) => { );
if (stu) {
if (req.body.asTherapist) return stuSup;
sup.office = stu.office; })
else .catch((err) => {
sup.office = null; StuSup.findOne({ student: req.param('id_stu'), supervisor: req.param('id_sup') })
delete sup.password; .then((stuSup) => {
sup.save(); // It was already there!
} if (stuSup)
}); return stuSup;
} else
}); throw err;
return res.ok(); });
}) })
.catch((err) => { .then((stuSup) => {
return res.serverError("Error: " + err); // update supervisor office if it is linked as therapist
}); Supervisor.findOne({id: req.param('id_sup')})
}, .then((sup) => {
if (sup) {
/** Student.findOne({id: req.param('id_stu')})
* Destroys a relation (drama queen) between the student and a given supervisor. .then((stu) => {
* It broadcasts the even unlinkSupervisorFromStudent to both the student room if (stu) {
* and the supervisor room. if (req.body.asTherapist)
* @param {request} {} (with studentId and supervisorId as url parameters) sup.office = stu.office;
* @param {response} {} else
*/ sup.office = null;
unlink_supervisor: function (req, res) { delete sup.password;
StuSup.findOne({ sup.save();
student: req.param('id_stu'), }
supervisor: req.param('id_sup') });
}) }
.then((stuSup) => { });
if (!stuSup) return res.ok();
throw new Error("student and supervisor are not linked"); })
.catch((err) => {
stuSup.destroy(); return res.serverError("Error: " + err);
});
const socketToOmit = req.isSocket ? req.socket : undefined; },
const unlinkSupervisorFromStudentEvent = sails.hooks.events.unlinkSupervisorFromStudent(
stuSup.student, /**
stuSup.supervisor * Destroys a relation (drama queen) between the student and a given supervisor.
); * It broadcasts the even unlinkSupervisorFromStudent to both the student room
* and the supervisor room.
sails.hooks.events.broadcastEvent( * @param {request} {} (with studentId and supervisorId as url parameters)
sails.hooks.rooms.supervisor(stuSup.supervisor), * @param {response} {}
unlinkSupervisorFromStudentEvent, */
socketToOmit unlink_supervisor: function (req, res) {
); StuSup.findOne({
student: req.param('id_stu'),
sails.hooks.events.broadcastEvent( supervisor: req.param('id_sup')
sails.hooks.rooms.student(stuSup.student), })
unlinkSupervisorFromStudentEvent, .then((stuSup) => {
socketToOmit if (!stuSup)
); throw new Error("student and supervisor are not linked");
return res.ok(); stuSup.destroy();
})
.catch((err) => { const socketToOmit = req.isSocket ? req.socket : undefined;
res.serverError("Error unliking student: " + err); const unlinkSupervisorFromStudentEvent = sails.hooks.events.unlinkSupervisorFromStudent(
}); stuSup.student,
}, stuSup.supervisor
);
/**
* Get all methods from a given student (with their instructions) sails.hooks.events.broadcastEvent(
* @param {request} req {} (with studentId as url parameter) sails.hooks.rooms.supervisor(stuSup.supervisor),
* @param {response} res unlinkSupervisorFromStudentEvent,
* [ socketToOmit
* { );
* id: methodId,
* student: studentId, sails.hooks.events.broadcastEvent(
* name: 'Method Name', sails.hooks.rooms.student(stuSup.student),
* description: 'Method description', unlinkSupervisorFromStudentEvent,
* registration: null, socketToOmit
* notes: 'Method notes', );
* last_ins: instructionId // Last instruccion executed,
* instructions: [ return res.ok();
* { })
* id: instructionId, .catch((err) => {
* name: 'Instruction Name', res.serverError("Error unliking student: " + err);
* objective: 'Instruction Objective', });
* status: 'instruction-status', },
* begin: '2015-07-14T07:23:03.000Z',
* end: '2015-07-14T07:28:03.000Z', /**
* method: methodId * Get all methods from a given student (with their instructions)
* }, * @param {request} req {} (with studentId as url parameter)
* ... * @param {response} res
* ] * [
* }, * {
* ... * id: methodId,
* ] * student: studentId,
*/ * name: 'Method Name',
methods: function (req, res) { * description: 'Method description',
var params = req.allParams(); * registration: null,
* notes: 'Method notes',
Method.find({ * last_ins: instructionId // Last instruccion executed,
student: params.id_stu * instructions: [
}) * {
.populate('instructions') * id: instructionId,
.exec(function (err, methods) { * name: 'Instruction Name',
if (err) * objective: 'Instruction Objective',
return res.json(500, { * status: 'instruction-status',
error: err * begin: '2015-07-14T07:23:03.000Z',
}); * end: '2015-07-14T07:28:03.000Z',
* method: methodId
if (!methods || methods.length == 0) * },
return res.json([]); // Return an empty array * ...
* ]
if (methods) { * },
* ...
var l_met = []; * ]
*/
async.eachSeries(methods, function (m, callback1) { methods: function (req, res) {
var l_ins = []; var params = req.allParams();
var last_ins = null;
var last_time = 0; Method.find({
student: params.id_stu
sails.log.debug('Loop methods: ' + m.name); })
.populate('instructions')
async.eachSeries(m.instructions, function (ins, callback2) { .exec(function (err, methods) {
sails.log.debug('Loop instructions: ' + ins.name); if (err)
return res.json(500, {
Instruction.findOne({ error: err
id: ins.id });
})
.populate('workingSessions', { if (!methods || methods.length == 0)
sort: 'begin DESC' return res.json([]); // Return an empty array
})
.exec(function (err, instruction) { if (methods) {
if (err) {
sails.log.debug('Error in method ' + m.name); var l_met = [];
}
async.eachSeries(methods, function (m, callback1) {
if (!instruction || !instruction.workingSessions || instruction.workingSessions var l_ins = [];
.length == 0) { var last_ins = null;
sails.log.debug('No working sessions found for instruction ' + var last_time = 0;
instruction.id);
} else { sails.log.debug('Loop methods: ' + m.name);
var last = instruction.workingSessions.length - 1;
instruction.begin = instruction.workingSessions[last].end; async.eachSeries(m.instructions, function (ins, callback2) {
instruction.end = instruction.workingSessions[0].end; sails.log.debug('Loop instructions: ' + ins.name);
if (instruction.end > last_time) { Instruction.findOne({
last_ins = instruction.id; id: ins.id
last_time = instruction.end; })
} .populate('workingSessions', {
} sort: 'begin DESC'
// Add instruction to list (with or without tries) })
l_ins.push(instruction); .exec(function (err, instruction) {
callback2(); if (err) {
}); sails.log.debug('Error in method ' + m.name);
}
// Finish function when each callback is done
// Optionaly it can be passed and err parameter if (!instruction || !instruction.workingSessions || instruction.workingSessions
}, function (err) { .length == 0) {
if (err) { sails.log.debug('No working sessions found for instruction ' +
// One of the iterations produced an error. instruction.id);
// All processing will now stop. } else {
return res.json(500, { var last = instruction.workingSessions.length - 1;
error: 'Error looping in tries: ' + err instruction.begin = instruction.workingSessions[last].end;
}); instruction.end = instruction.workingSessions[0].end;
}
m.instructions = l_ins; if (instruction.end > last_time) {
m.last_ins = last_ins; last_ins = instruction.id;
l_met.push(m); last_time = instruction.end;
callback1(); }
}); }
// Add instruction to list (with or without tries)
// Finish function when each callback is done l_ins.push(instruction);
// Optionaly it can be passed and err parameter callback2();
}, function (err) { });
if (err) {
// One of the iterations produced an error. // Finish function when each callback is done
// All processing will now stop. // Optionaly it can be passed and err parameter
return res.json(500, { }, function (err) {
error: 'Error looping in method instructions: ' + err if (err) {
}); // One of the iterations produced an error.
} else { // All processing will now stop.
// All end ok return res.json(500, {
//Assing the built list error: 'Error looping in tries: ' + err
return res.json(l_met); });
} }
}); m.instructions = l_ins;
m.last_ins = last_ins;
} l_met.push(m);
}); callback1();
}, });
// read action // Finish function when each callback is done
// get tries of the last working session // Optionaly it can be passed and err parameter
// }, function (err) {
lasttries: function (req, res) { if (err) {
var params = req.allParams(); // One of the iterations produced an error.
// All processing will now stop.
//Student.findOne({ id: params.id_stu }).populate('workingSessions') return res.json(500, {
VStuLastInstruction.findOne({ error: 'Error looping in method instructions: ' + err
where: { });
student: params.id_stu } else {
} // All end ok
}) //Assing the built list
.exec(function (err, ws) { return res.json(l_met);
if (err) { }
sails.log.debug('Finding student working sessions: ' + err); });
return res.json(500, {
error: 'No student last working session found' }
}); });
} },
if (!ws || ws.length == 0) { // read action
return res.json([]); // Return an empty array of last tries // get tries of the last working session
} //
lasttries: function (req, res) {
if (ws) { var params = req.allParams();
// Find the tries of this working session populating actions
sails.log.debug('Find WS ' + JSON.stringify(ws)); //Student.findOne({ id: params.id_stu }).populate('workingSessions')
Try.find({ VStuLastInstruction.findOne({
workingSession: ws.workingSession where: {
}) student: params.id_stu
.populate('actions', { }
//supervisor: null //recovering supervisor actions, too })
}) .exec(function (err, ws) {
.exec(function (err, tries) { if (err) {
sails.log.debug('Finding student working sessions: ' + err);
if (err) { return res.json(500, {
return res.json(500, { error: 'No student last working session found'
error: 'No student last tries found' });
}); }
}
if (!ws || ws.length == 0) {
if (!tries || tries.length == 0) { return res.json([]); // Return an empty array of last tries
return res.json([]); // Return an empty array of tries }
}
if (ws) {
if (tries) { // Find the tries of this working session populating actions
sails.log.debug('Find WS ' + JSON.stringify(ws));
// A list for one element: The last working session Try.find({
var l_ws = []; workingSession: ws.workingSession
l_ws.push({ })
'id': ws.workingSession, .populate('actions', {
'student': ws.student, //supervisor: null //recovering supervisor actions, too
'begin': ws.ws_begin, })
'end': ws.ws_end, .exec(function (err, tries) {
'description': ws.ws_description,
'tries': tries if (err) {
}); return res.json(500, {
error: 'No student last tries found'
return res.json(l_ws); });
} }
});
} if (!tries || tries.length == 0) {
}); return res.json([]); // Return an empty array of tries
}, }
/** if (tries) {
* Return all tries from a student
* @param {request} req {} (width studentId as url parameter) // A list for one element: The last working session
* @param {response} res var l_ws = [];
* { l_ws.push({
"methods": [ 'id': ws.workingSession,
{ 'student': ws.student,
"student": 24, 'begin': ws.ws_begin,
"id": 1, 'end': ws.ws_end,
"name": "Test Method", 'description': ws.ws_description,
"description": null, 'tries': tries
"registration": null, });
"notes": null
"instructions": [ return res.json(l_ws);
{ }
"id": 1, });
"name": "Test Instruction", }
"objective": null, });
"status": "started", },
"begin": null,
"end": null, /**
"method": 1, * Return all tries from a student
"working_sessions": [ * @param {request} req {} (width studentId as url parameter)
{ * @param {response} res
"id": 3, * {
"begin": "2016-09-09T08:26:24.500Z", "methods": [
"end": "2016-08-28T23:36:35.000Z", {
"current": null, "student": 24,
"description": "", "id": 1,
"supervisor": 23, "name": "Test Method",
"instruction": 1 "description": null,
"tries": [ "registration": null,
{ "notes": null
"actions": [], "instructions": [
"id": 1, {
"begin": "2016-08-28T23:36:35.474Z", "id": 1,
"end": "2016-08-28T23:36:44.000Z", "name": "Test Instruction",
"result": null, "objective": null,
"description": null, "status": "started",
"workingSession": 3 "begin": null,
}, "end": null,
{ "method": 1,
"actions": [], "working_sessions": [
"id": 2, {
"begin": "2016-08-28T23:36:44.050Z", "id": 3,
"end": "2016-08-29T01:36:51.710Z", "begin": "2016-09-09T08:26:24.500Z",
"result": "SUCCESS", "end": "2016-08-28T23:36:35.000Z",
"description": null, "current": null,
"workingSession": 3 "description": "",
}, "supervisor": 23,
{ "instruction": 1
"actions": [], "tries": [
"id": 3, {
"begin": "2016-08-28T23:36:51.942Z", "actions": [],
"end": "2016-08-28T23:36:53.000Z", "id": 1,
"result": "DISCARDED", "begin": "2016-08-28T23:36:35.474Z",
"description": null, "end": "2016-08-28T23:36:44.000Z",
"workingSession": 3 "result": null,
}, "description": null,
{ "workingSession": 3
"actions": [], },
"id": 4, {
"begin": "2016-08-28T23:36:53.877Z", "actions": [],
"end": "2016-08-28T23:37:13.000Z", "id": 2,
"result": "SPONTANEOUS SUCCESS", "begin": "2016-08-28T23:36:44.050Z",
"description": null, "end": "2016-08-29T01:36:51.710Z",
"workingSession": 3 "result": "SUCCESS",
} "description": null,
] "workingSession": 3
} },
} {
] "actions": [],
} "id": 3,
] "begin": "2016-08-28T23:36:51.942Z",
} "end": "2016-08-28T23:36:53.000Z",
*/ "result": "DISCARDED",
tries: function (req, res) { "description": null,
if (!req.params.id_stu) "workingSession": 3
return res.badRequest("Student not defined"); },
{
Student.tries(req.params.id_stu, function (err, l_met) { "actions": [],
if (err) return res.serverError(err); "id": 4,
return res.ok(l_met); "begin": "2016-08-28T23:36:53.877Z",
}); "end": "2016-08-28T23:37:13.000Z",
}, "result": "SPONTANEOUS SUCCESS",
"description": null,
/** "workingSession": 3
* Get all pictos from a given student }
* @param {request} req {} (with studentId as url parameter) ]
* @param {response} res }
* [ }
* { ]
* id: student-picto ID, }
* picto: { ]
* id: pictoId, }
* uri: 'uri/to/picto.png', */
* category: pictoCategoryId, tries: function (req, res) {
* source: 1 @TODO Other sources if (!req.params.id_stu)
* owner: supervisorId or null return res.badRequest("Student not defined");
* },
* expression: { Student.tries(req.params.id_stu, function (err, l_met) {
* id: expressionId, if (err) return res.serverError(err);
* lang: 'es-es', return res.ok(l_met);
* text: 'Picto Expression', });
* picto: pictoId },
* },
* attributes: { @see StuPicto.getValidAttributes() } /**
* }, * Get all pictos from a given student
* ... * @param {request} req {} (with studentId as url parameter)
* ] * @param {response} res
*/ * [
pictos: function (req, res) { * {
if (!req.params.id_stu) { * id: student-picto ID,
return res.json(500, { * picto: {
error: 'No student defined' * id: pictoId,
}); * uri: 'uri/to/picto.png',
} * category: pictoCategoryId,
* source: 1 @TODO Other sources
sails.log.debug('Pictos requested for student ' + req.params.id_stu); * owner: supervisorId or null
* },
Student.pictos(req.params.id_stu, function (err, pictos) { * expression: {
if (err) * id: expressionId,
return res.serverError("Error obtaining pictos: "+ err); * lang: 'es-es',
return res.ok(pictos); * text: 'Picto Expression',
}); * picto: pictoId
}, * },
* attributes: { @see StuPicto.getValidAttributes() }
// * },
// Returns all working sessions for the given student * ...
// * ]
ws: function (req, res) { */
if (!req.params.id_stu) { pictos: function (req, res) {
return res.json(500, { if (!req.params.id_stu) {
error: 'No student defined' return res.json(500, {
}); error: 'No student defined'
} });
}
sails.log.debug('Working Sessions requested for student ' + req.params.id_stu);
sails.log.debug('Pictos requested for student ' + req.params.id_stu);
Student.findOne(req.params.id_stu)
.populate('workingSessions') Student.pictos(req.params.id_stu, function (err, pictos) {
.exec(function (err, stu) { if (err)
if (err) return res.serverError("Error obtaining pictos: "+ err);
return res.json(500, { return res.ok(pictos);
error: err });
}); },
if (!stu || !stu.workingSessions || stu.workingSessions.length == 0) //
return res.json([]); // Return an empty array // Returns all working sessions for the given student
else //
return res.json(stu.workingSessions); ws: function (req, res) {
}); if (!req.params.id_stu) {
}, return res.json(500, {
error: 'No student defined'
/** });
* Add an existing picto to the student's collection }
* @param {request} req (with studentId and pictoId as url parameter)
* { sails.log.debug('Working Sessions requested for student ' + req.params.id_stu);
* attributes: { @see StuPicto.getValidAttributes() }
* } Student.findOne(req.params.id_stu)
* @param {response} res .populate('workingSessions')
* { .exec(function (err, stu) {
* id: stuPictoId (association betweet student and picto) if (err)
* student: studentId (speficied as url parameter) return res.json(500, {
* picto: { @see Picto model} error: err
* attributes: { @see StuPicto.getValidAttributes() } });
* expression: { @see Picto model }
* } if (!stu || !stu.workingSessions || stu.workingSessions.length == 0)
*/ return res.json([]); // Return an empty array
add_picto: function (req, res) { else
var params = req.allParams(); return res.json(stu.workingSessions);
});
Student.findOne({ id: params.id_stu }) },
.then((student) => {
if (!student) { /**
sails.log.error(`Student ${params.id_stu} not found`); * Add an existing picto to the student's collection
throw new Error("Student not found"); * @param {request} req (with studentId and pictoId as url parameter)
} * {
* attributes: { @see StuPicto.getValidAttributes() }
return [ * }
Picto.findOne({ id: params.id_picto }) * @param {response} res
.populate('expressions', {lang: student.lang}) * {
, * id: stuPictoId (association betweet student and picto)
student * student: studentId (speficied as url parameter)
]; * picto: { @see Picto model}
}) * attributes: { @see StuPicto.getValidAttributes() }
.spread((picto, student) => { * expression: { @see Picto model }
if (!picto) { * }
sails.log.error(`Picto ${params.id_picto} not found`); */
throw new Error("Picto not found"); add_picto: function (req, res) {
} var params = req.allParams();
return [ Student.findOne({ id: params.id_stu })
StuPicto.create({ .then((student) => {
student: student.id, if (!student) {
picto: picto.id, sails.log.error(`Student ${params.id_stu} not found`);
attributes: params.attributes throw new Error("Student not found");
}) }
,
picto return [
]; Picto.findOne({ id: params.id_picto })
}) .populate('expressions', {lang: student.lang})
.spread((stuPicto, picto) => { ,
if (!stuPicto) student
throw new Error("stu_picto not created"); ];
sails.log.debug("->>" + JSON.stringify(picto)); })
return res.ok({ .spread((picto, student) => {
id: stuPicto.id, if (!picto) {
student: params.id_stu, sails.log.error(`Picto ${params.id_picto} not found`);
attributes: stuPicto.attributes, throw new Error("Picto not found");
picto: picto, }
expression: picto.expressions[0]
}); return [
}) StuPicto.create({
.catch(err => res.serverError("Error adding picto: " + err)); student: student.id,
}, picto: picto.id,
attributes: params.attributes
/** })
* Removes an existing picto to the student's collection ,
* Warning: yout must send the **stuPictoId** as url parameter, not the **pictoId**. picto
* You can obtain this ID from the student picto collection. ];
* @param {request} req {} (with studentId and stuPictoId as url parameters) })
* @param {response} res {} .spread((stuPicto, picto) => {
*/ if (!stuPicto)
delete_picto: function (req, res) { throw new Error("stu_picto not created");
var params = req.allParams(); sails.log.debug("->>" + JSON.stringify(picto));
return res.ok({
StuPicto.destroy({ id: params.id_stuPicto }) id: stuPicto.id,
.then(destroyed => { student: params.id_stu,
if (!destroyed) attributes: stuPicto.attributes,
throw new Error(); picto: picto,
return res.ok(); expression: picto.expressions[0]
}) });
.catch(err => { })
return res.serverError('Not removed picto for student: ' + err); .catch(err => res.serverError("Error adding picto: " + err));
}); },
},
/**
// update action * Removes an existing picto to the student's collection
// update picto atributes for a studentPicto * Warning: yout must send the **stuPictoId** as url parameter, not the **pictoId**.
// * You can obtain this ID from the student picto collection.
update_picto: function (req, res) { * @param {request} req {} (with studentId and stuPictoId as url parameters)
var params = req.allParams(); * @param {response} res {}
console.log('Updating attributes for picto student ' + JSON.stringify(params)); */
console.log(JSON.stringify(params)); delete_picto: function (req, res) {
var params = req.allParams();
query = params.id_stuPicto ? {
id: params.id_stuPicto StuPicto.destroy({ id: params.id_stuPicto })
} : { .then(destroyed => {
id_stu: params.id_stu, if (!destroyed)
id_pic: params.id_pic throw new Error();
} return res.ok();
})
StuPicto.update(query, { .catch(err => {
attributes: params.attributes return res.serverError('Not removed picto for student: ' + err);
}) });
.then(updated => { },
if (!updated)
throw new Error ("error on update"); // update action
// update picto atributes for a studentPicto
console.log('Updated attributes for picto student:' + JSON.stringify(updated[0])); //
// return res.json(updated[0]); update_picto: function (req, res) {
return res.ok({ var params = req.allParams();
id: updated[0].id, // id of stu_picto console.log('Updating attributes for picto student ' + JSON.stringify(params));
attributes: updated[0].attributes, // picto attributes for student console.log(JSON.stringify(params));
picto: {
id: updated[0].picto // picto information query = params.id_stuPicto ? {
} id: params.id_stuPicto
}); } : {
}) id_stu: params.id_stu,
.catch(err => { id_pic: params.id_pic
return res.serverError('Unable to update picto for student: ' + err); }
});
}, StuPicto.update(query, {
attributes: params.attributes
// update action })
// update picto atributes for a studentPicto .then(updated => {
// if (!updated)
update_legend: function (req, res) { throw new Error ("error on update");
var params = req.allParams();
var query='update stu_picto'+ console.log('Updated attributes for picto student:' + JSON.stringify(updated[0]));
' set attributes=json_set(attributes, \'$.legend\',\''+params.legend_value+'\')'+ // return res.json(updated[0]);
' where id_stu='+params.id_stu; return res.ok({
console.log('Updating legend for student ' + params.id_stu +" collection to "+ id: updated[0].id, // id of stu_picto
params.legend_value+": "+query); attributes: updated[0].attributes, // picto attributes for student
picto: {
StuPicto.query(query, function(err, result) { id: updated[0].picto // picto information
if (err) }
throw new Error ("error on update"); });
else { })
console.log('Updated attributes for picto student:' + params.id_stu); .catch(err => {
return res.ok({ return res.serverError('Unable to update picto for student: ' + err);
id: params.id_stu, });
legend_value: params.legend_value, // picto attributes for student },
});
} // update action
}); // update picto atributes for a studentPicto
}, //
/** update_legend: function (req, res) {
* Updates the student profile image var params = req.allParams();
* @param {request} req var query='update stu_picto'+
* file: [The image binary data] ' set attributes=json_set(attributes, \'$.legend\',\''+params.legend_value+'\')'+
* { ' where id_stu='+params.id_stu;
* "id": "student ID" console.log('Updating legend for student ' + params.id_stu +" collection to "+
* } params.legend_value+": "+query);
* @param {response} res {}
*/ StuPicto.query(query, function(err, result) {
upload: function (req, res) { if (err)
var fs = require('fs'); throw new Error ("error on update");
var path = require('path'); else {
var newAvatarFileName; console.log('Updated attributes for picto student:' + params.id_stu);
var newAvatarFileDescriptor; return res.ok({
var newAvatarDirectory = sails.config.pictogram.paths.studentAvatarDirectory; id: params.id_stu,
legend_value: params.legend_value, // picto attributes for student
Student.findOne({ id: req.body.id }).then(function (student) { });
if (!student) { }
throw new Error("Student not found"); });
} },
newAvatarFileName = sails.config.pictogram.paths.getStudentAvatarFileName(student.id);
/**
req.file('file').upload({ * Updates the student profile image
maxBytes: 1000000, * @param {request} req
dirname: newAvatarDirectory, * file: [The image binary data]
saveAs: newAvatarFileName * {
}, function whenDone(error, uploadedFiles) { * "id": "student ID"
if (error || (uploadedFiles.length === 0)) { * }
throw new Error("upload failed"); * @param {response} res {}
} */
upload: function (req, res) {
try { var fs = require('fs');
newAvatarFileDescriptor = uploadedFiles[0].fd; var path = require('path');
if (student.pic !== sails.config.pictogram.paths.defaultAvatarFileName) { var newAvatarFileName;
fs.unlinkSync(path.join(newAvatarDirectory, student.pic)); var newAvatarFileDescriptor;
} var newAvatarDirectory = sails.config.pictogram.paths.studentAvatarDirectory;
student.pic = newAvatarFileName; Student.findOne({ id: req.body.id }).then(function (student) {
student.save(function (updateStudentError) { if (!student) {
if (updateStudentError) { throw new Error("Student not found");
throw updateStudentError; }
} newAvatarFileName = sails.config.pictogram.paths.getStudentAvatarFileName(student.id);
res.ok();
}); req.file('file').upload({
} catch (updateAvatarError) { maxBytes: 1000000,
fs.unlinkSync(newAvatarFileDescriptor); dirname: newAvatarDirectory,
res.serverError("Error when updating profile image in server"); saveAs: newAvatarFileName
} }, function whenDone(error, uploadedFiles) {
}); if (error || (uploadedFiles.length === 0)) {
}) throw new Error("upload failed");
.catch(function (err) { }
res.badRequest("Could not find supervisor: " + err);
}); try {
}, newAvatarFileDescriptor = uploadedFiles[0].fd;
if (student.pic !== sails.config.pictogram.paths.defaultAvatarFileName) {
// *************************************************************** fs.unlinkSync(path.join(newAvatarDirectory, student.pic));
// WEBSOCKETS }
// ***************************************************************
// student.pic = newAvatarFileName;
// Subscribe to websockets events student.save(function (updateStudentError) {
// if (updateStudentError) {
subscribe: function (req, res) { throw updateStudentError;
var action = req.param('action'); }
var attributes = req.param('attributes'); res.ok();
attributes.ui = attributes.ui ? attributes.ui : 'PCB'; });
} catch (updateAvatarError) {
if (req.isSocket) { fs.unlinkSync(newAvatarFileDescriptor);
sails.hooks.rooms.subscribeToRoom( res.serverError("Error when updating profile image in server");
sails.hooks.rooms.student(attributes.id_stu), }
req.socket, });
attributes.ui })
); .catch(function (err) {
} res.badRequest("Could not find supervisor: " + err);
res.ok({msg: "Subscribed to student "}); });
}, },
// // ***************************************************************
// Unsubscribe to websockets events // WEBSOCKETS
// // ***************************************************************
unsubscribe: function (req, res) { //
var action = req.param('action'); // Subscribe to websockets events
//var attributes = req.param('attributes'); //
subscribe: function (req, res) {
if (req.isSocket) { var action = req.param('action');
var attributes = req.param('attributes');
var rooms = sails.sockets.socketRooms(req.socket); attributes.ui = attributes.ui ? attributes.ui : 'PCB';
console.log("Subscribed rooms in socket: " + JSON.stringify(rooms));
if (req.isSocket) {
// Leave all rooms sails.hooks.rooms.subscribeToRoom(
for (var i = 0; i < rooms.length; i++) { sails.hooks.rooms.student(attributes.id_stu),
//sails.sockets.leave(req.socket, rooms[i]); MODIFICADO POR FERNANDO. SI NO, NO SE ACTUALIZA UPDATE_PEERS req.socket,
sails.hooks.rooms.unsubscribeFromRoom(rooms[i], req.socket); attributes.ui
sails.log.debug("Unsusbscribe from room " + rooms[i]); );
} }
res.ok({msg: "Subscribed to student "});
res.json({ },
msg: "Unsubscribed from all rooms"
}); //
} // Unsubscribe to websockets events
}, //
unsubscribe: function (req, res) {
// var action = req.param('action');
// Logs a vocabulary action and broadcast to anyone subscribed to this student //var attributes = req.param('attributes');
vocabulary: function (req, res) {
var action = req.param('action'); if (req.isSocket) {
var attributes = req.param('attributes');
var rooms = sails.sockets.socketRooms(req.socket);
var roomName = 'studentRoom' + attributes.id_stu; console.log("Subscribed rooms in socket: " + JSON.stringify(rooms));
sails.log.debug("Inside vocabulary"); // Leave all rooms
for (var i = 0; i < rooms.length; i++) {
if (req.isSocket) { //sails.sockets.leave(req.socket, rooms[i]); MODIFICADO POR FERNANDO. SI NO, NO SE ACTUALIZA UPDATE_PEERS
sails.log.debug("Inside vocabulary - isSocket"); sails.hooks.rooms.unsubscribeFromRoom(rooms[i], req.socket);
sails.log.debug("Unsusbscribe from room " + rooms[i]);
// Send to all sockets subscribed to this room except the own socket that sends the message }
// Parameters: room, action, data to send, socket to avoid sending (the socket that send this)
sails.sockets.broadcast(roomName, 'vocabulary', { res.json({
"action": action, msg: "Unsubscribed from all rooms"
"attributes": attributes });
}, req.socket); }
},
res.json({
msg: "Vocabulary " + action + " action from student " + attributes.id_stu //
}); // Logs a vocabulary action and broadcast to anyone subscribed to this student
} vocabulary: function (req, res) {
}, var action = req.param('action');
var attributes = req.param('attributes');
/**
* Logs a TRY action and broadcast to anyone subscribed to this student var roomName = 'studentRoom' + attributes.id_stu;
* @param {request} req
* { sails.log.debug("Inside vocabulary");
* "action": <action> ("add", "delete", ...),
* "attributes": { if (req.isSocket) {
* "id_stu": <id_stu>, sails.log.debug("Inside vocabulary - isSocket");
* "timestamp": <timestamp_string_in_ISO_format> (e.g.: "2016-07-13 17:50:00.224+0200"),
* "picto": {...} // Send to all sockets subscribed to this room except the own socket that sends the message
* } // Parameters: room, action, data to send, socket to avoid sending (the socket that send this)
* } sails.sockets.broadcast(roomName, 'vocabulary', {
* @param {response} res {<action_created>} "action": action,
*/ "attributes": attributes
action: function (req, res) { }, req.socket);
var action = req.param('action');
var attributes = req.param('attributes'); res.json({
msg: "Vocabulary " + action + " action from student " + attributes.id_stu
sails.log.debug("Inside action. Student:" + attributes.id_stu); });
}
if (!req.isSocket) { },
sails.log.debug("No socket request for action");
res.badRequest() /**
} else { * Logs a TRY action and broadcast to anyone subscribed to this student
sails.log.debug("websockets - room " + sails.hooks.rooms.student(attributes.id_stu)); * @param {request} req
// BROADCAST to everyone subscribed to this student * {
const socketToOmit = (req.isSocket) ? req.socket : undefined; * "action": <action> ("add", "delete", ...),
sails.hooks.events.broadcastEvent( * "attributes": {
sails.hooks.rooms.student(attributes.id_stu), * "id_stu": <id_stu>,
sails.hooks.events.actionPerformed(action, attributes), * "timestamp": <timestamp_string_in_ISO_format> (e.g.: "2016-07-13 17:50:00.224+0200"),
socketToOmit * "picto": {...}
); * }
* }
var sup = null; * @param {response} res {<action_created>}
if (attributes.id_sup) sup = attributes.id_sup; */
action: function (req, res) {
var desc = null; var action = req.param('action');
if (attributes.stu_picto) desc = attributes.stu_picto; // select, add and delete actions data var attributes = req.param('attributes');
if (attributes.pictos) desc = attributes.pictos; // show action data
sails.log.debug("Inside action. Student:" + attributes.id_stu);
Action.create({
type: action, if (!req.isSocket) {
timestamp: attributes.timestamp, // it comes already in ISO format sails.log.debug("No socket request for action");
supervisor: sup, res.badRequest()
student: attributes.id_stu, } else {
description: desc sails.log.debug("websockets - room " + sails.hooks.rooms.student(attributes.id_stu));
}) // BROADCAST to everyone subscribed to this student
.then(function (created) { const socketToOmit = (req.isSocket) ? req.socket : undefined;
res.json({ sails.hooks.events.broadcastEvent(
action: created sails.hooks.rooms.student(attributes.id_stu),
}); sails.hooks.events.actionPerformed(action, attributes),
}) socketToOmit
.fail(function(err) { );
sails.log.error(err.details);
res.serverError(err.details); var sup = null;
}); if (attributes.id_sup) sup = attributes.id_sup;
}
}, var desc = null;
if (attributes.stu_picto) desc = attributes.stu_picto; // select, add and delete actions data
// if (attributes.pictos) desc = attributes.pictos; // show action data
// Logs a config action and broadcast to anyone subscribed to this student
config: function (req, res) { Action.create({
var action = req.param('action'); type: action,
var attributes = req.param('attributes'); timestamp: attributes.timestamp, // it comes already in ISO format
sails.log.debug("Inside config"); supervisor: sup,
student: attributes.id_stu,
if (req.isSocket) { description: desc
const socketToOmit = (req.isSocket) ? req.socket : undefined; })
sails.hooks.events.broadcastEvent( .then(function (created) {
sails.hooks.rooms.student(attributes.id_stu), res.json({
'action', action: created
{ });
"action": action, })
"attributes": attributes .fail(function(err) {
}, sails.log.error(err.details);
socketToOmit res.serverError(err.details);
); });
} else { }
res.ok({ },
msg: "Config " + action + " action from student " + attributes.id_stu
}); //
} // Logs a config action and broadcast to anyone subscribed to this student
}, config: function (req, res) {
var action = req.param('action');
// var attributes = req.param('attributes');
// Stores in the action table a bulk of actions loaded from of the sails.log.debug("Inside config");
// recorded action log
// if (req.isSocket) {
actions_batch: function (req, res) { const socketToOmit = (req.isSocket) ? req.socket : undefined;
var params = req.allParams(); sails.hooks.events.broadcastEvent(
var count = 0; sails.hooks.rooms.student(attributes.id_stu),
'action',
sails.log.debug("Actions_batch request"); {
"action": action,
if (!params.actions) "attributes": attributes
return res.json(400, { },
'error': "no actions" socketToOmit
}); );
} else {
// We loop through the actions and store them in the database res.ok({
async.forEach(params.actions, function (action, cb) { msg: "Config " + action + " action from student " + attributes.id_stu
sails.log.debug("Actions batch is recording action: " + JSON.stringify(action)); });
var id_sup = null; }
if (action.attributes.id_sup) },
id_sup = action.attributes.sup;
//
var id_stu = null; // Stores in the action table a bulk of actions loaded from of the
if (action.attributes.id_stu) // recorded action log
id_stu = action.attributes.id_stu; //
actions_batch: function (req, res) {
var desc = null; var params = req.allParams();
if (action.attributes.picto) var count = 0;
desc = action.attributes.picto; // select, add and delete actions data
sails.log.debug("Actions_batch request");
if (action.attributes.pictos)
desc = action.attributes.pictos; // show action data if (!params.actions)
return res.json(400, {
Action.create({ 'error': "no actions"
type: action.action, });
timestamp: action.attributes.timestamp,
supervisor: id_sup, // We loop through the actions and store them in the database
student: id_stu, async.forEach(params.actions, function (action, cb) {
description: desc sails.log.debug("Actions batch is recording action: " + JSON.stringify(action));
}) var id_sup = null;
.exec(function (err, created) { if (action.attributes.id_sup)
if (err) { id_sup = action.attributes.sup;
console.log(err.details);
sails.log.error(err.details); var id_stu = null;
return cb(err); if (action.attributes.id_stu)
} else if (created) id_stu = action.attributes.id_stu;
count++;
cb(); var desc = null;
}); if (action.attributes.picto)
}, desc = action.attributes.picto; // select, add and delete actions data
function (err) { // function called when loop is done
if (err) { if (action.attributes.pictos)
console.log(err.details); desc = action.attributes.pictos; // show action data
sails.log.error(err.details);
return res.json({ Action.create({
'error': err.details type: action.action,
}); timestamp: action.attributes.timestamp,
} else supervisor: id_sup,
return res.json({ student: id_stu,
'result': 'Ok', description: desc
'total': count })
}); .exec(function (err, created) {
}); if (err) {
}, console.log(err.details);
sails.log.error(err.details);
// return cb(err);
// Returns the last instruction for the student } else if (created)
// count++;
last_instruction: function (req, res) { cb();
if (!req.params.id_stu) });
return res.json(400, { },
err: 'id_stu parameter is missing' function (err) { // function called when loop is done
}); if (err) {
console.log(err.details);
VStuLastInstruction.find({ sails.log.error(err.details);
id_stu: req.params.id_stu return res.json({
}) 'error': err.details
.exec(function (err, found) { });
if (err) } else
return res.json(500, err); return res.json({
if (!found) 'result': 'Ok',
return res.json({}); 'total': count
return res.json(found); });
}); });
} },
};
//
// Returns the last instruction for the student
//
last_instruction: function (req, res) {
if (!req.params.id_stu)
return res.json(400, {
err: 'id_stu parameter is missing'
});
VStuLastInstruction.find({
id_stu: req.params.id_stu
})
.exec(function (err, found) {
if (err)
return res.json(500, err);
if (!found)
return res.json({});
return res.json(found);
});
}
};
...@@ -462,7 +462,7 @@ module.exports = { ...@@ -462,7 +462,7 @@ module.exports = {
} }
Supervisor.students(req.params.id, function (err, stus) { Supervisor.students(req.params.id, function (err, stus) {
if (err) throw err; if (err) throw err;
return res.json(stus); return res.ok(stus);
}); });
}, },
......
/**
* License.js
*
* @description :: TODO: Write a short summary of how this model works and what it represents here.
* @docs :: http://sailsjs.org/#!documentation/models
*/
module.exports = {
tableName : 'license',
migrate : 'safe',
schema : true,
autoPK : false,
autoCreatedAt : false,
autoUpdatedAt : false,
attributes: {
id: {
type: "integer",
autoIncrement: true,
primaryKey: true,
unique: true
},
student: { // FK de Student. 1 a N
columnName: "id_stu",
type: "integer",
model: "Student",
unique: true
},
creation_ts: {
columnName: "creation_ts",
type: "datetime"
},
activation_ts: {
columnName: "activation_ts",
type: "datetime"
},
duration: {
columnName: "duration",
type: "integer"
},
expiration_ts: {
columnName: "expiration_ts",
type: "datetime"
},
number: {
type: "string",
columnName: "number",
size: 16,
unique: true
},
toJSON: function () {
var l = this.toObject();
delete l.id;
return l;
},
hasExpired: function () {
return (new Date(this.expiration_ts) - new Date() < 0);
}
},
/**
Class methods
*/
/**
Activates a license
@param {number} number License number
@param {ID} id_stu ID of the student the license is associated to
@param {function} callback Callback function with prototype: function(err, license)
*/
activate: function(number, id_stu, callback) {
// Check license
License.findOne({ number: number })
.then((l) => {
if (!l)
throw new Error("Invalid license: " + number);
if (l.activation_ts)
throw new Error("License in use");
// License ok, check student
Student.findOne(id_stu)
.then((s) => {
if (!s)
throw new Error("Student not found");
// Check if the student has a previous license
License.findOne({ student: s.id })
.then((l_old) => {
var left = 0;
if (l_old) {
// He had a license, if not expired, get remaining time
left = l_old.expiration_ts - new Date();
left = left < 0 ? 0 : left;
License.destroy({id: l_old.id}).exec((err) => {
if (err) throw err;
});
}
// Compute parameters for license to activate
var now = new Date();
l.student = s.id;
l.activation_ts = now;
l.expiration_ts = new Date(new Date(now.getFullYear(), now.getMonth()+l.duration, now.getDate()+1).getTime() + left);
l.save((err) => {
if (err)
throw err;
delete l.student;
delete l.id;
callback(null, l);
});
});
})
.catch((err) => {
throw err;
});
})
.catch((err) => {
callback(err, null);
});
},
/**
* Determines whether a license is activable (available) or not
* Callback function gets instantiated error if not available
*/
isActivable: function(number, cb) {
License.findOne({number: number})
.then ((l) => {
if (!l)
throw new Error('Invalid license');
if (l.activation_ts)
throw new Error('License in use');
cb();
})
.catch((err) => {cb(err)});
}
}
...@@ -88,11 +88,16 @@ module.exports = { ...@@ -88,11 +88,16 @@ module.exports = {
collection: 'stupicto', collection: 'stupicto',
via: 'student' via: 'student'
}, },
// Relación con VStuLastInstruction [1 Student to 1 StuPicto] // Relación con VStuLastInstruction [1 Student to 1 VStuLastInstruction]
lastInstruction: { lastInstruction: {
collection: 'vstulastinstruction', collection: 'vstulastinstruction',
via: 'student' via: 'student'
}, },
// Relación con licencias
license: {
collection: 'license',
via: 'student'
},
/** /**
* This "extra" property allow us to adapt the server to the student needs * This "extra" property allow us to adapt the server to the student needs
...@@ -109,6 +114,8 @@ module.exports = { ...@@ -109,6 +114,8 @@ module.exports = {
toJSON: function () { toJSON: function () {
var student = this.toObject(); var student = this.toObject();
student.pic = sails.config.pictogram.urls.getStudentAvatarUrl(student.pic); student.pic = sails.config.pictogram.urls.getStudentAvatarUrl(student.pic);
if (student.license)
student.license = student.license[0] ? student.license[0] : null;
student.attributes = Student.getValidAttributes(student.attributes); student.attributes = Student.getValidAttributes(student.attributes);
delete student.password; delete student.password;
return student; return student;
...@@ -238,6 +245,7 @@ module.exports = { ...@@ -238,6 +245,7 @@ module.exports = {
beforeCreate: function (attrs, next) { beforeCreate: function (attrs, next) {
attrs.attributes = Student.getValidAttributes(attrs.attributes); attrs.attributes = Student.getValidAttributes(attrs.attributes);
attrs.password = bcrypt.hashSync(attrs.password, bcrypt.genSaltSync()); attrs.password = bcrypt.hashSync(attrs.password, bcrypt.genSaltSync());
attrs.pic = sails.config.pictogram.urls.getStudentAvatarUrl();
next(); next();
}, },
...@@ -249,7 +257,6 @@ module.exports = { ...@@ -249,7 +257,6 @@ module.exports = {
* to the function if necesary) * to the function if necesary)
*/ */
beforeUpdate: function (attrs, next) { beforeUpdate: function (attrs, next) {
delete attrs.username;
attrs.attributes = Student.getValidAttributes(attrs.attributes); attrs.attributes = Student.getValidAttributes(attrs.attributes);
if (attrs.password) { if (attrs.password) {
sails.log.debug('password changed'); sails.log.debug('password changed');
...@@ -506,13 +513,14 @@ module.exports = { ...@@ -506,13 +513,14 @@ module.exports = {
logical_delete: function(id_stu, cb) { logical_delete: function(id_stu, cb) {
Student.findOne(id_stu).exec(function(err, student) { Student.findOne(id_stu).exec(function(err, student) {
if (err || !student) if (err || !student)
throw err; return cb(new Error("Unable to remove student"));
Student.update(id_stu,
student.office = null; {
student.save(function(err, saved) { username: Math.floor((Math.random() * 100000000) + 1) + "_" + student.username,
if (err) return cb(err); id_off: null
return cb(); })
}); .then((updated) => {cb()})
.catch((err) => {cb(err)});
}); });
} }
}; };
...@@ -209,6 +209,7 @@ module.exports = { ...@@ -209,6 +209,7 @@ module.exports = {
}, },
students: function(id, callback) { students: function(id, callback) {
var l = []; var l = [];
Supervisor.findOne(id) Supervisor.findOne(id)
.populate('office') .populate('office')
...@@ -236,37 +237,45 @@ module.exports = { ...@@ -236,37 +237,45 @@ module.exports = {
if (stuSup.student.office == null) if (stuSup.student.office == null)
next_cb(); next_cb();
var student = stuSup.student; // set current method and instruction if any
student.supervision = sup.office ? 2 : 1; Student.findOne(stuSup.student.id)
.populate('lastInstruction')
// set current methdo and instruction if any .populate('license')
student.current_method = "no_method"; .then(function (s) {
student.current_instruction = "no_instruction"; s.current_method = s.lastInstruction[0] ? s.lastInstruction[0].met_name : "no_method";
VStuLastInstruction.findOne({student: student.id}) s.current_instruction = s.lastInstruction[0] ? s.lastInstruction[0].ins_name : "no_instruction";
.then(function (stu_last_inst) { if (typeof(s.license[0]) != 'undefined') {
if (stu_last_inst) { s.licenseIsValid = new Date(s.license[0].expiration_ts) - new Date() > 0 ? true : false;
student.current_method = stu_last_inst.met_name; s.license = s.license[0];
student.current_instruction = stu_last_inst.ins_name; } else {
s.licenseIsValid = false;
s.license = null;
} }
l.push(student); s.supervision = sup.office ? 2 : 1; // if Supervisor has office, then is a therapist (2), a tutor (1) otherwise
next_cb(); l.push(s);
})
.error(err => {
l.push(student);
next_cb(); next_cb();
}); });
}, },
function (err) { // loop has end function (err) { // loop has end
// Get all students from the office if user is administrator // Get all students from the office if user is administrator
if (sup.office && sup.office.admin == sup.id) { if (sup.office && sup.office.admin == sup.id) {
var officeStudents; var officeStudents;
Student.find({ office: sup.office.id }).populate('lastInstruction') Student.find({ office: sup.office.id })
.populate('lastInstruction')
.populate('license')
.then(function (officeStudents) { .then(function (officeStudents) {
officeStudents = officeStudents.map((student) => { officeStudents = officeStudents.map((student) => {
student.supervision = student.supervision || 0; student.supervision = 0;
student.current_method = student.lastInstruction[0] ? student.lastInstruction[0].met_name : "no_method"; student.current_method = student.lastInstruction[0] ? student.lastInstruction[0].met_name : "no_method";
student.current_instruction = student.lastInstruction[0] ? student.lastInstruction[0].ins_name : "no_instruction"; student.current_instruction = student.lastInstruction[0] ? student.lastInstruction[0].ins_name : "no_instruction";
if (typeof(student.license[0]) != 'undefined') {
student.licenseIsValid = new Date(student.license[0].expiration_ts) - new Date() > 0 ? true : false;
student.license = student.license[0];
} else {
student.licenseIsValid = false;
student.license = null;
}
return student; return student;
}); });
l = l.concat(officeStudents); l = l.concat(officeStudents);
...@@ -275,9 +284,9 @@ module.exports = { ...@@ -275,9 +284,9 @@ module.exports = {
.catch(function (err) { .catch(function (err) {
callback(err, l); callback(err, l);
}); });
} else { } else {
callback(err, l); callback(err, l);
} }
}); // end async.eachSeries }); // end async.eachSeries
}) })
.catch((err) => { .catch((err) => {
......
/* global sails, Student */
module.exports = function isSupervisorOfStudentOrIsSupAdmin(req, res, next) {
const supervisorId = req.token.id;
const studentId = req.params.id_stu;
if (!studentId || !supervisorId) {
sails.log.error('This request needs an id_stu parameter and a authenticated supervisor');
res.json(401, { error: 'Access denied' });
} else {
Student.findOne(studentId)
.then(function (s) {
if (s.office == req.token.office.id && req.token.isSupAdmin) {
next();
}
else {
Student.supervisors(studentId, function (err, sups) {
const studentSupervisorsIds = sups.map((studentSupervisor) => studentSupervisor.id);
if (err || studentSupervisorsIds.length === 0) {
sails.log.error(`Student ${studentId} has no supervisor assigned`);
res.json(401, { error: 'Access denied' });
} else if (studentSupervisorsIds.indexOf(supervisorId) < 0) {
sails.log.error(`Supervisor ${supervisorId} is not assigned to Student ${studentId}`);
sails.log.debug(`Student supervisors: ${studentSupervisorsIds}`);
res.json(401, { error: 'Access denied' });
} else {
sails.log.debug(`Supervisor ${supervisorId} is assigned to Student ${studentId}`);
next();
}
});
}
});
}
};
...@@ -24,7 +24,7 @@ module.exports = function serverError (data, options) { ...@@ -24,7 +24,7 @@ module.exports = function serverError (data, options) {
// Log error to console // Log error to console
if (data !== undefined) { if (data !== undefined) {
sails.log.error('Sending 500 ("Server Error") response: \n',data); sails.log.error('Sending 500 ("Server Error") response: \n', JSON.stringify(data));
} }
else sails.log.error('Sending empty 500 ("Server Error") response'); else sails.log.error('Sending empty 500 ("Server Error") response');
...@@ -74,4 +74,3 @@ module.exports = function serverError (data, options) { ...@@ -74,4 +74,3 @@ module.exports = function serverError (data, options) {
}); });
}; };
...@@ -27,7 +27,9 @@ ...@@ -27,7 +27,9 @@
"angular-animate": "~1.4.1", "angular-animate": "~1.4.1",
"angular-sanitize": "~1.4.1", "angular-sanitize": "~1.4.1",
"angular-chart.js": "latest", "angular-chart.js": "latest",
"ng-lodash": "~0.3.0" "ng-lodash": "~0.3.0",
"bootstrap-filestyle": "~1.2.1",
"ngMask": "angular-mask#~3.1.1"
}, },
"resolutions": { "resolutions": {
"angular": ">=1 <1.3.0", "angular": ">=1 <1.3.0",
......
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
"country": "Country", "country": "Country",
"create_account": "Create account", "create_account": "Create account",
"create_an_account": "Create an account", "create_an_account": "Create an account",
"credentials": "Credentials",
"crop_image": "Crop image", "crop_image": "Crop image",
"data_no_saved": "Data can't be saved", "data_no_saved": "Data can't be saved",
"data_saved": "Data saved", "data_saved": "Data saved",
...@@ -72,6 +73,7 @@ ...@@ -72,6 +73,7 @@
"disabled": "Disabled. Clic for invisible", "disabled": "Disabled. Clic for invisible",
"double_click": "Double click", "double_click": "Double click",
"down": "Down", "down": "Down",
"duration_in_months": "Duration (in number of months)",
"drag": "Drag", "drag": "Drag",
"edit": "Edit", "edit": "Edit",
"email": "Email address", "email": "Email address",
...@@ -92,11 +94,12 @@ ...@@ -92,11 +94,12 @@
"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_on_request": "The request has not been processed. Please, check your fields", "error_on_request": "The request has not been processed. Please, check your fields",
"error_loading_pictos": "Error loading pictos information", "error_loading_pictos": "Error loading pictos information",
"error_general": "An error has been produced",
"expand_navigation": "Expand navigation", "expand_navigation": "Expand navigation",
"expand_navigation": "Expand navigation", "expand_navigation": "Expand navigation",
"expression": "Expression:", "expression": "Expression:",
"February": "February", "February": "February",
"feedback_picto": "Feedback when a pictogram is placed", "feedback_picto": "Selection feedback",
"filter": "Filter", "filter": "Filter",
"finish_session": "Finish session", "finish_session": "Finish session",
"finished": "Finished", "finished": "Finished",
...@@ -124,15 +127,23 @@ ...@@ -124,15 +127,23 @@
"June": "June", "June": "June",
"language": "Language", "language": "Language",
"large": "Large", "large": "Large",
"large_picto": "Large pictograms",
"last_session": "Last session", "last_session": "Last session",
"legend": "Legend", "legend": "Legend",
"legend_apply_all":"Apply to all pictograms",
"legend_none":"No legend", "legend_none":"No legend",
"legend_normal":"Normal legend", "legend_normal":"Normal legend",
"legend_full":"Only legend", "legend_full":"Only legend",
"legend_size": "Legend size", "legend_size": "Legend size",
"legend_apply_all":"Apply to all pictograms", "licenses": "Licenses",
"licenses": "Licenses (max. number of students)", "license_already_activated": "License already activated",
"license_created": "License created",
"license_expires": "License expires on ",
"license_expired": "License expired on ",
"license_invalid": "Invalid license number",
"licenses_left": "{{number}} licenses left", "licenses_left": "{{number}} licenses left",
"license_missing": "Account without license",
"license_number": "License number",
"light_up": "Light up", "light_up": "Light up",
"link": "Link", "link": "Link",
"loading_pictos": "Loading pictos", "loading_pictos": "Loading pictos",
...@@ -219,6 +230,7 @@ ...@@ -219,6 +230,7 @@
"press": "Long press", "press": "Long press",
"previous_actions": "Previous actions", "previous_actions": "Previous actions",
"previous_sessions": "Previous sessions", "previous_sessions": "Previous sessions",
"profile_picture": "Profile picture",
"read_picto": "Read picto", "read_picto": "Read picto",
"register": "Sign in", "register": "Sign in",
"remember": "Remember me", "remember": "Remember me",
...@@ -250,6 +262,7 @@ ...@@ -250,6 +262,7 @@
"show": "Show", "show": "Show",
"size": "Size", "size": "Size",
"small": "Small", "small": "Small",
"small_picto": "Small pictograms",
"sound_setup": "Sound setup", "sound_setup": "Sound setup",
"spanish": "Spanish", "spanish": "Spanish",
"started": "Started", "started": "Started",
...@@ -281,11 +294,12 @@ ...@@ -281,11 +294,12 @@
"supervisor_not_updated": "Supervisor not updated", "supervisor_not_updated": "Supervisor not updated",
"supervisor_note": "If the parent aren't going to register in the platform, the administrator can use the notes field to store their information.", "supervisor_note": "If the parent aren't going to register in the platform, the administrator can use the notes field to store their information.",
"supervisor_updated": "Supervisor updated", "supervisor_updated": "Supervisor updated",
"supervisors": "Therapist", "supervisors": "Supervisors",
"surname": "Surname", "surname": "Surname",
"tag_deleted": "Tag deleted", "tag_deleted": "Tag deleted",
"tape_background": "Tape background", "tape_background": "Tape background",
"template_deleted": "Template deleted", "template_deleted": "Template deleted",
"therapists": "Therapists",
"time_hours": "Time: {{hours}} hours", "time_hours": "Time: {{hours}} hours",
"time_instruction_method": "Time instructions of method", "time_instruction_method": "Time instructions of method",
"time_sessions_total": "Total sessions time", "time_sessions_total": "Total sessions time",
......
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
"country": "País", "country": "País",
"create_account": "Crear cuenta", "create_account": "Crear cuenta",
"create_an_account": "Crear una cuenta", "create_an_account": "Crear una cuenta",
"credentials": "Credenciales",
"crop_image": "Recortar imagen", "crop_image": "Recortar imagen",
"data_no_saved": "Los datos no se han podido guardar", "data_no_saved": "Los datos no se han podido guardar",
"data_saved": "Datos guardados", "data_saved": "Datos guardados",
...@@ -72,6 +73,7 @@ ...@@ -72,6 +73,7 @@
"disabled": "Desactivado. Clic para invisible", "disabled": "Desactivado. Clic para invisible",
"double_click": "Doble clic", "double_click": "Doble clic",
"down": "Abajo", "down": "Abajo",
"duration_in_months": "Duración (en número de meses)",
"drag": "Arrastrar", "drag": "Arrastrar",
"edit": "Editar", "edit": "Editar",
"email": "Correo electrónico", "email": "Correo electrónico",
...@@ -95,8 +97,9 @@ ...@@ -95,8 +97,9 @@
"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_on_request": "Se ha producido un error. Por favor, compruebe los valores introducidos.", "error_on_request": "Se ha producido un error. Por favor, compruebe los valores introducidos.",
"error_loading_pictos": "Error cargando información de los pictos", "error_loading_pictos": "Error cargando información de los pictos",
"error_general": "Se ha producido un error",
"February": "Febrero", "February": "Febrero",
"feedback_picto": "Feedback al colocar un pictograma", "feedback_picto": "Efecto de selección",
"filter": "Filtrar", "filter": "Filtrar",
"finish_session": "Terminar sesión", "finish_session": "Terminar sesión",
"finished": "Completada", "finished": "Completada",
...@@ -124,6 +127,7 @@ ...@@ -124,6 +127,7 @@
"June": "Junio", "June": "Junio",
"language": "Idioma", "language": "Idioma",
"large": "Grande", "large": "Grande",
"large_picto": "Pictograms grandes",
"last_session": "Última sesión", "last_session": "Última sesión",
"legend": "Leyenda", "legend": "Leyenda",
"legend_none":"Sin leyenda", "legend_none":"Sin leyenda",
...@@ -131,8 +135,15 @@ ...@@ -131,8 +135,15 @@
"legend_full":"Sólo leyenda", "legend_full":"Sólo leyenda",
"legend_apply_all":"Aplicar a todos los pictogramas", "legend_apply_all":"Aplicar a todos los pictogramas",
"legend_size": "Tamaño de la leyenda", "legend_size": "Tamaño de la leyenda",
"licenses": "Licencias (número máximo de estudiantes)", "licenses": "Licencias",
"licenses_left": "{{number}} licencias disponibles", "licenses_left": "{{number}} licencias disponibles",
"license_already_activated": "Licencia ya activada previamente",
"license_expires": "La licencia expira el ",
"license_expired": "La licencia expiró el ",
"license_created": "Licencia creada",
"license_invalid": "Licencia inválida",
"license_number": "Número de licencia",
"license_missing": "Cuenta sin licencia",
"light_up": "Iluminar", "light_up": "Iluminar",
"link": "Vincular", "link": "Vincular",
"loading_pictos": "Cargando pictos", "loading_pictos": "Cargando pictos",
...@@ -219,6 +230,7 @@ ...@@ -219,6 +230,7 @@
"press": "Pulsación larga", "press": "Pulsación larga",
"previous_actions": "Acciones anteriores", "previous_actions": "Acciones anteriores",
"previous_sessions": "Sesiones anteriores", "previous_sessions": "Sesiones anteriores",
"profile_picture": "Foto del perfil",
"read_picto": "Leer picto", "read_picto": "Leer picto",
"register": "Regístrate", "register": "Regístrate",
"register_button": "Registrar", "register_button": "Registrar",
...@@ -251,6 +263,7 @@ ...@@ -251,6 +263,7 @@
"show": "Mostrar", "show": "Mostrar",
"size": "Tamaño", "size": "Tamaño",
"small": "Pequeño", "small": "Pequeño",
"small_picto": "Pictogramas pequeños",
"sound_setup": "Ajustes de sonido", "sound_setup": "Ajustes de sonido",
"spanish": "Español", "spanish": "Español",
"started": "Iniciada", "started": "Iniciada",
...@@ -282,7 +295,8 @@ ...@@ -282,7 +295,8 @@
"supervisor_not_updated": "El supervisor no se ha podido actualizar", "supervisor_not_updated": "El supervisor no se ha podido actualizar",
"supervisor_note": "Si los padres no se van a dar de alta en la plataforma nunca, el administrador puede anotar la información de contacto en el campo notas.", "supervisor_note": "Si los padres no se van a dar de alta en la plataforma nunca, el administrador puede anotar la información de contacto en el campo notas.",
"supervisor_updated": "Supervisor actualizado", "supervisor_updated": "Supervisor actualizado",
"supervisors": "Terapeutas", "therapists": "Terapeutas",
"supervisors": "Supervisores",
"surname": "Apellidos", "surname": "Apellidos",
"tag_deleted": "Etiqueta borrada", "tag_deleted": "Etiqueta borrada",
"tape_background": "Fondo de la cinta", "tape_background": "Fondo de la cinta",
......
...@@ -17,7 +17,8 @@ var dashboardApp = angular.module('dashboardApp', [ ...@@ -17,7 +17,8 @@ var dashboardApp = angular.module('dashboardApp', [
'ngSanitize', 'ngSanitize',
'ngToast', 'ngToast',
'chart.js', 'chart.js',
'ngLodash' 'ngLodash',
'ngMask'
]); ]);
/* Main constants */ /* Main constants */
...@@ -149,14 +150,20 @@ dashboardApp.config(function ($stateProvider, $urlRouterProvider) { ...@@ -149,14 +150,20 @@ dashboardApp.config(function ($stateProvider, $urlRouterProvider) {
controller: 'AdminCtrl', controller: 'AdminCtrl',
abstract: true, abstract: true,
}) })
.state('licenses', {
url: '/admin/licenses',
parent: 'admin',
templateUrl: 'modules/admin/views/licenses.html',
controller: 'AdminLicensesCtrl',
})
.state('offices', { .state('offices', {
url: '/offices', url: '/admin/offices',
parent: 'admin', parent: 'admin',
templateUrl: 'modules/admin/views/offices.html', templateUrl: 'modules/admin/views/offices.html',
controller: 'AdminOfficesCtrl', controller: 'AdminOfficesCtrl',
}) })
.state('supervisors', { .state('supervisors', {
url: '/supervisors', url: '/admin/supervisors',
parent: 'admin', parent: 'admin',
templateUrl: 'modules/admin/views/supervisors.html', templateUrl: 'modules/admin/views/supervisors.html',
controller: 'AdminSupervisorsCtrl', controller: 'AdminSupervisorsCtrl',
......
'use strict';
//--------------------------
// Admin License Controller
//--------------------------
dashboardControllers.controller('AdminLicensesCtrl', function AdminLicensesCtrl($scope, $window, $http, config, $translate, ngToast) {
// The parameter 'config' is injected from config.js (defined in dashboardConfig module)
// Don't show the message at the begining
$scope.showmessagesupervisor = false;
$scope.formdatalicense = {
duration: ''
};
// This generates a new license and registers it in the database
$scope.create_license = function(supervisor){
$http
.post(config.backend+'/license', $scope.formdatalicense)
.success(function(data, status, headers, config) {
$scope.formdatalicense.duration = '';
$scope.new_number = data.number.substr(0,4) +
"-" + data.number.substr(4,4) +
"-" + data.number.substr(8,4) +
"-" + data.number.substr(12,4);
console.log($scope.new_number);
$scope.duration_registered = data.duration;
})
.error(function(data, status, headers, config) {
ngToast.danger({content: $translate.instant('error_general')});
console.log("Error from API: " + data.error);
});
};
});
...@@ -4,15 +4,15 @@ ...@@ -4,15 +4,15 @@
<header-admin></header-admin> <header-admin></header-admin>
<!-- Tab menu --> <!-- Tab menu -->
<ul class="nav nav-pills nav-justified" ng-init="selectedTab = 'supervisors'"> <ul class="nav nav-pills nav-justified" ng-init="selectedTab = 'licenses'">
<li role="presentation" ng-click="selectedTab = 'supervisors'" ng-class="{'active':selectedTab === 'supervisors'}"> <li role="presentation" ng-click="selectedTab = 'licenses'" ng-class="{'active':selectedTab === 'licenses'}">
<a href="#supervisors">{{ 'supervisors' | translate }}</a> <a href="/app/#/admin/licenses">{{ 'licenses' | translate }}</a>
</li> </li>
<li role="presentation" ng-click="selectedTab = 'offices'" ng-class="{'active':selectedTab === 'offices'}"> <li role="presentation" ng-click="selectedTab = 'offices'" ng-class="{'active':selectedTab === 'offices'}">
<a href="#offices">{{ 'offices' | translate }}</a> <a href="/app/#/admin/offices">{{ 'offices' | translate }}</a>
</li> </li>
<li role="presentation" ng-click="selectedTab = 'supervisors'" ng-class="{'active':selectedTab === 'supervisors'}"> <li role="presentation" ng-click="selectedTab = 'supervisors'" ng-class="{'active':selectedTab === 'supervisors'}">
<a href="#supervisors">{{ 'supervisors' | translate }}</a> <a href="/app/#/admin/supervisors">{{ 'supervisors' | translate }}</a>
</li> </li>
</ul> </ul>
......
<!-- Admin Licenses -->
<div class="row">
<div class="col-md-2">
</div>
<div class="col-md-4">
<h3 translate>licenses</h3>
<form role="form" ng-submit="create_license()">
<div class="form-group">
<input type="number" class="form-control" id="setup_duration" placeholder="{{ 'duration_in_months' | translate }}" required ng-model="formdatalicense.duration" required/>
</div>
<div class="form-group text-center">
<button type="submit" class="btn btn-primary" translate>generate</button>
</div>
</form>
</div>
<div class="col-md-4">
<div ng-show="new_number.length > 16" class="alert alert-info">
<p>{{ 'license_created' | translate }}: <strong>{{ new_number }}</strong></p>
<p>{{ 'duration_in_months' | translate }}: {{ duration_registered }}</p>
</div>
</div>
<div class="col-md-2">
</div>
</div>
...@@ -17,8 +17,9 @@ dashboardControllers.controller('LoginAdminCtrl', function LoginAdminCtrl($scope ...@@ -17,8 +17,9 @@ dashboardControllers.controller('LoginAdminCtrl', function LoginAdminCtrl($scope
.success(function(data, status, headers, config) { .success(function(data, status, headers, config) {
// Save token and user data y sessionStorage // Save token and user data y sessionStorage
$window.sessionStorage.token = data.token; $window.sessionStorage.token = data.token;
// Redirect to admin panel // Redirect to admin panel
$location.path('/devices'); $location.path('/admin/licenses');
$translate('login_success').then(function(translation) { $translate('login_success').then(function(translation) {
ngToast.success({ content: translation }); ngToast.success({ content: translation });
......
...@@ -17,6 +17,14 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -17,6 +17,14 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
// For tab navigation (here too, if the user refresh the page...) // For tab navigation (here too, if the user refresh the page...)
$scope.nav.tab = 'setup'; $scope.nav.tab = 'setup';
// Set upload button to filestyle
$(":file").filestyle({
buttonText: "&nbsp;" + $translate.instant('change_picture'),
input: false
});
$scope.supsForm = {};
/** /**
* Updates the student picture * Updates the student picture
* @param {Angular file array} $files Image to be uploaded * @param {Angular file array} $files Image to be uploaded
...@@ -82,14 +90,11 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -82,14 +90,11 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
if ($scope.formUser.password_confirm === $scope.formUser.password) { if ($scope.formUser.password_confirm === $scope.formUser.password) {
password = $scope.formUser.password; password = $scope.formUser.password;
} else { } else {
$translate('password_match').then(function (translation) { ngToast.danger({ content: $translate.instant('password_match') });
ngToast.danger({ content: translation });
});
return; return;
} }
} }
attrs = { attrs = {
birthdate: $scope.formUser.birthdate, birthdate: $scope.formUser.birthdate,
country: $scope.formUser.country, country: $scope.formUser.country,
...@@ -99,16 +104,17 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -99,16 +104,17 @@ 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,
license_number: $scope.formUser.license_number
}; };
if (password) if (password)
attrs.password = password; attrs.password = password;
// Update student data
$http.put(config.backend + '/stu/' + $scope.studentData.id, attrs) $http.put(config.backend + '/stu/' + $scope.studentData.id, attrs)
.success(function (data) { .success(function (data) {
$translate('student_updated').then(function (translation) { ngToast.success({ content: $translate.instant('student_updated') });
ngToast.success({ content: translation });
});
$scope.formUser.birthdate = data.birthdate; $scope.formUser.birthdate = data.birthdate;
$scope.formUser.country = data.country; $scope.formUser.country = data.country;
$scope.formUser.gender = data.gender; $scope.formUser.gender = data.gender;
...@@ -117,17 +123,25 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -117,17 +123,25 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
$scope.formUser.notes = data.notes; $scope.formUser.notes = data.notes;
$scope.formUser.surname = data.surname; $scope.formUser.surname = data.surname;
$scope.formUser.username = data.username; $scope.formUser.username = data.username;
$scope.formUser.license_number = data.license ? data.license.number : '';
$scope.studentData.license = data.license ? data.license : null;
$scope.updateLicenseExpiration();
// websocket emit vocabulary delete action // websocket emit update action
delete data.license;
io.socket.post('/stu/config', { io.socket.post('/stu/config', {
action: 'update', action: 'update',
attributes: data attributes: data
}, function () {}); }, function () {});
}) })
.error(function () { .error(function (err) {
$translate.danger('student_not_updated', function (translation) { console.log(err);
ngToast.danger({ content: translation }); if (err.message.search('nvalid license'))
}); ngToast.danger({ content: $translate.instant('license_invalid') });
else if (err.message.search('in use'))
ngToast.danger({ content: $translate.instant('license_already_activated') });
else
ngToast.danger({ content: $translate.instant('student_not_updated') });
}); });
}; };
...@@ -136,8 +150,9 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -136,8 +150,9 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
* The email used for search is fetched from $scope.email_sup. * The email used for search is fetched from $scope.email_sup.
*/ */
$scope.search_sup = function () { $scope.search_sup = function () {
console.log("--> " + $scope.supsForm.email_sup);
// Find tutor by email // Find tutor by email
$http.get(config.backend + '/sup/email/' + $scope.email_sup) $http.get(config.backend + '/sup/email/' + $scope.supsForm.email_sup)
.success(function (data) { .success(function (data) {
if (data) { if (data) {
$scope.supToAdd = data; $scope.supToAdd = data;
...@@ -173,7 +188,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -173,7 +188,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
// Add to the list of tutors in view // Add to the list of tutors in view
$scope.studentSupervisors.push($scope.supToAdd); $scope.studentSupervisors.push($scope.supToAdd);
// Delete the email form field // Delete the email form field
$scope.email_sup = ''; $scope.supsForm.email_sup = '';
// Hide the message of supervisor founded // Hide the message of supervisor founded
$scope.showmessagesupfound = false; $scope.showmessagesupfound = false;
}) })
...@@ -214,7 +229,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -214,7 +229,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
// Search tutor by email // Search tutor by email
$scope.search_tutor = function () { $scope.search_tutor = function () {
// Find tutor by email // Find tutor by email
$http.get(config.backend + '/sup/email/' + $scope.email_tutor) $http.get(config.backend + '/sup/email/' + $scope.supsForm.email_tutor)
.success(function (data) { .success(function (data) {
// If it found the length is > 0 // If it found the length is > 0
if (data) { if (data) {
...@@ -252,7 +267,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -252,7 +267,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
// Add to the list of tutors in view // Add to the list of tutors in view
$scope.studentTutors.push($scope.tutorToAdd); $scope.studentTutors.push($scope.tutorToAdd);
// Delete the email form field // Delete the email form field
$scope.email_tutor = ''; $scope.supsForm.email_tutor = '';
// Hide the message of tutor founded // Hide the message of tutor founded
$scope.showmessagetutorfound = false; $scope.showmessagetutorfound = false;
......
...@@ -28,6 +28,9 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl( ...@@ -28,6 +28,9 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
lang: '', lang: '',
notes: '', notes: '',
attributes: {}, attributes: {},
license: {
number: ''
},
office: { office: {
id: '', id: '',
name: '' name: ''
...@@ -74,6 +77,15 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl( ...@@ -74,6 +77,15 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
}); });
}); });
$scope.updateLicenseExpiration = function () {
if (!$scope.studentData.license)
return;
$scope.studentData.license_expired = new Date($scope.studentData.license.expiration_ts) - new Date() < 0;
moment.locale($translate.use().substr(0, 2));
$scope.studentData.expiration_date = moment($scope.studentData.license.expiration_ts).format('L');
};
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
// //
// Load student account information // Load student account information
...@@ -99,6 +111,9 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl( ...@@ -99,6 +111,9 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
$scope.studentData.current_method = data.current_method; $scope.studentData.current_method = data.current_method;
$scope.studentData.current_instruction = data.current_instruction; $scope.studentData.current_instruction = data.current_instruction;
$scope.studentData.supervision = data.supervision; // supervision level on student: 0->admin, 1->tutor, 2->therapist $scope.studentData.supervision = data.supervision; // supervision level on student: 0->admin, 1->tutor, 2->therapist
$scope.studentData.license = data.license;
$scope.updateLicenseExpiration();
// Setup section: Fill formUser (data able to be modified) from studentData parent object // Setup section: Fill formUser (data able to be modified) from studentData parent object
// It must go here to assign the values when studentData is recovered // It must go here to assign the values when studentData is recovered
...@@ -111,6 +126,9 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl( ...@@ -111,6 +126,9 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
$scope.formUser.gender = $scope.studentData.gender; $scope.formUser.gender = $scope.studentData.gender;
$scope.formUser.lang = $scope.studentData.lang; $scope.formUser.lang = $scope.studentData.lang;
$scope.formUser.notes = $scope.studentData.notes; $scope.formUser.notes = $scope.studentData.notes;
if ($scope.studentData.license) {
$scope.formUser.license_number = $scope.studentData.license.number;
}
// Subscribe to student's socket room // Subscribe to student's socket room
io.socket.post('/stu/subscribe', { io.socket.post('/stu/subscribe', {
......
...@@ -5,60 +5,106 @@ ...@@ -5,60 +5,106 @@
<div class="col-md-6"> <div class="col-md-6">
<div id="student_personal_edit"> <div id="student_personal_edit">
<form role="form" ng-submit="updateStudent()"> <form role="form" ng-submit="updateStudent()">
<!-- Cambiar imagen de perfil -->
<div class="form-group">
<h4><span class="glyphicon glyphicon-picture" aria-hidden="true"></span> {{ 'change_picture' | translate }}: </h4>
<!-- Input oculto para cargar la redimensión de la imagen (awesome-cropper) -->
<input type="file" ng-file-select="onFileSelect($files)" ng-model="picFile" accept="image/*">
</div>
<!-- Preview --> <h3>{{ 'account' | translate }}</h3>
<img class="thumbnail preview" ng-src="{{studentData.pic}}" />
<!-- Fin Cambiar imagen de perfil --> <div class="row">
<div class="col-md-6">
<legend translate>profile_picture</legend>
<!-- Preview -->
<img class="thumbnail preview" ng-src="{{studentData.pic}}" />
<!-- Fin Cambiar imagen de perfil -->
<div class="form-group">
<input type="file" class="filestyle">
</div>
<fieldset>
<legend translate>account</legend>
<div class="form-group">
<input type="username" class="form-control" id="setup_username" placeholder="{{ 'username' | translate }}" required ng-model="formUser.username" readonly/>
</div>
<div class="form-group">
<input style="display:none" type="password" name="fakepasswordremembered"/>
<input type="password" class="form-control" id="setup_password1" placeholder="{{ 'password_new_type' | translate }}" ng-model="formUser.password"/>
</div> </div>
<div class="form-group"> <div class="col-md-6">
<input type="password" class="form-control" id="setup_password2" placeholder="{{ 'password_confirm' | translate }}" ng-model="formUser.password_confirm"/>
<fieldset>
<legend translate>credentials</legend>
<div class="form-group">
<input type="username" class="form-control" id="setup_username" placeholder="{{ 'username' | translate }}" required ng-model="formUser.username" readonly/>
</div>
<div class="form-group">
<input style="display:none" type="password" name="fakepasswordremembered"/>
<input type="password" class="form-control" id="setup_password1" placeholder="{{ 'password_new_type' | translate }}" ng-model="formUser.password"/>
</div>
<div class="form-group">
<input type="password" class="form-control" id="setup_password2" placeholder="{{ 'password_confirm' | translate }}" ng-model="formUser.password_confirm"/>
</div>
<div class="form-group">
<input type="text" id="setup_license" mask="9999-9999-9999-9999" clean="true" placeholder="{{ 'license_number' | translate }}" ng-model="formUser.license_number" required>
<br/>
<span ng-show="studentData.license && !studentData.license_expired" class="text-info">
({{ 'license_expires' | translate }} {{ studentData.expiration_date }})
</span>
<span ng-show="studentData.license && studentData.license_expired" class="text-danger">
({{ 'license_expired' | translate }} {{ studentData.expiration_date }})
</span>
<span ng-show="!studentData.license" class="text-danger">
({{ 'license_missing' | translate }})
</span>
</div>
</fieldset>
</div> </div>
</div>
<fieldset>
<legend translate>personal_data</legend> <legend translate>personal_data</legend>
<div class="form-group">
<input type="text" class="form-control" id="student_personal_edit_name" placeholder="{{ 'name' | translate }}" ng-model="formUser.name" required /> <div class="row">
</div> <div class="col-md-6">
<div class="form-group">
<input type="text" class="form-control" id="student_personal_edit_surname" placeholder="{{ 'surname' | translate }}" ng-model="formUser.surname" required />
</div> <div class="form-group">
<div class="form-group"> <input type="text" class="form-control" id="student_personal_edit_name" placeholder="{{ 'name' | translate }}" ng-model="formUser.name" required />
<p class="input-group"> </div>
<input type="text" class="form-control" datepicker-popup=" {{ 'day_format' | translate }}" ng-model="formUser.birthdate" placeholder="{{ 'birthdate' | translate }}" is-open="opened" close-text="{{'close' | translate}}" required /> <div class="form-group">
<span class="input-group-btn"> <input type="text" class="form-control" id="student_personal_edit_surname" placeholder="{{ 'surname' | translate }}" ng-model="formUser.surname" required />
<button type="button" class="btn btn-default" ng-click="openCalendar($event)"><i class="glyphicon glyphicon-calendar"></i></button> </div>
</span> <div class="form-group">
</p> <p class="input-group">
</div> <input type="text" class="form-control" datepicker-popup=" {{ 'day_format' | translate }}" ng-model="formUser.birthdate" placeholder="{{ 'birthdate' | translate }}" is-open="opened" close-text="{{'close' | translate}}" required />
<div class="form-group"> <span class="input-group-btn">
<select class="form-control" name="student_gender" id="student_gender" ng-model="formUser.gender" required> <button type="button" class="btn btn-default" ng-click="openCalendar($event)"><i class="glyphicon glyphicon-calendar"></i></button>
<option value=" ">&nsp;</option> </span>
<option value="F" translate>woman</option> </p>
<option value="M" translate>man</option> </div>
</select>
</div> </div>
<div class="form-group"> <div class="col-md-6">
<select class="form-control" name="student_country" id="student_country" ng-model="formUser.country" required>
<option value="ES">España</option> <fieldset>
<option value="US">United States</option> <div class="form-group">
<option value="UK">United Kingdom</option> <select class="form-control" name="student_gender" id="student_gender" ng-model="formUser.gender" required>
<option value="IE">Ireland</option> <option value=" ">&nsp;</option>
</select> <option value="F" translate>woman</option>
<option value="M" translate>man</option>
</select>
</div>
<div class="form-group">
<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 class="form-group">
<span translate>language</span>:
<select class="form-control" name="supervisor_language" id="supervisor_language" ng-model="formUser.lang">
<option value="es-es">Español</option>
<option value="en-gb">English</option>
</select>
</div>
</fieldset>
</div>
</div> </div>
</fieldset>
<fieldset ng-if="studentData.supervision != 1"> <fieldset ng-if="studentData.supervision != 1">
<legend translate>notes</legend> <legend translate>notes</legend>
...@@ -67,16 +113,6 @@ ...@@ -67,16 +113,6 @@
</div> </div>
</fieldset> </fieldset>
<fieldset>
<legend translate>language</legend>
<div class="form-group">
<select class="form-control" name="supervisor_language" id="supervisor_language" ng-model="formUser.lang">
<option value="es-es">Español</option>
<option value="en-gb">English</option>
</select>
</div>
</fieldset>
<div class="form-group text-center"> <div class="form-group text-center">
<button type="submit" class="btn btn-primary" translate>save</button> <button type="submit" class="btn btn-primary" translate>save</button>
</div> </div>
...@@ -85,16 +121,246 @@ ...@@ -85,16 +121,246 @@
<!-- Fin de student_personal_edit --> <!-- Fin de student_personal_edit -->
</div> </div>
<!-- Parte derecha: Tutores y dispositivos -->
<!-- Configuración dispositivo -->
<div class="col-md-6"> <div class="col-md-6">
<div id="device_setup">
<h3>{{ 'device_setup' | translate }}</h3>
<div class="row">
<div class="col-md-6">
<form role="form" class="form">
<fieldset>
<legend>{{ 'categories' | translate }}</legend>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupUseCategories"
ng-model="studentData.attributes.categories"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupUseCategories">{{ 'use_categories' | translate }}</label>
</div>
<legend>{{ 'picto_size' | translate }}</legend>
<div class="form-group">
<div class="input-group">
<span class="input-group-addon">
<input type="radio"
value="small"
id="studentSetupPictoSizeSmall"
ng-model="studentData.attributes.size"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupPictoSizeSmall">
{{ 'small_picto' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="radio"
value="large"
id="studentSetupPictoSizeLarge"
ng-model="studentData.attributes.size"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupPictoSizeLarge">
{{ 'large_picto' | translate }}
</label>
</div>
</div>
</fieldset>
</div>
<div class="col-md-6">
<fieldset>
<legend>{{ 'feedback_picto' | translate }}</legend>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupVibrateOnTouch"
ng-model="studentData.attributes.input_feedback.vibration"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupVibrateOnTouch">{{ 'vibration' | translate }}</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupBeepOnTouch"
ng-model="studentData.attributes.input_feedback.beep"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupBeepOnTouch">{{ 'beep' | translate }}</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupReadOnTouch"
ng-model="studentData.attributes.input_feedback.read"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupReadOnTouch">{{ 'read_picto' | translate }}</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupHighlightOnTouch"
ng-model="studentData.attributes.input_feedback.highlight"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupHighlightOnTouch">
{{ 'highlight' | translate }}
</label>
</div>
</fieldset>
<!-- DISABLED, NOT IMPLEMENTED YET
<fieldset>
<legend>{{ 'input_selection' | translate }}</legend>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupSelectOnClick"
ng-model="studentData.attributes.input_selection.click"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupSelectOnClick">
{{ 'click' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupSelectOnDoubleClick"
ng-model="studentData.attributes.input_selection.double_click"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupSelectOnDoubleClick">
{{ 'double_click' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupSelectOnLongPress"
ng-model="studentData.attributes.input_selection.long_press"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupSelectOnLongPress">
{{ 'press' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupSelectOnDrag"
ng-model="studentData.attributes.input_selection.drag"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupSelectOnDrag">
{{ 'drag' | translate }}
</label>
</div>
</fieldset>
<fieldset>
<div class="input-group">
<span class="input-group-addon">
<input type="color"
id="studentSetupPictoBackground"
ng-model="studentData.attributes.picto_background"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupPictoBackground">
{{ 'background' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="color"
id="studentSetupTapeBackground"
ng-model="studentData.attributes.tape_background"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupTapeBackground">
{{ 'tape_background' | translate }}
</label>
</div>
</fieldset>
<fieldset>
<legend>{{ 'legend' | translate }}</legend>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupShowPictoLegend"
ng-model="studentData.attributes.legend"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupShowPictoLegend">
{{ 'show' | translate }}
</label>
</div>
<fieldset class="form-group" ng-disabled="!studentData.attributes.legend">
<h4>{{ 'legend_size' | translate }}</h4>
<div class="input-group">
<span class="input-group-addon">
<input type="radio"
value="small"
id="studentSetupPictoLegendSizeSmall"
ng-model="studentData.attributes.legend_size"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupPictoLegendSizeSmall">
{{ 'small' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="radio"
value="normal"
id="studentSetupPictoLegendSizeNormal"
ng-model="studentData.attributes.legend_size"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupPictoLegendSizeNormal">
{{ 'normal' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="radio"
value="large"
id="studentSetupPictoLegendSizeLarge"
ng-model="studentData.attributes.legend_size"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupPictoLegendSizeLarge">
{{ 'large' | translate }}
</label>
</div>
</fieldset>
</fieldset>
-->
</form>
</div>
</div>
</div>
<!-- /configuración dispositivo -->
<!-- Parte derecha: Tutores y dispositivos -->
<h3>{{ 'supervisors' | translate }}</h3>
<!-- Supervisores (terapeutas) del alumno --> <!-- Supervisores (terapeutas) del alumno -->
<div id="student_sups" ng-if="studentData.supervision != 1"> <div id="student_sups" ng-if="studentData.supervision != 1">
<h3 translate>supervisors</h3> <legend translate>therapists</legend>
<!-- Buscador de supervisores --> <!-- Buscador de supervisores -->
<p> <p>
<form role="search" ng-submit="search_sup()"> <form role="search" ng-submit="search_sup()">
<div class="input-group"> <div class="input-group" ng-if="user.isSupAdmin">
<input type="email" class="form-control" placeholder="{{ 'search_sup_email' | translate }}" name="email_sup" id="email_sup" ng-model="email_sup" required> <input type="email" class="form-control" placeholder="{{ 'search_sup_email' | translate }}" name="email_sup" id="email_sup" ng-model="supsForm.email_sup" required>
<div class="input-group-btn"> <div class="input-group-btn">
<button class="btn btn-default" type="submit"> <button class="btn btn-default" type="submit">
<span class="glyphicon glyphicon-search"></span> <span class="glyphicon glyphicon-search"></span>
...@@ -118,7 +384,7 @@ ...@@ -118,7 +384,7 @@
<!-- Imagen de perfil del supervisor --> <!-- Imagen de perfil del supervisor -->
<img ng-src="{{sup.pic}}" class="profile" alt="" title="" /> <img ng-src="{{sup.pic}}" class="profile" alt="" title="" />
{{sup.name}} {{sup.surname}} {{sup.name}} {{sup.surname}}
<a ng-click="delete_sup(sup.id)" class="delete_sup" title="{{ 'unlink' | translate}}"> <a ng-if="user.isSupAdmin" ng-click="delete_sup(sup.id)" class="delete_sup" title="{{ 'unlink' | translate}}">
<span class="color_red glyphicon glyphicon-remove-circle" aria-hidden="true"></span> <span class="color_red glyphicon glyphicon-remove-circle" aria-hidden="true"></span>
</a> </a>
</li> </li>
...@@ -129,12 +395,12 @@ ...@@ -129,12 +395,12 @@
<!-- Tutores (Padres) --> <!-- Tutores (Padres) -->
<div id="student_tutors" ng-if="studentData.supervision != 1"> <div id="student_tutors" ng-if="studentData.supervision != 1">
<h3>{{ 'tutors' | translate }}</h3> <legend translate>tutors</legend>
<!-- Buscador de tutores --> <!-- Buscador de tutores -->
<p> <p>
<form role="search" ng-submit="search_tutor()"> <form role="search" ng-submit="search_tutor()">
<div class="input-group"> <div class="input-group" ng-if="user.isSupAdmin">
<input type="email" class="form-control" placeholder="{{ 'search_tutor_email' | translate }}" name="email_tutor" id="email_tutor" ng-model="email_tutor" required> <input type="email" class="form-control" placeholder="{{ 'search_tutor_email' | translate }}" name="email_tutor" id="email_tutor" ng-model="supsForm.email_tutor" required>
<div class="input-group-btn"> <div class="input-group-btn">
<button class="btn btn-default" type="submit"> <button class="btn btn-default" type="submit">
<span class="glyphicon glyphicon-search"></span> <span class="glyphicon glyphicon-search"></span>
...@@ -159,7 +425,7 @@ ...@@ -159,7 +425,7 @@
<!-- Imagen de perfil del tutor --> <!-- Imagen de perfil del tutor -->
<img ng-src="{{tutor.pic}}" class="profile" alt="" title="" /> <img ng-src="{{tutor.pic}}" class="profile" alt="" title="" />
{{tutor.name}} {{tutor.surname}} {{tutor.name}} {{tutor.surname}}
<a ng-click="delete_tutor(tutor.id)" class="delete_tutor" title="{{ 'unlink' | translate}}"> <a ng-if="user.isSupAdmin" ng-click="delete_tutor(tutor.id)" class="delete_tutor" title="{{ 'unlink' | translate}}">
<span class="color_red glyphicon glyphicon-remove-circle" aria-hidden="true"></span> <span class="color_red glyphicon glyphicon-remove-circle" aria-hidden="true"></span>
</a> </a>
</li> </li>
...@@ -171,218 +437,6 @@ ...@@ -171,218 +437,6 @@
</div> </div>
<!-- Fin de id student-tutors --> <!-- Fin de id student-tutors -->
<div id="device_setup">
<h3>{{ 'device_setup' | translate }}</h3>
<form role="form" class="form">
<fieldset>
<legend>{{ 'categories' | translate }}</legend>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupUseCategories"
ng-model="studentData.attributes.categories"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupUseCategories">{{ 'use_categories' | translate }}</label>
</div>
<legend>{{ 'picto_style' | translate }}</legend>
<div class="form-group">
<h4>{{ 'size' | translate }}</h4>
<div class="input-group">
<span class="input-group-addon">
<input type="radio"
value="small"
id="studentSetupPictoSizeSmall"
ng-model="studentData.attributes.size"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupPictoSizeSmall">
{{ 'small' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="radio"
value="large"
id="studentSetupPictoSizeLarge"
ng-model="studentData.attributes.size"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupPictoSizeLarge">
{{ 'large' | translate }}
</label>
</div>
</div>
</fieldset>
<fieldset>
<legend>{{ 'feedback_picto' | translate }}</legend>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupVibrateOnTouch"
ng-model="studentData.attributes.input_feedback.vibration"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupVibrateOnTouch">{{ 'vibration' | translate }}</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupBeepOnTouch"
ng-model="studentData.attributes.input_feedback.beep"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupBeepOnTouch">{{ 'beep' | translate }}</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupReadOnTouch"
ng-model="studentData.attributes.input_feedback.read"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupReadOnTouch">{{ 'read_picto' | translate }}</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupHighlightOnTouch"
ng-model="studentData.attributes.input_feedback.highlight"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupHighlightOnTouch">
{{ 'highlight' | translate }}
</label>
</div>
</fieldset>
<!-- DISABLED, NOT IMPLEMENTED YET
<fieldset>
<legend>{{ 'input_selection' | translate }}</legend>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupSelectOnClick"
ng-model="studentData.attributes.input_selection.click"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupSelectOnClick">
{{ 'click' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupSelectOnDoubleClick"
ng-model="studentData.attributes.input_selection.double_click"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupSelectOnDoubleClick">
{{ 'double_click' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupSelectOnLongPress"
ng-model="studentData.attributes.input_selection.long_press"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupSelectOnLongPress">
{{ 'press' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupSelectOnDrag"
ng-model="studentData.attributes.input_selection.drag"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupSelectOnDrag">
{{ 'drag' | translate }}
</label>
</div>
</fieldset>
<fieldset>
<div class="input-group">
<span class="input-group-addon">
<input type="color"
id="studentSetupPictoBackground"
ng-model="studentData.attributes.picto_background"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupPictoBackground">
{{ 'background' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="color"
id="studentSetupTapeBackground"
ng-model="studentData.attributes.tape_background"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupTapeBackground">
{{ 'tape_background' | translate }}
</label>
</div>
</fieldset>
<fieldset>
<legend>{{ 'legend' | translate }}</legend>
<div class="input-group">
<span class="input-group-addon">
<input type="checkbox"
id="studentSetupShowPictoLegend"
ng-model="studentData.attributes.legend"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupShowPictoLegend">
{{ 'show' | translate }}
</label>
</div>
<fieldset class="form-group" ng-disabled="!studentData.attributes.legend">
<h4>{{ 'legend_size' | translate }}</h4>
<div class="input-group">
<span class="input-group-addon">
<input type="radio"
value="small"
id="studentSetupPictoLegendSizeSmall"
ng-model="studentData.attributes.legend_size"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupPictoLegendSizeSmall">
{{ 'small' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="radio"
value="normal"
id="studentSetupPictoLegendSizeNormal"
ng-model="studentData.attributes.legend_size"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupPictoLegendSizeNormal">
{{ 'normal' | translate }}
</label>
</div>
<div class="input-group">
<span class="input-group-addon">
<input type="radio"
value="large"
id="studentSetupPictoLegendSizeLarge"
ng-model="studentData.attributes.legend_size"
ng-change="update_attributes()">
</span>
<label class="form-control" for="studentSetupPictoLegendSizeLarge">
{{ 'large' | translate }}
</label>
</div>
</fieldset>
</fieldset>
-->
</form>
</div>
</div> </div>
</div> </div>
<!-- Fin de row --> <!-- Fin de row -->
......
...@@ -20,11 +20,10 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl( ...@@ -20,11 +20,10 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
name: '', name: '',
surname: '', surname: '',
birthdate: '', birthdate: '',
country: '', country: 'ES',
gender: 'F', gender: 'M',
lang: 'es-es', lang: 'es-es',
notes: '', notes: '',
pic: 'defaultAvatar.jpg',
office: $scope.user.office || { name: '' } office: $scope.user.office || { name: '' }
}; };
...@@ -39,15 +38,6 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl( ...@@ -39,15 +38,6 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
console.log("currentStudents: " + $scope.user.office.currentStudents); console.log("currentStudents: " + $scope.user.office.currentStudents);
console.log("maxStudents: " + $scope.user.office.maxStudents); console.log("maxStudents: " + $scope.user.office.maxStudents);
// Compute number of licenses left
if ($scope.user.office.currentStudents >= $scope.user.office.maxStudents) {
$scope.num_licenses_left = 0;
} else {
$scope.num_licenses_left =
$scope.user.office.maxStudents -
$scope.user.office.currentStudents;
}
} else { } else {
$scope.user.office = { name: '' }; $scope.user.office = { name: '' };
} }
...@@ -74,13 +64,14 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl( ...@@ -74,13 +64,14 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
name: '', name: '',
surname: '', surname: '',
birthdate: '', birthdate: '',
country: '', country: 'ES',
gender: 'F', gender: 'M',
lang: 'es-es', lang: 'es-es',
notes: '', notes: '',
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',
license_number: ''
}; };
// Hide the form // Hide the form
...@@ -113,34 +104,26 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl( ...@@ -113,34 +104,26 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
data.supervision = 0; // by default, only related to office administrator data.supervision = 0; // by default, only related to office administrator
data.current_method = $translate.instant('no_method'); data.current_method = $translate.instant('no_method');
data.current_instruction = $translate.instant('no_instruction'); data.current_instruction = $translate.instant('no_instruction');
data.licenseIsValid = new Date(data.license.expiration_ts) - new Date() > 0 ? true : false;
// Add to the list of students in view
$scope.students.push(data); $scope.students.push(data);
// Delete the fields of the form to avoid data binding
// between the new element created and the form fields
$scope.resetForm(); $scope.resetForm();
// Hide the add form to new adding
$scope.hidestudentadd = true; $scope.hidestudentadd = true;
// Update counters
$scope.user.office.currentStudents += 1;
$scope.num_licenses_left -= 1;
}) })
.error(function (err) { .error(function (err) {
var errorMessage = 'student_not_added'; var errorMessage = 'student_not_added';
console.log(err); if (err.message && err.message.search('nvalid license') > 0)
if (typeof err == "string" && err.search("Maximum number of enrolments reached") > 0) errorMessage = 'license_invalid';
else if (err.message && err.message.search('in use') > 0)
errorMessage = 'license_already_activated';
else if (typeof err == "string" && err.search("Maximum number of enrolments reached") > 0)
errorMessage = 'max_licenses_reached'; errorMessage = 'max_licenses_reached';
if (typeof err == "string" && err.search("already exists") > 0) else if (typeof err == "string" && err.search("already exists") > 0)
errorMessage = 'student_already_exists'; errorMessage = 'student_already_exists';
else if (err && err.status === 400) else if (err && err.status === 400)
errorMessage = 'invalid_fields'; errorMessage = 'invalid_fields';
ngToast.danger({ content: $translate.instant(errorMessage) }); ngToast.danger({ content: $translate.instant(errorMessage) });
$scope.hidestudentadd = true;
}); });
}; };
...@@ -148,32 +131,25 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl( ...@@ -148,32 +131,25 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
* Delete Student * Delete Student
*/ */
$scope.delete_student = function (student) { $scope.delete_student = function (student) {
$translate('confirmation').then(t => { if ($window.confirm($translate.instant('confirmation'))) {
if ($window.confirm(t)) $http.delete(config.backend + '/stu/' + student.id)
$http.delete(config.backend + '/stu/' + student.id) .success(function () {
.success(function () { var i;
var i; for (i = 0; i < $scope.students.length; i++) {
for (i = 0; i < $scope.students.length; i++) { if (student.id === $scope.students[i].id) {
if (student.id === $scope.students[i].id) { $scope.students.splice(i, 1);
$scope.students.splice(i, 1);
}
} }
$translate('student_deleted').then(function (translation) { }
ngToast.success({ content: translation }); ngToast.success({ content: $translate.instant('student_deleted') });
});
IOService.post('/stu/unsubscribe', {
IOService.post('/stu/unsubscribe', { action: 'unsubscribe'
action: 'unsubscribe'
});
// Update counters
$scope.user.office.currentStudents -= 1;
$scope.num_licenses_left += 1;
})
.error(function () {
ngToast.danger({ content: $translate.instant('student_not_deleted') });
}); });
}); })
.error(function () {
ngToast.danger({ content: $translate.instant('student_not_deleted') });
});
}
}; };
// When a new student is added to the supervisor, we should update // When a new student is added to the supervisor, we should update
......
...@@ -28,6 +28,9 @@ ...@@ -28,6 +28,9 @@
<!-- Fin .panel-body --> <!-- Fin .panel-body -->
<!-- Table --> <!-- Table -->
<div ng-show="students.length == 0">
{{ no_students_for_user | translate }}
</div>
<table id="table_students" class="table table-hover"> <table id="table_students" class="table table-hover">
<tr ng-repeat="student in students | filter:search_students | orderBy: ['surname', 'name']"> <tr ng-repeat="student in students | filter:search_students | orderBy: ['surname', 'name']">
<td> <td>
...@@ -36,6 +39,9 @@ ...@@ -36,6 +39,9 @@
</div> </div>
</td> </td>
<td> <td>
<span ng-show="!student.licenseIsValid" class="license-warning text-danger glyphicon glyphicon-exclamation-sign" aria-hidden="true" popover="{{ 'license_invalid' | translate}}" popover-trigger="mouseenter"></span>
</td>
<td>
<h4>{{student.surname}}, {{student.name}}</h4> <h4>{{student.surname}}, {{student.name}}</h4>
</td> </td>
<td> <td>
...@@ -43,7 +49,6 @@ ...@@ -43,7 +49,6 @@
</td> </td>
<td> <!-- BUTTONS --> <td> <!-- BUTTONS -->
<a class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/collections" alt="{{ 'collections' | translate}}" popover="{{ 'collections' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision != 0"><span class="glyphicon glyphicon-th" aria-hidden="true"></span></a> <a class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/collections" alt="{{ 'collections' | translate}}" popover="{{ 'collections' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision != 0"><span class="glyphicon glyphicon-th" aria-hidden="true"></span></a>
<span class="btn btn-default btn-lg" role="button" alt="{{ 'collections' | translate}}" popover="{{ 'collections' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision == 0"><span class="glyphicon glyphicon-th" style="color: #bbb" aria-hidden="true"></span></span> <span class="btn btn-default btn-lg" role="button" alt="{{ 'collections' | translate}}" popover="{{ 'collections' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision == 0"><span class="glyphicon glyphicon-th" style="color: #bbb" aria-hidden="true"></span></span>
...@@ -65,7 +70,7 @@ ...@@ -65,7 +70,7 @@
</td> <!-- /BUTTONS --> </td> <!-- /BUTTONS -->
<td> <td>
<a ng-click="delete_student(student)" class="delete_stu" title="{{ 'delete' | translate}}"> <a ng-if="user.isSupAdmin" ng-click="delete_student(student)" class="delete_stu" title="{{ 'delete' | translate}}">
<span class="color_red glyphicon glyphicon-remove-circle" aria-hidden="true"></span> <span class="color_red glyphicon glyphicon-remove-circle" aria-hidden="true"></span>
</a> </a>
</td> </td>
......
...@@ -18,61 +18,65 @@ ...@@ -18,61 +18,65 @@
<input type="password" class="form-control" id="setup_password2" placeholder="{{ 'password_confirm' | translate }}" ng-model="formdatastudent.password_confirm" /> <input type="password" class="form-control" id="setup_password2" placeholder="{{ 'password_confirm' | translate }}" ng-model="formdatastudent.password_confirm" />
</div> </div>
<legend translate>personal_data</legend>
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" id="student_name" placeholder="{{ 'name' | translate }}" required ng-model="formdatastudent.name" /> {{ 'language' | translate }}: <select class="form-control" name="student_language" id="student_language" ng-model="formdatastudent.lang">
<option value="es-es" selected>Español</option>
<option value="en-us">English</option>
</select>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" id="student_surname" placeholder="{{ 'surname' | translate }}" required ng-model="formdatastudent.surname" /> <input type="text" id="student_license" mask="9999-9999-9999-9999" clean="true" placeholder="{{ 'license_number' | translate }}" ng-model="formdatastudent.license_number" required>
</div> </div>
<div class="form-group">
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="{{ 'day_format' | translate }}" ng-model="formdatastudent.birthdate" placeholder="{{ 'birthdate' | translate }}" is-open="opened_cal_student" close-text="{{'close' | translate}}" required />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open_calendar($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
<div class="form-group">
<select class="form-control" name="student_gender" id="student_gender" ng-model="formdatastudent.gender" required>
<option value="F" selected translate>woman</option>
<option value="M" translate>man</option>
</select>
</div>
<div class="form-group">
<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>
</fieldset>
</div> </div>
<div class="col-sm-6"> <div class="col-sm-6">
<fieldset>
<legend translate>language</legend>
<div class="form-group">
<select class="form-control" name="student_language" id="student_language" ng-model="formdatastudent.lang">
<option value="es-es" selected>Español</option>
<option value="en-us">English</option>
</select>
</div>
</fieldset>
<fieldset> <legend translate>personal_data</legend>
<legend translate>notes</legend>
<div class="form-group"> <div class="form-group">
<textarea class="form-control" name="student_notes" id="student_notes" ng-model="formdatastudent.notes" rows="5"></textarea> <input type="text" class="form-control" id="student_name" placeholder="{{ 'name' | translate }}" required ng-model="formdatastudent.name" />
</div>
<div class="form-group">
<input type="text" class="form-control" id="student_surname" placeholder="{{ 'surname' | translate }}" required ng-model="formdatastudent.surname" />
</div>
<div class="row">
<div class="col-sm-6">
<div class="form-group">
<p class="input-group">
<input type="text" class="form-control" datepicker-popup="{{ 'day_format' | translate }}" ng-model="formdatastudent.birthdate" placeholder="{{ 'birthdate' | translate }}" is-open="opened_cal_student" close-text="{{'close' | translate}}" required />
<span class="input-group-btn">
<button type="button" class="btn btn-default" ng-click="open_calendar($event)"><i class="glyphicon glyphicon-calendar"></i></button>
</span>
</p>
</div>
</div> </div>
</fieldset> <div class="col-sm-6">
<div class="form-group">
<select class="form-control" name="student_gender" id="student_gender" ng-model="formdatastudent.gender" required>
<option value="F" selected translate>woman</option>
<option value="M" translate>man</option>
</select>
</div>
</div>
</div>
<div class="form-group">
<span translate>country</span>:
<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> </div>
</div> </div>
<!-- Fin de row --> <!-- Fin de row -->
......
...@@ -774,8 +774,8 @@ img.profile{ ...@@ -774,8 +774,8 @@ img.profile{
margin-top: 4px; margin-top: 4px;
} }
.delete_stu{ .delete_stu, .license-warning{
font-size: 24px; font-size: 18px;
margin-top: 10px; margin-top: 10px;
} }
......
...@@ -7,7 +7,7 @@ var UPLOAD_PATH = path.join(__dirname, '..', '..', 'upload'); ...@@ -7,7 +7,7 @@ var UPLOAD_PATH = path.join(__dirname, '..', '..', 'upload');
module.exports.pictogram = { module.exports.pictogram = {
admin: { admin: {
email: 'amontejo@ujaen.es', email: 'amontejo@ujaen.es',
password: '$2a$10$omHwM62JNI5O1hbkfFGncOYwq3mZATmh8NnTQhN3f7JV3Q1/S9fGG' password: '$2a$06$flEEOc15SerMeYWARrN9w.KSpJuM.jDsaTgrtD0ESzbxKHPl0f/zq' //y00ttaa!!
}, },
serialSize: 10, // number of characters in generated serial numbers serialSize: 10, // number of characters in generated serial numbers
pageLimit: 10, // number of elements per "page" pageLimit: 10, // number of elements per "page"
...@@ -33,7 +33,7 @@ module.exports.pictogram = { ...@@ -33,7 +33,7 @@ module.exports.pictogram = {
*/ */
getStudentAvatarUrl: function (filename) { getStudentAvatarUrl: function (filename) {
if (!filename) if (!filename)
filename = sails.config.pictogram.paths.deafultAvatarFileName; filename = sails.config.pictogram.paths.defaultAvatarFileName;
return `/upload/studentAvatar/${filename}`; return `/upload/studentAvatar/${filename}`;
}, },
......
...@@ -83,12 +83,12 @@ module.exports.policies = { ...@@ -83,12 +83,12 @@ module.exports.policies = {
StudentController: { StudentController: {
eternal: true, eternal: true,
getInfo: ['tokenAuth'], getInfo: ['tokenAuth', 'isSupervisorOfStudentOrIsSupAdmin'],
supervisors: ['tokenAuth'], supervisors: ['tokenAuth'],
therapists: ['tokenAuth'], therapists: ['tokenAuth'],
tutors: ['tokenAuth'], tutors: ['tokenAuth'],
link_supervisor: ['tokenAuth'], link_supervisor: ['tokenAuth', 'isSupAdmin'],
pictos: ['tokenAuth'], pictos: ['tokenAuth', 'isSupervisorOfStudent'],
methods: ['tokenAuth'], methods: ['tokenAuth'],
lasttries: ['tokenAuth'], lasttries: ['tokenAuth'],
tries: ['tokenAuth'], tries: ['tokenAuth'],
...@@ -98,7 +98,7 @@ module.exports.policies = { ...@@ -98,7 +98,7 @@ module.exports.policies = {
update_picto: ['tokenAuth', 'isSupervisorOfStudent'], update_picto: ['tokenAuth', 'isSupervisorOfStudent'],
update_legend: ['tokenAuth'], update_legend: ['tokenAuth'],
login: true, login: true,
create: ['tokenAuth'], create: ['tokenAuth', 'isSupAdmin'],
upload: ['tokenAuth'], upload: ['tokenAuth'],
add_picto: ['tokenAuth', 'isSupervisorOfStudent'], add_picto: ['tokenAuth', 'isSupervisorOfStudent'],
subscribe: ['tokenAuth'], subscribe: ['tokenAuth'],
...@@ -107,16 +107,16 @@ module.exports.policies = { ...@@ -107,16 +107,16 @@ module.exports.policies = {
action: true, action: true,
config: true, config: true,
actions_batch: ['tokenAuth'], actions_batch: ['tokenAuth'],
delete: ['tokenAuth'], delete: ['tokenAuth', 'isSupAdmin'],
unlink_supervisor: ['tokenAuth'], unlink_supervisor: ['tokenAuth', 'isSupAdmin'],
delete_picto: ['tokenAuth', 'isSupervisorOfStudent'] delete_picto: ['tokenAuth', 'isSupervisorOfStudent']
}, },
LicenseController: { LicenseController: {
/* create: ['tokenAuth', 'isAdmin'], // create: ['tokenAuth', 'isAdmin'],
activate: ['tokenAuth']*/ // activate: ['tokenAuth']
create: true, create: true,
activate: true activate: true
}, },
SupervisorController: { SupervisorController: {
......
...@@ -25,6 +25,8 @@ module.exports = function (grunt) { ...@@ -25,6 +25,8 @@ module.exports = function (grunt) {
'assets/app/bower_components/angular-chart.js/dist/angular-chart.min.js', 'assets/app/bower_components/angular-chart.js/dist/angular-chart.min.js',
'assets/app/bower_components/ng-lodash/build/ng-lodash.js', 'assets/app/bower_components/ng-lodash/build/ng-lodash.js',
'assets/app/bower_components/ng-file-upload/angular-file-upload-shim.js', 'assets/app/bower_components/ng-file-upload/angular-file-upload-shim.js',
'assets/app/bower_components/bootstrap-filestyle/src/bootstrap-filestyle.min.js',
'assets/app/bower_components/ngMask/dist/ngMask.min.js',
'assets/scripts/lib/sails.io.js' 'assets/scripts/lib/sails.io.js'
]; ];
...@@ -39,6 +41,7 @@ module.exports = function (grunt) { ...@@ -39,6 +41,7 @@ module.exports = function (grunt) {
'assets/scripts/modules/login/controllers/signin.js', 'assets/scripts/modules/login/controllers/signin.js',
'assets/scripts/modules/login/controllers/login_admin.js', 'assets/scripts/modules/login/controllers/login_admin.js',
'assets/scripts/modules/admin/controllers/admin.js', 'assets/scripts/modules/admin/controllers/admin.js',
'assets/scripts/modules/admin/controllers/licenses.js',
'assets/scripts/modules/admin/controllers/offices.js', 'assets/scripts/modules/admin/controllers/offices.js',
'assets/scripts/modules/admin/controllers/supervisors.js', 'assets/scripts/modules/admin/controllers/supervisors.js',
'assets/scripts/modules/supervisor/controllers/supervisor.js', 'assets/scripts/modules/supervisor/controllers/supervisor.js',
......
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