Commit f257d092 by Pablo Molina

Solved #428, añadida categoría libre.

- Añadida comprobación de atributos de estudiante y picto válidos
- Actualizados modelos MetaStu y MetaPicto
- Actualizado el test CAJA
- Actualizado el log de la política isSupervisorOfStudent
- Actualizado el log de StudentController.add_picto
- Añadido log para la creación/actualización del modelo StuPicto
- Añadidos parámetros extra para el test POST /stu/stu_id/picto
parent ff51658d
......@@ -51,18 +51,67 @@ INSERT INTO `meta_stu` (`id`, `name`, `default`, `domain`, `description`) VALUES
(10, 'tts_options', '{"phrase": false}', 'phrase', 'Setup if phrase is spoken'),
(11, 'picto_background', '"#0000ff"', 'RGB colors', 'Picto background color'),
(12, 'phrase_background', '"#ff0000"', 'RGB colors', 'Phrase background color'),
(13, 'legendsize', '"10"', '10-90', 'Size of legend in percentage ');
(13, 'legendsize', '"10"', '10-90', 'Size of legend in percentage '),
(14, 'show_free_category', 'false', 'false,true', 'Show the free category to the student instead of the category grid');
--
-- Picto meta-properties
--
INSERT INTO `meta_picto` (`id`, `name`, `default`, `domain`, `description`) VALUES
(1,'id_cat',NULL, NULL,'pictogram category, if any'),
(2,'coord_x',NULL, '1,2,3,4,5','picto screen row'),
(3,'coord_y',NULL, '1,2,3,4,5,6,7,8,9,10','picto screen column'),
(4,'status','"invisible"','enabled, disabled, invisible','enabled: the user can select the picto; disabled: the user see the picto but it is not selectable; invisible: neither selectable nor usable'),
(5,'highlight','false','false, true','Highlight the pictogram in the PCB'),
(6, 'color',NULL,'000000-FFFFFF','Picto background color');
INSERT INTO `meta_picto` (
`id`,
`name`,
`default`,
`domain`,
`description`
) VALUES (
1,
'id_cat',
NULL,
NULL,
'pictogram category, if any'
), (
2,
'coord_x',
NULL,
'1,2,3,4,5',
'picto screen row'
), (
3,
'coord_y',
NULL,
'1,2,3,4,5,6,7,8,9,10',
'picto screen column'
), (
4,
'status',
'invisible',
'enabled,disabled,invisible',
'enabled: the user can select the picto; disabled: the user see the picto but it is not selectable; invisible: neither selectable nor usable'
), (
5,
'highlight',
'false',
'false,true',
'Highlight the pictogram in the PCB'
), (
6,
'color',
NULL,
'000000,FFFFFF',
'Picto background color'
), (
7,
'free_category_coord_x',
NULL,
'1,2,3,4,5',
'Picto screen row when only a free category is being shown'
), (
8,
'free_category_coord_y',
NULL,
'1,2,3,4,5,6,7,8,9,10',
'Picto screen column when only a free category is being shown'
);
--
-- Volcado de datos para la tabla `source`
......
......@@ -335,6 +335,14 @@ INSERT INTO `student` (
-- CAJA student-supervisor
--
INSERT INTO `stu_sup` (
id_stu,
id_sup
) VALUES (
(SELECT id FROM student WHERE username='aaa0002'),
(SELECT id FROM supervisor WHERE email='dofer@ujaen.es')
);
INSERT INTO `stu_sup`(id_stu,id_sup) SELECT DISTINCT A.id, B.id FROM student A, supervisor B WHERE A.name='Fernandito' and B.name='Fernando';
INSERT INTO `stu_sup`(id_stu,id_sup) SELECT DISTINCT A.id, B.id FROM student A, supervisor B WHERE A.name='Fernandita' and B.name='Fernando';
INSERT INTO `stu_sup`(id_stu,id_sup) SELECT DISTINCT A.id, B.id FROM student A, supervisor B WHERE A.name='Arturito' and B.name='Fernando';
......
......@@ -819,6 +819,8 @@ module.exports = {
})
.then(function (stuPicto) {
if (!stuPicto) {
sails.log.error(`Can't create StuPicto for picto ${params.id_picto} \
and student ${params.id_stu}`);
throw new Error('stupicto not added');
}
......@@ -831,12 +833,16 @@ module.exports = {
});
})
.catch(function () {
sails.log.debug(`Trying to find an existing StuPicto for picto ${params.id_picto} \
and student ${params.id_stu}`);
StuPicto.findOne({
student: params.id_stu,
picto: params.id_picto
})
.then((stuPicto) => {
if (!stuPicto) {
sails.log.error(`Can't create or find StuPicto for picto ${params.id_picto} \
and student ${params.id_stu}. Check if both picto and student exist and check`);
res.badRequest();
} else {
res.ok({
......@@ -849,6 +855,8 @@ module.exports = {
}
})
.catch(() => {
sails.log.error(`Server error finding StuPicto for picto ${params.id_picto} \
and student ${params.id_stu}`);
res.serverError();
});
});
......
/* global sails, MetaPicto */
/**
* MetaPicto.js
*
* @description :: Represents possible attributes for a Picto
* @docs :: http://sailsjs.org/#!documentation/models
*/
module.exports = {
tableName : 'meta_picto',
migrate : 'safe',
schema : true,
autoPK : false,
autoCreatedAt : false,
autoUpdatedAt : false,
tableName: 'meta_picto',
migrate: 'safe',
schema: true,
autoPK: false,
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
id: {
type: "integer",
type: 'integer',
autoIncrement: true,
primaryKey: true,
unique: true
},
name: {
required: true,
type: "string",
type: 'string',
size: 40
},
default_val: {
......@@ -31,9 +32,54 @@ module.exports = {
size: 40,
required: false,
},
domain: {
columnName: 'domain',
type: 'string',
size: 250
},
description: {
type: "string",
type: 'string',
size: 1024
}
},
/**
* For a given attribute object this function returns a promise which receives a valid result
* which can be stored safely in the database.
* It also adds the necesary default attributes if they are not present in the original object.
* This method can be used as class method (does not require a MetaPicto instance).
* @param {Object} attributes StuPicto attributes object
* @return {Object} StuPicto valid attributes object
*/
getValidAttributes(attributes) {
return MetaPicto.find().then(function (metaAttributes) {
var attributeValue;
var attributeDomains = {};
var validAttributes = {};
metaAttributes.forEach((metaAttribute) => {
validAttributes[metaAttribute.name] = metaAttribute.default_val;
if (typeof metaAttribute.domain === 'string') {
attributeDomains[metaAttribute.name] = metaAttribute.domain.split(',');
} else {
attributeDomains[metaAttribute.name] = true;
}
});
Object.keys(attributes).forEach((attributeKey) => {
attributeValue = attributes[attributeKey];
if (attributeDomains[attributeKey] &&
(attributeDomains[attributeKey] === true ||
attributeDomains[attributeKey].indexOf(attributeValue) !== -1
)) {
validAttributes[attributeKey] = attributeValue;
} else {
sails.log.error(`MetaPicto: received invalid attribute \
${attributeKey} with value "${attributeValue}`);
}
});
return validAttributes;
});
}
};
\ No newline at end of file
};
/**
* MetaStu.js
*
* @description :: TODO: You might write a short summary of how this model works and what it represents here.
* @docs :: http://sailsjs.org/#!documentation/models
*/
/* global sails, MetaStu */
/**
* This model contains all the valid attributes that the student "attributes" property can
* store. It is used as testing entity.
* @type {Object}
*/
module.exports = {
tableName : 'meta_stu',
migrate : 'safe',
schema : true,
autoPK : false,
autoCreatedAt : false,
autoUpdatedAt : false,
tableName: 'meta_stu',
migrate: 'safe',
schema: true,
autoPK: false,
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
id: {
type: "integer",
id: {
type: 'integer',
autoIncrement: true,
primaryKey: true,
unique: true
},
name: {
required: true,
type: "string",
type: 'string',
size: 50
},
default_val: {
......@@ -32,15 +32,48 @@ module.exports = {
required: false
},
domain: {
type: "string",
type: 'string',
size: 250,
required: false
},
description: {
type: "string",
type: 'string',
size: 120,
required: false
}
},
/**
* For a given attribute object this function returns a promise which receives a valid result
* which can be stored safely in the database.
* It also adds the necesary default attributes if they are not present in the original object.
* This method can be used as class method (does not require a MetaStu instance).
* @param {Object} attributes Student attributes object
* @return {Object} Student valid attributes object
*/
getValidAttributes(attributes) {
return MetaStu.find().then(function (metaAttributes) {
var attributeValue;
var attributeDomains = {};
var validAttributes = {};
metaAttributes.forEach((metaAttribute) => {
validAttributes[metaAttribute.name] = metaAttribute.default_val;
attributeDomains[metaAttribute.name] = metaAttribute.domain.split(',');
});
Object.keys(attributes).forEach((attributeKey) => {
attributeValue = attributes[attributeKey];
if (attributeDomains[attributeKey] &&
attributeDomains[attributeKey].indexOf(attributeValue) !== -1) {
validAttributes[attributeKey] = attributeValue;
} else {
sails.log.error(`Received invalid attribute \
${attributeKey} with value "${attributeValue}`);
}
});
return validAttributes;
});
}
};
/* global sails, MetaPicto */
/**
* StuPicto.js
*
* @description :: This model represents the collection of pictos for a student,
* with the properties associated to it
* @docs :: http://sailsjs.org/#!documentation/models
* StuPicto model represents the collection of pictos for a student,
* with the properties associated to it
*/
module.exports = {
tableName : 'stu_picto',
migrate : 'safe',
schema : true,
autoPK : false,
autoCreatedAt : false,
autoUpdatedAt : false,
tableName: 'stu_picto',
migrate: 'safe',
schema: true,
autoPK: false,
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
id: {
type: "integer",
type: 'integer',
autoIncrement: true,
primaryKey: true,
unique: true
},
student: { // FK de Student. 1 a N
columnName: "id_stu",
columnName: 'id_stu',
required: true,
type: "integer",
model: "Student"
type: 'integer',
model: 'Student'
},
picto: { // FK de Picto. 1 a N
columnName: "id_pic",
columnName: 'id_pic',
required: true,
type: "integer",
model: "Picto"
type: 'integer',
model: 'Picto'
},
attributes: { //JSON with attributes defined in meta_picto
columnName: "attributes",
attributes: { // JSON with attributes defined in meta_picto
columnName: 'attributes',
required: true,
type: "json"
type: 'json'
}
},
//
// Model hook for storing default picto configuration adding a new picto
//
beforeCreate: function(attrs, next) {
MetaPicto.find().exec(function(err, metaPicto) {
if (err || !metaPicto || metaPicto.length == 0)
return next(err);
/* Default Configuration:
var configuration = {
"id_cat" : null,
"coord_x" : null,
"coord_y" : null,
"status": "enabled"
};
*/
var configuration = [];
// The default configuration is built by meta_picto fields and its default values
for (var j = 0; j< metaPicto.length; j++) {
configuration.push('"' + metaPicto[j].name + '":' + metaPicto[j].default_val);
}
// If the picto is from picto_core_cat, it has defined the coords
if(attrs.attributes.coord_x !== undefined && attrs.attributes.coord_y !== undefined){
var previous_coord_x = attrs.attributes.coord_x;
var previous_coord_y = attrs.attributes.coord_y;
}
if(attrs.attributes.id_cat !== undefined)
var previous_id_cat = attrs.attributes.id_cat;
if(attrs.attributes.status !== undefined)
var previous_status = attrs.attributes.status;
if(attrs.attributes.color !== undefined)
var previous_color = attrs.attributes.color;
attrs.attributes = JSON.parse("{" + configuration.toString() + "}");
// And we store them (If the picto is from picto_core, it has defined the coords)
if(previous_coord_x !== undefined && previous_coord_y !== undefined){
attrs.attributes.coord_x = previous_coord_x;
attrs.attributes.coord_y = previous_coord_y;
}
if(previous_id_cat !== undefined)
attrs.attributes.id_cat = previous_id_cat;
if(previous_status !== undefined)
attrs.attributes.status = previous_status;
if(previous_color !== undefined)
attrs.attributes.color = previous_color;
/**
* Checks the given attributes before creating a new StuPicto
* @param {Object} attrs All StuPicto properties to be stored
* @param {Function} next Function to be executed when the check process
* has been completed (an error object will be passed
* to the function if necesary)
*/
beforeCreate: function (attrs, next) {
sails.log.debug('Checking attributes for new StuPicto');
MetaPicto.getValidAttributes(attrs.attributes).then((validAttributes) => {
attrs.attributes = validAttributes;
next();
}).catch((error) => {
sails.log.error(`Error while checking attributes for new StuPicto: ${error}`);
next(error || new Error());
});
},
/**
* Checks the given properties before updating a new Student
* @param {Object} attrs All student properties to be stored
* @param {Function} next Function to be executed when the check process
* has been completed (an error object will be passed
* to the function if necesary)
*/
beforeUpdate: function (attrs, next) {
sails.log.debug(`Checking attributes for StuPicto ${attrs.id}`);
delete attrs.username;
MetaPicto.getValidAttributes(attrs.attributes).then((validAttributes) => {
attrs.attributes = validAttributes;
next();
}).catch((error) => {
sails.log.error(`Error while checking attributes for StuPicto ${attrs.id}: ${error}`);
next(error || new Error());
});
}
}
};
/* global sails, MetaStu */
const bcrypt = require('bcrypt-nodejs')
const bcrypt = require('bcrypt-nodejs');
/**
* student.js
......@@ -10,65 +10,65 @@ const bcrypt = require('bcrypt-nodejs')
*/
module.exports = {
tableName : 'student',
migrate : 'safe',
schema : true,
autoPK : false,
autoCreatedAt : false,
autoUpdatedAt : false,
tableName: 'student',
migrate: 'safe',
schema: true,
autoPK: false,
autoCreatedAt: false,
autoUpdatedAt: false,
attributes: {
id: {
type: "integer",
type: 'integer',
autoIncrement: true,
primaryKey: true,
unique: true
},
username: {
required: true,
type: "string",
type: 'string',
size: 40
},
password: {
required: true,
type: "string",
type: 'string',
size: 40
},
name: {
required: true,
type: "string",
type: 'string',
size: 40
},
surname: {
required: true,
type: "string",
type: 'string',
size: 60
},
birthdate: {
required: true,
type: "date"
type: 'date'
},
gender:{
gender: {
required: true,
type: "string",
type: 'string',
size: 1
},
country:{
type: "string",
country: {
type: 'string',
size: 2
},
pic:{
type: "string",
pic: {
type: 'string',
size: 255
},
notes: {
type: "string",
type: 'string',
size: 1024
},
lang: {
columnName: "lang",
columnName: 'lang',
required: true,
type: "string",
type: 'string',
size: 2
},
attributes: {
......@@ -84,26 +84,26 @@ module.exports = {
},
// Relación con StuSup
stuSup: {
collection: "StuSup",
via: "student"
collection: 'StuSup',
via: 'student'
},
// Relación con Method. [1 Student to N Method]
methods: {
collection: "Method",
via: "student"
collection: 'Method',
via: 'student'
},
// Relación con StuPicto. [1 Student to N StuPicto]
stuPicto: {
collection: "StuPicto",
via: "student"
collection: 'StuPicto',
via: 'student'
},
},
toJSON: function () {
var student = this.toObject();
student.pic = sails.config.pictogram.urls.getStudentAvatarUrl(student.pic);
delete student.password;
return student;
toJSON: function () {
var student = this.toObject();
student.pic = sails.config.pictogram.urls.getStudentAvatarUrl(student.pic);
delete student.password;
return student;
},
},
/**
......@@ -114,34 +114,32 @@ module.exports = {
* to the function if necesary)
*/
beforeCreate: function (attrs, next) {
MetaStu.find().then(function (metaAttributes) {
var attributeValue;
var attributeDomains = {};
var validAttributes = {};
metaAttributes.forEach((metaAttribute) => {
validAttributes[metaAttribute.name] = metaAttribute.default_val;
attributeDomains[metaAttribute.name] = metaAttribute.domain.split(',');
});
attrs.attributes.keys().forEach((attributeKey) => {
attributeValue = attrs.attributes[attributeKey];
if (attributeDomains[attributeKey] &&
attributeDomains[attributeKey].indexOf(attributeValue) !== -1) {
validAttributes[attributeKey] = attributeValue;
} else {
sails.log.error(`Received invalid attribute \
${attributeKey} with value "${attributeValue}`);
}
});
MetaStu.getValidAttributes(attrs.attributes).then((validAttributes) => {
attrs.attributes = validAttributes;
attrs.password = bcrypt.hashSync(attrs.password, bcrypt.genSaltSync());
next();
}).catch((error) => {
if (error) {
next(error);
} else {
next(new Error());
next(error || new Error());
});
},
/**
* Checks the given properties before updating a new Student
* @param {Object} attrs All student properties to be stored
* @param {Function} next Function to be executed when the check process
* has been completed (an error object will be passed
* to the function if necesary)
*/
beforeUpdate: function (attrs, next) {
delete attrs.username;
MetaStu.getValidAttributes(attrs.attributes).then((validAttributes) => {
attrs.attributes = validAttributes;
if (attrs.password) {
attrs.password = bcrypt.hashSync(attrs.password, bcrypt.genSaltSync());
}
next();
}).catch((error) => {
next(error || new Error());
});
},
......@@ -327,31 +325,5 @@ module.exports = {
return cb();
});
});
},
//
// Model hook for storing encrypted password when updating an account
// and to check that the user new username is not already registered
//
beforeUpdate: function(attrs, next) {
// remove username as it shouldn't be changed
delete attrs.username;
//
// Encrypt password before insertion
//
var bcrypt = require('bcrypt-nodejs');
bcrypt.genSalt(10, function(err, salt) {
if (err || !attrs.password)
return next(err);
bcrypt.hash(attrs.password, salt, null, function(err, hash) {
if (err)
return next(err);
attrs.password = hash;
next();
});
});
},
}
};
module.exports = function isSupervisorOfStudent (req, res, next) {
/* global sails, Student */
if (!req.params.id_stu)
return res.json(401, {error: 'Access denied'});
module.exports = function isSupervisorOfStudent(req, res, next) {
const supervisorId = req.token.id;
const studentId = req.params.id_stu;
// Get list of supervisors for the student
Student.supervisors(req.params.id_stu, function(err, sups) {
if (err || !sups || sups.length == 0)
return res.json(401, {error: 'Access denied'});
if (!studentId || !supervisorId) {
sails.log.error('This request needs an id_stu parameter and a authenticated supervisor');
res.json(401, { error: 'Access denied' });
} else {
Student.supervisors(studentId, function (err, sups) {
const studentSupervisorsIds = sups.map((studentSupervisor) => studentSupervisor.id);
// if supervisor is not in the list of supervisors
if (sups.map(function(e) {return e.id}).indexOf(req.token.id) < 0)
return res.json(401, {error: 'Access denied'});
// Finally, if the user has a clean record, we'll call the `next()` function
// to let them through to the next policy or our controller
next();
});
};
\ No newline at end of file
if (err || studentSupervisorsIds.length === 0) {
sails.log.error(`Student ${studentId} has no supervisor assigned`);
res.json(401, { error: 'Access denied' });
} else if (studentSupervisorsIds.indexOf(supervisorId) < 0) {
sails.log.error(`Supervisor ${supervisorId} is not assigned to Student ${studentId}`);
sails.log.debug(`Student supervisors: ${studentSupervisorsIds}`);
res.json(401, { error: 'Access denied' });
} else {
sails.log.debug(`Supervisor ${supervisorId} is assigned to Student ${studentId}`);
next();
}
});
}
};
const path = require('path');
describe('Student API', function () {
it('GET /stu/:id_stu', function (done) {
const studentAgentData = Object.assign({}, studentAgent.data);
......@@ -275,8 +277,6 @@ describe('Student API', function () {
});
it('POST /stu');
it('POST /stu/upload', function (done) {
const path = require('path');
studentAgent
.post('/stu/upload')
.field('id', studentAgent.data.id)
......@@ -310,7 +310,9 @@ describe('Student API', function () {
id_cat: 12345,
color: null,
highlight: false,
status: 'invisible'
status: 'invisible',
free_category_coord_x: null,
free_category_coord_y: null
}
})
.end(done);
......
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