gestion de terapeutas tutores y altas/bajas operativo

parent dc1fad4c
...@@ -146,7 +146,7 @@ CREATE TABLE IF NOT EXISTS `office` ( ...@@ -146,7 +146,7 @@ CREATE TABLE IF NOT EXISTS `office` (
`phone1` varchar(20) COLLATE utf8_unicode_ci NOT NULL, `phone1` varchar(20) COLLATE utf8_unicode_ci NOT NULL,
`phone2` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL, `phone2` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
`admin` int(11) DEFAULT NULL COMMENT 'Teacher administrator, able to associate students to teachers', `admin` int(11) DEFAULT NULL COMMENT 'Teacher administrator, able to associate students to teachers',
`max_students` int(4) DEFAULT 2 COMMENT 'Maximun number of enrolments for a given office', `max_students` int(4) DEFAULT 10 COMMENT 'Maximun number of enrolments for a given office',
`current_students` int(4) DEFAULT 0 COMMENT 'calculable attr: current number of enrolments', `current_students` int(4) DEFAULT 0 COMMENT 'calculable attr: current number of enrolments',
PRIMARY KEY (`id`), PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`), UNIQUE KEY `email` (`email`),
......
...@@ -90,38 +90,57 @@ module.exports = { ...@@ -90,38 +90,57 @@ module.exports = {
* exp: 1231292, * exp: 1231292,
* attributes: { @see Student.getValidAttributes() }, * attributes: { @see Student.getValidAttributes() },
* current_method: 'Do Things', // May be null * current_method: 'Do Things', // May be null
* current_instruction: 'Do Stuff' // May be null * current_instruction: 'Do Stuff', // May be null
* supervision: 0|1|2, // supervision level according to requester 0 -> office admin, 1 -> tutor, 2 -> therapist
* } * }
*/ */
getInfo: function (req, res) { getInfo: function (req, res) {
Student.findOne({ Student.findOne({id: req.params.id_stu})
id: req.params.id_stu .then(function (student) {
}) if (!student)
.then(function (student) { throw new Error("student not found");
if (student) {
student.current_method = null; student.current_method = null;
student.current_instruction = null; student.current_instruction = null;
VStuLastInstruction
.findOne({ // recover last instruction to complete student info
student: student.id var stu_last_inst = VStuLastInstruction.findOne({student: student.id})
}) .then(function (stu_last_inst) {
.then(function (studentLastInstruction) { return stu_last_inst;
if (studentLastInstruction) { });
student.current_method = studentLastInstruction.met_name;
student.current_instruction = studentLastInstruction.ins_name; // determine supervision level of the requester on the student
} var stu_sup = StuSup.findOne({id_stu: student.id, id_sup: req.token.id})
res.ok(student); .then(function (stu_sup) {
}) return stu_sup
.catch(function (error) { });
res.serverError();
}); return [student, stu_last_inst, stu_sup];
} else { })
res.notFound(); .spread(function (student, stu_last_inst, stu_sup) {
} if (stu_last_inst) {
}) student.current_method = stu_last_inst.met_name;
.catch(function () { student.current_instruction = stu_last_inst.ins_name;
res.serverError(); }
});
// requester has no relation
student.supervision = -1;
if (!stu_sup && req.token.office && student.office == req.token.office.id)
student.supervision = 0; // requester is admin of the office
else if (stu_sup && !req.token.office)
student.supervision = 1; // requester is tutor of the studend
else if (stu_sup && req.token.office && student.office == req.token.office.id)
student.supervision = 2; // requester is supervisor of student
if (student.supervision == -1) // should not hace access!!!
return res.forbidden("Access to this student should not be granted to you");
return res.ok(student);
})
.catch(function (err) {
return res.notFound(err);
});
}, },
// //
...@@ -353,7 +372,9 @@ module.exports = { ...@@ -353,7 +372,9 @@ module.exports = {
* Creates a relation between the student and a given supervisor. * Creates a relation between the student and a given supervisor.
* It broadcasts the even linkSupervisorToStudent to both the student room * It broadcasts the even linkSupervisorToStudent to both the student room
* and the supervisor room. * and the supervisor room.
* @param {request} {} (with studentId and supervisorId as url parameters) * @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} {} * @param {response} {}
*/ */
link_supervisor: function (req, res) { link_supervisor: function (req, res) {
...@@ -362,9 +383,8 @@ module.exports = { ...@@ -362,9 +383,8 @@ module.exports = {
supervisor: req.param('id_sup') supervisor: req.param('id_sup')
}) })
.then(function (stuSup) { .then(function (stuSup) {
if (!stuSup) { if (!stuSup)
throw new Error('stusup not created'); throw new Error('stusup not created');
}
const socketToOmit = (req.isSocket) ? req.socket : undefined; const socketToOmit = (req.isSocket) ? req.socket : undefined;
const linkSupervisorToStudentEvent = sails.hooks.events.linkSupervisorToStudent( const linkSupervisorToStudentEvent = sails.hooks.events.linkSupervisorToStudent(
...@@ -383,18 +403,40 @@ module.exports = { ...@@ -383,18 +403,40 @@ module.exports = {
socketToOmit socketToOmit
); );
res.ok(); return stuSup;
}) })
.catch(() => { .catch((err) => {
StuSup.findOne({ student: req.param('id_stu'), supervisor: req.param('id_sup') }) StuSup.findOne({ student: req.param('id_stu'), supervisor: req.param('id_sup') })
.then((stuSup) => { .then((stuSup) => {
if (stuSup) { // It was already there!
res.ok(); if (stuSup)
} else { return stuSup;
res.badRequest(); else
throw err;
});
})
.then((stuSup) => {
// 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')})
.then((stu) => {
if (stu) {
if (req.body.asTherapist)
sup.office = stu.office;
else
sup.office = NULL;
delete sup.password;
sup.save();
}
});
} }
}) });
.catch(() => res.serverError()); return res.ok();
})
.catch((err) => {
return res.serverError("Error: " + err);
}); });
}, },
...@@ -412,15 +454,9 @@ module.exports = { ...@@ -412,15 +454,9 @@ module.exports = {
}) })
.then((stuSup) => { .then((stuSup) => {
if (!stuSup) if (!stuSup)
res.badRequest(); throw new Error("student and supervisor are not linked");
stuSup.destroy({ stuSup.destroy();
student: req.param('id_stu'),
supervisor: req.param('id_sup')
}).then(function(err){
if (err)
throw err;
});
const socketToOmit = req.isSocket ? req.socket : undefined; const socketToOmit = req.isSocket ? req.socket : undefined;
const unlinkSupervisorFromStudentEvent = sails.hooks.events.unlinkSupervisorFromStudent( const unlinkSupervisorFromStudentEvent = sails.hooks.events.unlinkSupervisorFromStudent(
...@@ -440,10 +476,10 @@ module.exports = { ...@@ -440,10 +476,10 @@ module.exports = {
socketToOmit socketToOmit
); );
res.ok(); return res.ok();
}) })
.catch(() => { .catch((err) => {
res.serverError(); res.serverError("Error unliking student: " + err);
}); });
}, },
......
...@@ -58,43 +58,53 @@ module.exports = { ...@@ -58,43 +58,53 @@ module.exports = {
var email = req.body.email; var email = req.body.email;
var password = req.body.password; var password = req.body.password;
if (email && password) { if (!email || !password)
Supervisor.findOneByEmail(email).then(function (supervisor) { return res.badRequest('No email or or password');
if (supervisor) {
if (bcrypt.compareSync(password, supervisor.password)) { Supervisor.findOneByEmail(email).then(function (supervisor) {
Office.findOne(supervisor.office).then(function (office) {
if (office) { if (!supervisor)
supervisor.office = office; throw new Error('Invalid user');
supervisor.isSupAdmin = (office.admin === supervisor.id);
res.ok({ if (!bcrypt.compareSync(password, supervisor.password))
user: supervisor, throw new Error('Invalid password');
server_time: (new Date()).getTime(),
token: sailsTokenAuth.issueToken(supervisor, sails.config.jwt.expiresInMinutes) return (supervisor);
});
} else { }).then(function (supervisor) {
res.ok({
user: supervisor, var stuSup = StuSup.findOne({id_sup: supervisor.id})
server_time: (new Date()).getTime(), .then(function (stuSup) {
token: sailsTokenAuth.issueToken(supervisor, sails.config.jwt.expiresInMinutes) if (!stuSup)
}); throw new Error("User without students linked to");
}
}) return stuSup;
.catch(function () { });
res.serverError();
}); var office = Office.findOne({id: supervisor.office})
} else { .then(function (office) {
res.badRequest('Invalid password'); return office;
} });
} else {
res.badRequest('Invalid user'); return [office, stuSup, supervisor];
}
}) }).spread(function(office, stuSup, supervisor) {
.catch(function () {
res.badRequest('Invalid user or password'); supervisor.office = office;
if (office)
supervisor.isSupAdmin = (office.admin === supervisor.id);
return res.ok({
user: supervisor,
server_time: (new Date()).getTime(),
token: sailsTokenAuth.issueToken(supervisor, sails.config.jwt.expiresInMinutes)
}); });
} else { })
res.badRequest('No email or or password');
} .catch(function (err) {
return res.badRequest("Error in login: " + err);
});
}, },
/** /**
...@@ -115,29 +125,23 @@ module.exports = { ...@@ -115,29 +125,23 @@ module.exports = {
* } * }
*/ */
activate: function (req, res) { activate: function (req, res) {
if (req.body.code && req.body.email) { if (!req.params.token)
Supervisor.findOne({ email: req.body.email }).then(function (supervisor) { return res.badRequest("Invalid activation URL");
if (supervisor) { sailsTokenAuth.verifyToken(req.params.token, function(err, token) {
/* @TODO check user validation code */ if (err)
supervisor.active = true; return res.badRequest("Invalid token");
supervisor.save(function (error) { Supervisor.findOne(token).then(function (supervisor) {
if (error) { if (!supervisor)
res.serverError(); throw new Error("Error when looking for user");
} else { supervisor.active = true;
res.ok(); delete supervisor.password;
} supervisor.save();
}); return res.view('accountActivated', {sup: supervisor, login_url: req.protocol + '://' + req.headers.host + '/app'});
res.ok(supervisor);
} else {
res.badRequest();
}
}) })
.catch(function () { .catch(function (err) {
res.serverError(); return res.serverError("Error when activating account " + err);
}); });
} else { });
req.badRequest();
}
}, },
/** /**
...@@ -202,6 +206,8 @@ module.exports = { ...@@ -202,6 +206,8 @@ module.exports = {
create: function (req, res) { create: function (req, res) {
var params = req.params.all(); var params = req.params.all();
sails.log.debug("Creating supervisor with params " + JSON.stringify(params));
if (params.name && if (params.name &&
params.surname && params.surname &&
params.gender && params.gender &&
...@@ -220,20 +226,39 @@ module.exports = { ...@@ -220,20 +226,39 @@ module.exports = {
lang: params.lang || null, lang: params.lang || null,
ttsEngine: params.ttsEngine || null ttsEngine: params.ttsEngine || null
}).then(function (supervisor) { }).then(function (supervisor) {
if (supervisor) {
/* @TODO send email confirmation */ if (!supervisor)
res.serverError("Supervisor created but returned null");
sails.log.debug("SUPERVISOR: " + JSON.stringify(supervisor));
/* Send email confirmation */
var message = sails.__({
phrase: 'signin_mail',
locale: params.lang || 'es-es'
}) + req.protocol + '://' + req.headers.host + '/sup/activate/' + sailsTokenAuth.issueToken(supervisor.id, 60*24*7); // expires in 1 week
sails.log.debug("Sending activation email: \n" + message);
mailService.mailer()
.send({
to: params.email,
text: message
})
.then(() => {
res.ok({ res.ok({
user: supervisor, user: supervisor,
token: sailsTokenAuth.issueToken(supervisor.id) token: sailsTokenAuth.issueToken(supervisor.id)
}); });
} else { })
res.serverError(); .catch((err) => {
} res.serverError("Mail could not be sent " + err);
}).catch(function () { });
res.serverError();
}).catch(function (err) {
res.serverError("Supervisor could not be created: " + err);
}); });
} else { } else {
res.badRequest(); res.badRequest("Invalid params");
} }
}, },
......
...@@ -274,6 +274,7 @@ module.exports = { ...@@ -274,6 +274,7 @@ module.exports = {
// //
// Class method for getting the list of therapists associated to a given // Class method for getting the list of therapists associated to a given
// student // student
// NOTE: A therapist is a supervisor assigned to an office
therapists: function(id_stu, callback) { therapists: function(id_stu, callback) {
StuSup.find({id_stu: id_stu}).populate('supervisor').exec(function(err, stuSups) { StuSup.find({id_stu: id_stu}).populate('supervisor').exec(function(err, stuSups) {
var l = []; var l = [];
...@@ -300,6 +301,7 @@ module.exports = { ...@@ -300,6 +301,7 @@ module.exports = {
// //
// Class method for getting the list of tutors associated to a given // Class method for getting the list of tutors associated to a given
// student // student
// NOTE: A tutor is a supervisor not assigned to any office
tutors: function(id_stu, callback) { tutors: function(id_stu, callback) {
StuSup.find({id_stu: id_stu}).populate('supervisor').exec(function(err, stuSups) { StuSup.find({id_stu: id_stu}).populate('supervisor').exec(function(err, stuSups) {
var l = []; var l = [];
......
...@@ -125,49 +125,6 @@ module.exports = { ...@@ -125,49 +125,6 @@ module.exports = {
return supervisor; return supervisor;
}, },
//
// sendMail
// adds a new entry into pending_registration
// and send an email confirmation to the user
sendMail: function() {
var hash_code = (this.name + this.email + this.phone);
// Send email confirmation
var nodemailer = require('nodemailer');
// create reusable transporter object using SMTP transport
var transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: 'noreply@yottacode.com',
pass: ''
}
});
var textBody = '';
// Diferents links for tutor (set password too) and parents
if(this.office) textBody = '<p>You must to click in the following link to validate your account and set your password</p><p><a href="http://localhost:1337/app/#/logintutor/'+ hash_code +'/'+ this.email +'">Click here</a></p>';
else textBody = '<p>You must to click in the following link to validate your account: </p><p><a href="http://localhost:1337/app/#/login/'+ hash_code +'/'+ this.email +'">Click here</a></p>';
// setup e-mail data with unicode symbols
var mailOptions = {
from: 'Pictogram <noreply@yottacode.com>', // sender address
to: this.email, // list of receivers
subject: 'Account activation', // Subject line
//text: 'plain text', // plaintext body
html: textBody // html body
};
// send mail with defined transport object
transporter.sendMail(mailOptions, function(error, info){
if(error){
console.log(error);
}else{
console.log('Message sent: ' + info.response);
}
});
/* End email confirmation */
}
}, },
// //
......
...@@ -26,13 +26,13 @@ module.exports = function(req, res, next) { ...@@ -26,13 +26,13 @@ module.exports = function(req, res, next) {
} else { // No token provided } else { // No token provided
return res.json(401, {err: 'No Authorization header was found'}); return res.json(401, {err: 'No Authorization header was found'});
} }
// //
// We have a token, let's verify it // We have a token, let's verify and decode it
// //
sailsTokenAuth.verifyToken(token, function(err, token) { sailsTokenAuth.verifyToken(token, function(err, token) {
if (err) return res.json(401, {err: 'Invalid token ' + token}); if (err) return res.json(401, {err: 'Invalid token ' + token});
req.token = token; req.token = token;
next(); next();
}); });
}; };
\ No newline at end of file
var mailerService = require('sails-service-mailer');
module.exports.mailer = function() {
return mailerService('sendmail', {
from: 'no-replay@yottacode.com',
subject: sails.__('notification_from_pictogram'),
provider: {
path: '/usr/sbin/sendmail'
}
});
};
...@@ -3,7 +3,7 @@ var jwt = require('jsonwebtoken'); ...@@ -3,7 +3,7 @@ var jwt = require('jsonwebtoken');
module.exports.issueToken = function(payload, minutes) { module.exports.issueToken = function(payload, minutes) {
var token; var token;
if (minutes) if (minutes)
token = jwt.sign(payload, sails.config.jwt.secret, {'expiresInMinutes': 120}); token = jwt.sign(payload, sails.config.jwt.secret, {'expiresInMinutes': minutes});
else else
token = jwt.sign(payload, sails.config.jwt.secret); token = jwt.sign(payload, sails.config.jwt.secret);
return token; return token;
...@@ -11,4 +11,4 @@ module.exports.issueToken = function(payload, minutes) { ...@@ -11,4 +11,4 @@ module.exports.issueToken = function(payload, minutes) {
module.exports.verifyToken = function(token, verified) { module.exports.verifyToken = function(token, verified) {
return jwt.verify(token, sails.config.jwt.secret, {}, verified); return jwt.verify(token, sails.config.jwt.secret, {}, verified);
}; };
\ No newline at end of file
...@@ -148,6 +148,7 @@ ...@@ -148,6 +148,7 @@
"nobegin": "No started", "nobegin": "No started",
"no_method": "No method defined", "no_method": "No method defined",
"no_instruction": "No instruction defined", "no_instruction": "No instruction defined",
"no_students_for_user": "You are not associated to any students. Please ask your office to link your account to a Pictogram student.",
"no_space_in_category": "No space left in category", "no_space_in_category": "No space left in category",
"normal": "Normal", "normal": "Normal",
"notes": "Notes", "notes": "Notes",
...@@ -288,7 +289,7 @@ ...@@ -288,7 +289,7 @@
"use_categories": "Use categories", "use_categories": "Use categories",
"username": "Name of account (no spaces)", "username": "Name of account (no spaces)",
"username_exists": "There is already an account with name '{{username}}'", "username_exists": "There is already an account with name '{{username}}'",
"user_created": "User {{name}} {{surname}} created. Please, contact your therapist office to validate the account.", "user_created": "User {{name}} {{surname}} created. An email with instructions to activate the account has been sent.",
"user_exists": "User with email {{email}} already exists", "user_exists": "User with email {{email}} already exists",
"validate_fail": "The user account couldn't be validated", "validate_fail": "The user account couldn't be validated",
"validate_success": "The user account has been validated. Now, you can login from the link below", "validate_success": "The user account has been validated. Now, you can login from the link below",
......
...@@ -147,6 +147,7 @@ ...@@ -147,6 +147,7 @@
"no": "No", "no": "No",
"no_method": "Método sin definir", "no_method": "Método sin definir",
"no_instruction": "Instrucción sin definir", "no_instruction": "Instrucción sin definir",
"no_students_for_user": "Su cuenta no está asociada a ningún estudiante. Por favor, contacto con su gabinete para enlazar su cuenta a un estudiante.",
"no_space_in_category": "No queda espacio en la categoría", "no_space_in_category": "No queda espacio en la categoría",
"nobegin": "Sin iniciar", "nobegin": "Sin iniciar",
"normal": "Normal", "normal": "Normal",
...@@ -289,7 +290,7 @@ ...@@ -289,7 +290,7 @@
"use_categories": "Usar categorías", "use_categories": "Usar categorías",
"username": "Nombre cuenta (sin espacios)", "username": "Nombre cuenta (sin espacios)",
"username_exists": "Ya existe un usuario con nombre de cuenta '{{username}}'", "username_exists": "Ya existe un usuario con nombre de cuenta '{{username}}'",
"user_created": "Usuario {{name}} {{surname}} creado. Contacte con su gabinete para validar la cuenta.", "user_created": "Usuario {{name}} {{surname}} creado. Se ha enviado un email de confirmación para activar su cuenta.",
"user_exists": "Ya existe el usuario con email '{{email}}'", "user_exists": "Ya existe el usuario con email '{{email}}'",
"validate_fail": "La cuenta no se ha podido validar", "validate_fail": "La cuenta no se ha podido validar",
"validate_success": "Cuenta validada. Puede iniciar sesión desde el enlace inferior", "validate_success": "Cuenta validada. Puede iniciar sesión desde el enlace inferior",
......
...@@ -68,11 +68,17 @@ function LoginCtrl( ...@@ -68,11 +68,17 @@ function LoginCtrl(
// Redirección // Redirección
$location.path('/students'); $location.path('/students');
}) })
.error(function () { .error(function (err) {
delete $window.sessionStorage.token; delete $window.sessionStorage.token;
$translate('login_fail').then(function (translation) { if (err.search("without students") > 0) {
ngToast.danger({ content: translation }); $translate('no_students_for_user').then(function (translation) {
}); ngToast.warning({ content: translation });
});
} else {
$translate('login_fail').then(function (translation) {
ngToast.danger({ content: translation });
});
}
}); });
}; };
}); });
...@@ -22,7 +22,7 @@ function SignInCtrl($scope, ...@@ -22,7 +22,7 @@ function SignInCtrl($scope,
email_confirm: '', email_confirm: '',
password: '', password: '',
password_confirm: '', password_confirm: '',
lang: 'es' lang: 'es-es'
}; };
reCAPTCHA.setPublicKey('6LdLjh0TAAAAANblo_KUGNnmRZuIetOkdjdhj1b6'); reCAPTCHA.setPublicKey('6LdLjh0TAAAAANblo_KUGNnmRZuIetOkdjdhj1b6');
...@@ -47,11 +47,10 @@ function SignInCtrl($scope, ...@@ -47,11 +47,10 @@ function SignInCtrl($scope,
$scope.captcha = ''; $scope.captcha = '';
if ($scope.signInForm.$valid) { if (!$scope.signInForm.$valid)
$scope.showdialog = true;
} else {
return; return;
}
$scope.showdialog = true;
$http $http
.post(config.backend + '/sup', $scope.formdata) .post(config.backend + '/sup', $scope.formdata)
...@@ -61,7 +60,7 @@ function SignInCtrl($scope, ...@@ -61,7 +60,7 @@ function SignInCtrl($scope,
}); });
}) })
.error(function () { .error(function () {
$translate('user_exists').then(function (translation) { $translate('user_exists', {email: $scope.formdata.email}).then(function (translation) {
ngToast.danger({ content: translation }); ngToast.danger({ content: translation });
}); });
}); });
......
...@@ -5,17 +5,17 @@ ...@@ -5,17 +5,17 @@
<!-- Rejilla 3 elementos de igual ancho --> <!-- Rejilla 3 elementos de igual ancho -->
<div class="col-lg-2">&nbsp;</div> <div class="col-lg-2">&nbsp;</div>
<div class="col-lg-8"> <div class="col-lg-8">
<p class="text-center"> <p class="text-center">
<img src="img/logo_pictogram.png" alt="Pictogram" title="Pictogram" /> <img src="img/logo_pictogram.png" alt="Pictogram" title="Pictogram" />
</p> </p>
<div class="page-header"> <div class="page-header">
<h2 translate>register</h2> <h2 translate>register</h2>
</div> </div>
<div id="signin"> <div id="signin">
<!-- Logo Pictogram --> <!-- Logo Pictogram -->
<!-- Formulario --> <!-- Formulario -->
<form name="signInForm" role="form" ng-submit="signin()"> <form name="signInForm" role="form" ng-submit="signin()">
<fieldset> <fieldset>
...@@ -27,10 +27,10 @@ ...@@ -27,10 +27,10 @@
<input type="text" class="form-control" id="signin_surname" placeholder="{{ 'surname' | translate }}" required ng-model="formdata.surname"/> <input type="text" class="form-control" id="signin_surname" placeholder="{{ 'surname' | translate }}" required ng-model="formdata.surname"/>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" id="signin_address" placeholder="{{ 'address' | translate }}" required ng-model="formdata.address"/> <input type="text" class="form-control" id="signin_address" placeholder="{{ 'address' | translate }}" ng-model="formdata.address"/>
</div> </div>
<div class="form-group"> <div class="form-group">
<input type="text" class="form-control" id="signin_phone" placeholder="{{ 'phone' | translate }}" required ng-model="formdata.phone"/> <input type="text" class="form-control" id="signin_phone" placeholder="{{ 'phone' | translate }}" ng-model="formdata.phone"/>
</div> </div>
<div class="form-group"> <div class="form-group">
<select class="form-control" name="signin_gender" id="signin_gender" ng-model="formdata.gender"> <select class="form-control" name="signin_gender" id="signin_gender" ng-model="formdata.gender">
...@@ -41,9 +41,9 @@ ...@@ -41,9 +41,9 @@
</fieldset> </fieldset>
<fieldset> <fieldset>
<legend> <legend>
{{ 'email' | translate }} {{ 'email' | translate }}
<span class="color_red text_sm pull-right" ng-show="formdata.email != formdata.email_confirm" translate>email_match</span> <span class="color_red text_sm pull-right" ng-show="formdata.email != formdata.email_confirm" translate>email_match</span>
</legend> </legend>
<div class="form-group"> <div class="form-group">
...@@ -71,8 +71,8 @@ ...@@ -71,8 +71,8 @@
<legend translate>language</legend> <legend translate>language</legend>
<div class="form-group"> <div class="form-group">
<select class="form-control" name="signin_language" id="signin_language" ng-model="formdata.lang"> <select class="form-control" name="signin_language" id="signin_language" ng-model="formdata.lang">
<option value="es" selected>Español</option> <option value="es-es" selected>Español</option>
<option value="en">English</option> <option value="en-gb">English</option>
</select> </select>
</div> </div>
</fieldset> </fieldset>
......
...@@ -131,7 +131,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -131,7 +131,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
* Get a supervisor by their email and updates the $scope.supToAdd element. * Get a supervisor by their email and updates the $scope.supToAdd element.
* The email used for search is fetched from $scope.email_sup. * The email used for search is fetched from $scope.email_sup.
*/ */
$scope.getSupervisorByEmail = function () { $scope.search_sup = function () {
// Find tutor by email // Find tutor by email
$http.get(config.backend + '/sup/email/' + $scope.email_sup) $http.get(config.backend + '/sup/email/' + $scope.email_sup)
.success(function (data) { .success(function (data) {
...@@ -154,14 +154,14 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -154,14 +154,14 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
}); });
}; };
// Add supervisor (father or mother) // Links supervisor with student
$scope.add_sup = function () { $scope.add_sup = function () {
var stusup = { var stusup = {
student: $scope.studentData.id, student: $scope.studentData.id,
supervisor: $scope.supToAdd.id supervisor: $scope.supToAdd.id
}; };
$http.get(config.backend + '/stu/' + $scope.studentData.id + '/sup/' + $scope.supToAdd.id) $http.post(config.backend + '/stu/' + $scope.studentData.id + '/sup/' + $scope.supToAdd.id, {asTherapist: true})
.success(function (data) { .success(function (data) {
// Assign the info of supervisor to add // Assign the info of supervisor to add
stusup.supervisor = $scope.supToAdd; stusup.supervisor = $scope.supToAdd;
...@@ -240,7 +240,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl( ...@@ -240,7 +240,7 @@ dashboardControllers.controller('StudentSetupCtrl', function StudentSetupCtrl(
supervisor: $scope.tutorToAdd.id supervisor: $scope.tutorToAdd.id
}; };
$http.get(config.backend + '/stu/' + $scope.studentData.id + '/sup/' + $scope.tutorToAdd.id) $http.post(config.backend + '/stu/' + $scope.studentData.id + '/sup/' + $scope.tutorToAdd.id)
.success(function (data) { .success(function (data) {
// Assign the info of supervisor to add // Assign the info of supervisor to add
stusup.supervisor = $scope.tutorToAdd; stusup.supervisor = $scope.tutorToAdd;
......
...@@ -93,6 +93,7 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl( ...@@ -93,6 +93,7 @@ dashboardControllers.controller('StudentCtrl', function StudentCtrl(
$scope.studentData.attributes = data.attributes; $scope.studentData.attributes = data.attributes;
$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
// 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
......
...@@ -13,8 +13,8 @@ ...@@ -13,8 +13,8 @@
</div> </div>
<!-- Agrupar los enlaces de navegación, los formularios y cualquier otro elemento que se pueda ocultar al minimizar la barra --> <!-- Agrupar los enlaces de navegación, los formularios y cualquier otro elemento que se pueda ocultar al minimizar la barra -->
<div class="row"> <div class="row">
<div class="col-md-1"> <div class="col-md-1">
<div class="thumbnail img_profile"> <div class="thumbnail img_profile">
<img ng-src="{{studentData.pic}}" alt="" title="" /> <img ng-src="{{studentData.pic}}" alt="" title="" />
</div> </div>
...@@ -29,19 +29,19 @@ ...@@ -29,19 +29,19 @@
<div class="row"> <div class="row">
<div class="col-md-12"> <div class="col-md-12">
<ul class="nav nav-tabs tabs_student"> <ul class="nav nav-tabs tabs_student">
<li role="presentation" ng-class="{'active' : nav.tab == 'collections'}"> <li role="presentation" ng-class="{'active' : nav.tab == 'collections'}" ng-if="studentData.supervision != 0">
<a href="/app/#/student/{{studentData.id}}/collections" ng-click="nav.tab = ''"><span class="glyphicon glyphicon-th" aria-hidden="true"></span> {{ 'collections' | translate }}</a> <a href="/app/#/student/{{studentData.id}}/collections" ng-click="nav.tab = ''"><span class="glyphicon glyphicon-th" aria-hidden="true"></span> {{ 'collections' | translate }}</a>
</li> </li>
<li role="presentation" ng-class="{'active' : nav.tab == 'instructions'}"> <li role="presentation" ng-class="{'active' : nav.tab == 'instructions'}" ng-if="studentData.supervision == 2">
<a href="/app/#/student/{{studentData.id}}/instructions" ng-click="nav.tab = 'instructions'"><span class="glyphicon glyphicon-tasks" aria-hidden="true"></span> {{ 'instructions' | translate }}</a> <a href="/app/#/student/{{studentData.id}}/instructions" ng-click="nav.tab = 'instructions'"><span class="glyphicon glyphicon-tasks" aria-hidden="true"></span> {{ 'instructions' | translate }}</a>
</li> </li>
<li role="presentation" ng-class="{'active' : nav.tab == 'session'}"> <li role="presentation" ng-class="{'active' : nav.tab == 'session'}" ng-if="studentData.supervision == 2">
<a href="/app/#/student/{{studentData.id}}/session" ng-click="nav.tab = 'session'"><span class="glyphicon glyphicon-transfer" aria-hidden="true"></span> {{ 'session' | translate }}</a> <a href="/app/#/student/{{studentData.id}}/session" ng-click="nav.tab = 'session'"><span class="glyphicon glyphicon-transfer" aria-hidden="true"></span> {{ 'session' | translate }}</a>
</li> </li>
<li role="presentation" ng-class="{'active' : nav.tab == 'reports'}"> <li role="presentation" ng-class="{'active' : nav.tab == 'reports'}" ng-if="studentData.supervision != 1">
<a href="/app/#/student/{{studentData.id}}/reports" ng-click="nav.tab = 'reports'"><span class="glyphicon glyphicon-file" aria-hidden="true"></span> {{ 'reports' | translate }}</a> <a href="/app/#/student/{{studentData.id}}/reports" ng-click="nav.tab = 'reports'"><span class="glyphicon glyphicon-file" aria-hidden="true"></span> {{ 'reports' | translate }}</a>
</li> </li>
<li role="presentation" ng-class="{'active' : nav.tab == 'setup'}"> <li role="presentation" ng-class="{'active' : nav.tab == 'setup'}" ng-if="studentData.supervision != 1">
<a href="/app/#/student/{{studentData.id}}/setup" ng-click="nav.tab = 'setup'" <a href="/app/#/student/{{studentData.id}}/setup" ng-click="nav.tab = 'setup'"
><span class="glyphicon glyphicon-cog" aria-hidden="true"></span> {{ 'setup' | translate }}</a> ><span class="glyphicon glyphicon-cog" aria-hidden="true"></span> {{ 'setup' | translate }}</a>
</li> </li>
......
...@@ -90,7 +90,7 @@ ...@@ -90,7 +90,7 @@
<h3 translate>supervisors</h3> <h3 translate>supervisors</h3>
<!-- Buscador de supervisores --> <!-- Buscador de supervisores -->
<p> <p>
<form role="search" ng-submit="getSupervisorByEmail()"> <form role="search" ng-submit="search_sup()">
<div class="input-group"> <div class="input-group">
<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="email_sup" required>
<div class="input-group-btn"> <div class="input-group-btn">
......
<!-- StudentsCtrl controls here, see app.js --> <!-- StudentsCtrl controls here, see app.js -->
<div class="panel panel-default"> <div class="panel panel-default">
<!-- Default panel contents --> <!-- Default panel contents -->
<div class="panel-heading"><h3 class="panel-title" translate>students</h3> <div class="panel-heading"><h3 class="panel-title" translate>students</h3>
<span ng-if="user.isAdmin">({{user.office.currentStudents}}/{{user.office.maxStudents}} - <span translate="licenses_left" translate-values="{number: num_licenses_left}"></span>)</span> <span ng-if="user.isAdmin">({{user.office.currentStudents}}/{{user.office.maxStudents}} - <span translate="licenses_left" translate-values="{number: num_licenses_left}"></span>)</span>
</divuser.office.currentStudents}}/ivuser.office.maxStudents}} <div class="panel-body"> </divuser.office.currentStudents}}/ivuser.office.maxStudents}} <div class="panel-body">
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<div class="row"> <div class="row">
<div class="col-xs-3"> <div class="col-xs-3">
<button type="button" class="btn btn-success btn-circle btn-lg" ng-click="resetForm(); hidestudentadd = false" title="{{'add'|translate}}" ng-hide="!user.isSupAdmin || !num_licenses_left"><i class="glyphicon glyphicon-plus"></i></button> <button type="button" class="btn btn-success btn-circle btn-lg" ng-click="resetForm(); hidestudentadd = false" title="{{'add'|translate}}" ng-hide="!user.isSupAdmin || !num_licenses_left"><i class="glyphicon glyphicon-plus"></i></button>
</div> </div>
<div class="col-xs-6 input-group"> <div class="col-xs-6 input-group">
<input type="text" ng-model="search_students" id="search_students" placeholder="{{ 'filter' | translate }}" class="form-control" aria-describedby="basic-addon2"> <input type="text" ng-model="search_students" id="search_students" placeholder="{{ 'filter' | translate }}" class="form-control" aria-describedby="basic-addon2">
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
<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>
<a class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/instructions" alt="{{ 'instructions' | translate}}" popover="{{ 'instructions' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision == 2"><span class="glyphicon glyphicon-tasks" aria-hidden="true"></span></a> <a class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/instructions" alt="{{ 'instructions' | translate}}" popover="{{ 'instructions' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision == 2"><span class="glyphicon glyphicon-tasks" aria-hidden="true"></span></a>
<span class="btn btn-default btn-lg" role="button" alt="{{ 'instructions' | translate}}" popover="{{ 'instructions' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision != 2"><span class="glyphicon glyphicon-tasks" aria-hidden="true" style="color: #bbb" ></span></span> <span class="btn btn-default btn-lg" role="button" alt="{{ 'instructions' | translate}}" popover="{{ 'instructions' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision != 2"><span class="glyphicon glyphicon-tasks" aria-hidden="true" style="color: #bbb" ></span></span>
...@@ -57,7 +57,9 @@ ...@@ -57,7 +57,9 @@
<span class="btn btn-default btn-lg" role="button" alt="{{ 'reports' | translate}}" popover="{{ 'reports' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision == 1"><span class="glyphicon glyphicon-file" aria-hidden="true" style="color: #bbb"></span></span> <span class="btn btn-default btn-lg" role="button" alt="{{ 'reports' | translate}}" popover="{{ 'reports' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision == 1"><span class="glyphicon glyphicon-file" aria-hidden="true" style="color: #bbb"></span></span>
<a class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/setup" alt="{{ 'setup' | translate}}" popover="{{ 'setup' | translate}}" popover-trigger="mouseenter"><span class="glyphicon glyphicon-cog" aria-hidden="true"></span></a> <a class="btn btn-default btn-lg" role="button" href="/app/#/student/{{student.id}}/setup" alt="{{ 'setup' | translate}}" popover="{{ 'setup' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision != 1"><span class="glyphicon glyphicon-cog" aria-hidden="true"></span></a>
<span class="btn btn-default btn-lg" role="button" alt="{{ 'setup' | translate}}" popover="{{ 'setup' | translate}}" popover-trigger="mouseenter" ng-if="student.supervision == 1"><span class="glyphicon glyphicon-file" aria-hidden="true" style="color: #bbb"></span></span>
</td> <!-- /BUTTONS --> </td> <!-- /BUTTONS -->
<td> <td>
...@@ -69,4 +71,3 @@ ...@@ -69,4 +71,3 @@
</table> </table>
</div> </div>
<!-- Fin de row --> <!-- Fin de row -->
...@@ -25,9 +25,9 @@ module.exports.connections = { ...@@ -25,9 +25,9 @@ module.exports.connections = {
adapter: 'sails-mysql', adapter: 'sails-mysql',
host: 'localhost', host: 'localhost',
port: 3306, port: 3306,
user: 'pictodbuser', user: 'pictodbu',
password: 'p1KT0!15.', password: 'p1KT0!15.',
database: 'pictodbu', database: 'pictodb',
charset : 'utf8', charset : 'utf8',
collation : 'utf8_unicode_ci' collation : 'utf8_unicode_ci'
} }
......
...@@ -23,7 +23,7 @@ module.exports.i18n = { ...@@ -23,7 +23,7 @@ module.exports.i18n = {
* * * *
***************************************************************************/ ***************************************************************************/
// locales: ['en', 'es', 'fr', 'de'] locales: ['en-gb', 'es-es'],
/**************************************************************************** /****************************************************************************
* * * *
...@@ -34,7 +34,7 @@ module.exports.i18n = { ...@@ -34,7 +34,7 @@ module.exports.i18n = {
* * * *
****************************************************************************/ ****************************************************************************/
// defaultLocale: 'en', defaultLocale: 'es-es',
/**************************************************************************** /****************************************************************************
* * * *
......
{ {
"Welcome": "Welcome", "Welcome": "Welcome",
"A brand new app.": "A brand new app." "A brand new app.": "A brand new app.",
"notification_from_pictogram": "Notification from Pictogram",
"signin_mail": "To activate your Pictogram account, click on this link:\n"
} }
{ {
"Welcome": "Bienvenido", "Welcome": "Bienvenido",
"A brand new app.": "Una aplicación de la nueva marca." "A brand new app.": "Una aplicación de la nueva marca.",
"notification_from_pictogram": "Notificación desde Pictogram",
"signin_mail": "Para activar su cuenta en Pictogram, haga click en el siguiente enlace:\n"
} }
...@@ -59,7 +59,7 @@ module.exports.routes = { ...@@ -59,7 +59,7 @@ module.exports.routes = {
'GET /stu/:id_stu/supervisors': 'StudentController.supervisors', 'GET /stu/:id_stu/supervisors': 'StudentController.supervisors',
'GET /stu/:id_stu/therapists': 'StudentController.therapists', 'GET /stu/:id_stu/therapists': 'StudentController.therapists',
'GET /stu/:id_stu/tutors': 'StudentController.tutors', 'GET /stu/:id_stu/tutors': 'StudentController.tutors',
'GET /stu/:id_stu/sup/:id_sup': 'StudentController.link_supervisor', 'POST /stu/:id_stu/sup/:id_sup': 'StudentController.link_supervisor',
'GET /stu/:id_stu/pictos': 'StudentController.pictos', 'GET /stu/:id_stu/pictos': 'StudentController.pictos',
'GET /stu/:id_stu/methods': 'StudentController.methods', 'GET /stu/:id_stu/methods': 'StudentController.methods',
'GET /stu/:id_stu/lasttries': 'StudentController.lasttries', 'GET /stu/:id_stu/lasttries': 'StudentController.lasttries',
...@@ -95,7 +95,7 @@ module.exports.routes = { ...@@ -95,7 +95,7 @@ module.exports.routes = {
'PUT /sup/:id': 'SupervisorController.update', 'PUT /sup/:id': 'SupervisorController.update',
'POST /sup': 'SupervisorController.create', 'POST /sup': 'SupervisorController.create',
'POST /sup/login': 'SupervisorController.login', 'POST /sup/login': 'SupervisorController.login',
'POST /sup/activate': 'SupervisorController.activate', 'GET /sup/activate/:token': 'SupervisorController.activate',
'POST /sup/upload': 'SupervisorController.upload', 'POST /sup/upload': 'SupervisorController.upload',
'POST /sup/subscribe': 'SupervisorController.subscribe', 'POST /sup/subscribe': 'SupervisorController.subscribe',
'POST /sup/unsubscribe': 'SupervisorController.unsubscribe', 'POST /sup/unsubscribe': 'SupervisorController.unsubscribe',
......
...@@ -31,7 +31,7 @@ module.exports.views = { ...@@ -31,7 +31,7 @@ module.exports.views = {
****************************************************************************/ ****************************************************************************/
engine: 'ejs', engine: 'ejs',
/**************************************************************************** /****************************************************************************
* * * *
...@@ -57,7 +57,7 @@ module.exports.views = { ...@@ -57,7 +57,7 @@ module.exports.views = {
* * * *
****************************************************************************/ ****************************************************************************/
layout: 'layout' layout: false
/**************************************************************************** /****************************************************************************
* * * *
...@@ -77,5 +77,5 @@ module.exports.views = { ...@@ -77,5 +77,5 @@ module.exports.views = {
* * * *
****************************************************************************/ ****************************************************************************/
}; };
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
"sails-disk": "~0.10.0", "sails-disk": "~0.10.0",
"sails-generate-auth": "^0.2.0", "sails-generate-auth": "^0.2.0",
"sails-mysql": "^0.12", "sails-mysql": "^0.12",
"sails-service-mailer": "^3.2.1",
"sails-test-helper": "^0.3.5", "sails-test-helper": "^0.3.5",
"socket.io": "~1.3.2", "socket.io": "~1.3.2",
"socket.io-redis": "^0.1.4", "socket.io-redis": "^0.1.4",
......
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Pictogram Dashboard</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="/app/css/main.css">
</head>
<body>
<img title="Pictogram" alt="Pictogram" src="/app/img/logo_pictogram.png">
<p><strong>Welcome to Pictogram, <%= sup.name %>!</strong></p>
<p>Your account is now active, so you can proceed to <a href="<%= login_url %>">login</a>.</p>
<p></p>
<p><strong>¡Bienvenido a Pictogram, <%= sup.name %>!</strong></p>
<p>Su cuenta está ahora activa, por lo que puede <a href="<%= login_url %>">acceder</a>.</p>
</body>
</html>
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