Commit 9675647e by Pablo Molina

Merge branch 'issue/386' into develop

Conflicts:
	sails/src/assets/app/modules/supervisor/controllers/students.js
parents d0f6aea1 e39c1c30
/* global Student, PictoCoreCat, VStuLastInstruction, StuPicto, StuSup, sailsTokenAuth, sails */
/** /**
/* StudentController /* StudentController
* *
...@@ -7,932 +9,967 @@ ...@@ -7,932 +9,967 @@
module.exports = { module.exports = {
// //
// login action // login action
// expected params in post body: username, password // expected params in post body: username, password
// //
login: function (req, res) { login: function (req, res) {
var bcrypt = require('bcrypt-nodejs');
var bcrypt = require('bcrypt-nodejs'); var username = req.body.username;
var username = req.body.username; var password = req.body.password;
var password = req.body.password;
// no credentials
// no credentials if (!username || !password) {
if (!username || !password) return res.json(401, { error: 'No credentials sent' });
return res.json(401, {error: 'No credentials sent'}); }
// search user by username // search user by username
Student.findOneByUsername(username).exec(function (err, stu) { Student.findOneByUsername(username).exec(function (err, stu) {
if (err) {
if (err) return res.json(500, { error: 'DB error' });
return res.json(500, { error: 'DB error' }); }
if (!stu) if (!stu) {
return res.json(401, {error: 'User not found'}); return res.json(401, { error: 'User not found' });
}
// if found, check password in encrypted form
bcrypt.compare(password, stu.password, function (err, match) { // if found, check password in encrypted form
if (err) bcrypt.compare(password, stu.password, function (err, match) {
return res.json(500, { error: 'Server error' }); if (err) {
return res.json(500, { error: 'Server error' });
if (match) { // password match }
// all supervisor information is passed as payload to the token if (match) { // password match
stu.isStudent = true; // all supervisor information is passed as payload to the token
return res.json({ stu.isStudent = true;
user: stu, return res.json({
token: sailsTokenAuth.issueToken(stu, sails.config.jwt.expiresInMinutes), user: stu,
server_time: (new Date()).getTime() token: sailsTokenAuth.issueToken(stu, sails.config.jwt.expiresInMinutes),
server_time: (new Date()).getTime()
}); });
} else {
} else { return res.json(401, { error: 'Invalid password' });
return res.json(401, { error: 'Invalid password' }); }
} });
}); });
}); },
},
//
// // Get a student with the given id, population also the office and the last method/instruction
// Get a student with the given id, population also the office and the last method/instruction //
// getInfo: function(req, res) {
getInfo: function(req, res) { if (!req.params.id_stu) {
if (!req.params.id_stu) return res.json(500, { err: 'no id_stu' });
return res.json(500, {err: 'no id_stu'}); }
Student.findOne(req.params.id_stu).populate('office').populate('stuSup').exec(function(err, student){ Student.findOne(req.params.id_stu).populate('office').populate('stuSup')
if (err) { .exec(function(err, student){
sails.log.debug(err); if (err) {
console.log(JSON.stringify(err)); sails.log.debug(err);
console.log(JSON.stringify(err));
return res.json(500, err); return res.json(500, err);
} }
if (!student) if (!student) {
return res(500, {err: JSON.stringify(err)}); return res(500, {err: JSON.stringify(err)});
}
// get student last method/instruction
VStuLastInstruction.findOne({ student : student.id }).exec(function(err, row) { // get student last method/instruction
if (err) { VStuLastInstruction.findOne({ student : student.id })
sails.log.debug(err); .exec(function(err, row) {
console.log(JSON.stringify(err)); if (err) {
return res.json(500, err); sails.log.debug(err);
} console.log(JSON.stringify(err));
if (!row || row.length == 0) { return res.json(500, err);
student.current_method = 'no_method'; }
student.current_instruction = 'no_instruction'; if (!row || row.length === 0) {
} else { student.current_method = 'no_method';
student.current_method = row.met_name; student.current_instruction = 'no_instruction';
student.current_instruction = row.ins_name; } else {
} student.current_method = row.met_name;
return res.json(student); student.current_instruction = row.ins_name;
}); }
}); return res.json(student);
}, });
});
// },
// create action
// adds a new student into the database //
// // create action
create: function(req, res) { // adds a new student into the database
var params = req.params.all(); //
Student.create(params).exec(function (err, created) { create: function(req, res) {
if (err) { var params = req.params.all();
sails.log.debug(err); Student.create(params).exec(function (err, created) {
console.log(JSON.stringify(err)); if (err) {
sails.log.debug(err);
return res.json(500, err); console.log(JSON.stringify(err));
} return res.json(500, err);
if (created) { }
sails.log.debug("Student "+ created.id +" created: " + JSON.stringify(created)); if (created) {
sails.log.debug("Student "+ created.id +" created: " + JSON.stringify(created));
// Assign the initial collection of pictos to the student
PictoCoreCat.find().exec(function(err, pictoCoreCat) { // Assign the initial collection of pictos to the student
PictoCoreCat.find().exec(function(err, pictoCoreCat) {
if (err || !pictoCoreCat || pictoCoreCat.length == 0){ if (err || !pictoCoreCat || pictoCoreCat.length == 0){
sails.log.debug("PictoCoreCat: " + err); sails.log.debug("PictoCoreCat: " + err);
return; return;
} }
sails.log.debug("PictoCoreCat Length: " + pictoCoreCat.length); sails.log.debug("PictoCoreCat Length: " + pictoCoreCat.length);
sails.log.debug(pictoCoreCat); sails.log.debug(pictoCoreCat);
// Every picto from 'picto_core_cat' is going to be created
// Every picto from 'picto_core_cat' is going to be created // in 'stu_picto'
// in 'stu_picto' for (var i = 0; i < pictoCoreCat.length; i++) {
for (var i = 0; i < pictoCoreCat.length; i++) { sails.log.debug("Loop: " + i);
sails.log.debug("Picto Category: " + pictoCoreCat[i].category);
sails.log.debug("Loop: " + i); sails.log.debug("User id: " + created.id);
sails.log.debug("Picto Category: " + pictoCoreCat[i].category);
sails.log.debug("User id: " + created.id); StuPicto.create({
student: created.id,
StuPicto.create({ picto: pictoCoreCat[i].picto,
student: created.id, attributes: {
picto: pictoCoreCat[i].picto, id_cat: pictoCoreCat[i].category,
attributes: { coord_x: pictoCoreCat[i].coord_x,
id_cat: pictoCoreCat[i].category, coord_y: pictoCoreCat[i].coord_y,
coord_x: pictoCoreCat[i].coord_x, status: 'invisible', // Default, the pictos don't appear to the user
coord_y: pictoCoreCat[i].coord_y, color: pictoCoreCat[i].color
status: 'invisible', // Default, the pictos don't appear to the user }
color: pictoCoreCat[i].color }).exec(function(err, added){
} if(err) sails.log.debug('StuPicto.create: ' + err);
}).exec(function(err, added){ if(added) {
if(err) sails.log.debug("StuPicto.create: " + err); sails.log.debug(
if(added) sails.log.debug("Picto " + added.picto + " added to student " + created.id + " with attributes: " + JSON.stringify(added.attributes)); 'Picto ' + added.picto +
}); ' added to student ' + created.id +
} ' with attributes: ' + JSON.stringify(added.attributes));
}
}); });
}
return res.json({student: created}); });
} return res.json({ student: created });
}); }
}, });
},
//
// Deletes logically a student by removing id_off and linked supervisors //
// // Deletes logically a student by removing id_off and linked supervisors
delete: function(req, res) { //
if (!req.params.id_stu) delete: function(req, res) {
return res.json(500, {error: "No student defined"}); if (!req.params.id_stu)
Student.logical_delete(req.params.id_stu, function(err) { return res.json(500, {error: "No student defined"});
if (err) Student.logical_delete(req.params.id_stu, function(err) {
return res.json(500, {error: err}); if (err)
return res.json({result: 'Deleted'}); return res.json(500, {error: err});
}); return res.json({result: 'Deleted'});
}, });
},
//
// Updates student information //
// // Updates student information
update: function(req, res) { //
if (!req.params.id_stu) { update: function(req, res) {
return res.json(500, {error: "No student defined"}); if (!req.params.id_stu) {
} return res.json(500, {error: "No student defined"});
Student.findOne(req.params.id_stu).exec(function(err, stu){ }
if (err || !stu) Student.findOne(req.params.id_stu).exec(function(err, stu){
return res.json(500, {error: "No student found"}); if (err || !stu)
// copy attributes return res.json(500, {error: "No student found"});
for (k in req.body) stu[k] = req.body[k]; // copy attributes
stu.save(function(err, saved) { for (k in req.body) stu[k] = req.body[k];
if (err) stu.save(function(err, saved) {
return res.json(500, {error: "Error when saving student"}); if (err)
return res.json({result: 'Deleted'}); return res.json(500, {error: "Error when saving student"});
}); return res.json({result: 'Deleted'});
}); });
}, });
},
//
// Returns all supervisors (therapists + tutors) for the given student //
// // Returns all supervisors (therapists + tutors) for the given student
supervisors: function(req, res) { //
if (!req.params.id_stu) { supervisors: function(req, res) {
return res.json(500, {error: "No student defined"}); if (!req.params.id_stu) {
} return res.json(500, {error: "No student defined"});
Student.supervisors(req.params.id_stu, function(err, sups) { }
if (err) throw err; Student.supervisors(req.params.id_stu, function(err, sups) {
return res.json(sups); if (err) throw err;
}); return res.json(sups);
}, });
},
//
// Returns all therapists for the given student //
// // Returns all therapists for the given student
therapists: function(req, res) { //
if (!req.params.id_stu) { therapists: function(req, res) {
return res.json(500, {error: "No student defined"}); if (!req.params.id_stu) {
} return res.json(500, {error: "No student defined"});
Student.therapists(req.params.id_stu, function(err, sups) { }
if (err) throw err; Student.therapists(req.params.id_stu, function(err, sups) {
return res.json(sups); if (err) throw err;
}); return res.json(sups);
}, });
},
//
// Returns all tutors for the given student //
// // Returns all tutors for the given student
tutors: function(req, res) { //
if (!req.params.id_stu) { tutors: function(req, res) {
return res.json(500, {error: "No student defined"}); if (!req.params.id_stu) {
} return res.json(500, {error: "No student defined"});
Student.tutors(req.params.id_stu, function(err, sups) { }
if (err) throw err; Student.tutors(req.params.id_stu, function(err, sups) {
return res.json(sups); if (err) throw err;
}); return res.json(sups);
}, });
},
// Add action
// Create a relation between supervisor and student (stu_sup) /**
// * Creates a relation between the student and a given supervisor.
link_supervisor: function(req, res) { * It broadcasts the even linkSupervisorToStudent to both the student room
var params = req.allParams(); * and the supervisor room.
console.log(JSON.stringify(params)); */
StuSup.create({student: params.id_stu, supervisor: params.id_sup}).exec(function (err, created) { link_supervisor: function(req, res) {
if (err) { var params = req.allParams();
console.log(err);
return res.json(500, {error: 'Relation student/supervisor NO created'}); StuSup.create({
} student: params.id_stu,
if (created) { supervisor: params.id_sup
console.log("Relation student/supervisor created" ); }).exec(function (err, created) {
return res.json(created); var response;
} var socketToOmit;
}); var linkSupervisorToStudentEvent;
},
if (err) {
// destroy action response = res.serverError(
// remove the relation between supervisor and student (stu_sup) 'Relation student/supervisor not created: ' + err
// );
unlink_supervisor: function(req, res) { }
var params = req.allParams();
console.log(JSON.stringify(params)); if (created) {
StuSup.destroy({student: params.id_stu, supervisor: params.id_sup}).exec(function (err, destroyed) { socketToOmit = (req.isSocket) ? req.socket : undefined;
if (err) { linkSupervisorToStudentEvent = sails.hooks.events.linkSupervisorToStudent(
console.log(err); params.id_sup,
return res.json(500, {error: 'Relation student/supervisor not removed'}); params.id_stu
} );
if (destroyed) {
console.log("Relation student/supervisor removed" ); sails.hooks.events.broadcastEvent(
return res.json({stusup: destroyed}); sails.hooks.rooms.supervisor(params.id_sup),
} linkSupervisorToStudentEvent,
}); socketToOmit
}, );
sails.hooks.events.broadcastEvent(
// sails.hooks.rooms.student(params.id_stu),
// Returns all the devices associated to the student linkSupervisorToStudentEvent,
// socketToOmit
devices: function(req, res) { );
if (!req.params.id_stu) {
return res.json(500, {error: "No student defined"}); response = res.ok(created);
} } else {
Student.devices(req.params.id_stu, function(err, devs) { response = res.serverError(
if (err) throw err; 'Relation student/supervisor not created, ' +
return res.json(devs); 'but no error was received'
}); );
}, }
return response;
// });
// Link a device to a student },
//
link_device: function(req, res) { // destroy action
var params = req.params.all(); // remove the relation between supervisor and student (stu_sup)
console.log(params); //
unlink_supervisor: function(req, res) {
if (!params.id_stu) var params = req.allParams();
return res.json(500, {error: "No student defined"}); console.log(JSON.stringify(params));
StuSup.destroy({student: params.id_stu, supervisor: params.id_sup}).exec(function (err, destroyed) {
if (!params.id_dev_firm) if (err) {
return res.json(500, {error: "No device defined"}); console.log(err);
return res.json(500, {error: 'Relation student/supervisor not removed'});
Student.link_device(params.id_stu, params.id_dev_firm, function(err, created) { }
if (err || !created) if (destroyed) {
return res.json(500, {error: 'The device could not be linked with the student'}); console.log("Relation student/supervisor removed" );
return res.json({stusup: destroyed});
return res.json(created); }
}); });
}, },
// destroy action //
// remove the relation between student and device (stu_dev) // Returns all the devices associated to the student
// //
unlink_device: function(req, res) { devices: function(req, res) {
var params = req.allParams(); if (!req.params.id_stu) {
console.log(JSON.stringify(params)); return res.json(500, {error: "No student defined"});
StuDev.destroy({id_stu: params.id_stu, id_dev: params.id_dev}).exec(function (err, destroyed) { }
if (err) { Student.devices(req.params.id_stu, function(err, devs) {
console.log(err); if (err) throw err;
return res.json(500, {error: 'Relation student/device not removed'}); return res.json(devs);
} });
if (destroyed) { },
console.log("Relation student/device removed");
return res.json(destroyed[0]);
} //
}); // Link a device to a student
}, //
link_device: function(req, res) {
// read action var params = req.params.all();
// get methods of a student console.log(params);
//
methods: function(req, res) { if (!params.id_stu)
var params = req.allParams(); return res.json(500, {error: "No student defined"});
Method.find({ student: params.id_stu }).populate('instructions').exec(function (err, methods) { if (!params.id_dev_firm)
if (err) return res.json(500, {error: "No device defined"});
return res.json(500, {error: err});
Student.link_device(params.id_stu, params.id_dev_firm, function(err, created) {
if(!methods || methods.length == 0) if (err || !created)
return res.json([]); // Return an empty array return res.json(500, {error: 'The device could not be linked with the student'});
if (methods) { return res.json(created);
});
var l_met = []; },
async.eachSeries(methods, function(m, callback1) { // destroy action
// remove the relation between student and device (stu_dev)
sails.log.debug("Loop methods: " + m.name); //
unlink_device: function(req, res) {
var l_ins = []; var params = req.allParams();
var last_ins; console.log(JSON.stringify(params));
var last_time = 0; StuDev.destroy({id_stu: params.id_stu, id_dev: params.id_dev}).exec(function (err, destroyed) {
if (err) {
async.eachSeries(m.instructions, function(ins, callback2) { console.log(err);
return res.json(500, {error: 'Relation student/device not removed'});
sails.log.debug("Loop instructions: " + ins.name); }
if (destroyed) {
Instruction.findOne({ id: ins.id }).populate('workingSessions', { sort: 'begin DESC' }).exec(function (err, instruction) { console.log("Relation student/device removed");
if (err) { return res.json(destroyed[0]);
sails.log.debug("Error in method " + m.name); }
} });
},
if(!instruction || !instruction.workingSessions || instruction.workingSessions.length == 0) {
sails.log.debug("No working sessions found for instruction " + instruction.id); // read action
}else{ // get methods of a student
console.log("Total working sessions: " + instruction.workingSessions.length); //
methods: function(req, res) {
var last = instruction.workingSessions.length-1; var params = req.allParams();
instruction.begin = instruction.workingSessions[last].end;
instruction.end = instruction.workingSessions[0].end; Method.find({ student: params.id_stu }).populate('instructions').exec(function (err, methods) {
if (err)
if(instruction.end > last_time){ return res.json(500, {error: err});
last_ins = instruction.id;
last_time = instruction.end; if(!methods || methods.length == 0)
} return res.json([]); // Return an empty array
}
// Add instruction to list (with or without tries) if (methods) {
l_ins.push(instruction);
callback2(); var l_met = [];
});
async.eachSeries(methods, function(m, callback1) {
// Finish function when each callback is done
// Optionaly it can be passed and err parameter sails.log.debug("Loop methods: " + m.name);
}, function(err){
if( err ) { var l_ins = [];
// One of the iterations produced an error. var last_ins;
// All processing will now stop. var last_time = 0;
console.log('An error ocurred with a try');
return res.json(500, { error: "Error looping in tries: " + err }); async.eachSeries(m.instructions, function(ins, callback2) {
}
m.instructions = l_ins; sails.log.debug("Loop instructions: " + ins.name);
m.last_ins = last_ins;
l_met.push(m); Instruction.findOne({ id: ins.id }).populate('workingSessions', { sort: 'begin DESC' }).exec(function (err, instruction) {
callback1(); 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.length == 0) {
}, function(err){ sails.log.debug("No working sessions found for instruction " + instruction.id);
if( err ) { }else{
// One of the iterations produced an error. console.log("Total working sessions: " + instruction.workingSessions.length);
// All processing will now stop.
console.log('An error ocurred with an instruction'); var last = instruction.workingSessions.length-1;
return res.json(500, { error: "Error looping in method instructions: " + err }); instruction.begin = instruction.workingSessions[last].end;
} else { instruction.end = instruction.workingSessions[0].end;
// All end ok
console.log('All instructions have been processed successfully'); if(instruction.end > last_time){
//Assing the built list last_ins = instruction.id;
return res.json(l_met); last_time = instruction.end;
} }
}); }
} // Add instruction to list (with or without tries)
}); l_ins.push(instruction);
}, callback2();
});
// read action
// get tries of the last working session // Finish function when each callback is done
// // Optionaly it can be passed and err parameter
lasttries: function(req, res) { }, function(err){
var params = req.allParams(); if( err ) {
// One of the iterations produced an error.
//Student.findOne({ id: params.id_stu }).populate('workingSessions') // All processing will now stop.
VStuLastInstruction.findOne({ where: {student: params.id_stu }}).exec(function (err, ws) { console.log('An error ocurred with a try');
if (err){ return res.json(500, { error: "Error looping in tries: " + err });
sails.log.debug("Finding student working sessions: " + err); }
return res.json(500, {error: 'No student last working session found'}); m.instructions = l_ins;
} m.last_ins = last_ins;
l_met.push(m);
if(!ws || ws.length == 0){ callback1();
sails.log.debug("The user has not any working session"); });
return res.json([]); // Return an empty array of last tries
} // Finish function when each callback is done
// Optionaly it can be passed and err parameter
if (ws) { }, function(err){
// Find the tries of this working session populating actions if( err ) {
sails.log.debug("Find WS "+ JSON.stringify(ws)); // One of the iterations produced an error.
Try.find({ // All processing will now stop.
workingSession: ws.workingSession console.log('An error ocurred with an instruction');
}).populate('actions',{supervisor:null}).exec(function (err, tries) { return res.json(500, { error: "Error looping in method instructions: " + err });
} else {
if (err){ // All end ok
sails.log.debug("Finding student last tries: " + err); console.log('All instructions have been processed successfully');
return res.json(500, {error: 'No student last tries found'}); //Assing the built list
} return res.json(l_met);
}
if(!tries || tries.length == 0){ });
sails.log.debug("The user has not any tries for working session id. "+ws.workingSession); }
return res.json([]); // Return an empty array of tries });
} },
if(tries){ // read action
console.log("Found student tries for last working session: " + ws.workingSession); // get tries of the last working session
console.log("Last tries: " + JSON.stringify(tries)); //
lasttries: function(req, res) {
// A list for one element: The last working session var params = req.allParams();
var l_ws = [];
l_ws.push({ //Student.findOne({ id: params.id_stu }).populate('workingSessions')
"id": ws.workingSession, VStuLastInstruction.findOne({ where: {student: params.id_stu }}).exec(function (err, ws) {
"student": ws.student, if (err){
"begin": ws.ws_begin, sails.log.debug("Finding student working sessions: " + err);
"end": ws.ws_end, return res.json(500, {error: 'No student last working session found'});
"description":ws.ws_description, }
"tries": tries
}); if(!ws || ws.length == 0){
sails.log.debug("The user has not any working session");
return res.json(l_ws); return res.json([]); // Return an empty array of last tries
} }
});
} if (ws) {
}); // Find the tries of this working session populating actions
}, sails.log.debug("Find WS "+ JSON.stringify(ws));
Try.find({
// workingSession: ws.workingSession
// Returns all the tries of the student }).populate('actions',{supervisor:null}).exec(function (err, tries) {
//
tries: function(req, res) { if (err){
if (!req.params.id_stu) sails.log.debug("Finding student last tries: " + err);
return res.json(500, {error: "No student defined"}); return res.json(500, {error: 'No student last tries found'});
}
Student.tries(req.params.id_stu, function(err, l_met) {
if (err) throw err; if(!tries || tries.length == 0){
return res.json(l_met); sails.log.debug("The user has not any tries for working session id. "+ws.workingSession);
}); return res.json([]); // Return an empty array of tries
}, }
if(tries){
// console.log("Found student tries for last working session: " + ws.workingSession);
// Returns all pictos for the given student console.log("Last tries: " + JSON.stringify(tries));
//
pictos: function(req, res) { // A list for one element: The last working session
if (!req.params.id_stu) { var l_ws = [];
return res.json(500, {error: "No student defined"}); l_ws.push({
} "id": ws.workingSession,
"student": ws.student,
sails.log.debug("Pictos requested for student " + req.params.id_stu); "begin": ws.ws_begin,
"end": ws.ws_end,
Student.pictos(req.params.id_stu, function(err, pictos) { "description":ws.ws_description,
if (err) throw err; "tries": tries
return res.json(pictos); });
});
}, return res.json(l_ws);
}
// });
// Returns all working sessions for the given student }
// });
ws: function(req, res) { },
if (!req.params.id_stu) {
return res.json(500, {error: "No student defined"}); //
} // Returns all the tries of the student
//
sails.log.debug("Working Sessions requested for student " + req.params.id_stu); tries: function(req, res) {
if (!req.params.id_stu)
Student.findOne(req.params.id_stu).populate('workingSessions').exec(function (err, stu) { return res.json(500, {error: "No student defined"});
if (err)
return res.json(500, {error: err}); Student.tries(req.params.id_stu, function(err, l_met) {
if (err) throw err;
if(!stu || !stu.workingSessions || stu.workingSessions.length == 0) return res.json(l_met);
return res.json([]); // Return an empty array });
else },
return res.json(stu.workingSessions);
});
}, //
// Returns all pictos for the given student
// add action //
// add picto to studentPicto pictos: function(req, res) {
// if (!req.params.id_stu) {
add_picto: function(req, res) { return res.json(500, {error: "No student defined"});
var params = req.allParams(); }
console.log(JSON.stringify(params));
StuPicto.create({ sails.log.debug("Pictos requested for student " + req.params.id_stu);
student: params.id_stu,
picto: params.id_picto, Student.pictos(req.params.id_stu, function(err, pictos) {
attributes: params.attributes if (err) throw err;
}).exec(function (err, added) { return res.json(pictos);
if (err) { });
console.log(err); },
return res.json(500, {error: 'Not added picto to student'});
} //
if (added) { // Returns all working sessions for the given student
console.log("Added picto to student: " + JSON.stringify(added)); //
return res.json({ ws: function(req, res) {
id: added.id, if (!req.params.id_stu) {
picto: { id: added.picto, uri: params.uri }, return res.json(500, {error: "No student defined"});
attributes: added.attributes }
});
} sails.log.debug("Working Sessions requested for student " + req.params.id_stu);
});
}, Student.findOne(req.params.id_stu).populate('workingSessions').exec(function (err, stu) {
if (err)
// destroy action return res.json(500, {error: err});
// remove a picto for a student
// if(!stu || !stu.workingSessions || stu.workingSessions.length == 0)
delete_picto: function(req, res) { return res.json([]); // Return an empty array
var params = req.allParams(); else
return res.json(stu.workingSessions);
console.log(JSON.stringify(params)); });
StuPicto.destroy({id: params.id_stuPicto}).exec(function (err, destroyed) { },
if (err) {
console.log(err); // add action
return res.json(500, {error: 'Not removed picto for student'}); // add picto to studentPicto
} //
if (destroyed) { add_picto: function(req, res) {
console.log("Removed picto "+destroyed[0].id_pic+ "for the student "); var params = req.allParams();
return res.json({ console.log(JSON.stringify(params));
id: destroyed[0].id, StuPicto.create({
attributes: JSON.parse(destroyed[0].attributes), student: params.id_stu,
picto: { id: destroyed[0].id_pic } picto: params.id_picto,
}); attributes: params.attributes
} }).exec(function (err, added) {
}); if (err) {
}, console.log(err);
return res.json(500, {error: 'Not added picto to student'});
// update action }
// update picto atributes for a studentPicto if (added) {
// console.log("Added picto to student: " + JSON.stringify(added));
update_picto: function(req, res) { return res.json({
var params = req.allParams(); id: added.id,
console.log("Updating attributes for picto student "+ JSON.stringify(params)); picto: { id: added.picto, uri: params.uri },
console.log(JSON.stringify(params)); attributes: added.attributes
});
query=params.id_stuPicto ? {id: params.id_stuPicto} }
: {id_stu:params.id_stu, id_pic:params.id_pic} });
StuPicto.update(query, {attributes: params.attributes}).exec(function (err, updated) { },
if (err) {
console.log(err); // destroy action
return res.json(500, {error: 'Not updated picto for student'}); // remove a picto for a student
} //
if (updated) { delete_picto: function(req, res) {
console.log("Updated attributes for picto student:"+JSON.stringify(updated[0])); var params = req.allParams();
// return res.json(updated[0]);
return res.json({ console.log(JSON.stringify(params));
id: updated[0].id, StuPicto.destroy({id: params.id_stuPicto}).exec(function (err, destroyed) {
attributes: updated[0].attributes, if (err) {
picto: { id:updated[0].picto } console.log(err);
}); return res.json(500, {error: 'Not removed picto for student'});
} }
}); if (destroyed) {
}, console.log("Removed picto "+destroyed[0].id_pic+ "for the student ");
return res.json({
// id: destroyed[0].id,
// Upload pic file attributes: JSON.parse(destroyed[0].attributes),
// picto: { id: destroyed[0].id_pic }
upload: function (req, res) { });
if(req.method === 'GET') }
return res.json({'status':'GET not allowed'}); });
// Call to /upload via GET is error },
var uploadFile = req.file('file'); // update action
// update picto atributes for a studentPicto
console.log("Nombre de archivo: " + req.body.filename); //
console.log("Nombre de carpeta: " + req.body.folder); update_picto: function(req, res) {
console.log("Id del usuario: " + req.body.id); var params = req.allParams();
console.log("Updating attributes for picto student "+ JSON.stringify(params));
var fs = require('fs'), path = require('path'); console.log(JSON.stringify(params));
var dirUpload = path.join(process.cwd(), '../upload/' + req.body.folder); query=params.id_stuPicto ? {id: params.id_stuPicto}
: {id_stu:params.id_stu, id_pic:params.id_pic}
req.body.filename = sailsHash.hashCode(req.body.filename + Date().toString()) + '.' + req.body.extension; StuPicto.update(query, {attributes: params.attributes}).exec(function (err, updated) {
uploadFile.upload({ if (err) {
dirname: dirUpload, console.log(err);
saveAs: req.body.filename return res.json(500, {error: 'Not updated picto for student'});
}, }
function onUploadComplete (err, files) { if (updated) {
console.log("Updated attributes for picto student:"+JSON.stringify(updated[0]));
// Files will be uploaded to .tmp/uploads // return res.json(updated[0]);
if (err) return res.serverError(err); return res.json({
// IF ERROR Return and send 500 error with error id: updated[0].id,
attributes: updated[0].attributes,
// Delete old pic (if it isn't the default pic) picto: { id:updated[0].picto }
Student.findOne({id: req.body.id}).exec(function(err, found){ });
if(found && found.pic != "/app/img/default.jpg"){ }
console.log("Found: " + found.pic); });
},
// Path: ../upload/students/xxxx.jpg
fs.unlink('..' + found.pic, function (err) { //
if (err) throw err; // Upload pic file
console.log('Old pic destroyed'); //
}); upload: function (req, res) {
} if(req.method === 'GET')
}); return res.json({'status':'GET not allowed'});
// Store in DB new URL pic // Call to /upload via GET is error
Student.update({id: req.body.id}, {pic: '/upload/' + req.body.folder + '/' + req.body.filename}).exec(function(err, updated){
if(updated) var uploadFile = req.file('file');
console.log("Student picture updated");
}); console.log("Nombre de archivo: " + req.body.filename);
console.log("Nombre de carpeta: " + req.body.folder);
files[0].name = req.body.filename; console.log("Id del usuario: " + req.body.id);
delete files[0].fd;
var fs = require('fs'), path = require('path');
console.log(files);
var dirUpload = path.join(process.cwd(), '../upload/' + req.body.folder);
var file = dirUpload + '/' + req.body.filename;
req.body.filename = sailsHash.hashCode(req.body.filename + Date().toString()) + '.' + req.body.extension;
uploadFile.upload({
dirname: dirUpload,
saveAs: req.body.filename
},
function onUploadComplete (err, files) {
// Files will be uploaded to .tmp/uploads
if (err) return res.serverError(err);
// IF ERROR Return and send 500 error with error
// Delete old pic (if it isn't the default pic)
Student.findOne({id: req.body.id}).exec(function(err, found){
if(found && found.pic != "/app/img/default.jpg"){
console.log("Found: " + found.pic);
// Path: ../upload/students/xxxx.jpg
fs.unlink('..' + found.pic, function (err) {
if (err) throw err;
console.log('Old pic destroyed');
});
}
});
// Store in DB new URL pic
Student.update({id: req.body.id}, {pic: '/upload/' + req.body.folder + '/' + req.body.filename}).exec(function(err, updated){
if(updated)
console.log("Student picture updated");
});
files[0].name = req.body.filename;
delete files[0].fd;
console.log(files);
var file = dirUpload + '/' + req.body.filename;
//setTimeout(function(){ alert("Hello"); }, 3000); //setTimeout(function(){ alert("Hello"); }, 3000);
var interval = setInterval( var interval = setInterval(
function(){ function(){
fs.exists(file, function(exists) { fs.exists(file, function(exists) {
if (exists) { if (exists) {
console.log("Existe el archivo"); console.log("Existe el archivo");
clearInterval(interval); clearInterval(interval);
res.json({status:200,file:files[0]}); res.json({status:200,file:files[0]});
} else { } else {
console.log("No Existe el archivo"); console.log("No Existe el archivo");
} }
}); });
} , 5); } , 5);
} }
); );
}, },
// *************************************************************** // ***************************************************************
// WEBSOCKETS // WEBSOCKETS
// *************************************************************** // ***************************************************************
// //
// Subscribe to websockets events // Subscribe to websockets events
// //
subscribe: function(req, res) { subscribe: function(req, res) {
var action = req.param('action'); var action = req.param('action');
var attributes = req.param('attributes'); var attributes = req.param('attributes');
var roomName = 'studentRoom' + attributes.id_stu; var roomName = 'studentRoom' + attributes.id_stu;
sails.log.debug("Inside /stu/subscribe. Action: " + action); sails.log.debug("Inside /stu/subscribe. Action: " + action);
if (req.isSocket) { if (req.isSocket) {
sails.log.debug("Inside /stu/subscribe - isSocket"); sails.log.debug("Inside /stu/subscribe - isSocket");
sails.sockets.join(req.socket, roomName); sails.sockets.join(req.socket, roomName);
sails.io.sockets.in(roomName).clients(function(err, ids) { sails.io.sockets.in(roomName).clients(function(err, ids) {
sails.log.debug("number of clients in room: " + ids.length); sails.log.debug("number of clients in room: " + ids.length);
if (!err) if (!err)
sails.io.to(roomName).emit('update_peers', {count: ids.length}); sails.io.to(roomName).emit('update_peers', {count: ids.length});
}); });
res.json({ res.json({
msg: "Subscribed to student " + roomName msg: "Subscribed to student " + roomName
}); });
} }
}, },
// //
// Unsubscribe to websockets events // Unsubscribe to websockets events
// //
unsubscribe: function(req, res) { unsubscribe: function(req, res) {
var action = req.param('action'); var action = req.param('action');
//var attributes = req.param('attributes'); //var attributes = req.param('attributes');
sails.log.debug("Inside /stu/unsubscribe. Action: " + action); sails.log.debug("Inside /stu/unsubscribe. Action: " + action);
if (req.isSocket) { if (req.isSocket) {
sails.log.debug("Inside /stu/unsubscribe - isSocket"); sails.log.debug("Inside /stu/unsubscribe - isSocket");
var rooms = sails.sockets.socketRooms(req.socket); var rooms = sails.sockets.socketRooms(req.socket);
console.log("Subscribed rooms in socket: " + JSON.stringify(rooms)); console.log("Subscribed rooms in socket: " + JSON.stringify(rooms));
// Leave all rooms // Leave all rooms
for(var i=0; i<rooms.length; i++){ for(var i=0; i<rooms.length; i++){
sails.sockets.leave(req.socket, rooms[i]); sails.sockets.leave(req.socket, rooms[i]);
sails.log.debug("Unsusbscribe from room " + rooms[i]); sails.log.debug("Unsusbscribe from room " + rooms[i]);
} }
res.json({ res.json({
msg: "Unsubscribed from all rooms" msg: "Unsubscribed from all rooms"
}); });
} }
}, },
// //
// Logs a vocabulary action and broadcast to anyone subscribed to this student // Logs a vocabulary action and broadcast to anyone subscribed to this student
vocabulary: function(req, res) { vocabulary: function(req, res) {
var action = req.param('action'); var action = req.param('action');
var attributes = req.param('attributes'); var attributes = req.param('attributes');
var roomName = 'studentRoom' + attributes.id_stu; var roomName = 'studentRoom' + attributes.id_stu;
sails.log.debug("Inside vocabulary"); sails.log.debug("Inside vocabulary");
if (req.isSocket) { if (req.isSocket) {
sails.log.debug("Inside vocabulary - isSocket"); sails.log.debug("Inside vocabulary - isSocket");
// Send to all sockets subscribed to this room except the own socket that sends the message // 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) // Parameters: room, action, data to send, socket to avoid sending (the socket that send this)
sails.sockets.broadcast(roomName, 'vocabulary', { "action": action, "attributes": attributes }, req.socket); sails.sockets.broadcast(roomName, 'vocabulary', { "action": action, "attributes": attributes }, req.socket);
res.json({ res.json({
msg: "Vocabulary "+ action +" action from student " + attributes.id_stu msg: "Vocabulary "+ action +" action from student " + attributes.id_stu
}); });
} }
}, },
// //
// Logs a TRY action and broadcast to anyone subscribed to this student // Logs a TRY action and broadcast to anyone subscribed to this student
action: function(req, res) { action: function(req, res) {
var action = req.param('action'); var action = req.param('action');
var attributes = req.param('attributes'); var attributes = req.param('attributes');
var roomName = 'studentRoom' + attributes.id_stu; var roomName = 'studentRoom' + attributes.id_stu;
sails.log.debug("Inside action. Student:"+attributes.id_stu); sails.log.debug("Inside action. Student:"+attributes.id_stu);
if (req.isSocket) { if (req.isSocket) {
sails.log.debug("websockets - room "+roomName); sails.log.debug("websockets - room "+roomName);
// BROADCAST to everyone subscribed to this student // BROADCAST to everyone subscribed to this student
sails.sockets.broadcast(roomName, 'action', { "action": action, "attributes": attributes }); sails.sockets.broadcast(roomName, 'action', { "action": action, "attributes": attributes });
// PROBAR DESDE AQUÍ // PROBAR DESDE AQUÍ
// NEW DB STORAGE in table action // NEW DB STORAGE in table action
var dev = null; var dev = null;
if(attributes.device) dev = attributes.device; if(attributes.device) dev = attributes.device;
var sup = null; var sup = null;
if(attributes.supervisor) sup = attributes.supervisor; if(attributes.supervisor) sup = attributes.supervisor;
var desc = null; var desc = null;
if(attributes.picto) desc = attributes.picto; // select, add and delete actions data if(attributes.picto) desc = attributes.picto; // select, add and delete actions data
if(attributes.pictos) desc = attributes.pictos; // show action data if(attributes.pictos) desc = attributes.pictos; // show action data
Action.create({ Action.create({
type: action, type: action,
timestamp: attributes.timestamp, timestamp: attributes.timestamp,
supervisor: sup, supervisor: sup,
student: attributes.id_stu, student: attributes.id_stu,
device: dev, device: dev,
description: desc description: desc
}).exec(function (err, created) { }).exec(function (err, created) {
if (err) if (err)
res.serverError(err.details); res.serverError(err.details);
else if (created) else if (created)
res.json({action: created}); res.json({action: created});
}); });
/* /*
res.json({ res.json({
msg: "Action "+ action +" action from student " + attributes.id_stu msg: "Action "+ action +" action from student " + attributes.id_stu
}); });
*/ */
} }
}, },
// //
// Logs a config action and broadcast to anyone subscribed to this student // Logs a config action and broadcast to anyone subscribed to this student
config: function(req, res) { config: function(req, res) {
var action = req.param('action'); var action = req.param('action');
var attributes = req.param('attributes'); var attributes = req.param('attributes');
var roomName = 'studentRoom' + attributes.id_stu; var roomName = 'studentRoom' + attributes.id_stu;
sails.log.debug("Inside config"); sails.log.debug("Inside config");
if (req.isSocket) { if (req.isSocket) {
sails.log.debug("Inside config - isSocket"); sails.log.debug("Inside config - isSocket");
sails.sockets.broadcast(roomName, 'config', { "action": action, "attributes": attributes }); sails.sockets.broadcast(roomName, 'config', { "action": action, "attributes": attributes });
res.json({ res.json({
msg: "Config "+ action +" action from student " + attributes.id_stu msg: "Config "+ action +" action from student " + attributes.id_stu
}); });
} }
}, },
// //
// Stores in the action table a bulk of actions loaded from of the // Stores in the action table a bulk of actions loaded from of the
// recorded action log // recorded action log
// //
actions_batch: function(req, res) { actions_batch: function(req, res) {
var params = req.allParams(); var params = req.allParams();
var count = 0; var count = 0;
sails.log.debug("Actions_batch request"); sails.log.debug("Actions_batch request");
if (!params.actions) if (!params.actions)
return res.json(400, {'error': "no actions"}); return res.json(400, {'error': "no actions"});
// We loop through the actions and store them in the database // We loop through the actions and store them in the database
async.forEach(params.actions, function(action, cb){ async.forEach(params.actions, function(action, cb){
sails.log.debug("Actions batch is recording action: "+JSON.stringify(action)); sails.log.debug("Actions batch is recording action: "+JSON.stringify(action));
var id_dev = null; var id_dev = null;
if (action.attributes.id_dev) if (action.attributes.id_dev)
id_dev = action.attributes.id_dev; id_dev = action.attributes.id_dev;
var id_sup = null; var id_sup = null;
if(action.attributes.id_sup) if(action.attributes.id_sup)
id_sup = action.attributes.sup; id_sup = action.attributes.sup;
var id_stu = null; var id_stu = null;
if (action.attributes.id_stu) if (action.attributes.id_stu)
id_stu = action.attributes.id_stu; id_stu = action.attributes.id_stu;
var desc = null; var desc = null;
if(action.attributes.picto) if(action.attributes.picto)
desc = action.attributes.picto; // select, add and delete actions data desc = action.attributes.picto; // select, add and delete actions data
if(action.attributes.pictos) if(action.attributes.pictos)
desc = action.attributes.pictos; // show action data desc = action.attributes.pictos; // show action data
Action.create({ Action.create({
type: action.action, type: action.action,
timestamp: action.attributes.timestamp, timestamp: action.attributes.timestamp,
supervisor: id_sup, supervisor: id_sup,
student: id_stu, student: id_stu,
device: id_dev, device: id_dev,
description: desc description: desc
}).exec(function (err, created) { }).exec(function (err, created) {
if (err) { if (err) {
console.log(err.details); console.log(err.details);
sails.log.error(err.details); sails.log.error(err.details);
return cb(err); return cb(err);
} }
else if (created) else if (created)
count++; count++;
cb(); cb();
}); });
}, },
function(err) { // function called when loop is done function(err) { // function called when loop is done
if (err) { if (err) {
console.log(err.details); console.log(err.details);
sails.log.error(err.details); sails.log.error(err.details);
return res.json({'error': err.details}); return res.json({'error': err.details});
} else } else
return res.json({'result': 'Ok', 'total': count}); return res.json({'result': 'Ok', 'total': count});
}); });
}, },
// //
// Returns the last instruction for the student // Returns the last instruction for the student
// //
last_instruction: function(req, res) { last_instruction: function(req, res) {
if (!req.params.id_stu) if (!req.params.id_stu)
return res.json(400, {err: 'id_stu parameter is missing'}); return res.json(400, {err: 'id_stu parameter is missing'});
VStuLastInstruction.find({id_stu: req.params.id_stu}).exec(function(err, found) { VStuLastInstruction.find({id_stu: req.params.id_stu}).exec(function(err, found) {
if (err) if (err)
return res.json(500, err); return res.json(500, err);
if (!found) if (!found)
return res.json({}); return res.json({});
return res.json(found); return res.json(found);
}); });
}, },
// //
// Test raw sql on models // Test raw sql on models
sqlquery: function(req, res) { sqlquery: function(req, res) {
/* Example of data recieved via POST /* Example of data recieved via POST
{ {
"id_stu": 80, "id_stu": 80,
"begin": "2015-07-01", "begin": "2015-07-01",
"end": "2015-07-31" "end": "2015-07-31"
} }
*/ */
var params = req.params.all(); var params = req.params.all();
console.log(JSON.stringify(params)); console.log(JSON.stringify(params));
// Data to return // Data to return
var l_queries = { var l_queries = {
"results" : [], "results" : [],
"totals" : 0 "totals" : 0
}; };
if (!params.id_stu) if (!params.id_stu)
return res.json(500, {error: "No student defined"}); return res.json(500, {error: "No student defined"});
var sql = "SELECT result, COUNT(result) percentage" + var sql = "SELECT result, COUNT(result) percentage" +
" FROM method m, student s, instruction i, working_session w, try t" + " FROM method m, student s, instruction i, working_session w, try t" +
" WHERE m.id_stu=s.id and i.id_met=m.id and w.id_ins=i.id and t.id_ws=w.id and s.id="+ params.id_stu + " WHERE m.id_stu=s.id and i.id_met=m.id and w.id_ins=i.id and t.id_ws=w.id and s.id="+ params.id_stu +
" and t.begin>='"+ params.begin +"' and t.end<='" + params.end + "'" + " and t.begin>='"+ params.begin +"' and t.end<='" + params.end + "'" +
" GROUP BY result" + " GROUP BY result" +
" ORDER BY result"; " ORDER BY result";
// 1st query // 1st query
Student.sqlquery(sql, function (err,data) { Student.sqlquery(sql, function (err,data) {
if (err) sails.log.debug(err); if (err) sails.log.debug(err);
else l_queries.results = data; else l_queries.results = data;
sql = "SELECT COUNT(result)" + sql = "SELECT COUNT(result)" +
" FROM method m, student s, instruction i, working_session w, try t" + " FROM method m, student s, instruction i, working_session w, try t" +
" WHERE m.id_stu=s.id and i.id_met=m.id and w.id_ins=i.id and t.id_ws=w.id and s.id="+ params.id_stu; " WHERE m.id_stu=s.id and i.id_met=m.id and w.id_ins=i.id and t.id_ws=w.id and s.id="+ params.id_stu;
// 2nd query // 2nd query
Student.sqlquery(sql, function (err,data) { Student.sqlquery(sql, function (err,data) {
if (err) sails.log.debug(err); if (err) sails.log.debug(err);
else l_queries.totals = data; else l_queries.totals = data;
return res.json(l_queries); return res.json(l_queries);
}); });
}); });
} }
}; };
...@@ -10,19 +10,19 @@ module.exports = { ...@@ -10,19 +10,19 @@ module.exports = {
// login action // login action
// expected params in post body: email, password // expected params in post body: email, password
// //
login: function (req, res) { login: function (req, res) {
var bcrypt = require('bcrypt-nodejs'); var bcrypt = require('bcrypt-nodejs');
var email = req.body.email; var email = req.body.email;
var password = req.body.password; var password = req.body.password;
// no credentials // no credentials
if (!email || !password) if (!email || !password)
return res.json(401, {error: 'No credentials sent'}); return res.json(401, {error: 'No credentials sent'});
sails.log.debug("Supervisor Login request:"+email); sails.log.debug("Supervisor Login request:"+email);
// search user by email // search user by email
Supervisor.findOneByEmail(email).exec(function (err, sup) { Supervisor.findOneByEmail(email).exec(function (err, sup) {
if (err) if (err)
return res.json(500, { error: 'DB error' }); return res.json(500, { error: 'DB error' });
if (!sup) if (!sup)
...@@ -30,16 +30,16 @@ module.exports = { ...@@ -30,16 +30,16 @@ module.exports = {
// if found, check password in encrypted form // if found, check password in encrypted form
bcrypt.compare(password, sup.password, function (err, match) { bcrypt.compare(password, sup.password, function (err, match) {
if (err) if (err)
return res.json(500, { error: 'Server error' }); return res.json(500, { error: 'Server error' });
if (match) { // password match if (match) { // password match
var user = sup; var user = sup;
// Check if user is an Office administrator // Check if user is an Office administrator
Office.findOne(user.office).exec(function(err, off) { Office.findOne(user.office).exec(function(err, off) {
if (off){ if (off){
if (off.admin == sup.id) { if (off.admin == sup.id) {
console.log("This supervisor with id "+ user.id + " is admin of the office " + off.id); console.log("This supervisor with id "+ user.id + " is admin of the office " + off.id);
user.isSupAdmin = true; user.isSupAdmin = true;
...@@ -52,10 +52,10 @@ module.exports = { ...@@ -52,10 +52,10 @@ module.exports = {
// all supervisor information is passed as payload to the token // all supervisor information is passed as payload to the token
return res.json({ return res.json({
user: user, user: user,
server_time: (new Date()).getTime(), server_time: (new Date()).getTime(),
token: sailsTokenAuth.issueToken(user, sails.config.jwt.expiresInMinutes)}); token: sailsTokenAuth.issueToken(user, sails.config.jwt.expiresInMinutes)});
}); });
} else { } else {
return res.json(401, { error: 'Invalid password' }); return res.json(401, { error: 'Invalid password' });
} }
...@@ -69,17 +69,17 @@ module.exports = { ...@@ -69,17 +69,17 @@ module.exports = {
// //
activate: function(req, res) { activate: function(req, res) {
if (!req.body.code) if (!req.body.code)
return res.json(500, {error: "Code must be specified"}); return res.json(500, {error: "Code must be specified"});
if (!req.body.email) if (!req.body.email)
return res.json(500, {error: "Email must be specified"}); return res.json(500, {error: "Email must be specified"});
// Find the supervisor by email and check the code // Find the supervisor by email and check the code
Supervisor.findOne({email: req.body.email}).exec(function findOneCB(err,sup){ Supervisor.findOne({email: req.body.email}).exec(function findOneCB(err,sup){
if (err) if (err)
return res.json(500, {error: "Error finding the user"}); return res.json(500, {error: "Error finding the user"});
// PROBAR A DEVOLVER EL SUPERVISOR VACÍO!! // PROBAR A DEVOLVER EL SUPERVISOR VACÍO!!
if(sup){ if(sup){
var hash_code = sailsHash.hashCode(sup.name + sup.email + sup.phone); var hash_code = sailsHash.hashCode(sup.name + sup.email + sup.phone);
...@@ -94,8 +94,8 @@ module.exports = { ...@@ -94,8 +94,8 @@ module.exports = {
// Model.update(find criteria, updated records).exec(callbacks returns(error, updated records)) // Model.update(find criteria, updated records).exec(callbacks returns(error, updated records))
Supervisor.update({email: req.body.email},{active:'true', password: req.body.password }).exec(function afterwards(err,updated){ Supervisor.update({email: req.body.email},{active:'true', password: req.body.password }).exec(function afterwards(err,updated){
if (err) if (err)
return res.json(500, {error: "Error activating account"}); return res.json(500, {error: "Error activating account"});
console.log('User activated: '+updated[0].email); console.log('User activated: '+updated[0].email);
return res.json(200, {user: updated[0]}); return res.json(200, {user: updated[0]});
}); });
...@@ -103,8 +103,8 @@ module.exports = { ...@@ -103,8 +103,8 @@ module.exports = {
// Model.update(find criteria, updated records).exec(callbacks returns(error, updated records)) // Model.update(find criteria, updated records).exec(callbacks returns(error, updated records))
Supervisor.update({email: req.body.email},{active:'true' }).exec(function afterwards(err,updated){ Supervisor.update({email: req.body.email},{active:'true' }).exec(function afterwards(err,updated){
if (err) if (err)
return res.json(500, {error: "Error activating account"}); return res.json(500, {error: "Error activating account"});
console.log('User activated: '+updated[0].email); console.log('User activated: '+updated[0].email);
return res.json(200, {user: updated[0]}); return res.json(200, {user: updated[0]});
}); });
...@@ -124,19 +124,19 @@ module.exports = { ...@@ -124,19 +124,19 @@ module.exports = {
// //
deactivate: function(req, res) { deactivate: function(req, res) {
if (!req.body.email) if (!req.body.email)
return res.json(500, {error: "Email must be specified"}); return res.json(500, {error: "Email must be specified"});
// Find the supervisor by email // Find the supervisor by email
Supervisor.findOne({email: req.body.email}).exec(function findOneCB(err,sup){ Supervisor.findOne({email: req.body.email}).exec(function findOneCB(err,sup){
if (err) if (err)
return res.json(500, {error: "Error finding the user"}); return res.json(500, {error: "Error finding the user"});
if(sup){ if(sup){
// Model.update(find criteria, updated records).exec(callbacks returns(error, updated records)) // Model.update(find criteria, updated records).exec(callbacks returns(error, updated records))
Supervisor.update({email: req.body.email},{active:'false'}).exec(function afterwards(err,updated){ Supervisor.update({email: req.body.email},{active:'false'}).exec(function afterwards(err,updated){
if (err) if (err)
return res.json(500, {error: "Error activating account"}); return res.json(500, {error: "Error activating account"});
console.log('User deactivated: '+updated[0].email); console.log('User deactivated: '+updated[0].email);
return res.json(200, {user: updated[0]}); return res.json(200, {user: updated[0]});
}); });
...@@ -158,16 +158,16 @@ module.exports = { ...@@ -158,16 +158,16 @@ module.exports = {
Supervisor.create(params).exec(function (err, created) { Supervisor.create(params).exec(function (err, created) {
if (err) { if (err) {
console.log(err); console.log(err);
return res.json(500, {error: 'User not created'}); return res.json(500, {error: 'User not created'});
} }
if (created) { if (created) {
// TODO: Send email confirmation // TODO: Send email confirmation
// created.sendMail(); // created.sendMail();
return res.json({ return res.json({
user: created, user: created,
token: sailsTokenAuth.issueToken(created.id)}); token: sailsTokenAuth.issueToken(created.id)});
} else { } else {
return res.json(500, {error: 'User not created'}); return res.json(500, {error: 'User not created'});
} }
}); });
}, },
...@@ -202,7 +202,7 @@ module.exports = { ...@@ -202,7 +202,7 @@ module.exports = {
[ [
// //
// First, let's get the list of students of the supervisor // First, let's get the list of students of the supervisor
// //
function (cb) { function (cb) {
console.log("getting supervisor's students"); console.log("getting supervisor's students");
StuSup.find({supervisor: idSup}).populate('student').exec(function(err, stusups){ StuSup.find({supervisor: idSup}).populate('student').exec(function(err, stusups){
...@@ -217,7 +217,7 @@ module.exports = { ...@@ -217,7 +217,7 @@ module.exports = {
stusup.student.supervision = 1; stusup.student.supervision = 1;
l_stu.push(stusup.student); l_stu.push(stusup.student);
next(); next();
}, },
function(err) { // when forEach has finished function(err) { // when forEach has finished
if (err) return cb(err); if (err) return cb(err);
cb(); cb();
...@@ -232,7 +232,7 @@ module.exports = { ...@@ -232,7 +232,7 @@ module.exports = {
function (cb) { function (cb) {
console.log("getting office students"); console.log("getting office students");
if (req.token.isSupAdmin) { if (req.token.isSupAdmin) {
Student.find({office: req.token.office.id }).exec(function(err, students){ Student.find({office: req.token.office.id }).exec(function(err, students){
if (err) return cb(err); if (err) return cb(err);
var _ = require('lodash'); var _ = require('lodash');
var l_stu_id = _.pluck(l_stu, 'id'); // get a list of supervised student ids var l_stu_id = _.pluck(l_stu, 'id'); // get a list of supervised student ids
...@@ -264,9 +264,9 @@ module.exports = { ...@@ -264,9 +264,9 @@ module.exports = {
if (err) { if (err) {
sails.log.debug(err); sails.log.debug(err);
console.log(JSON.stringify(err)); console.log(JSON.stringify(err));
return res.json(500, err); return res.json(500, err);
} }
if ( !row || row.length == 0) { if ( !row || row.length == 0) {
student.current_method = 'no_method'; student.current_method = 'no_method';
student.current_instruction = 'no_instruction'; student.current_instruction = 'no_instruction';
} else { } else {
...@@ -279,7 +279,7 @@ module.exports = { ...@@ -279,7 +279,7 @@ module.exports = {
function(err) { function(err) {
if (err) return cb(err); if (err) return cb(err);
cb(); cb();
}); });
} }
], ],
function(err) { // All steps done! return list of students if no error function(err) { // All steps done! return list of students if no error
...@@ -295,7 +295,7 @@ module.exports = { ...@@ -295,7 +295,7 @@ module.exports = {
// returns all the supervisor's pictos // returns all the supervisor's pictos
pictos: function(req, res){ pictos: function(req, res){
var idSup = req.params.id; var idSup = req.params.id;
Supervisor.findOne(idSup).exec(function(err, sup) { Supervisor.findOne(idSup).exec(function(err, sup) {
if(err) if(err)
return res.json(500, {error: 'DB error'}); return res.json(500, {error: 'DB error'});
...@@ -317,7 +317,7 @@ module.exports = { ...@@ -317,7 +317,7 @@ module.exports = {
// //
upload: function (req, res) { upload: function (req, res) {
if(req.method === 'GET') if(req.method === 'GET')
return res.json({'status':'GET not allowed'}); return res.json({'status':'GET not allowed'});
// Call to /upload via GET is error // Call to /upload via GET is error
var uploadFile = req.file('file'); var uploadFile = req.file('file');
...@@ -331,16 +331,16 @@ module.exports = { ...@@ -331,16 +331,16 @@ module.exports = {
var dirUpload = path.join(process.cwd(), '../upload/' + req.body.folder); var dirUpload = path.join(process.cwd(), '../upload/' + req.body.folder);
req.body.filename = sailsHash.hashCode(req.body.filename + Date().toString()) + '.' + req.body.extension; req.body.filename = sailsHash.hashCode(req.body.filename + Date().toString()) + '.' + req.body.extension;
uploadFile.upload({ uploadFile.upload({
dirname: dirUpload, dirname: dirUpload,
saveAs: req.body.filename saveAs: req.body.filename
}, },
function onUploadComplete (err, files) { function onUploadComplete (err, files) {
// Files will be uploaded to .tmp/uploads // Files will be uploaded to .tmp/uploads
if (err) return res.serverError(err); if (err) return res.serverError(err);
// IF ERROR Return and send 500 error with error // IF ERROR Return and send 500 error with error
// Delete old pic (if it isn't the default pic) // Delete old pic (if it isn't the default pic)
Supervisor.findOne({id: req.body.id}).exec(function(err, found){ Supervisor.findOne({id: req.body.id}).exec(function(err, found){
if(found && found.pic != "/app/img/default.jpg"){ if(found && found.pic != "/app/img/default.jpg"){
...@@ -352,13 +352,13 @@ module.exports = { ...@@ -352,13 +352,13 @@ module.exports = {
console.log('Old pic destroyed'); console.log('Old pic destroyed');
}); });
} }
}); });
// Store in DB new URL pic // Store in DB new URL pic
Supervisor.update({id: req.body.id}, {pic: '/upload/' + req.body.folder + '/' + req.body.filename}).exec(function(err, updated){ Supervisor.update({id: req.body.id}, {pic: '/upload/' + req.body.folder + '/' + req.body.filename}).exec(function(err, updated){
if(updated) if(updated)
console.log("Supervisor picture updated"); console.log("Supervisor picture updated");
}); });
files[0].name = req.body.filename; files[0].name = req.body.filename;
delete files[0].fd; delete files[0].fd;
...@@ -381,5 +381,59 @@ module.exports = { ...@@ -381,5 +381,59 @@ module.exports = {
} , 5); } , 5);
} }
); );
} },
/**
* Subscribe to supervisor's room
* @param {Object} request must have the following format:
*
* {
* action: subscribe,
* attributes: { id_sup: '' }
* }
*
* @param {Object} response
* @return {Object} response
*/
subscribe: function(request, response) {
var action = request.param('action');
var attributes = request.param('attributes');
if (action === 'subscribe' && request.isSocket && attributes.id_sup) {
sails.hooks.rooms.subscribeToRoom(
sails.hooks.rooms.supervisor(attributes.id_sup),
request.socket
);
return response.ok();
} else {
return response.badRequest();
}
},
/**
* Unsubscribe from supervisor's room
* @param {Object} request must have the following format:
*
* {
* action: unsubscribe,
* attributes: { id_sup: '' }
* }
*
* @param {Object} response
* @return {Object} response
*/
unsubscribe: function(request, response) {
var action = request.param('action');
var attributes = request.param('attributes');
if (action === 'unsubscribe' && request.isSocket && attributes.id_sup) {
sails.hooks.rooms.unsubscribeFromRoom(
sails.hooks.rooms.supervisor(attributes.ip_sup),
request.socket
);
return response.ok();
} else {
return response.badRequest();
}
}
}; };
/**
* Functions defining the events sent inside the sockets.
* In order to create a new event you have to call the corresponding
* function with the necesary parameters, and you will receive an Object:
*
* {
* name: 'eventName',
* data: 'eventData'
* }
*
* Where 'name' is the name of the event and 'data' is the block of
* information to send.
* @type {Object}
*/
module.exports = function eventsHook (sails) {
return {
/**
* Speacial Function wrapping sails.sockets.broadcast that uses a room
* from sails.hooks.rooms and an event from sails.hooks.events in
* order to broadcast information. This also logs the event as debug
* level information with the message:
*
* "websocketEvent": {
* "room",
* "name",
* "data": { ... }
* }
*
* @param {RoomId} rooms RoomID or IDs where the event will be sent
* @param {Object} event A tuple {name, data} returned by a
* function inside sails.sockets.events
* @param {socket} socketToOmit If specified, this socket will not receive
* the event
*/
broadcastEvent: function (rooms, event, socketToOmit) {
// Ensure rooms is an Array
if (!(rooms instanceof Array)) {
rooms = [rooms];
}
sails.log.debug('"websocketEvent":', JSON.stringify({
rooms: rooms,
name: event.name,
data: event.data,
socketToOmit: socketToOmit ? socketToOmit.id : undefined
}));
sails.sockets.broadcast(
rooms,
event.name,
event.data,
socketToOmit || undefined
);
},
/**
* A supervisor has been linked to a student.
* @param {ID} supId ID of the supervisor
* @param {ID} stu_id ID of the student
* @return {Object} {name, data}
*/
linkSupervisorToStudent: function (supId, stuId) {
return {
name: 'linkSupervisorToStudent',
data: {
sup_id: supId,
stu_id: stuId
}
};
},
/**
* Someone has subscribed/unsubscribed to/from a room. The number
* of subscribes is sent
* @param {number} subscriberCount Number of subscribers
* @return {Object} {name, data}
*/
roomSubscribersChange: function (subscriberCount) {
return {
name: 'update_peers',
data: {
count: subscriberCount
}
};
}
};
};
/**
* This hook is used for managing the rooms IDs.
* Every room created should call one of this functions in order
* to obtain an ID.
* @type {Object}
*/
module.exports = function roomsHook (sails) {
return {
/**
* Special function that subscribes a socket to a given room.
* This creates the connection and logs to debug with this format:
*
* {
* websocketSubscribe: {
* room,
* socket
* }
* }
*
* @param {RoomID} room Room to subscribe
* @param {Socket} socket Socket added to the subscription
*/
subscribeToRoom: function (room, socket) {
sails.log.debug('"websocketSubscribe":', JSON.stringify({
room: room,
socket: socket.id
}));
sails.sockets.join(socket, room, function () {
sails.io.sockets.in(room).clients(function(error, ids) {
if (!error) {
sails.hooks.events.broadcastEvent(
room,
sails.hooks.events.roomSubscribersChange(ids.length)
);
}
});
});
},
/**
* Special function that unsubscribes a socket from a given room.
* This remotes the connection and logs to debug with this format:
*
* {
* websocketUnsubscribe: {
* room,
* socket
* }
* }
*
* @param {RoomID} room Room to unsubscribe from
* @param {Socket} socket Socket removed from the subscription
*/
unsubscribeFromRoom: function (room, socket) {
sails.log.debug('"websocketUnsubscribe":', JSON.stringify({
room: room,
socket: socket.id
}));
sails.sockets.leave(socket, room, function () {
sails.io.sockets.in(room).clients(function(error, ids) {
if (!error) {
sails.hooks.events.broadcastEvent(
room,
sails.hooks.events.roomSubscribersChange(ids.length)
);
}
});
});
},
/**
* Student related rooms
* @param {ID} studentId Student's ID used for creating a room.
* @return {RoomID} RoomID generated
*/
student: function (studentId) {
return 'studentRoom' + studentId;
},
/**
* Supervisor related rooms
* @param {ID} supervisorId Supervisor's ID used for creating a room.
* @return {RoomID} RoomID generated
*/
supervisor: function (supervisorId) {
return 'supervisorRoom' + supervisorId;
}
};
};
'use strict'; /* global io */
/* Services */
// 'use strict';
// This defines an interceptor service in every HTTP request to add session
// token in the header and in every HTTP response to handle non authorized
// connections
//
var module = angular.module('dashboardServices', []);
angular.module('dashboardServices', []) /**
.service('authInterceptor', function ($rootScope, $q, $window) { * This defines an interceptor service in every HTTP request to add session
* token in the header and in every HTTP response to handle non authorized
* connections.
*/
module.factory('AuthInterceptorService', function ($rootScope, $q, $window) {
return { return {
request: function (config) { request: function (config) {
config.headers = config.headers || {}; config.headers = config.headers || {};
...@@ -30,7 +29,68 @@ angular.module('dashboardServices', []) ...@@ -30,7 +29,68 @@ angular.module('dashboardServices', [])
return $q.reject(rejection); return $q.reject(rejection);
} }
}; };
}) });
.config(function ($httpProvider) {
$httpProvider.interceptors.push('authInterceptor'); // Add AuthInterceptorService to $httpProvider.interceptors
}); module.config(function ($httpProvider) {
\ No newline at end of file $httpProvider.interceptors.push('AuthInterceptorService');
});
/**
* IOService that must be used as communication with the server.
* It allows to subscribe/unsubscribe to/from rooms and listen/stop listening
* to events.
*/
module.factory('IOService', function ($window) {
return {
/**
* Send a get request to the server via websocket
* @param {String} url Url of the request
* @param {Function} callback Function to be called on response
*/
get: function(url, callback) {
io.socket.get(url, { token: $window.sessionStorage.token }, callback);
},
/**
* Send a post request to the server via websocket
* @param {String} url Url of the request
* @param {Object} data If this parameter is not an object, it will
* be wrapped inside an object:
* { data, token }, otherwise the token will be
* appended to the parameters
* @param {Function} callback Function to be called on response
*/
post: function (url, data, callback) {
// Create the request data and convert it to an object if necesary
// Then append the token
var requestData = data;
if (typeof requestData !== 'object') {
requestData = { data: data };
}
requestData.token = $window.sessionStorage.token;
io.socket.post(url, requestData, callback);
},
/**
* Subscribes to an event with the given function using websockets
* @param {String} event Event to subscribe to
* @param {Function} callback Function to be executed when the event occurs
*/
on: function (event, callback) {
io.socket.on(event, callback);
},
/**
* Unsubscribe from an event
* @param {String} event Event to unsubscribe from
* @param {Function} callback If specified, only this funcion will be
* unsuscribed from the event, otherwise all
* functions subscribed to this event will be
* removed
*/
off: function (event, callback) {
io.socket.off(event, callback);
}
};
});
...@@ -12,7 +12,8 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl( ...@@ -12,7 +12,8 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
config, config,
$window, $window,
$translate, $translate,
ngToast) { ngToast,
IOService) {
$scope.formdatastudent = { $scope.formdatastudent = {
username: '', username: '',
password: '', password: '',
...@@ -148,6 +149,10 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl( ...@@ -148,6 +149,10 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
$translate('student_deleted').then(function (translation) { $translate('student_deleted').then(function (translation) {
ngToast.success({ content: translation }); ngToast.success({ content: translation });
}); });
IOService.post('/stu/unsubscribe', {
action: 'unsubscribe'
});
}) })
.error(function () { .error(function () {
$translate('student_not_deleted').then(function (translation) { $translate('student_not_deleted').then(function (translation) {
...@@ -157,7 +162,30 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl( ...@@ -157,7 +162,30 @@ dashboardControllers.controller('StudentsCtrl', function StudentsCtrl(
} }
}; };
io.socket.post('/stu/unsubscribe', { action: 'unsubscribe' }); // When a new student is added to the supervisor, we should update
// the student list if necesary
IOService.on('linkSupervisorToStudent', function (eventData) {
eventData.sup_id = parseInt(eventData.sup_id, 10);
eventData.stu_id = parseInt(eventData.stu_id, 10);
if (eventData.sup_id === $scope.user.id) {
IOService.get('/stu/' + eventData.stu_id, function (studentData) {
var i;
var studentAlreadyAdded = false;
for (i = 0; i < $scope.students.length; i++) {
if ($scope.students[i].id === eventData.stu_id) {
studentAlreadyAdded = true;
break;
}
}
if (!studentAlreadyAdded) {
$scope.students.push(studentData);
$scope.$apply();
}
});
}
});
}); });
/** /**
......
/* global io */
'use strict'; 'use strict';
//----------------------- //-----------------------
// Supervisor Controller // Supervisor Controller
//----------------------- //-----------------------
dashboardControllers.controller('SupervisorCtrl', function SupervisorCtrl($scope, $window, $location) { dashboardControllers.controller('SupervisorCtrl', function SupervisorCtrl($scope, $window, $location, IOService) {
// Restore user data from session // Restore user data from session
var user = JSON.parse($window.sessionStorage.user); var user = JSON.parse($window.sessionStorage.user);
// Assign values this way (like an object) to ensure it's the parent scope // Assign values this way (like an object) to ensure it's the parent scope
$scope.user.id = user.id; $scope.user.id = user.id;
$scope.user.name = user.name; $scope.user.name = user.name;
...@@ -22,4 +24,11 @@ dashboardControllers.controller('SupervisorCtrl', function SupervisorCtrl($scope ...@@ -22,4 +24,11 @@ dashboardControllers.controller('SupervisorCtrl', function SupervisorCtrl($scope
$location.path('/setup'); $location.path('/setup');
}; };
}); // Subscribe to the supervisor's room
\ No newline at end of file IOService.post('/sup/subscribe', {
action: 'subscribe',
attributes: {
id_sup: $scope.user.id
}
});
});
...@@ -45,7 +45,9 @@ module.exports.policies = { ...@@ -45,7 +45,9 @@ module.exports.policies = {
destroy: ['tokenAuth', 'isAdmin'], destroy: ['tokenAuth', 'isAdmin'],
students: ['tokenAuth'], students: ['tokenAuth'],
pictos: ['tokenAuth'], pictos: ['tokenAuth'],
upload: ['tokenAuth'] upload: ['tokenAuth'],
subscribe: ['tokenAuth'],
unsubscribe: ['tokenAuth']
}, },
DeviceController: { DeviceController: {
register: true, register: true,
......
/** /**
* Route Mappings * Route Mappings
* (sails.config.routes) * (sails.config.routes)
* *
* Your routes map URLs to views and controllers. * Your routes map URLs to views and controllers.
* *
* If Sails receives a URL that doesn't match any of the routes below, * If Sails receives a URL that doesn't match any of the routes below,
...@@ -22,16 +22,7 @@ ...@@ -22,16 +22,7 @@
module.exports.routes = { module.exports.routes = {
/*************************************************************************** // Supervisor
* *
* Make the view located at `views/homepage.ejs` (or `views/homepage.jade`, *
* etc. depending on your default view engine) your home page. *
* *
* (Alternatively, remove this and add an `index.html` file in your *
* `assets` directory) *
* *
***************************************************************************/
'post /sup/login': 'SupervisorController.login', 'post /sup/login': 'SupervisorController.login',
'post /sup': 'SupervisorController.create', 'post /sup': 'SupervisorController.create',
'put /sup': 'SupervisorController.update', 'put /sup': 'SupervisorController.update',
...@@ -40,13 +31,15 @@ module.exports.routes = { ...@@ -40,13 +31,15 @@ module.exports.routes = {
'get /sup/get/:id': 'SupervisorController.findOne', 'get /sup/get/:id': 'SupervisorController.findOne',
'post /sup/activate': 'SupervisorController.activate', 'post /sup/activate': 'SupervisorController.activate',
'get /sup/:id/students': 'SupervisorController.students', 'get /sup/:id/students': 'SupervisorController.students',
'post /sup/subscribe': 'SupervisorController.subscribe',
'post /sup/unsubscribe': 'SupervisorController.unsubscribe',
'post /sup/upload': 'SupervisorController.upload', // upload profile img file
// Pictos // Pictos
'get /sup/:id/pictos': 'SupervisorController.pictos', 'get /sup/:id/pictos': 'SupervisorController.pictos',
'get /sup/:id/pic_categories/:id_cat': 'Picto.categories', 'get /sup/:id/pic_categories/:id_cat': 'Picto.categories',
'get /sup/:id/pic_fromcategory/:id_cat': 'Picto.fromcategory', 'get /sup/:id/pic_fromcategory/:id_cat': 'Picto.fromcategory',
'post /sup/upload': 'SupervisorController.upload', // upload profile img file
// Pictogram administration // Pictogram administration
'post /admin/login': 'AdminController.login', 'post /admin/login': 'AdminController.login',
...@@ -65,7 +58,7 @@ module.exports.routes = { ...@@ -65,7 +58,7 @@ module.exports.routes = {
'get /office/get/:id': 'OfficeController.get', 'get /office/get/:id': 'OfficeController.get',
'get /office/get_all': 'OfficeController.getAll', 'get /office/get_all': 'OfficeController.getAll',
'get /office/get/:id/supervisors': 'OfficeController.supervisors', 'get /office/get/:id/supervisors': 'OfficeController.supervisors',
///////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////
// STUDENTS ///////////////////////////////////////////////////////////// // STUDENTS /////////////////////////////////////////////////////////////
// get /student --> List all students // get /student --> List all students
...@@ -127,16 +120,16 @@ module.exports.routes = { ...@@ -127,16 +120,16 @@ module.exports.routes = {
// (check them by inspecting the policies.js file) // (check them by inspecting the policies.js file)
// Reports // Reports
'get /ws/:id_stu/year/:year': 'WorkingSessionController.per_year', 'get /ws/:id_stu/year/:year': 'WorkingSessionController.per_year',
'get /ws/:id_stu/month/:month': 'WorkingSessionController.per_month', 'get /ws/:id_stu/month/:month': 'WorkingSessionController.per_month',
// Working Sessions > Tries // Working Sessions > Tries
'get /ws/:id_ws/tries': 'WorkingSession.tries', 'get /ws/:id_ws/tries': 'WorkingSession.tries',
'post /workingsession/:id_ws/close': 'WorkingSession.close', 'post /workingsession/:id_ws/close': 'WorkingSession.close',
'put /try/:id': 'Try.update', 'put /try/:id': 'Try.update',
'post /try': 'Try.create', 'post /try': 'Try.create',
// Instructions > Working Sessions // Instructions > Working Sessions
'get /instruction/:id_ins/ws': 'Instruction.ws', 'get /instruction/:id_ins/ws': 'Instruction.ws',
//'get /instruction/:id_ins/tries': 'Instruction.tries', // BORRAR aquí y en wiki //'get /instruction/:id_ins/tries': 'Instruction.tries', // BORRAR aquí y en wiki
......
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