Commit 5413736a by Fernando Martínez Santiago

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

parents 6b6c2ff8 51c69a8d
......@@ -13,7 +13,7 @@
<item name="darkpurple" type="color">#FF9933CC</item>
<item name="darkgreen" type="color">#669900</item>
<item name="darkorange" type="color">#FFFF8800</item>
<item name="darkred" type="color">#FFCC0000</item>
<item name="darkred" type="color">#cc0000</item>
<integer-array name="androidcolors">
<item>@color/blue</item>
......
......@@ -3,12 +3,15 @@ package com.yottacode.pictogram.tabletlibrary.gui;
import android.app.Activity;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import com.yottacode.pictogram.dao.UserLogin;
import com.yottacode.pictogram.tabletlibrary.R;
......@@ -52,7 +55,8 @@ public class SerialActivity extends Activity {
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_serial);
//Initial keyboard hide
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
final EditText mSerialViewMail = (EditText) findViewById(R.id.serialmail);
final EditText mSerialViewPass = (EditText) findViewById(R.id.serialpass);
......@@ -65,6 +69,29 @@ public class SerialActivity extends Activity {
mSerialViewMail.setText(username);
mSerialViewPass.setText(password);
final LinearLayout stuList = (LinearLayout) findViewById(R.id.stuLay);
final LinearLayout supList = (LinearLayout) findViewById(R.id.supLay);
mSerialViewMail.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view,boolean hasFocus) {
if(hasFocus){
stuList.setVisibility(View.INVISIBLE);
supList.setVisibility(View.INVISIBLE);
}
}
});
mSerialViewPass.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view,boolean hasFocus) {
if(hasFocus){
stuList.setVisibility(View.INVISIBLE);
supList.setVisibility(View.INVISIBLE);
}
}
});
// Escribo el último valor indicado de username
......
......@@ -10,17 +10,15 @@
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".gui.SerialActivity">
<EditText
android:id="@+id/serialmail"
android:layout_width="400px"
android:layout_height="wrap_content"
android:hint="@string/prompt_serial_mail"
android:imeActionId="@+id/login"
android:imeOptions="actionUnspecified"
android:inputType="text"
android:maxLines="1"
android:singleLine="true"
android:layout_alignParentTop="true" />
<ImageView
android:layout_width="200px"
android:layout_height="120px"
android:layout_marginLeft="30px"
android:orientation="horizontal"
android:src="@drawable/pictogram_logo"
android:id="@+id/imageView"
android:layout_alignParentTop="true"
android:layout_toEndOf="@+id/serialmail" />
<EditText
android:id="@+id/serialpass"
......@@ -34,28 +32,91 @@
android:maxLines="1"
android:singleLine="true"
android:layout_below="@+id/serialmail"
/>
android:layout_toStartOf="@+id/imageView" />
<Button
android:id="@+id/entrar_button" style="?android:textAppearanceSmall"
android:layout_width="400px"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginTop="11dp"
android:text="@string/action_entrar"
android:textStyle="bold"
android:layout_below="@+id/serialpass"/>
android:layout_below="@+id/serialpass"
android:layout_alignStart="@+id/serialpass" />
<ImageView
android:layout_width="200px"
android:layout_height="120px"
android:layout_marginLeft="30px"
android:orientation="horizontal"
android:src="@drawable/pictogram_logo"
android:layout_centerHorizontal="true"
android:id="@+id/imageView"
android:layout_toRightOf="@+id/serialmail"
android:layout_alignParentTop="true"/>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@android:color/darker_gray"
android:layout_below="@+id/entrar_button"
android:layout_alignParentEnd="true"
android:layout_marginEnd="18dp" />
<EditText
android:id="@+id/serialmail"
android:layout_width="400px"
android:layout_height="wrap_content"
android:hint="@string/prompt_serial_mail"
android:imeActionId="@+id/login"
android:imeOptions="actionUnspecified"
android:inputType="text"
android:maxLines="1"
android:layout_marginStart="212dp"
android:layout_alignParentTop="true"
android:layout_alignParentStart="true" />
<LinearLayout
android:orientation="vertical"
android:background="@color/blue"
android:layout_height="350px"
android:layout_width="400px"
android:layout_alignParentBottom="true"
android:layout_alignStart="@+id/entrar_button"
android:layout_marginStart="270dp"
android:id="@+id/supLay">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/labelSup"
tools:text="Supervisores"
android:text="@string/supervisores"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textAlignment="center"
android:textSize="18sp" />
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/supList" />
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_height="350px"
android:layout_marginStart="67dp"
android:background="@color/darkred"
android:layout_width="400px"
android:layout_alignParentBottom="true"
android:layout_alignParentStart="true"
android:id="@+id/stuLay">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/labelStu"
tools:text="Alumnos"
android:text="@string/alumnos"
android:textAppearance="@style/TextAppearance.AppCompat"
android:textAlignment="center"
android:textSize="18sp" />
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/stuList" />
</LinearLayout>
</RelativeLayout>
......
<resources>
<string name="app_name">com.yottacode.pictogram.Tablet</string>
<string name="alumnos">Alumnos</string>
<string name="supervisores">Supervisores</string>
<item name="maxInTape" type="integer">8</item>
<item name="maxInTape_big" type="integer">6</item>
......
/* global Student, PictoCore, VStuLastInstruction, StuPicto, StuSup, sailsTokenAuth, sails,
Picto */
/**
/* StudentController
*
* @description :: Server-side logic for managing students
* @help :: See http://links.sailsjs.org/docs/controllers
*/
module.exports = {
// dummy function to test timeouts
eternal: function (req, res) {
setTimeout(function(){return;}, 1000*60*6);
},
/**
* Login in the server as student, getting a toker for interacting with the platform
* @param {request} req
* {
* username: 'johnydoe',
* password: '12312'
* }
* @param {response} res
* {
* user: {
* id: 12,
* office: 1234,
* username: 'johnydoe',
* name: 'Johny',
* surname: 'Doe',
* birthdate: '2009-12-10T00:00:00.000Z',
* gender: 'F',
* country: 'ES',
* pic: 'avatar/nice/url.jpg',
* notes: null,
* lang: 'en-en',
* iat: 123512,
* exp: 1231292,
* attributes: { @see Student.getValidAttributes() }
* },
* token: '... asd90jkas ...',
* server_time: 123912932312
* }
*/
login: function (req, res) {
var bcrypt = require('bcrypt-nodejs');
Student.findOne({
username: req.body.username
})
.then(function (student) {
if (student) {
if (bcrypt.compareSync(req.body.password, student.password)) {
student.isStudent = true;
res.ok({
user: student,
token: sailsTokenAuth.issueToken(student, sails.config.jwt.expiresInMinutes),
server_time: (new Date())
.getTime()
});
} else {
sails.log.error(`Invalid student login: user ${student.username}, password\
"${req.body.password}"`);
res.badRequest();
}
} else {
sails.log.error(`Tried to login as non-existing student ${req.body.username}`);
res.badRequest();
}
})
.catch(function () {
sails.log.error(`Error getting student ${req.body.username} for login`);
res.serverError();
});
},
/**
* Get a student by id
* @param {request} req {} (with studentId specified as url parameters)
* @param {response} res
* {
* id: 12,
* office: 1234,
* username: 'johnydoe',
* name: 'Johny',
* surname: 'Doe',
* birthdate: '2009-12-10T00:00:00.000Z',
* gender: 'F',
* country: 'ES',
* pic: 'avatar/nice/url.jpg',
* notes: null,
* lang: 'en-en',
* iat: 123512,
* exp: 1231292,
* attributes: { @see Student.getValidAttributes() },
* current_method: 'Do Things', // 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) {
Student.findOne({id: req.params.id_stu}).populate('lastInstruction')
.then(function (student) {
if (!student)
throw new Error("student not found");
student.current_method = student.lastInstruction[0] ? student.lastInstruction[0].met_name : "no_method";
student.current_instruction = student.lastInstruction[0] ? student.lastInstruction[0].ins_name : "no_instruction";
// recover last instruction to complete student info
var stu_last_inst = VStuLastInstruction.findOne({student: student.id})
.then(function (stu_last_inst) {
return stu_last_inst;
})
.error(err => {throw err});
// determine supervision level of the requester on the student
var stu_sup = StuSup.findOne({id_stu: student.id, id_sup: req.token.id})
.then(function (stu_sup) {
return stu_sup;
})
.error(err => {throw err});
return [student, stu_last_inst, stu_sup];
})
.spread(function (student, stu_last_inst, stu_sup) {
if (stu_last_inst) {
student.current_method = stu_last_inst.met_name;
student.current_instruction = stu_last_inst.ins_name;
}
// 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);
});
},
//
// Adds a new student into the database
//
create: function (req, res) {
var params = req.params.all();
Student.create(params)
.then(function(created) {
sails.log.debug('Student ' + created.id + ' created: ' + JSON.stringify(created));
return res.ok(created);
})
.error(function(err) {
if (err.message.search("Maximum number of enrolments reached") > 0) {
// This is a MySQL error triggered by TRG_NEW_STUDENT_MAXENROLMENTS trigger
// (see triggers-enroments-integrity-constraints.sql)
// As the format is not that of a normal error, we just get message
sails.log.debug(err.message);
return res.serverError(err.message);
}
else {
sails.log.debug(err.message);
return res.serverError(err.message);
}
});
},
/**
* Deletes an existing student by removing him/her from his/her office
* and all his supervisors.
* @param {request} req {} (with id_stu as url parameter)
* @param {response} res {}
*/
delete: function (req, res) {
if (!req.params.id_stu)
return res.json(500, {
error: 'No student defined'
});
Student.logical_delete(req.params.id_stu, function (err) {
if (err) {
return res.json(500, {
error: err
});
}
return res.json({
result: 'Deleted'
});
});
},
//
// Updates student information
//
update: function (req, res) {
if (!req.params.id_stu) {
res.badRequest();
}
Student.findOne(req.params.id_stu).then(function(stu) {
var k;
// copy attributes
for (k in req.body) stu[k] = req.body[k];
if (!req.body.password) // to avoid change password when no one is provided
delete stu.password;
stu.save().then(function (saved) {
res.ok(stu);
// Send websocket message
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.student(stu.id),
sails.hooks.events.updateStudent(stu),
(req.isSocket) ? req.socket : undefined
);
})
.catch(function(err) {
res.severError();
});
})
.catch(function (err) {
res.notFound();
});
},
/**
* Return all existing supervisor and therapist from a given student
* @param {request} req {} (with studentId as url parameter)
* @param {response} res
* [
* {
* id: supervisorId,
* name: 'John',
* surname: 'Doe',
* gender: 'M/F',
* pic: 'supervisor/avatar.jpg',
* address: 'My Adress, n1',
* country: 'ES',
* email: 'john@doe.es',
* phone: '+123123123',
* lang: 'es-es',
* active: true,
* ttsEngine: 'IVONA-Text',
* office: officeId
* },
* ...
* ]
*/
supervisors: function (req, res) {
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;
return res.json(sups);
});
},
/**
* Return all existing therapists from a given student
* @param {request} req {} (with studentId as url parameter)
* @param {response} res
* [
* {
* id: therapistId,
* name: 'John',
* surname: 'Doe',
* gender: 'M/F',
* pic: 'supervisor/avatar.jpg',
* address: 'My Address, n1',
* country: 'ES',
* email: 'john@doe.es',
* phone: '+123123123',
* lang: 'es-es',
* active: true,
* ttsEngine: 'IVONA-Text',
* office: officeId
* },
* ...
* ]
*/
therapists: function (req, res) {
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;
return res.json(sups);
});
},
/**
* Return all existing tutors from a given student
* @param {request} req {} (with studentId as url parameter)
* @param {response} res
* [
* {
* id: tutorId,
* name: 'John',
* surname: 'Doe',
* gender: 'M/F',
* pic: 'supervisor/avatar.jpg',
* address: 'My Address, n1',
* country: 'ES',
* email: 'john@doe.es',
* phone: '+123123123',
* lang: 'es-es',
* active: true,
* ttsEngine: 'IVONA-Text',
* office: officeId
* },
* ...
* ]
*/
tutors: function (req, res) {
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;
return res.json(sups);
});
},
/**
* Creates a relation between the student and a given supervisor.
* It broadcasts the event linkSupervisorToStudent to both the student room
* and the supervisor room.
* @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} {}
*/
link_supervisor: function (req, res) {
StuSup.create({
student: req.param('id_stu'),
supervisor: req.param('id_sup')
})
.then(function (stuSup) {
if (!stuSup)
throw new Error('stusup not created');
const socketToOmit = (req.isSocket) ? req.socket : undefined;
const linkSupervisorToStudentEvent = sails.hooks.events.linkSupervisorToStudent(
stuSup.supervisor,
stuSup.student
);
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.supervisor(stuSup.supervisor),
linkSupervisorToStudentEvent,
socketToOmit
);
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.student(stuSup.student),
linkSupervisorToStudentEvent,
socketToOmit
);
return stuSup;
})
.catch((err) => {
StuSup.findOne({ student: req.param('id_stu'), supervisor: req.param('id_sup') })
.then((stuSup) => {
// It was already there!
if (stuSup)
return stuSup;
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();
}
});
}
});
return res.ok();
})
.catch((err) => {
return res.serverError("Error: " + err);
});
},
/**
* Destroys a relation (drama queen) between the student and a given supervisor.
* It broadcasts the even unlinkSupervisorFromStudent to both the student room
* and the supervisor room.
* @param {request} {} (with studentId and supervisorId as url parameters)
* @param {response} {}
*/
unlink_supervisor: function (req, res) {
StuSup.findOne({
student: req.param('id_stu'),
supervisor: req.param('id_sup')
})
.then((stuSup) => {
if (!stuSup)
throw new Error("student and supervisor are not linked");
stuSup.destroy();
const socketToOmit = req.isSocket ? req.socket : undefined;
const unlinkSupervisorFromStudentEvent = sails.hooks.events.unlinkSupervisorFromStudent(
stuSup.student,
stuSup.supervisor
);
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.supervisor(stuSup.supervisor),
unlinkSupervisorFromStudentEvent,
socketToOmit
);
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.student(stuSup.student),
unlinkSupervisorFromStudentEvent,
socketToOmit
);
return res.ok();
})
.catch((err) => {
res.serverError("Error unliking student: " + err);
});
},
/**
* Get all methods from a given student (with their instructions)
* @param {request} req {} (with studentId as url parameter)
* @param {response} res
* [
* {
* id: methodId,
* student: studentId,
* name: 'Method Name',
* description: 'Method description',
* registration: null,
* notes: 'Method notes',
* last_ins: instructionId // Last instruccion executed,
* instructions: [
* {
* id: instructionId,
* name: 'Instruction Name',
* objective: 'Instruction Objective',
* status: 'instruction-status',
* begin: '2015-07-14T07:23:03.000Z',
* end: '2015-07-14T07:28:03.000Z',
* method: methodId
* },
* ...
* ]
* },
* ...
* ]
*/
methods: function (req, res) {
var params = req.allParams();
Method.find({
student: params.id_stu
})
.populate('instructions')
.exec(function (err, methods) {
if (err)
return res.json(500, {
error: err
});
if (!methods || methods.length == 0)
return res.json([]); // Return an empty array
if (methods) {
var l_met = [];
async.eachSeries(methods, function (m, callback1) {
var l_ins = [];
var last_ins = null;
var last_time = 0;
sails.log.debug('Loop methods: ' + m.name);
async.eachSeries(m.instructions, function (ins, callback2) {
sails.log.debug('Loop instructions: ' + ins.name);
Instruction.findOne({
id: ins.id
})
.populate('workingSessions', {
sort: 'begin DESC'
})
.exec(function (err, instruction) {
if (err) {
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);
} else {
var last = instruction.workingSessions.length - 1;
instruction.begin = instruction.workingSessions[last].end;
instruction.end = instruction.workingSessions[0].end;
if (instruction.end > last_time) {
last_ins = instruction.id;
last_time = instruction.end;
}
}
// Add instruction to list (with or without tries)
l_ins.push(instruction);
callback2();
});
// Finish function when each callback is done
// Optionaly it can be passed and err parameter
}, function (err) {
if (err) {
// One of the iterations produced an error.
// All processing will now stop.
return res.json(500, {
error: 'Error looping in tries: ' + err
});
}
m.instructions = l_ins;
m.last_ins = last_ins;
l_met.push(m);
callback1();
});
// Finish function when each callback is done
// Optionaly it can be passed and err parameter
}, function (err) {
if (err) {
// One of the iterations produced an error.
// All processing will now stop.
return res.json(500, {
error: 'Error looping in method instructions: ' + err
});
} else {
// All end ok
//Assing the built list
return res.json(l_met);
}
});
}
});
},
// read action
// get tries of the last working session
//
lasttries: function (req, res) {
var params = req.allParams();
//Student.findOne({ id: params.id_stu }).populate('workingSessions')
VStuLastInstruction.findOne({
where: {
student: params.id_stu
}
})
.exec(function (err, ws) {
if (err) {
sails.log.debug('Finding student working sessions: ' + err);
return res.json(500, {
error: 'No student last working session found'
});
}
if (!ws || ws.length == 0) {
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
})
.populate('actions', {
//supervisor: null //recovering supervisor actions, too
})
.exec(function (err, tries) {
if (err) {
return res.json(500, {
error: 'No student last tries found'
});
}
if (!tries || tries.length == 0) {
return res.json([]); // Return an empty array of tries
}
if (tries) {
// A list for one element: The last working session
var l_ws = [];
l_ws.push({
'id': ws.workingSession,
'student': ws.student,
'begin': ws.ws_begin,
'end': ws.ws_end,
'description': ws.ws_description,
'tries': tries
});
return res.json(l_ws);
}
});
}
});
},
/**
* Return all tries from a student
* @param {request} req {} (width studentId as url parameter)
* @param {response} res
* {
"methods": [
{
"student": 24,
"id": 1,
"name": "Test Method",
"description": null,
"registration": null,
"notes": null
"instructions": [
{
"id": 1,
"name": "Test Instruction",
"objective": null,
"status": "started",
"begin": null,
"end": null,
"method": 1,
"working_sessions": [
{
"id": 3,
"begin": "2016-09-09T08:26:24.500Z",
"end": "2016-08-28T23:36:35.000Z",
"current": null,
"description": "",
"supervisor": 23,
"instruction": 1
"tries": [
{
"actions": [],
"id": 1,
"begin": "2016-08-28T23:36:35.474Z",
"end": "2016-08-28T23:36:44.000Z",
"result": null,
"description": null,
"workingSession": 3
},
{
"actions": [],
"id": 2,
"begin": "2016-08-28T23:36:44.050Z",
"end": "2016-08-29T01:36:51.710Z",
"result": "SUCCESS",
"description": null,
"workingSession": 3
},
{
"actions": [],
"id": 3,
"begin": "2016-08-28T23:36:51.942Z",
"end": "2016-08-28T23:36:53.000Z",
"result": "DISCARDED",
"description": null,
"workingSession": 3
},
{
"actions": [],
"id": 4,
"begin": "2016-08-28T23:36:53.877Z",
"end": "2016-08-28T23:37:13.000Z",
"result": "SPONTANEOUS SUCCESS",
"description": null,
"workingSession": 3
}
]
}
}
]
}
]
}
*/
tries: function (req, res) {
if (!req.params.id_stu)
return res.badRequest("Student not defined");
Student.tries(req.params.id_stu, function (err, l_met) {
if (err) return res.serverError(err);
return res.ok(l_met);
});
},
/**
* Get all pictos from a given student
* @param {request} req {} (with studentId as url parameter)
* @param {response} res
* [
* {
* id: student-picto ID,
* picto: {
* id: pictoId,
* uri: 'uri/to/picto.png',
* category: pictoCategoryId,
* source: 1 @TODO Other sources
* owner: supervisorId or null
* },
* expression: {
* id: expressionId,
* lang: 'es-es',
* text: 'Picto Expression',
* picto: pictoId
* },
* attributes: { @see StuPicto.getValidAttributes() }
* },
* ...
* ]
*/
pictos: function (req, res) {
if (!req.params.id_stu) {
return res.json(500, {
error: 'No student defined'
});
}
sails.log.debug('Pictos requested for student ' + req.params.id_stu);
Student.pictos(req.params.id_stu, function (err, pictos) {
if (err)
return res.serverError("Error obtaining pictos: "+ err);
return res.ok(pictos);
});
},
//
// 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'
});
}
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)
return res.json(500, {
error: err
});
if (!stu || !stu.workingSessions || stu.workingSessions.length == 0)
return res.json([]); // Return an empty array
else
return res.json(stu.workingSessions);
});
},
/**
* Add an existing picto to the student's collection
* @param {request} req (with studentId and pictoId as url parameter)
* {
* attributes: { @see StuPicto.getValidAttributes() }
* }
* @param {response} res
* {
* id: stuPictoId (association betweet student and picto)
* student: studentId (speficied as url parameter)
* picto: { @see Picto model}
* attributes: { @see StuPicto.getValidAttributes() }
* expression: { @see Picto model }
* }
*/
add_picto: function (req, res) {
var params = req.allParams();
Student.findOne({ id: params.id_stu })
.then((student) => {
if (!student) {
sails.log.error(`Student ${params.id_stu} not found`);
throw new Error("Student not found");
}
return [
Picto.findOne({ id: params.id_picto })
.populate('expressions', {lang: student.lang})
,
student
];
})
.spread((picto, student) => {
if (!picto) {
sails.log.error(`Picto ${params.id_picto} not found`);
throw new Error("Picto not found");
}
return [
StuPicto.create({
student: student.id,
picto: picto.id,
attributes: params.attributes
})
,
picto
];
})
.spread((stuPicto, picto) => {
if (!stuPicto)
throw new Error("stu_picto not created");
sails.log.debug("->>" + JSON.stringify(picto));
return res.ok({
id: stuPicto.id,
student: params.id_stu,
attributes: stuPicto.attributes,
picto: picto,
expression: picto.expressions[0]
});
})
.catch(err => res.serverError("Error adding picto: " + err));
},
/**
* Removes an existing picto to the student's collection
* Warning: yout must send the **stuPictoId** as url parameter, not the **pictoId**.
* You can obtain this ID from the student picto collection.
* @param {request} req {} (with studentId and stuPictoId as url parameters)
* @param {response} res {}
*/
delete_picto: function (req, res) {
var params = req.allParams();
StuPicto.destroy({ id: params.id_stuPicto })
.then(destroyed => {
if (!destroyed)
throw new Error();
return res.ok();
})
.catch(err => {
return res.serverError('Not removed picto for student: ' + err);
});
},
// update action
// update picto atributes for a studentPicto
//
update_picto: function (req, res) {
var params = req.allParams();
console.log('Updating attributes for picto student ' + JSON.stringify(params));
console.log(JSON.stringify(params));
query = params.id_stuPicto ? {
id: params.id_stuPicto
} : {
id_stu: params.id_stu,
id_pic: params.id_pic
}
StuPicto.update(query, {
attributes: params.attributes
})
.then(updated => {
if (!updated)
throw new Error ("error on update");
console.log('Updated attributes for picto student:' + JSON.stringify(updated[0]));
// return res.json(updated[0]);
return res.ok({
id: updated[0].id, // id of stu_picto
attributes: updated[0].attributes, // picto attributes for student
picto: {
id: updated[0].picto // picto information
}
});
})
.catch(err => {
return res.serverError('Unable to update picto for student: ' + err);
});
},
/**
* Updates the student profile image
* @param {request} req
* file: [The image binary data]
* {
* "id": "student ID"
* }
* @param {response} res {}
*/
upload: function (req, res) {
var fs = require('fs');
var path = require('path');
var newAvatarFileName;
var newAvatarFileDescriptor;
var newAvatarDirectory = sails.config.pictogram.paths.studentAvatarDirectory;
Student.findOne({ id: req.body.id }).then(function (student) {
if (!student) {
throw new Error("Student not found");
}
newAvatarFileName = sails.config.pictogram.paths.getStudentAvatarFileName(student.id);
req.file('file').upload({
maxBytes: 1000000,
dirname: newAvatarDirectory,
saveAs: newAvatarFileName
}, function whenDone(error, uploadedFiles) {
if (error || (uploadedFiles.length === 0)) {
throw new Error("upload failed");
}
try {
newAvatarFileDescriptor = uploadedFiles[0].fd;
if (student.pic !== sails.config.pictogram.paths.defaultAvatarFileName) {
fs.unlinkSync(path.join(newAvatarDirectory, student.pic));
}
student.pic = newAvatarFileName;
student.save(function (updateStudentError) {
if (updateStudentError) {
throw updateStudentError;
}
res.ok();
});
} catch (updateAvatarError) {
fs.unlinkSync(newAvatarFileDescriptor);
res.serverError("Error when updating profile image in server");
}
});
})
.catch(function (err) {
res.badRequest("Could not find supervisor: " + err);
});
},
// ***************************************************************
// WEBSOCKETS
// ***************************************************************
//
// Subscribe to websockets events
//
subscribe: function (req, res) {
var action = req.param('action');
var attributes = req.param('attributes');
attributes.ui = attributes.ui ? attributes.ui : 'PCB';
if (req.isSocket) {
sails.hooks.rooms.subscribeToRoom(
sails.hooks.rooms.student(attributes.id_stu),
req.socket,
attributes.ui
);
}
res.ok({msg: "Subscribed to student "});
},
//
// Unsubscribe to websockets events
//
unsubscribe: function (req, res) {
var action = req.param('action');
//var attributes = req.param('attributes');
if (req.isSocket) {
var rooms = sails.sockets.socketRooms(req.socket);
console.log("Subscribed rooms in socket: " + JSON.stringify(rooms));
// Leave all rooms
for (var i = 0; i < rooms.length; i++) {
//sails.sockets.leave(req.socket, rooms[i]); MODIFICADO POR FERNANDO. SI NO, NO SE ACTUALIZA UPDATE_PEERS
sails.hooks.rooms.unsubscribeFromRoom(rooms[i], req.socket);
sails.log.debug("Unsusbscribe from room " + rooms[i]);
}
res.json({
msg: "Unsubscribed from all rooms"
});
}
},
//
// Logs a vocabulary action and broadcast to anyone subscribed to this student
vocabulary: function (req, res) {
var action = req.param('action');
var attributes = req.param('attributes');
var roomName = 'studentRoom' + attributes.id_stu;
sails.log.debug("Inside vocabulary");
if (req.isSocket) {
sails.log.debug("Inside vocabulary - isSocket");
// Send to all sockets subscribed to this room except the own socket that sends the message
// Parameters: room, action, data to send, socket to avoid sending (the socket that send this)
sails.sockets.broadcast(roomName, 'vocabulary', {
"action": action,
"attributes": attributes
}, req.socket);
res.json({
msg: "Vocabulary " + action + " action from student " + attributes.id_stu
});
}
},
/**
* Logs a TRY action and broadcast to anyone subscribed to this student
* @param {request} req
* {
* "action": <action> ("add", "delete", ...),
* "attributes": {
* "id_stu": <id_stu>,
* "timestamp": <timestamp_string_in_ISO_format> (e.g.: "2016-07-13 17:50:00.224+0200"),
* "picto": {...}
* }
* }
* @param {response} res {<action_created>}
*/
action: function (req, res) {
var action = req.param('action');
var attributes = req.param('attributes');
sails.log.debug("Inside action. Student:" + attributes.id_stu);
if (!req.isSocket) {
sails.log.debug("No socket request for action");
res.badRequest()
} else {
sails.log.debug("websockets - room " + sails.hooks.rooms.student(attributes.id_stu));
// BROADCAST to everyone subscribed to this student
const socketToOmit = (req.isSocket) ? req.socket : undefined;
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.student(attributes.id_stu),
sails.hooks.events.actionPerformed(action, attributes),
socketToOmit
);
var sup = null;
if (attributes.id_sup) sup = attributes.id_sup;
var desc = null;
if (attributes.stu_picto) desc = attributes.stu_picto; // select, add and delete actions data
if (attributes.pictos) desc = attributes.pictos; // show action data
Action.create({
type: action,
timestamp: attributes.timestamp, // it comes already in ISO format
supervisor: sup,
student: attributes.id_stu,
description: desc
})
.then(function (created) {
res.json({
action: created
});
})
.fail(function(err) {
sails.log.error(err.details);
res.serverError(err.details);
});
}
},
//
// Logs a config action and broadcast to anyone subscribed to this student
config: function (req, res) {
var action = req.param('action');
var attributes = req.param('attributes');
sails.log.debug("Inside config");
if (req.isSocket) {
const socketToOmit = (req.isSocket) ? req.socket : undefined;
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.student(attributes.id_stu),
'action',
{
"action": action,
"attributes": attributes
},
socketToOmit
);
} else {
res.ok({
msg: "Config " + action + " action from student " + attributes.id_stu
});
}
},
//
// Stores in the action table a bulk of actions loaded from of the
// recorded action log
//
actions_batch: function (req, res) {
var params = req.allParams();
var count = 0;
sails.log.debug("Actions_batch request");
if (!params.actions)
return res.json(400, {
'error': "no actions"
});
// We loop through the actions and store them in the database
async.forEach(params.actions, function (action, cb) {
sails.log.debug("Actions batch is recording action: " + JSON.stringify(action));
var id_sup = null;
if (action.attributes.id_sup)
id_sup = action.attributes.sup;
var id_stu = null;
if (action.attributes.id_stu)
id_stu = action.attributes.id_stu;
var desc = null;
if (action.attributes.picto)
desc = action.attributes.picto; // select, add and delete actions data
if (action.attributes.pictos)
desc = action.attributes.pictos; // show action data
Action.create({
type: action.action,
timestamp: action.attributes.timestamp,
supervisor: id_sup,
student: id_stu,
description: desc
})
.exec(function (err, created) {
if (err) {
console.log(err.details);
sails.log.error(err.details);
return cb(err);
} else if (created)
count++;
cb();
});
},
function (err) { // function called when loop is done
if (err) {
console.log(err.details);
sails.log.error(err.details);
return res.json({
'error': err.details
});
} else
return res.json({
'result': 'Ok',
'total': count
});
});
},
//
// Returns the last instruction for the student
//
last_instruction: function (req, res) {
if (!req.params.id_stu)
return res.json(400, {
err: 'id_stu parameter is missing'
});
VStuLastInstruction.find({
id_stu: req.params.id_stu
})
.exec(function (err, found) {
if (err)
return res.json(500, err);
if (!found)
return res.json({});
return res.json(found);
});
}
};
/* global Student, PictoCore, VStuLastInstruction, StuPicto, StuSup, sailsTokenAuth, sails,
Picto */
/**
/* StudentController
*
* @description :: Server-side logic for managing students
* @help :: See http://links.sailsjs.org/docs/controllers
*/
module.exports = {
// dummy function to test timeouts
eternal: function (req, res) {
setTimeout(function(){return;}, 1000*60*6);
},
/**
* Login in the server as student, getting a toker for interacting with the platform
* @param {request} req
* {
* username: 'johnydoe',
* password: '12312'
* }
* @param {response} res
* {
* user: {
* id: 12,
* office: 1234,
* username: 'johnydoe',
* name: 'Johny',
* surname: 'Doe',
* birthdate: '2009-12-10T00:00:00.000Z',
* gender: 'F',
* country: 'ES',
* pic: 'avatar/nice/url.jpg',
* notes: null,
* lang: 'en-en',
* iat: 123512,
* exp: 1231292,
* attributes: { @see Student.getValidAttributes() }
* },
* token: '... asd90jkas ...',
* server_time: 123912932312
* }
*/
login: function (req, res) {
var bcrypt = require('bcrypt-nodejs');
Student.findOne({
username: req.body.username
})
.then(function (student) {
if (student) {
if (bcrypt.compareSync(req.body.password, student.password)) {
student.isStudent = true;
res.ok({
user: student,
token: sailsTokenAuth.issueToken(student, sails.config.jwt.expiresInMinutes),
server_time: (new Date())
.getTime()
});
} else {
sails.log.error(`Invalid student login: user ${student.username}, password\
"${req.body.password}"`);
res.badRequest();
}
} else {
sails.log.error(`Tried to login as non-existing student ${req.body.username}`);
res.badRequest();
}
})
.catch(function () {
sails.log.error(`Error getting student ${req.body.username} for login`);
res.serverError();
});
},
/**
* Get a student by id
* @param {request} req {} (with studentId specified as url parameters)
* @param {response} res
* {
* id: 12,
* office: 1234,
* username: 'johnydoe',
* name: 'Johny',
* surname: 'Doe',
* birthdate: '2009-12-10T00:00:00.000Z',
* gender: 'F',
* country: 'ES',
* pic: 'avatar/nice/url.jpg',
* notes: null,
* lang: 'en-en',
* iat: 123512,
* exp: 1231292,
* attributes: { @see Student.getValidAttributes() },
* current_method: 'Do Things', // 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) {
Student.findOne({id: req.params.id_stu}).populate('lastInstruction')
.then(function (student) {
if (!student)
throw new Error("student not found");
student.current_method = student.lastInstruction[0] ? student.lastInstruction[0].met_name : "no_method";
student.current_instruction = student.lastInstruction[0] ? student.lastInstruction[0].ins_name : "no_instruction";
// recover last instruction to complete student info
var stu_last_inst = VStuLastInstruction.findOne({student: student.id})
.then(function (stu_last_inst) {
return stu_last_inst;
})
.error(err => {throw err});
// determine supervision level of the requester on the student
var stu_sup = StuSup.findOne({id_stu: student.id, id_sup: req.token.id})
.then(function (stu_sup) {
return stu_sup;
})
.error(err => {throw err});
return [student, stu_last_inst, stu_sup];
})
.spread(function (student, stu_last_inst, stu_sup) {
if (stu_last_inst) {
student.current_method = stu_last_inst.met_name;
student.current_instruction = stu_last_inst.ins_name;
}
// 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);
});
},
//
// Adds a new student into the database
//
create: function (req, res) {
var params = req.params.all();
Student.create(params)
.then(function(created) {
sails.log.debug('Student ' + created.id + ' created: ' + JSON.stringify(created));
return res.ok(created);
})
.error(function(err) {
if (err.message.search("Maximum number of enrolments reached") > 0) {
// This is a MySQL error triggered by TRG_NEW_STUDENT_MAXENROLMENTS trigger
// (see triggers-enroments-integrity-constraints.sql)
// As the format is not that of a normal error, we just get message
sails.log.debug(err.message);
return res.serverError(err.message);
}
else {
sails.log.debug(err.message);
return res.serverError(err.message);
}
});
},
/**
* Deletes an existing student by removing him/her from his/her office
* and all his supervisors.
* @param {request} req {} (with id_stu as url parameter)
* @param {response} res {}
*/
delete: function (req, res) {
if (!req.params.id_stu)
return res.json(500, {
error: 'No student defined'
});
Student.logical_delete(req.params.id_stu, function (err) {
if (err) {
return res.json(500, {
error: err
});
}
return res.json({
result: 'Deleted'
});
});
},
//
// Updates student information
//
update: function (req, res) {
if (!req.params.id_stu) {
res.badRequest();
}
Student.findOne(req.params.id_stu).then(function(stu) {
var k;
// copy attributes
for (k in req.body) stu[k] = req.body[k];
if (!req.body.password) // to avoid change password when no one is provided
delete stu.password;
stu.save().then(function (saved) {
res.ok(stu);
// Send websocket message
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.student(stu.id),
sails.hooks.events.updateStudent(stu),
(req.isSocket) ? req.socket : undefined
);
})
.catch(function(err) {
res.severError();
});
})
.catch(function (err) {
res.notFound();
});
},
/**
* Return all existing supervisor and therapist from a given student
* @param {request} req {} (with studentId as url parameter)
* @param {response} res
* [
* {
* id: supervisorId,
* name: 'John',
* surname: 'Doe',
* gender: 'M/F',
* pic: 'supervisor/avatar.jpg',
* address: 'My Adress, n1',
* country: 'ES',
* email: 'john@doe.es',
* phone: '+123123123',
* lang: 'es-es',
* active: true,
* ttsEngine: 'IVONA-Text',
* office: officeId
* },
* ...
* ]
*/
supervisors: function (req, res) {
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;
return res.json(sups);
});
},
/**
* Return all existing therapists from a given student
* @param {request} req {} (with studentId as url parameter)
* @param {response} res
* [
* {
* id: therapistId,
* name: 'John',
* surname: 'Doe',
* gender: 'M/F',
* pic: 'supervisor/avatar.jpg',
* address: 'My Address, n1',
* country: 'ES',
* email: 'john@doe.es',
* phone: '+123123123',
* lang: 'es-es',
* active: true,
* ttsEngine: 'IVONA-Text',
* office: officeId
* },
* ...
* ]
*/
therapists: function (req, res) {
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;
return res.json(sups);
});
},
/**
* Return all existing tutors from a given student
* @param {request} req {} (with studentId as url parameter)
* @param {response} res
* [
* {
* id: tutorId,
* name: 'John',
* surname: 'Doe',
* gender: 'M/F',
* pic: 'supervisor/avatar.jpg',
* address: 'My Address, n1',
* country: 'ES',
* email: 'john@doe.es',
* phone: '+123123123',
* lang: 'es-es',
* active: true,
* ttsEngine: 'IVONA-Text',
* office: officeId
* },
* ...
* ]
*/
tutors: function (req, res) {
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;
return res.json(sups);
});
},
/**
* Creates a relation between the student and a given supervisor.
* It broadcasts the event linkSupervisorToStudent to both the student room
* and the supervisor room.
* @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} {}
*/
link_supervisor: function (req, res) {
StuSup.create({
student: req.param('id_stu'),
supervisor: req.param('id_sup')
})
.then(function (stuSup) {
if (!stuSup)
throw new Error('stusup not created');
const socketToOmit = (req.isSocket) ? req.socket : undefined;
const linkSupervisorToStudentEvent = sails.hooks.events.linkSupervisorToStudent(
stuSup.supervisor,
stuSup.student
);
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.supervisor(stuSup.supervisor),
linkSupervisorToStudentEvent,
socketToOmit
);
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.student(stuSup.student),
linkSupervisorToStudentEvent,
socketToOmit
);
return stuSup;
})
.catch((err) => {
StuSup.findOne({ student: req.param('id_stu'), supervisor: req.param('id_sup') })
.then((stuSup) => {
// It was already there!
if (stuSup)
return stuSup;
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();
}
});
}
});
return res.ok();
})
.catch((err) => {
return res.serverError("Error: " + err);
});
},
/**
* Destroys a relation (drama queen) between the student and a given supervisor.
* It broadcasts the even unlinkSupervisorFromStudent to both the student room
* and the supervisor room.
* @param {request} {} (with studentId and supervisorId as url parameters)
* @param {response} {}
*/
unlink_supervisor: function (req, res) {
StuSup.findOne({
student: req.param('id_stu'),
supervisor: req.param('id_sup')
})
.then((stuSup) => {
if (!stuSup)
throw new Error("student and supervisor are not linked");
stuSup.destroy();
const socketToOmit = req.isSocket ? req.socket : undefined;
const unlinkSupervisorFromStudentEvent = sails.hooks.events.unlinkSupervisorFromStudent(
stuSup.student,
stuSup.supervisor
);
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.supervisor(stuSup.supervisor),
unlinkSupervisorFromStudentEvent,
socketToOmit
);
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.student(stuSup.student),
unlinkSupervisorFromStudentEvent,
socketToOmit
);
return res.ok();
})
.catch((err) => {
res.serverError("Error unliking student: " + err);
});
},
/**
* Get all methods from a given student (with their instructions)
* @param {request} req {} (with studentId as url parameter)
* @param {response} res
* [
* {
* id: methodId,
* student: studentId,
* name: 'Method Name',
* description: 'Method description',
* registration: null,
* notes: 'Method notes',
* last_ins: instructionId // Last instruccion executed,
* instructions: [
* {
* id: instructionId,
* name: 'Instruction Name',
* objective: 'Instruction Objective',
* status: 'instruction-status',
* begin: '2015-07-14T07:23:03.000Z',
* end: '2015-07-14T07:28:03.000Z',
* method: methodId
* },
* ...
* ]
* },
* ...
* ]
*/
methods: function (req, res) {
var params = req.allParams();
Method.find({
student: params.id_stu
})
.populate('instructions')
.exec(function (err, methods) {
if (err)
return res.json(500, {
error: err
});
if (!methods || methods.length == 0)
return res.json([]); // Return an empty array
if (methods) {
var l_met = [];
async.eachSeries(methods, function (m, callback1) {
var l_ins = [];
var last_ins = null;
var last_time = 0;
sails.log.debug('Loop methods: ' + m.name);
async.eachSeries(m.instructions, function (ins, callback2) {
sails.log.debug('Loop instructions: ' + ins.name);
Instruction.findOne({
id: ins.id
})
.populate('workingSessions', {
sort: 'begin DESC'
})
.exec(function (err, instruction) {
if (err) {
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);
} else {
var last = instruction.workingSessions.length - 1;
instruction.begin = instruction.workingSessions[last].end;
instruction.end = instruction.workingSessions[0].end;
if (instruction.end > last_time) {
last_ins = instruction.id;
last_time = instruction.end;
}
}
// Add instruction to list (with or without tries)
l_ins.push(instruction);
callback2();
});
// Finish function when each callback is done
// Optionaly it can be passed and err parameter
}, function (err) {
if (err) {
// One of the iterations produced an error.
// All processing will now stop.
return res.json(500, {
error: 'Error looping in tries: ' + err
});
}
m.instructions = l_ins;
m.last_ins = last_ins;
l_met.push(m);
callback1();
});
// Finish function when each callback is done
// Optionaly it can be passed and err parameter
}, function (err) {
if (err) {
// One of the iterations produced an error.
// All processing will now stop.
return res.json(500, {
error: 'Error looping in method instructions: ' + err
});
} else {
// All end ok
//Assing the built list
return res.json(l_met);
}
});
}
});
},
// read action
// get tries of the last working session
//
lasttries: function (req, res) {
var params = req.allParams();
//Student.findOne({ id: params.id_stu }).populate('workingSessions')
VStuLastInstruction.findOne({
where: {
student: params.id_stu
}
})
.exec(function (err, ws) {
if (err) {
sails.log.debug('Finding student working sessions: ' + err);
return res.json(500, {
error: 'No student last working session found'
});
}
if (!ws || ws.length == 0) {
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
})
.populate('actions', {
//supervisor: null //recovering supervisor actions, too
})
.exec(function (err, tries) {
if (err) {
return res.json(500, {
error: 'No student last tries found'
});
}
if (!tries || tries.length == 0) {
return res.json([]); // Return an empty array of tries
}
if (tries) {
// A list for one element: The last working session
var l_ws = [];
l_ws.push({
'id': ws.workingSession,
'student': ws.student,
'begin': ws.ws_begin,
'end': ws.ws_end,
'description': ws.ws_description,
'tries': tries
});
return res.json(l_ws);
}
});
}
});
},
/**
* Return all tries from a student
* @param {request} req {} (width studentId as url parameter)
* @param {response} res
* {
"methods": [
{
"student": 24,
"id": 1,
"name": "Test Method",
"description": null,
"registration": null,
"notes": null
"instructions": [
{
"id": 1,
"name": "Test Instruction",
"objective": null,
"status": "started",
"begin": null,
"end": null,
"method": 1,
"working_sessions": [
{
"id": 3,
"begin": "2016-09-09T08:26:24.500Z",
"end": "2016-08-28T23:36:35.000Z",
"current": null,
"description": "",
"supervisor": 23,
"instruction": 1
"tries": [
{
"actions": [],
"id": 1,
"begin": "2016-08-28T23:36:35.474Z",
"end": "2016-08-28T23:36:44.000Z",
"result": null,
"description": null,
"workingSession": 3
},
{
"actions": [],
"id": 2,
"begin": "2016-08-28T23:36:44.050Z",
"end": "2016-08-29T01:36:51.710Z",
"result": "SUCCESS",
"description": null,
"workingSession": 3
},
{
"actions": [],
"id": 3,
"begin": "2016-08-28T23:36:51.942Z",
"end": "2016-08-28T23:36:53.000Z",
"result": "DISCARDED",
"description": null,
"workingSession": 3
},
{
"actions": [],
"id": 4,
"begin": "2016-08-28T23:36:53.877Z",
"end": "2016-08-28T23:37:13.000Z",
"result": "SPONTANEOUS SUCCESS",
"description": null,
"workingSession": 3
}
]
}
}
]
}
]
}
*/
tries: function (req, res) {
if (!req.params.id_stu)
return res.badRequest("Student not defined");
Student.tries(req.params.id_stu, function (err, l_met) {
if (err) return res.serverError(err);
return res.ok(l_met);
});
},
/**
* Get all pictos from a given student
* @param {request} req {} (with studentId as url parameter)
* @param {response} res
* [
* {
* id: student-picto ID,
* picto: {
* id: pictoId,
* uri: 'uri/to/picto.png',
* category: pictoCategoryId,
* source: 1 @TODO Other sources
* owner: supervisorId or null
* },
* expression: {
* id: expressionId,
* lang: 'es-es',
* text: 'Picto Expression',
* picto: pictoId
* },
* attributes: { @see StuPicto.getValidAttributes() }
* },
* ...
* ]
*/
pictos: function (req, res) {
if (!req.params.id_stu) {
return res.json(500, {
error: 'No student defined'
});
}
sails.log.debug('Pictos requested for student ' + req.params.id_stu);
Student.pictos(req.params.id_stu, function (err, pictos) {
if (err)
return res.serverError("Error obtaining pictos: "+ err);
return res.ok(pictos);
});
},
//
// 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'
});
}
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)
return res.json(500, {
error: err
});
if (!stu || !stu.workingSessions || stu.workingSessions.length == 0)
return res.json([]); // Return an empty array
else
return res.json(stu.workingSessions);
});
},
/**
* Add an existing picto to the student's collection
* @param {request} req (with studentId and pictoId as url parameter)
* {
* attributes: { @see StuPicto.getValidAttributes() }
* }
* @param {response} res
* {
* id: stuPictoId (association betweet student and picto)
* student: studentId (speficied as url parameter)
* picto: { @see Picto model}
* attributes: { @see StuPicto.getValidAttributes() }
* expression: { @see Picto model }
* }
*/
add_picto: function (req, res) {
var params = req.allParams();
Student.findOne({ id: params.id_stu })
.then((student) => {
if (!student) {
sails.log.error(`Student ${params.id_stu} not found`);
throw new Error("Student not found");
}
return [
Picto.findOne({ id: params.id_picto })
.populate('expressions', {lang: student.lang})
,
student
];
})
.spread((picto, student) => {
if (!picto) {
sails.log.error(`Picto ${params.id_picto} not found`);
throw new Error("Picto not found");
}
return [
StuPicto.create({
student: student.id,
picto: picto.id,
attributes: params.attributes
})
,
picto
];
})
.spread((stuPicto, picto) => {
if (!stuPicto)
throw new Error("stu_picto not created");
sails.log.debug("->>" + JSON.stringify(picto));
return res.ok({
id: stuPicto.id,
student: params.id_stu,
attributes: stuPicto.attributes,
picto: picto,
expression: picto.expressions[0]
});
})
.catch(err => res.serverError("Error adding picto: " + err));
},
/**
* Removes an existing picto to the student's collection
* Warning: yout must send the **stuPictoId** as url parameter, not the **pictoId**.
* You can obtain this ID from the student picto collection.
* @param {request} req {} (with studentId and stuPictoId as url parameters)
* @param {response} res {}
*/
delete_picto: function (req, res) {
var params = req.allParams();
StuPicto.destroy({ id: params.id_stuPicto })
.then(destroyed => {
if (!destroyed)
throw new Error();
return res.ok();
})
.catch(err => {
return res.serverError('Not removed picto for student: ' + err);
});
},
// update action
// update picto atributes for a studentPicto
//
update_picto: function (req, res) {
var params = req.allParams();
console.log('Updating attributes for picto student ' + JSON.stringify(params));
console.log(JSON.stringify(params));
query = params.id_stuPicto ? {
id: params.id_stuPicto
} : {
id_stu: params.id_stu,
id_pic: params.id_pic
}
StuPicto.update(query, {
attributes: params.attributes
})
.then(updated => {
if (!updated)
throw new Error ("error on update");
console.log('Updated attributes for picto student:' + JSON.stringify(updated[0]));
// return res.json(updated[0]);
return res.ok({
id: updated[0].id, // id of stu_picto
attributes: updated[0].attributes, // picto attributes for student
picto: {
id: updated[0].picto // picto information
}
});
})
.catch(err => {
return res.serverError('Unable to update picto for student: ' + err);
});
},
// update action
// update picto atributes for a studentPicto
//
update_legend: function (req, res) {
var params = req.allParams();
var query='update stu_picto'+
' set attributes=json_set(attributes, \'$.legend\',\''+params.legend_value+'\')'+
' where id_stu='+params.id_stu;
console.log('Updating legend for student ' + params.id_stu +" collection to "+
params.legend_value+": "+query);
StuPicto.query(query, function(err, result) {
if (err)
throw new Error ("error on update");
else {
console.log('Updated attributes for picto student:' + params.id_stu);
return res.ok({
id: params.id_stu,
legend_value: params.legend_value, // picto attributes for student
});
}
});
},
/**
* Updates the student profile image
* @param {request} req
* file: [The image binary data]
* {
* "id": "student ID"
* }
* @param {response} res {}
*/
upload: function (req, res) {
var fs = require('fs');
var path = require('path');
var newAvatarFileName;
var newAvatarFileDescriptor;
var newAvatarDirectory = sails.config.pictogram.paths.studentAvatarDirectory;
Student.findOne({ id: req.body.id }).then(function (student) {
if (!student) {
throw new Error("Student not found");
}
newAvatarFileName = sails.config.pictogram.paths.getStudentAvatarFileName(student.id);
req.file('file').upload({
maxBytes: 1000000,
dirname: newAvatarDirectory,
saveAs: newAvatarFileName
}, function whenDone(error, uploadedFiles) {
if (error || (uploadedFiles.length === 0)) {
throw new Error("upload failed");
}
try {
newAvatarFileDescriptor = uploadedFiles[0].fd;
if (student.pic !== sails.config.pictogram.paths.defaultAvatarFileName) {
fs.unlinkSync(path.join(newAvatarDirectory, student.pic));
}
student.pic = newAvatarFileName;
student.save(function (updateStudentError) {
if (updateStudentError) {
throw updateStudentError;
}
res.ok();
});
} catch (updateAvatarError) {
fs.unlinkSync(newAvatarFileDescriptor);
res.serverError("Error when updating profile image in server");
}
});
})
.catch(function (err) {
res.badRequest("Could not find supervisor: " + err);
});
},
// ***************************************************************
// WEBSOCKETS
// ***************************************************************
//
// Subscribe to websockets events
//
subscribe: function (req, res) {
var action = req.param('action');
var attributes = req.param('attributes');
attributes.ui = attributes.ui ? attributes.ui : 'PCB';
if (req.isSocket) {
sails.hooks.rooms.subscribeToRoom(
sails.hooks.rooms.student(attributes.id_stu),
req.socket,
attributes.ui
);
}
res.ok({msg: "Subscribed to student "});
},
//
// Unsubscribe to websockets events
//
unsubscribe: function (req, res) {
var action = req.param('action');
//var attributes = req.param('attributes');
if (req.isSocket) {
var rooms = sails.sockets.socketRooms(req.socket);
console.log("Subscribed rooms in socket: " + JSON.stringify(rooms));
// Leave all rooms
for (var i = 0; i < rooms.length; i++) {
//sails.sockets.leave(req.socket, rooms[i]); MODIFICADO POR FERNANDO. SI NO, NO SE ACTUALIZA UPDATE_PEERS
sails.hooks.rooms.unsubscribeFromRoom(rooms[i], req.socket);
sails.log.debug("Unsusbscribe from room " + rooms[i]);
}
res.json({
msg: "Unsubscribed from all rooms"
});
}
},
//
// Logs a vocabulary action and broadcast to anyone subscribed to this student
vocabulary: function (req, res) {
var action = req.param('action');
var attributes = req.param('attributes');
var roomName = 'studentRoom' + attributes.id_stu;
sails.log.debug("Inside vocabulary");
if (req.isSocket) {
sails.log.debug("Inside vocabulary - isSocket");
// Send to all sockets subscribed to this room except the own socket that sends the message
// Parameters: room, action, data to send, socket to avoid sending (the socket that send this)
sails.sockets.broadcast(roomName, 'vocabulary', {
"action": action,
"attributes": attributes
}, req.socket);
res.json({
msg: "Vocabulary " + action + " action from student " + attributes.id_stu
});
}
},
/**
* Logs a TRY action and broadcast to anyone subscribed to this student
* @param {request} req
* {
* "action": <action> ("add", "delete", ...),
* "attributes": {
* "id_stu": <id_stu>,
* "timestamp": <timestamp_string_in_ISO_format> (e.g.: "2016-07-13 17:50:00.224+0200"),
* "picto": {...}
* }
* }
* @param {response} res {<action_created>}
*/
action: function (req, res) {
var action = req.param('action');
var attributes = req.param('attributes');
sails.log.debug("Inside action. Student:" + attributes.id_stu);
if (!req.isSocket) {
sails.log.debug("No socket request for action");
res.badRequest()
} else {
sails.log.debug("websockets - room " + sails.hooks.rooms.student(attributes.id_stu));
// BROADCAST to everyone subscribed to this student
const socketToOmit = (req.isSocket) ? req.socket : undefined;
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.student(attributes.id_stu),
sails.hooks.events.actionPerformed(action, attributes),
socketToOmit
);
var sup = null;
if (attributes.id_sup) sup = attributes.id_sup;
var desc = null;
if (attributes.stu_picto) desc = attributes.stu_picto; // select, add and delete actions data
if (attributes.pictos) desc = attributes.pictos; // show action data
Action.create({
type: action,
timestamp: attributes.timestamp, // it comes already in ISO format
supervisor: sup,
student: attributes.id_stu,
description: desc
})
.then(function (created) {
res.json({
action: created
});
})
.fail(function(err) {
sails.log.error(err.details);
res.serverError(err.details);
});
}
},
//
// Logs a config action and broadcast to anyone subscribed to this student
config: function (req, res) {
var action = req.param('action');
var attributes = req.param('attributes');
sails.log.debug("Inside config");
if (req.isSocket) {
const socketToOmit = (req.isSocket) ? req.socket : undefined;
sails.hooks.events.broadcastEvent(
sails.hooks.rooms.student(attributes.id_stu),
'action',
{
"action": action,
"attributes": attributes
},
socketToOmit
);
} else {
res.ok({
msg: "Config " + action + " action from student " + attributes.id_stu
});
}
},
//
// Stores in the action table a bulk of actions loaded from of the
// recorded action log
//
actions_batch: function (req, res) {
var params = req.allParams();
var count = 0;
sails.log.debug("Actions_batch request");
if (!params.actions)
return res.json(400, {
'error': "no actions"
});
// We loop through the actions and store them in the database
async.forEach(params.actions, function (action, cb) {
sails.log.debug("Actions batch is recording action: " + JSON.stringify(action));
var id_sup = null;
if (action.attributes.id_sup)
id_sup = action.attributes.sup;
var id_stu = null;
if (action.attributes.id_stu)
id_stu = action.attributes.id_stu;
var desc = null;
if (action.attributes.picto)
desc = action.attributes.picto; // select, add and delete actions data
if (action.attributes.pictos)
desc = action.attributes.pictos; // show action data
Action.create({
type: action.action,
timestamp: action.attributes.timestamp,
supervisor: id_sup,
student: id_stu,
description: desc
})
.exec(function (err, created) {
if (err) {
console.log(err.details);
sails.log.error(err.details);
return cb(err);
} else if (created)
count++;
cb();
});
},
function (err) { // function called when loop is done
if (err) {
console.log(err.details);
sails.log.error(err.details);
return res.json({
'error': err.details
});
} else
return res.json({
'result': 'Ok',
'total': count
});
});
},
//
// Returns the last instruction for the student
//
last_instruction: function (req, res) {
if (!req.params.id_stu)
return res.json(400, {
err: 'id_stu parameter is missing'
});
VStuLastInstruction.find({
id_stu: req.params.id_stu
})
.exec(function (err, found) {
if (err)
return res.json(500, err);
if (!found)
return res.json({});
return res.json(found);
});
}
};
......@@ -3,7 +3,7 @@
// Please note that $modalInstance represents a modal window (instance) dependency.
// It is not the same as the $modal service used above.
dashboardControllers.controller('PictoConfigCtrl', function ($scope, $modalInstance, $http, config, studentPicto, sup, stu) {
dashboardControllers.controller('PictoConfigCtrl', function ($window, $scope, $modalInstance, $http, config, studentPicto, sup, stu) {
// Last parameter passed from collections.js
// Basic data passed from the main window
......@@ -34,7 +34,7 @@ dashboardControllers.controller('PictoConfigCtrl', function ($scope, $modalInsta
console.log("Expression changed: " + JSON.stringify(data)+": "+$scope.studentPicto.expression.text);
$scope.studentPicto.attributes.expression=$scope.studentPicto.expression.text;
// Close the modal instance
$modalInstance.close($scope.studentPicto.expression.text);
//$modalInstance.close($scope.studentPicto.expression.text);
// Notifcar cambio
io.socket.post('/stu/vocabulary', {
......@@ -70,7 +70,7 @@ dashboardControllers.controller('PictoConfigCtrl', function ($scope, $modalInsta
console.log("Properties updated");
// Close the modal instance
$modalInstance.close($scope.expression);
//$modalInstance.close($scope.expression);
// /////////////////////////////////////////////////////////////
// Websocket: Emit vocabulary update action
......@@ -92,6 +92,21 @@ dashboardControllers.controller('PictoConfigCtrl', function ($scope, $modalInsta
});
};
$scope.update_legend = function(){
console.log("Legend: " + $scope.studentPicto.attributes.legend+" to be modified");
$http
.put(config.backend+'/stu/'+ $scope.stu.id + '/legend/' + $scope.studentPicto.attributes.legend)
.success(function(data, status, headers, config) {
console.log("Legend updated");
// Close the modal instance
$modalInstance.close($scope.expression);
$window.location.reload();
});
};
$scope.close = function () {
// Lo que se devuelve a collection
$modalInstance.dismiss('cancel');
......
......@@ -62,12 +62,12 @@
<hr>
<div class="row">
<div class="col-md-8">
<input type="checkbox">
<input type="checkbox" ng-model="update_all_legend" ng-init="update_all_legend=false">
<span translate>legend_apply_all</span>
</div>
<div class="col-md-4">
<div class="form-group text-center">
<button type="submit" class="btn btn-primary ng-scope" ng-click="" disabled translate>apply</button>
<button type="submit" class="btn btn-primary ng-scope" ng-click="update_all_legend ? update_legend() : close()" translate>apply</button>
</div>
</div>
</div>
......
......@@ -96,6 +96,7 @@ module.exports.policies = {
ws: ['tokenAuth'],
update: ['tokenAuth'],
update_picto: ['tokenAuth', 'isSupervisorOfStudent'],
update_legend: ['tokenAuth'],
login: true,
create: ['tokenAuth'],
upload: ['tokenAuth'],
......
......@@ -81,6 +81,7 @@ module.exports.routes = {
'GET /stu/:id_stu/ws': 'StudentController.ws',
'PUT /stu/:id_stu': 'StudentController.update',
'PUT /stu/:id_stu/picto/:id_stuPicto': 'StudentController.update_picto',
'PUT /stu/:id_stu/legend/:legend_value': 'StudentController.update_legend',
'PUT /stu/:id_stu/picto': 'StudentController.update_picto',
'POST /stu/login': 'StudentController.login',
'POST /stu': 'StudentController.create',
......
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