Commit a9388700 by Sebastián Collado Montañez

Merge branch 'develop' of http://gitlab.ujaen.es/yotta/pictogram into develop

parents b4dc86eb e197815f
Showing with 395 additions and 123 deletions
......@@ -15,6 +15,8 @@ sails/arasaac*
sails/arasaac
sails/src/assets/symbolstx*
sails/src/assets/upload
sails/src/assets/arasaac*
sails/src/assets/arasaac
sails/upload.tgz
# Webapp #
......@@ -24,7 +26,6 @@ sails/src/assets/app/bower_components
sails/src/assets/app/modules
sails/src/assets/app/css
sails/src/assets/app/js
sails/src/assets/scripts/config.js
sails/src/config/ssl/**/*.key
sails/src/config/ssl/**/*.crt
sails/src/config/ssl/**/*.csr
......
......@@ -178,6 +178,19 @@ public class Device extends SQLiteOpenHelper {
return user;
}
public Vector<User> recoverSupervisors(Integer id_stu) throws JSONException{
SQLiteDatabase db = this.getReadableDatabase();
Vector<User> users = new Vector<>();
Cursor cursor = db.query("users_detail", null, null, null, null, null, null, null);
while (cursor.moveToNext())
if (cursor.getInt(0) == id_stu)
users.add(new User(cursor.getInt(0), cursor.getString(1), cursor.getString(2), cursor.getString(3), cursor.getString(4), cursor.getString(5), cursor.getString(6), cursor.getString(7), cursor.getString(8),
cursor.getInt(9), cursor.getString(10), cursor.getString(11), cursor.getString(12), cursor.getString(13), cursor.getString(14), cursor.getString(15), cursor.getString(16), cursor.getString(17), cursor.getString(18)));
cursor.close();
//db.close(); <--no es necesario cerrar la bbdd https://groups.google.com/forum/#!msg/android-developers/NwDRpHUXt0U/jIam4Q8-cqQJ
return users;
}
public Vector<User> recoverStudents(Integer id_sup) throws JSONException{
SQLiteDatabase db = this.getReadableDatabase();
Vector<User> users = new Vector<>();
......
......@@ -52,7 +52,6 @@ public class Picto extends Img {
public static String LEGEND = "legend";
public static String URI_SOUND = "uri_sound";
public static String USER_AVATAR = "user_avatar";
public static String ASSOCIATED_PERSON = "associated_person";
}
......@@ -91,7 +90,7 @@ public class Picto extends Img {
Log.e(LOG_TAG,e.getMessage());
}
}
public Picto(int id, String url, String translation, int cat, int row, int column, int freeRow, int freeColumn, int stupicto_id, String uri_sound, String user_avatar, String associated_person) throws JSONException {
public Picto(int id, String url, String translation, int cat, int row, int column, int freeRow, int freeColumn, int stupicto_id, String uri_sound, String user_avatar) throws JSONException {
this(id, url, new JSONObject()
.put(JSON_ATTTRS.CATEGORY, cat)
.put(JSON_ATTTRS.COLUMN, column)
......@@ -104,7 +103,6 @@ public class Picto extends Img {
.put(JSON_ATTTRS.EXPRESSION,translation)
.put(JSON_ATTTRS.URI_SOUND,uri_sound)
.put(JSON_ATTTRS.USER_AVATAR,user_avatar)
.put(JSON_ATTTRS.ASSOCIATED_PERSON,associated_person)
);
}
public Picto(int id, String url,String translation, String attributes) throws JSONException {
......@@ -305,14 +303,14 @@ public class Picto extends Img {
*
* @return the associated person of a picto
*/
public String get_associated_person() {
String ass_person = null;
public String get_user_avatar() {
String associated_user = null;
try {
ass_person = this.attributes.getString(JSON_ATTTRS.ASSOCIATED_PERSON);
associated_user = this.attributes.getString(JSON_ATTTRS.USER_AVATAR);
} catch (JSONException e) {
e.printStackTrace();
}
return ass_person;
return associated_user;
}
/**
......@@ -377,6 +375,18 @@ public class Picto extends Img {
/**
*
* @return the uri of associated sound of the picto
*/
public void setUriSound(String path){
try {
this.attributes.put(JSON_ATTTRS.URI_SOUND, path);
} catch (JSONException e) {
e.printStackTrace();
}
}
/**
*
* @return the associated user avatar of the picto
*/
public String getUserAvatar(){
......
......@@ -427,7 +427,7 @@ public class Vocabulary implements Iterable<Picto> {
/*
* It saves locally a new picto obtained from the PCB
*/
public void saveLocalPicto(String url, String exp, int cat, int coord_x, int coord_y, int free_category_coord_x, int free_category_coord_y,String uri_sound,String user_avatar,String associated_person, final iLocalPicto listener) {
public void saveLocalPicto(String url, String exp, int cat, int coord_x, int coord_y, int free_category_coord_x, int free_category_coord_y,String uri_sound,String user_avatar, final iLocalPicto listener) {
Picto prev_picto=PCBcontext.getPcbdb().getCurrentUser().has_categories() ? find_picto(cat, coord_x,coord_y) : find_picto(coord_x,coord_y); //¿estamos reemplazanddo un picto que ya existe?
......@@ -439,7 +439,7 @@ public class Vocabulary implements Iterable<Picto> {
int id=PCBcontext.getDevice().getNextLocalPictoID();
try {
final Picto picto = new Picto(id, url, exp, cat, coord_x, coord_y, free_category_coord_x, free_category_coord_y,prev_picto!=null ? prev_picto.get_stupicto_id() : Picto.STUPICTO_NULL,uri_sound,user_avatar,associated_person);
final Picto picto = new Picto(id, url, exp, cat, coord_x, coord_y, free_category_coord_x, free_category_coord_y,prev_picto!=null ? prev_picto.get_stupicto_id() : Picto.STUPICTO_NULL,uri_sound,user_avatar);
addPicto(picto, ImgDownloader.tsource.local, new ImgDownloader.iImgDownloaderListener() {
@Override
public void loadComplete() {
......
......@@ -9,6 +9,7 @@ import com.koushikdutta.ion.Response;
import com.yottacode.net.RestapiWrapper;
import com.yottacode.pictogram.R;
import com.yottacode.pictogram.action.VocabularyAction;
import com.yottacode.pictogram.dao.Device;
import com.yottacode.pictogram.dao.Picto;
import com.yottacode.pictogram.tools.Img;
import com.yottacode.pictogram.tools.PCBcontext;
......@@ -23,7 +24,7 @@ import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Hashtable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
/**
......@@ -98,7 +99,66 @@ public class PictoUploader {
return success;
}
//TODO: Refinar el metodo
private boolean uploadSound(File audioFile) throws UnsupportedEncodingException {
boolean success;
Response<JsonObject> response=null;
String [] path = audioFile.getPath().split("\\.");
String extension = path[1];
if (extension != "mp3")
throw new UnsupportedEncodingException("Extension "+extension+" is not supported. Only mp3 files");
Ion ion = Ion.getDefault(PCBcontext.getContext());
try {
response=ion.with(PCBcontext.getContext())
.load("POST", PCBcontext.getContext().getResources().getString(R.string.server) + "/picto/upload_sound/"+picto.get_stupicto_id())
.setMultipartParameter("filename", "id_del _sonido")
.setMultipartParameter("extension", "mp3")
.setMultipartParameter("owner", Integer.toString(PCBcontext.getPcbdb().getCurrentUser().get_id_sup()))
.setMultipartParameter("folder", "pictoSound")
.setMultipartParameter("token", PCBcontext.getRestapiWrapper().getToken())
.setMultipartFile("file", "audio/mp3", audioFile)
.asJsonObject()
.withResponse().get();
if (response != null && response.getHeaders().code() == 200) {
Log.i(LOG_TAG, "Uploaded image result: " + response.getHeaders() + ":" + response.getResult());
//int img_id=response.getResult().get("id").getAsInt();
//String img_uri=response.getResult().get("uri").getAsString();
//img.set_url(img_uri);
//img.update_id(img_id);
success=true;
} else {
success=false;
Log.i(LOG_TAG, "Uploaded Sound failed ");
if (response != null)
Log.i(LOG_TAG, "Uploaded Sound failed, headers: " + response.getHeaders());
}
} catch (InterruptedException e) {
Log.e(LOG_TAG, "Sound upload error: " + e.getMessage()+ "Code: "+
(response == null ? -1 : response.getHeaders().code()));
success=false;
} catch (ExecutionException e) {
Log.e(LOG_TAG, "Sound upload error: " + e.getMessage()+
(response == null ? -1 : response.getHeaders().code()));
success=false;
} /*catch (IOException e) {
Log.e(Img.class.getCanonicalName(), "Error when decoding "+audioFile.getName());
GUITools.show_alert(PCBcontext.getContext(), R.string.imguserLoadingErrMsg);
success=false;
}*/
// ion.dump();
return success;
}
/**
* if a picto was included from the PCB, the translation is uploaded to the server
......@@ -121,6 +181,7 @@ public class PictoUploader {
e.printStackTrace();
Log.e(LOG_TAG, " Error: " + e.getLocalizedMessage());
}
PCBcontext.getRestapiWrapper().ask(PCBcontext.getPcbdb().getCurrentUser().get_restapi_operation_stu() + "/picto/"+id_picto, params, "post", true, new RestapiWrapper.iRestapiListener() {
@Override
public void preExecute() {
......@@ -238,15 +299,26 @@ public class PictoUploader {
(stupicto_id!=Picto.STUPICTO_NULL ? ", stupicto to be deleted:"+stupicto_id+")" : " .New picto"));
iPictoUploaderListener listener = new PictoUploaderListener(local_id,elements_to_be_uploaded);
if (imgUpload_success) {
if (imgUpload_success){
if(this.picto.getUriSound() != "" && this.picto.getUriSound() != null ){ //Si el picto tiene audio en local
Log.i("TAG_PRUEBAS","Hay uri: "+this.picto.getUriSound()+" -Procede a subir archivo");
File file = new File(picto.getUriSound()); //Obtengo el fichero de audio local
boolean soundUpload_success = uploadSound(file); //Llamo a la subida
if(soundUpload_success) {
Log.i("TAG_PRUEBAS","Se sube sonido hay EXITO");
}else {
GUITools.show_alert(PCBcontext.getActivityContext(),Integer.parseInt(R.string.upload_error+"Audio"), PictoUploader.this.picto.get_translation());
}
}
if (stupicto_id!=Picto.STUPICTO_NULL) {
Log.i(LOG_TAG, "Remote Picto to be deleted:"+this.picto.get_translation()+"("+stupicto_id+")");
deletePicto(stupicto_id,listener);
} else
} else {
uploadTranslation(picto.get_id(), listener);
}
else {
GUITools.show_alert(PCBcontext.getActivityContext(), R.string.upload_error, PictoUploader.this.picto.get_translation());
}
}else{
GUITools.show_alert(PCBcontext.getActivityContext(), Integer.parseInt(R.string.upload_error+"Imagen"), PictoUploader.this.picto.get_translation());
}
}
......
......@@ -1155,18 +1155,18 @@ protected void showOnlyTape(boolean onlyTape) {
int freeRow = edit ? data.getExtras().getInt(Picto.JSON_ATTTRS.FREE_ROW) : getIntent().getIntExtra(Picto.JSON_ATTTRS.FREE_ROW, -1);
int freeColumn = edit ? data.getExtras().getInt(Picto.JSON_ATTTRS.FREE_COLUMN) : getIntent().getIntExtra(Picto.JSON_ATTTRS.FREE_COLUMN, -1);
String uri_sound = edit ? data.getExtras().getString(Picto.JSON_ATTTRS.URI_SOUND) : getIntent().getStringExtra(Picto.JSON_ATTTRS.URI_SOUND);
String associated_person = edit ? data.getExtras().getString(Picto.JSON_ATTTRS.ASSOCIATED_PERSON) : getIntent().getStringExtra(Picto.JSON_ATTTRS.ASSOCIATED_PERSON);
String uri_sound = data.getExtras().getString(Picto.JSON_ATTTRS.URI_SOUND);
String user_avatar = data.getExtras().getString(Picto.JSON_ATTTRS.USER_AVATAR);
int cat = edit ? data.getIntExtra(Picto.JSON_ATTTRS.CATEGORY, -1) : getIntent().getIntExtra(Picto.JSON_ATTTRS.CATEGORY, -1);
String path = data.getExtras().getString(PictoMenu.PATH);
String legend = data.getExtras().getString(Picto.JSON_ATTTRS.EXPRESSION);
Log.i(EditPictoActivity.DEBUG_MESSAGE,"Antes de chooseText...DATOS--> ASS_PERSON: "+associated_person+"..URI_SOUND: "+uri_sound+"..PATH_IMAGE: "+path+"..EXPRESION: "+legend);
Log.i(EditPictoActivity.DEBUG_MESSAGE,"Antes de chooseText...DATOS--> ASS_PERSON: "+user_avatar+"..URI_SOUND: "+uri_sound+"..PATH_IMAGE: "+path+"..EXPRESION: "+legend);
//TODO: COGER URI DEL SONIDO,EL USER AVATAR Y LA PERSONA ASOCIADA AL PICTO
chooseTextAndSavePicto(path, row, col, freeRow, freeColumn, cat, legend, uri_sound, null, associated_person);
chooseTextAndSavePicto(path, row, col, freeRow, freeColumn, cat, legend, uri_sound,user_avatar);
refresh();
}
break;
......@@ -1178,7 +1178,7 @@ protected void showOnlyTape(boolean onlyTape) {
* función para la edición de un texto asociado a una nueva imagen y guardar el nuevo picto
*/
public void chooseTextAndSavePicto(final String selectedImagePath, final int row, final int col, final int freeRow, final int freeColumn,
final int category, final String legend,final String uri_sound,final String user_avatar, String associated_person) {
final int category, final String legend,final String uri_sound,final String user_avatar) {
// Set up the buttons
int cat = category != -1 ? category : Picto.NO_CATEGORY;
......@@ -1194,7 +1194,6 @@ protected void showOnlyTape(boolean onlyTape) {
freeColumn,
uri_sound,
user_avatar,
associated_person,
new iLocalPicto() {
@Override
public void saved(Picto localPicto) {
......
......@@ -57,7 +57,9 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;
import pl.droidsonroids.gif.GifTextView;
import static java.lang.Thread.sleep;
......@@ -233,27 +235,39 @@ public class EditPictoActivity extends Activity {
ActivityCompat.requestPermissions(this, permissions, REQUEST_RECORD_AUDIO_PERMISSION);
//Editar la lista desplegable de supervisores asociados
mDrawerList = (ListView)findViewById(R.id.navList);
/**Obtener la lista de supervisores y aplicarle formato*/
Log.i(DEBUG_MESSAGE,"Usuario: "+ PCBcontext.getPcbdb().getCurrentUser().get_name_stu());
String supervisors = PCBcontext.getPcbdb().getCurrentUser().get_Supervisors();
Log.i(DEBUG_MESSAGE,"Supervisores: "+ supervisors);
ArrayList<String> supervisores = new ArrayList<>();
ArrayList<String> supervisoresAdapter = new ArrayList<>();
Vector<User> supervisores = null;
try {
supervisores = PCBcontext.getDevice().recoverSupervisors(PCBcontext.getPcbdb().getCurrentUser().get_id_stu());
} catch (JSONException e) {
e.printStackTrace();
}
if (supervisores != null){
for(User supervisor: supervisores){
supervisoresAdapter.add(supervisor.get_name_sup()+", "+supervisor.get_surname_sup()+ "\n" +supervisor.get_email_sup() );
}
}
//String supervisors = PCBcontext.getPcbdb().getCurrentUser().get_Supervisors();
//Log.i(DEBUG_MESSAGE,"Supervisores: "+ supervisors);
//ArrayList<String> supervisores = new ArrayList<>();
if(supervisors != null) {
/*if(supervisors != null) {
String[] separated = supervisors.split(";");
for (String supervisor : separated) {
String[] detalles = supervisor.split(",");
supervisores.add(detalles[0] + "\n" + detalles[1]);
}
}else
Log.i(DEBUG_MESSAGE,"No tiene supervisores...");
Log.i(DEBUG_MESSAGE,"No tiene supervisores...");*/
mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, supervisores);
mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, supervisoresAdapter);
mDrawerList.setAdapter(mAdapter);
mDrawerLayout = (DrawerLayout)findViewById(R.id.drawer_layout);
......@@ -304,7 +318,7 @@ public class EditPictoActivity extends Activity {
if(!fileAudio.exists()){
fileAudio.mkdirs();
}
previewAudioPath = dirAudioPath+File.separator+"test.3gp";
previewAudioPath = dirAudioPath+File.separator+"test.mp3";
// Initialize Views.
......@@ -352,7 +366,7 @@ public class EditPictoActivity extends Activity {
if(editar){
p = PCBcontext.getVocabulary().get_picto(getIntent().getExtras().getInt(Picto.JSON_ATTTRS.CATEGORY),getIntent().getExtras().getInt(PictoMenu.ID_PICTO_IMAGE));
legend.setText(p.get_translation());
supAsociado.setText(p.get_associated_person());
supAsociado.setText(p.get_user_avatar());
}
legend.setHorizontallyScrolling(false);
legend.setMaxLines(1);
......@@ -360,7 +374,7 @@ public class EditPictoActivity extends Activity {
//Obtener imagen del intent
byte[] byteArray = getIntent().getByteArrayExtra(PictoMenu.IMAGE_PICTO);
Bitmap imagePicto = scale_image(BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length));
final Bitmap imagePicto = scale_image(BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length));
cropImageView.setAspectRatio(editar ? imagePicto.getWidth() : 4,editar ? imagePicto.getHeight() : 3); //Si es editar un picto la ventana de recorte mantiene el tamaño de la imagen, sino 4:3
cropImageView.setImageBitmap(imagePicto);
......@@ -447,8 +461,8 @@ public class EditPictoActivity extends Activity {
if (file.exists()) {
File from = new File(previewAudioPath);
File to = new File(editar ? dirAudioPath + File.separator + legend.getText().toString() + "_" + id_pic + "_audio.3gp" :
dirAudioPath + File.separator + legend.getText().toString() + "_new_" + pathNumber + "_audio.3gp");
File to = new File(editar ? dirAudioPath + File.separator + legend.getText().toString() + "_" + id_pic + "_audio.mp3" :
dirAudioPath + File.separator + legend.getText().toString() + "_new_" + pathNumber + "_audio.mp3");
if (from.exists()){
from.renameTo(to);
Log.i(DEBUG_MESSAGE,to.getPath().toString());
......@@ -457,13 +471,16 @@ public class EditPictoActivity extends Activity {
}
}
Intent intent = getIntent(); //Mandar a pictogram activity el path y el texto de la imagen
intent.putExtra(PictoMenu.IS_EDIT, editar); //Para saber despues si estas editando o añadiendo nuevo y coger los datos de intent o de data en OnActivityResult
intent.putExtra(PictoMenu.PATH, filepath); //Mandar Path imagen
intent.putExtra(Picto.JSON_ATTTRS.URI_SOUND,audioPath); //Mandar el path del audio
intent.putExtra(Picto.JSON_ATTTRS.EXPRESSION, legend.getText().toString()); //Mandar expresion nueva
intent.putExtra(Picto.JSON_ATTTRS.CATEGORY, getIntent().getIntExtra(Picto.JSON_ATTTRS.CATEGORY, -1));
intent.putExtra(Picto.JSON_ATTTRS.ASSOCIATED_PERSON, supAsociado.getText());
intent.putExtra(Picto.JSON_ATTTRS.USER_AVATAR, supAsociado.getText());
//p.setUriSound(audioPath);
setResult(RESULT_OK, intent);
......@@ -613,7 +630,7 @@ public class EditPictoActivity extends Activity {
private void startRecording() {
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mRecorder.setOutputFile(previewAudioPath);
......
......@@ -203,7 +203,7 @@ public class StudentFragmentGrid extends Fragment{
downloader.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, imgs);
}
private void download_supervisors(int stu_id) {
private void download_supervisors(final int stu_id) {
String token = getActivity().getIntent().getExtras().getString("token");
RestapiWrapper wrapper = PCBcontext.getRestapiWrapper();
......@@ -225,17 +225,20 @@ public class StudentFragmentGrid extends Fragment{
}
@Override
public void result(JSONArray supervisors) {
String supervisorsFormat = "";
//String supervisorsFormat = "";
for (int i=0;i<supervisors.length();i++) {
JSONObject supervisor;
try {
supervisor = supervisors.getJSONObject(i);
supervisorsFormat += supervisor.get("name") +" " + supervisor.get("surname") + "," + supervisor.get("email") + ";";
//PCBcontext.getDevice().insertUser(new User(stu_id,null,null,null,null,null,null,null,null,(int) supervisor.get("id"),supervisor.get("email").toString(),null,supervisor.get("name").toString(),
// supervisor.get("surname").toString(), supervisor.get("pic").toString(),supervisor.get("gender").toString(),supervisor.get("lang").toString(),supervisor.get("ttsEngine").toString(),supervisor.get("office").toString()));
//supervisorsFormat += supervisor.get("name") +" " + supervisor.get("surname") + "," + supervisor.get("email") + ";";
} catch (JSONException e) {
e.printStackTrace();
}
}
PCBcontext.getPcbdb().getCurrentUser().set_Supervisors(supervisorsFormat);
//PCBcontext.getPcbdb().getCurrentUser().set_Supervisors(supervisorsFormat);
}
@Override
......
......@@ -62,13 +62,6 @@
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/rs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/shaders" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/assets" type="java-test-resource" />
......@@ -76,6 +69,13 @@
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/test/shaders" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/res" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/resources" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/assets" type="java-test-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/aidl" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/rs" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/src/androidTest/shaders" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/annotations" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/blame" />
<excludeFolder url="file://$MODULE_DIR$/build/intermediates/bundles" />
......@@ -121,17 +121,5 @@
<orderEntry type="library" exported="" name="ion-2.1.9" level="project" />
<orderEntry type="library" exported="" name="play-services-ads-lite-9.2.1" level="project" />
<orderEntry type="module" module-name="commonlibrary" exported="" />
<orderEntry type="library" exported="" name="android-android-24" level="project" />
<orderEntry type="library" exported="" name="okhttp-ws-2.3.0" level="project" />
<orderEntry type="library" exported="" name="socket.io-client-0.5.0" level="project" />
<orderEntry type="library" exported="" name="okhttp-2.3.0" level="project" />
<orderEntry type="library" exported="" name="support-annotations-23.0.0" level="project" />
<orderEntry type="library" exported="" name="okio-1.3.0" level="project" />
<orderEntry type="library" exported="" name="gson-2.3" level="project" />
<orderEntry type="library" exported="" name="engine.io-client-0.5.0" level="project" />
<orderEntry type="library" exported="" name="appcompat-v7-21.0.3" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="hamcrest-core-1.3" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="junit-4.12" level="project" />
<orderEntry type="library" exported="" scope="TEST" name="json-20090211" level="project" />
</component>
</module>
\ No newline at end of file
Estos son scripts de automatización para las siguientes tareas en el servidor:
- Renovación de certificados
- Backup automático
## Renovación de certificados
Ver [letsencrypt.md](http://gitlab.ujaen.es/yotta/pictogram/blob/master/sails/src/config/ssl/letsencrypt.md)
## Backup automático
### Dependencias:
- Se está usando el script [ec2-automate-backup](https://github.com/colinbjohnson/aws-missing-tools/tree/master/ec2-automate-backup).
- Es necesario, para su uso, instalar [AWS-CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html)
### Configuración:
Desde AWS IAM, debemos crear un usuario dentro de un grupo. Los permisos son los siguientes:
```
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Stmt1489386668000",
"Effect": "Allow",
"Action": [
"ec2:CreateSnapshot",
"ec2:CreateTags",
"ec2:DeleteSnapshot",
"ec2:DeleteTags",
"ec2:DescribeSnapshotAttribute",
"ec2:DescribeSnapshots",
"ec2:DescribeVolumes",
"ec2:ModifySnapshotAttribute"
],
"Resource": "*"
}
]
}
```
El comando cron que hay de muestra en `crontab` se encargará de hacer una copia cada día, para los últimos 30 días.
\ No newline at end of file
#!/bin/bash
# This script is to be run in a remote machine with access to pre
DUMPFILE=/home/ubuntu/backup/`date +%d`-mysqldump.sql.gz
ssh pre "nice mysqldump -u root -p'r00...Tt' pictodb | gzip > $DUMPFILE"
scp pre:$DUMPFILE /home/amontejo/backup
#!/bin/bash -
# export PATH=/bin is required for cut, date, grep
# export PATH=/usr/bin is required for AWS Command Line Interface tools
export PATH=/bin:/usr/bin:/home/ubuntu/.local/bin
# AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY needed for AWS CLI tools
export AWS_ACCESS_KEY_ID=AKIAJJZNE3V5QMKSMU6A
export AWS_SECRET_ACCESS_KEY=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
# AWS_CONFIG_FILE required for AWS Command Line Interface tools (f.e. ".aws")
export AWS_CONFIG_FILE=/root/.aws/config
@reboot /home/ubuntu/pictogram/sails/bootstrap.sh
# Let's encrypt certs autorenew
@monthly /home/ubuntu/cron/renewcerts.sh
# AWS Volume backups
@daily /home/ubuntu/cron/aws-missing-tools/ec2-automate-backup/ec2-automate-backup.sh -c /home/ubuntu/cron/cron-primer.sh -v vol-0cd5c496 -k 30 -p -u -r "eu-central-1"
#!/bin/bash
# Renew certs
/home/ubuntu/letsencrypt/letsencrypt-auto --config /etc/letsencrypt/cli.ini -d login.pictogramweb.com -d api.pictogramweb.com -d app.pictogramweb.com -d pre.yottacode.com -d pictogram.yottacode.com -d apk.pictogramweb.com certonly
# Reload Sails
(cd /home/ubuntu/pictogram/sails/src; /usr/bin/forever stopall; /usr/bin/forever start app.js)
......@@ -309,6 +309,7 @@ CREATE TABLE IF NOT EXISTS `license` (
`activation_ts` datetime NULL,
`expiration_ts` datetime NULL,
`duration` int(11) DEFAULT 0,
`creator` varchar(40) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `number` (`number`),
KEY `id_stu` (`id_stu`)
......
......@@ -43,6 +43,9 @@ Changes to be performed manually in servers to upgrade
(already done in dev & pre)
- add new column to license:
`alter table license add column creator varchar(40) DEFAULT NULL;`
- add arasaac to source table
`INSERT INTO `source` (`id`, `name`, `description`) VALUES (3, 'Arasaac', 'Arasaac pictograms collection');`
- alter table supervisor add arasaac license:
......
......@@ -2,39 +2,44 @@
* AdminController which manages the administration login and login
* @type {Object}
*/
const bcrypt = require('bcrypt-nodejs');
module.exports = {
// @TODO ¿?¿?
login: function (req, res) {
var bcrypt = require('bcrypt-nodejs');
var email = req.body.email;
var password = req.body.password;
// no credentials
if (!email || !password)
return res.json(401, {error: 'No credentials sent'});
return res.badRequest('No credentials sent');
// Check email
if (email != sails.config.pictogram.admin.email)
return res.json(401, {error: 'User not found'});
// if found, check password in encrypted form
bcrypt.compare(password, sails.config.pictogram.admin.password, function (err, match) {
if (err)
return res.json(500, { error: 'Server error' });
if (!match) // password do not match
return res.json(401, { error: 'Invalid password' });
// credentials are valid, return token with max life span
return res.json(200, {
token: sailsTokenAuth.issueToken({'isAdmin': true}, sails.config.jwt.expiresInMinutes)});
var admin = sails.config.pictogram.admins.find((a) => a.email == email);
if (typeof admin == 'undefined')
return res.badRequest('User not found');
// if found, check password in encrypted form
bcrypt.compare(password, admin.password, function (err, match) {
if (err)
return res.serverError('Server error' + err);
if (!match) // password do not match
return res.unauthorized('Invalid password');
// credentials are valid, return token with max life span
var returned_admin = (JSON.parse(JSON.stringify(admin)));
delete returned_admin.password;
return res.ok({
token: sailsTokenAuth.issueToken({'isAdmin': true, 'email': email}, sails.config.jwt.expiresInMinutes),
user: returned_admin
});
});
},
// @TODO 357
logout: function (req, res) {
delete req.token;
res.json(200, {notice: 'Session closed'});
res.ok('Session closed');
}
};
......@@ -8,6 +8,24 @@
module.exports = {
/**
* Get all the licenses for the given creator
* @param {request} req
* {
* email (creator email)
* }
* @param {response} res
* [list of licenses]
*/
getByEmail: function (req, res) {
if (!req.params.email)
return res.badRequest("No email provided");
License.find({creator: req.params.email})
.then((l) => res.ok(l))
.catch((e) => res.serverError(e));
},
/**
* Create a new Instruction, which is associated to the given method
* @param {request} req
* {
......@@ -67,7 +85,8 @@ module.exports = {
get_new_random_license(function (license) {
License.create({
number: license,
duration: params.duration
duration: params.duration,
creator: req.token.email
})
.then((l) => {
if (l) {
......
......@@ -58,7 +58,7 @@ module.exports = {
Office.findOne({ id: id }).populate('admin').then(function (office) {
if (office) {
res.ok({logo_url: office.logo_url, name: office.name});
res.ok({logo_url: office.logo_url || '/app/img/logo_pictogram.png', name: office.name});
} else {
res.notFound();
}
......
......@@ -442,7 +442,7 @@ module.exports = {
// update supervisor office if it is linked as therapist
Supervisor.findOne({id: req.param('id_sup')})
.then((sup) => {
if (sup) {
if (sup && !sup.office) {
Student.findOne({id: req.param('id_stu')})
.then((stu) => {
if (stu) {
......@@ -1168,7 +1168,8 @@ module.exports = {
fs.unlinkSync(path.join(newAvatarDirectory, student.pic));
}
student.pic = newAvatarFileName;
student.pic = newAvatarFileName;
delete student.password;
student.save(function (updateStudentError) {
if (updateStudentError) {
throw updateStudentError;
......
......@@ -48,6 +48,10 @@ module.exports = {
size: 16,
unique: true
},
creator: {
columnName: "creator",
type: "string",
},
toJSON: function () {
var l = this.toObject();
delete l.id;
......
......@@ -87,7 +87,11 @@ module.exports = {
students: {
collection: "Student",
via: 'office'
}
},
toJSON: function() {
if (!attrs.logoUrl)
attrs.logoUrl = '/app/img/logo_pictogram.png';
}
},
beforeCreate: function (attrs, next) {
......
......@@ -2,7 +2,7 @@ var mailerService = require('sails-service-mailer');
module.exports.mailer = function() {
return mailerService('sendmail', {
from: 'no-reply@yottacode.com',
from: 'no-reply@pictogramweb.com',
subject: sails.__('notification_from_pictogram'),
provider: {
path: '/usr/sbin/sendmail'
......
......@@ -11,6 +11,7 @@
"action-tryend": "Try end",
"action-tryinit": "Try begin",
"action-unshow": "Writing mode",
"activation": "Activation",
"add": "Add",
"add_expression": "Add expression",
"add_instruction": "Add instruction",
......@@ -77,6 +78,7 @@
"country_office_requested": "Country for office/center is mandatory",
"create_account": "Create account",
"create_an_account": "Create an account",
"creation": "Creation",
"credentials": "Credentials",
"crop_image": "Crop image",
"data_no_saved": "Data can't be saved",
......@@ -178,6 +180,7 @@
"legend_normal":"Normal legend",
"legend_full":"Only legend",
"legend_size": "Legend size",
"license": "License",
"licenses": "Licenses",
"license_already_activated": "License already activated",
"license_created": "License created",
......
......@@ -11,6 +11,7 @@
"action-tryend": "Fin de ensayo",
"action-tryinit": "Inicio de ensayo",
"action-unshow": "Modo escritura",
"activation": "Activación",
"add": "Añadir",
"add_expression": "Añadir expresión",
"add_instruction": "Añadir instrucción",
......@@ -77,6 +78,7 @@
"country_office_requested": "Debe especificar el país del gabinete/centro",
"create_account": "Crear cuenta",
"create_an_account": "Crear una cuenta",
"creation": "Creación",
"credentials": "Credenciales",
"crop_image": "Recortar imagen",
"data_no_saved": "Los datos no se han podido guardar",
......@@ -178,6 +180,7 @@
"legend_full":"Sólo leyenda",
"legend_apply_all":"Aplicar a todos los pictogramas",
"legend_size": "Tamaño de la leyenda",
"license": "Licencia",
"licenses": "Licencias",
"licenses_left": "{{number}} licencias disponibles",
"license_already_activated": "Licencia ya activada previamente",
......
/vagrant/arasaac
\ No newline at end of file
/* global angular */
angular.module('dashboardConfig', []).constant('config', {
backend: 'https://' + window.location.host,
});
......@@ -3,11 +3,10 @@
//-------------------
// Admin Controller
//-------------------
dashboardControllers.controller('AdminCtrl', function AdminCtrl($scope) {
dashboardControllers.controller('AdminCtrl', function AdminCtrl($scope, $window) {
// The last parameter, config, is injected from config.js (defined in dashboardConfig module)
// Assign values this way (like an object) to ensure it's the parent scope $scope.user.name ='Super';
$scope.user.surname = 'Admin';
$scope.user.pic ='img/arturo.jpg';
$scope.user = JSON.parse($window.sessionStorage.user);
});
\ No newline at end of file
});
......@@ -10,9 +10,20 @@ dashboardControllers.controller('AdminLicensesCtrl', function AdminLicensesCtrl(
$scope.showmessagesupervisor = false;
$scope.new_numbers = [];
$scope.formdatalicense = {
duration: 1,
duration: 3,
repeat: 1
};
$scope.licenses = [];
// Get all licenses for the user
$http
.get(config.backend + '/license/' + $scope.user.email)
.success(function(data, status, headers, config) {
$scope.licenses = data;
})
.error(function(data, status, headers, config) {
ngToast.danger({content: $translate.instant('error_general')});
});
// This generates a new license and registers it in the database
$scope.create_licenses = function(supervisor) {
......@@ -21,14 +32,15 @@ dashboardControllers.controller('AdminLicensesCtrl', function AdminLicensesCtrl(
$http
.post(config.backend+'/license', $scope.formdatalicense)
.success(function(data, status, headers, config) {
$scope.new_numbers.push(data.number.substr(0,4) +
/*$scope.new_numbers.push(data.number.substr(0,4) +
"-" + data.number.substr(4,4) +
"-" + data.number.substr(8,4) +
"-" + data.number.substr(12,4));
*/
$scope.new_numbers.push(data.number);
})
.error(function(data, status, headers, config) {
ngToast.danger({content: $translate.instant('error_general')});
console.log("Error from API: " + data.error);
});
}
$scope.duration_registered = $scope.formdatalicense.duration;
......
......@@ -15,8 +15,9 @@ dashboardControllers.controller('LoginAdminCtrl', function LoginAdminCtrl($scope
$http
.post(config.backend+'/admin/login', $scope.credentials)
.success(function(data, status, headers, config) {
// Save token and user data y sessionStorage
// Save token, user data in sessionStorage
$window.sessionStorage.token = data.token;
$window.sessionStorage.user = JSON.stringify(data.user);
// Redirect to admin panel
$location.path('/admin/licenses');
......@@ -28,6 +29,10 @@ dashboardControllers.controller('LoginAdminCtrl', function LoginAdminCtrl($scope
.error(function(data, status, headers, config) {
// Delete token if user fails to login
delete $window.sessionStorage.token;
delete $window.sessionStorage.user;
// Redirect to admin panel
$location.path('/admin/login');
$translate('login_fail').then(function(translation) {
ngToast.danger({ content: translation });
......
......@@ -12,7 +12,7 @@
<a class="navbar-brand" href="/app/#/admin"><img src="img/logo_pictogram.png" alt="Pictogram" title="Pictogram" style="height: 70px;" /></a>
</div>
<!-- Agrupar los enlaces de navegación, los formularios y cualquier otro elemento que se pueda ocultar al minimizar la barra -->
<div class="collapse navbar-collapse navbar-ex6-collapse">
......@@ -21,9 +21,9 @@
</div>
<div class="nav navbar-nav navbar-right navbar-text">
<h4 class="text-right">{{user.getFullName()}}</h4>
<h4 class="text-right">{{ user.email }}</h4>
<p class="text-right">
<!-- Botón salir -->
<a ng-click="logout()" class="btn btn-default btn-sm" role="button">
<span class="glyphicon glyphicon-log-out" aria-hidden="true"></span> {{ 'logout' | translate }}
......@@ -33,4 +33,4 @@
</div>
</div>
</nav>
\ No newline at end of file
</nav>
......@@ -2,13 +2,10 @@
<div class="row">
<div class="col-md-2">
</div>
<div class="col-md-4">
<h3 translate>licenses</h3>
<div class="col-md-5">
<form role="form" ng-submit="create_licenses()">
<div class="form-group">
......@@ -27,18 +24,40 @@
</form>
<div ng-show="new_numbers.length > 0" class="alert alert-info">
<p>{{ 'licenses_created' | translate }}:</p><p>&nbsp;</p>
<div ng-repeat="number in new_numbers track by $index">
<p> <strong>{{ number }}</strong></p>
</div>
<p>{{ 'duration_in_months' | translate }}: {{ duration_registered }}</p>
</div>
<div class="col-md-4">
<div ng-show="new_numbers.length > 0" class="alert alert-info">
<p>{{ 'licenses_created' | translate }}:</p><p>&nbsp;</p>
<div ng-repeat="number in new_numbers track by $index">
<p> <strong>{{ number }}</strong></p>
</div>
<p>{{ 'duration_in_months' | translate }}: {{ duration_registered }}</p>
</div>
</div>
<div class="col-md-2">
</div>
<div class="col-md-5">
<p> {{ 'licenses_created' | translate }}: {{ licenses.length }}</p>
<div class="pre-scrollable">
<table class="table table-striped">
<thead>
<tr>
<th>{{ 'license' | translate }}</th>
<th>{{ 'creation' | translate }}</th>
<th>{{ 'activation' | translate }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="l in licenses | orderBy : 'creation_ts' : true" ng-class-odd="'striped'">
<td><tt>{{ l.number }}</tt></a></td>
<td>{{ l.creation_ts | date : "dd/MM/y" }}</td>
<td>{{ l.activation_ts | date : "dd/MM/y" }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
......@@ -49,7 +49,7 @@
</div>
<div class="form-group">
<label translate>license_number</label>
<input type="text" id="setup_license" class="form-control" mask="9999-9999-9999-9999" clean="true" placeholder="{{ 'license_number' | translate }}" ng-model="formUser.license_number" required>
<input type="text" id="setup_license" class="form-control" mask="wwww-wwww-wwww-wwww" clean="true" placeholder="{{ 'license_number' | translate }}" ng-model="formUser.license_number" required>
<div ng-show="studentData.license && !studentData.license_expired" class="alert alert-info" role="alert">
<i class="fa fa-info-circle" aria-hidden="true"></i> {{ 'license_expires' | translate }} {{ studentData.expiration_date }}
......
......@@ -26,7 +26,7 @@
</div>
<div class="form-group">
<input class="form-control" type="text" id="student_license" mask="9999-9999-9999-9999" clean="true" placeholder="{{ 'license_number' | translate }}" ng-model="formdatastudent.license_number" required>
<input class="form-control" type="text" id="student_license" mask="wwww-wwww-wwww-wwww" clean="true" placeholder="{{ 'license_number' | translate }}" ng-model="formdatastudent.license_number" required>
</div>
......
......@@ -11,12 +11,12 @@
</div>
<div class="col-xs-4">
<div class="languages-margin text-center text-muted">
Powered by &nbsp; <a href="http://www.yottacode.com"><img src="img/logo_pictogram.png" width="40px" alt="Pictogram" title="Pictogram" /></a>
Powered by &nbsp; <a href="http://www.pictogramweb.com"><img src="img/logo_pictogram.png" width="40px" alt="Pictogram" title="Pictogram" /></a>
</div>
</div>
<div class="col-xs-4">
<div class="languages-margin text-right text-muted">
<a href="mailto:soporte@yottacode.com" translate>support</a> |
<a href="mailto:soporte@pictogramweb.com" translate>support</a> |
<a href='/app/#/disclaimer' target='_blank' translate>disclaimer</a>
</div>
</div>
......
......@@ -59,6 +59,10 @@
margin-top: 7px;
}
.table .table-striped .striped {
background-color: lightgray !important;
}
/* Evitar scrolling horizontal */
body{
......
......@@ -6,10 +6,18 @@ var UPLOAD_PATH = path.join(__dirname, '..', '..', 'upload');
module.exports.pictogram = {
version: "1.1", // actual version of the server, to be checked by the client
admin: {
email: 'amontejo@ujaen.es',
password: '$2a$06$flEEOc15SerMeYWARrN9w.KSpJuM.jDsaTgrtD0ESzbxKHPl0f/zq' //y00ttaa!!
},
admins: [
{
email: 'amontejo@ujaen.es',
password: '$2a$06$flEEOc15SerMeYWARrN9w.KSpJuM.jDsaTgrtD0ESzbxKHPl0f/zq', //y00ttaa!!
lang: "es-es"
},
{
email: 'luisballesteros@editorialcepe.es',
password: '$2a$10$Re9LxejAm6GjYLvw7LCbQeLiNn4HDGxjQMbZxg0paP3SGIJI6Rn52', //ba!!3sTe;0s
lang: "es-es"
}
],
serialSize: 10, // number of characters in generated serial numbers
pageLimit: 10, // number of elements per "page"
......
......@@ -121,8 +121,9 @@ module.exports.policies = {
LicenseController: {
// create: ['tokenAuth', 'isAdmin'],
// activate: ['tokenAuth']
create: true,
activate: true
create: ['tokenAuth'],
activate: ['tokenAuth'],
getByEmail: ['tokenAuth']
},
SupervisorController: {
......
......@@ -39,6 +39,7 @@ module.exports.routes = {
'POST /instruction/template': 'MetaInstructionController.create',
'DELETE /instruction/template/:id': 'MetaInstructionController.destroy',
'GET /license/:email': 'LicenseController.getByEmail',
'POST /license': 'LicenseController.create',
'PUT /license/:number': 'LicenseController.activate',
......
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