martes, 14 de junio de 2016

Introducción a Sails JS - Parte 2 - Creando Backend de Whatsapp

                                                                                                                Hola, en esta segunda parte de nuestro curso de SailsJS vamos a crear una aplicación completa, haremos una copia de Whatsapp, lo vamos a dividir en dos partes, l primer en el backend y la segunda el frontend.
Puedes revisar la primera parte para tener mas claro algunos conceptos.











Las tecnologias que vamos a ocupar son:

  • SailsJS (Backend).
  • Ionic (Frontend).
  • MongoDB (Database).
Así que manos a la obra y vamos a comenzar este tutorial y espero que sea de su agrado.

Modelos.
Vamos a tener 3 modelos, en nuestra base de datos se representan como colecciones, tenemos las siguientes:
  • Users: Esta colección es la representación de nuestros usuarios, que tendrá la información personal de cada uno de ellos, como su numero telefonico.
  • Messages: Representa los mensajes de cada uno de nuestros usuarios.
  • Groups: Los grupos que contendrá tanto usuarios como mensajes.
Backend.
Ahora crearemos nuestra aplicativo con SailsJS, ¿Que vamos hacer? por el momento solo necesitamos el Backend por lo cual vamos a diseñar nuestra API Rest y después consumirla desde la aplicación móvil (Esto sera explicado en el siguiente tutorial).

En sails existe un comando para generar nuestro proyecto sin la carpeta assets y así poder trabajar solo en nuestro backend.
Nos dirigimos a nuestra terminal y creamos nuestro proyecto.


sails new whatsapp --no-frontend

Y nos genera nuestro proyecto.



Ahora bien vamos a abrirlo desde nuestro editor de texto.


Generar modelos y controladores.
En terminal nos dirigimos a la carpeta del proyecto y vamos a generar nuestros modelos y nuestros controladores con el siguiente comando, recuerden que nuestros modelos son los siguientes: users, messages, groups.
El comando sails generate api nos permite generar nuestros modelos y controladores al mismo tiempo con el mismo nombre.

sails generate api users

sails generate api messages

sails generate api groups


Si revisamos en nuestro editor se nos generaron dichos archivos en la carpeta controllers y models.




Agregando atributos a los modelos.
Tenemos nuestros modelos, ahora le vamos a agregar atributos, vamos a comenzar con Users.

Users




module.exports = {
  schema:true,
  attributes: {
    //Nombre del usuario
    name:{
      type:'string'
    },
    //Apellidos del usuario
    lastname:{
      type:'string'
    },
    //Número telefonico del usuario
    phone:{
      type:'string',
      required:true,
      unique:true
    },
    //Estado del usuario
    state:{
      type:'string'
    },
    //Imagen de perfil del usuario
    image:{
      type:'string'
    },
    /*Si el usuario ha sido activado a traves de un mensaje a su número
    telefonico.*/
    activated:{
      type:'boolean',
      defaultsTo:'false'
    },
    //Código de verificación que se envia al usuario en un mensaje de texto.
    code:{
      type:'string',
      unique:true
    },
    //El usuario pertenece a un grupo de contactos de otros usuarios.
    contactsOf:{
      collection:'users',
      via:'contacts'
    },
    //El usuario tiene un grupo de contactos.
    contacts:{
      collection:'users',
      via:'contactsOf'
    },
    //El usuario puede enviar muchos mensajes
    sentMessages:{
      collection:'messages',
      via:'transmitter'
    },
    //El usuario puede recibir muchos mensajes
    receivedMessages:{
      collection:'messages',
      via:'receiver'
    },
    //El usuario puede estar en muchos grupos.
    groups:{
      collection:'groups',
      via:'members',
      dominant:true
    }
  }
};


Messages



module.exports = {
  schema:true,
  attributes: {
    //Contenido del mensaje.
    message:{
      type:'string',
      required:true
    },
    //Quien envia el mensaje.
    transmitter:{
      model:'users'
    },
    //Quien recibe el mensaje.
    receiver:{
      model:'users'
    },
    //El mensaje es enviado a un grupo en especifico.
    group:{
      model:'groups'
    }
  }
};



Groups



module.exports = {
  schema:true,
  attributes: {
    //Nombre del grupo
    name:{
      type:'string',
      required:true
    },
    //El grupo contiene miembros.
    members:{
      collection:'users',
      via:'groups'
    },
    //El grupo almacena muchos mensajes.
    messages:{
      collection:'messages',
      via:'group'
    },
    //El grupo es creado por un usuario.
    creator:{
      model:'users'
    }
  }
};


Ya tenemos listos nuestros modelos así que ahora haremos nuestras pruebas conceptuales, nos dirigimos a la consola e ingresamos el siguiente comando:

sails lift



De ahi, tenemos que seleccionar una de estas opciones:
  1.  safe  - never auto-migrate my database(s). I will do it myself (by hand)
  2.  alter - auto-migrate, but attempt to keep my existing data (experimental)
  3.  drop  - wipe/drop ALL my data and rebuild models every time I lift Sails

Seleccionamos 3, esto limpiara todos nuestros datos de la BD y se van a crear de nuevo nuestros modelos.



Y listo, tenemos nuestra API REST corriendo en la URL : http://localhost:1337
Ahora utilizaremos POSTMAN para hacer peticiones a nuestra API.

Users

POST: http://localhost:1337/users
Crear un usuario


Resultado



GET: http://localhost:1337/users
Obtener todos los usuarios.



GET: http://localhost:1337/users/:id
Obtener un usuario en específico.



PUT: http://localhost:1337/users/:id
Actualizar un usuario en específico.



Resultado



DELETE: http://localhost:1337/users/:id
Eliminar un usuario en específico.



Hemos probado nuestra API REST y notamos que funciona perfectamente con nuestro modelos Users, la implementación en SailsJS es muy fácil. Podemos hacer las pruebas con los demás modelos: Messages y Groups.


Devoluciones de llamada de los ciclos de vida en lo modelos.
Sails incorpora funciones que son llamadas o antes o después de ciertas acciones de los modelos, para más información puedes revisar en la pagina oficial: Lifecycle callbacks.
¿Para que necesitamos esto? bien, esto nos ayudará a que antes que se cree un usuario, se pueda mandar un mensaje de texto al usuario para validar su número telefonico y así ellos puedan ingresar un código que le vamos a generar y su usuario se active, esto lo mencionamos en la parte de los modelos.
Nota: Para enviar el mensaje de texto podemos utilizar Twilio o Plivo, nosotros escogimos plivo para hacer este tutorial.

Crear código de verificación.
Necesitamos crear un código de verificación para esto vamos hacer un pequeño código en JavaScript


new Date().getTime().toString(32);

Nos dirigimos a nuestro archivo Users.js dentro de la carpeta Models y vamos a agregar la función beforeCreate después de attributes:{}



Corremos nuestra aplicación y probamos creando un usuario nuevo, el resultado será el siguiente.



Y tenemos en code el "código" que se genera en nuestra función beforeCreate.

Instalar paquete Plivo.

Con la API de Plivo vamos a enviar el mensaje de texto al numero que acaba de ingresar el usuario.
Pero antes debemos instalar algunos paquetes que nos ayudaran, vamos a la terminal e instalamos el paquete de plivo para nodejs.


npm install git://github.com/plivo/plivo-node.git

Tenemos ya el paquete instalado.




Crear cuenta de Plivo.
Recuerda que utilizar los servicios que ofrece Plivo, necesitas crear una cuenta en su pagina oficial.
Ya que tengas tu cuenta podrás acceder al dashboard. Te generan un AUTH ID y un AUTH TOKEN que son los que vamos a utilizar en nuestra aplicación.



Crean un servicio.
SailsJS nos permite crear servicios, un servicio es un conjunto de funciones que puedes acceder globalmente dentro de nuestro proyecto, para más información pueden visitar el siguiente link.



Agregando Servicio a la función beforeCreate del modelo Users.
En nuestra función beforeCreate vamos a llamar a nuestra función y ella se hará a cargo de enviar nuestro mensaje de texto.



Nos dirigimos a nuestra terminal y corremos nuestra aplicación, después creamos otro usuario con un número valido, en unos segundos te llegará el mensaje de texto.




El resultado que nos regresa al crear el usuario es el siguiente:

Activar usuario.
Nuestro usuario se encuentra inactivo, por lo cual vamos a generar un método dentro del controlador de Users para activarlo con dicho código, para esto nos dirigimos a la carpeta controllers y abrimos el archivo UsersController.js y vamos agregar nuestro método llamado, active.



Vamos a la terminal e iniciamos nuestra aplicación, hacemos una petición POST a la URL: http://localhost:1337/users/active


Y tenemos nuestro usuario activado en nuestra plataforma.

Resumiendo, si dan cuenta estamos desarrollando todo para el usuario por el momento, así que ya tenemos el método para activar a los usuarios mediante un mensaje de texto, ahora vamos listar lo que vamos hacer.

En el controller Users:


  • Agregar seguridad para que no pueda enviar mensajes, o cambiar su estado o el hecho de cambiar su imagen de perfil.
  • Cambiar estado del usuario por ejemplo, "Estoy ocupado", "No disponible" o algún otro estado.
  • Cambiar imagen de perfil del usuario.
  • Crear grupos.
  • Agregar contactos
  • Enviar mensajes
Bien vamos por pasos, algunas de estos puntos van a surgir otros puntos donde tengamos que especificar alguna funcionalidad, así que adelante, sigamos.

Agregando seguridad.
Todo sistema debe tener seguridad, sabemos que ningún sistema es seguro y lo hemos visto como empresas como facebook, twitter o linkedin, pero bueno, vamos a implementar algo de seguridad en nuestra plataforma, ¿En que consiste lo que vamos a implementar? Que nuestro usuario no pueda cambiar su estado, o subir su imagen de perfil, mucho menos enviar un mensaje sin que su cuenta este activada, y para que esto suceda, tiene que verificar su número.
 Entonces hablemos de las Policies en Sails, son un conjunto de herramientas para el control de acceso y  dar autorización a ciertos controladores a los usuarios, así que nosotros decidimos quien o quienes pueden acceder a dichas rutas según nuestros criterios, para más información pueden revisar la información en la pagina.

Instalando JSONWebToken.
Vamos a instalar un paquete llamado JSON webtoken, es de gran utilidad si queremos agregar una autenticación para consumir nuestra API REST.
Nos dirigimos a nuestra terminal y agregamos el comando siguiente:

npm install jsonwebtoken --save



Creando servicio JSON Webtoken.
Vamos a crear un nuevo servicio para que nos genere los token al momento de que nuestra cuenta sea activada. Nos dirigimos a nuestra carpeta Services y creamos el archivo jwToken.js e ingresamos lo siguiente:


var jwt = require('jsonwebtoken');
var tokenSecret = "whatsappsecret";

// Genera un token 
module.exports.issue = function(payload) {
  return jwt.sign(
    payload,
    tokenSecret, // Token Secret 
    {
      expiresIn : 43800 // Token expira en un mes
    }
  );
};

// Verifica el token en un  request
module.exports.verify = function(token, callback) {
  return jwt.verify(
    token, // El token a verificar
    tokenSecret, // Utiliza el token secret para que coincidan
    {},
    callback //Una devolucion de llamada
  );
};

Ahora implementamos nuestro servicio en nuestro método active en el archivo UsersController.js


Al validar de nuevo nuestra cuenta con nuestro código (Cambie el valor de activated a false para hacer la prueba) nos regresa nuestro usuario y nuestro token que incluye nuestro id y el numero telefonico.



Este token nos va a servir para hacer nuestra Policies ya que con esa vamos a validar nuestro usuario.

Creando Policies.
Nuestro criterio va hacer que la cuenta de usuario se encuentre activa. Abrimos la carpeta policies y creamos el archivo isActivated.js y agregamos el siguiente código:


module.exports = function(req,res, next){
    var token;
    if(req.headers && req.headers.authorization){
        var cabecera = req.headers.authorization.split(' ');
        if(cabecera.length == 2){
            var esquema = cabecera[0];
            var credenciales = cabecera[1];
            if (/^Bearer$/i.test(esquema)) {
                token = credenciales;
            }
            
        }else{
           return res.json(401,{error:'Formato de Authorization es: Bearer [token]'});
        }
        
        
    }else{
        return res.json(401,{error:'No Authorization header'});
    }
    
    jwToken.verify(token, function (err, token) {
        if (err) return res.json(401, {err: 'Token invalido'});
        Users.findOne({id:token.id,phone:token.phone,activated:true}).exec(function(error,user){
            if(!user){
                return res.json(401,{error:'Necesitas activar tu cuenta'});
            }
            req.token = token; // Desencripta el token
            next();
        });
       
    });

}


Bien, ya tenemos nuestra Policies y ahora, ¿Como hacemos que funcione? Nos dirigimos a la carpeta config de nuestro proyecto y abrimos el archivo policies.js y agregamos lo siguiente:



Decimos que a nuestro controlador UsersController va a tener nuestra policies isActivated y la probamos reiniciando nuestro proyecto.



Al hacer la petición nos muestra el siguiente error, ya que no enviamos el token por la cabecera. Si nuestra cuenta no estuviera activada nos mostraría lo siguiente aunque enviáramos el token por la cabecera.




Si todo esta correcto y enviamos la cabecera nos regresa la información de la petición.


Cambiar estado.
Vamos a cambiar el estado del usuario por alguno que decida poner, como "Estoy ocupado", "No disponible" o alguno otro. Para esto nos dirigimos a nuestro controlador Users(UsersController.js) y agregamos lo siguiente:


changeStatus:function(req,res){
        var estado = req.param('estado');
        var id = req.token.id;
        Users.update({id:id},{state:estado}).exec(function(error,user){
           return res.json(user);
        });
    }

Ahora nos dirigimos a la carpeta config y en el archivo policies.js agregamos nuestra policies al método que acabamos de crear.



Ahora si en nuestra petición a changeStatus, req.token.id nos va a dar el id de nuestro usuario y solo basta con actualizar. Veamos su implementación.



Cambiar imagen de perfil.
Tenemos la funcionalidad de poder cambiar la imagen de nuestro perfil y esto va hacer muy fácil, para esto vamos a crear un método llamado changeImage en UsersController.js, para poder acceder a req.token.id recuerda agregar la policies en ese método como la venimos haciendo.


changeImage:function(req,res){
        var folder = '/images/perfil';
        var id = req.token.id;
  if(req.file){
            req.file('file').upload({dirname:sails.config.appPath+'/assets'+folder},function(error,files){
    if(error) return error;
    var nameFile =  files[0].fd.split('/');
                nameFile = nameFile[(nameFile.length - 1)];
    var baseUrl = Config.getUrl();
                Users.update({id:id},{image:baseUrl + folder + "/"+nameFile}).exec(function(error,user){
                    return res.json(user);
                });
   });
  }
    }

Como podemos observar, tenemos un servicio llamado Config lo cual contiene lo siguiente:



Nuestra imagen se va a guardar en la ruta /assets/images/perfil/[tu imagen], para que puedan acceder a tu imagen necesitamos agregar unas lineas en el archivo .sailsrc y quedaría así:




Hacemos la prueba ahora en POSTMAN






Creando grupos.
Recordemos que en nuestra aplicación vamos a poder crear nuestros grupos de conversación, al crear un grupo nos debemos de integrar a el, para esto vamos a recurrir a la función afterCreate del modelo de Groups.



Después hacemos la prueba en POSTMAN



Ahora si observamos , se creo el grupo, vamos a ver la información especifica de ese grupo.



Y nuestra función afterCreate hizo su función de agregarnos como miembros al momento de crear el grupo.


Agregar contactos.
Vamos a poder agregar contactos que usen la aplicación para esto en el frontend, vamos a leer nuestra lista de contactos y validar que se encuentren en el sistema dados de alta, pero eso va hacer en otro tutorial. Bien, vamos a crear un método para agregar contactos y una policies para que los valide antes de que sean agregados.

Creando Policies
Vamos a crear nuestra policies para validar que el numero este dentro de nuestro sistema y se encuentre activado. Creamos un archivo llamado isValidPhone.js y agregamos lo siguiente.


Nuestro método en UsersControllers.js tiene lo siguiente:


addContact:function(req,res){
        var id = req.token.id;
        var contacto = req.contacto;
        Users.findOne(id).exec(function(error,user){
            console.log(user);
            user.contacts.add(contacto.id);
            user.save(function(e){

            });
            return res.json(user);
        });
    }

Tenemos dos request, los dos se generan es nuestras policies, no se olviden en agregarla en policies.js dentro de la carpeta config.
Creamos un usuario para hacer nuestras pruebas, el usuario esta inactivo, veamos que pasa al tratar de agregarlo.



Tenemos el error de nuestra policies, ahora lo activamos y el resultado es diferente.


Se ha agregado y al revisar la información de nuestro usuario Daniel vemos que tenemos un contacto.



Al revisar el perfil de Mario notamos que el es contado de Daniel, pero Mario no tiene agregado a Daniel.





Crear mensajes
Esta es la parte que todos queremos llegar, vamos a enviar mensajes a otros usuarios y a grupos donde existen un numero de usuarios. Un mensaje contiene un emisor y un receptor y obviamente el texto del mensaje. En MessagesController.js vamos a sobreescribir el método create.



Ahora vamos a probarlo.


Al verificar la información de nuestro usuario observamos el mensaje enviado.


Si accedemos al perfil de Mario(El usuario que recibió el mensaje) notamos que tiene el mensaje recibido.


Enviar un mensaje a un grupo.
Podemos enviar un grupo a un usuario en especifico o a un grupo, vamos a modificar nuestro método Create de MessagesController.js



Hacemos la prueba.



Y al revisar la información del grupo, efectivamente vemos el mensaje reflejado.



Por el momento va hacer todo, en resumen tenemos nuestro Backend donde esta funcionando nuestra API REST lista para ser consumida.
El proyecto lo puedes clonar en GitHub, cualquier cosa que modifiquemos vamos a estar compartiéndolo, todavía nos faltan muchas cosas, como más seguridad u otras cosas que con el tiempo van a surgir pero vamos a ir explicando cada cambio.

Espero que les haya gustado y esperan la implementación en la parte del frontend, estoy viendo si utilizamos Ionic para desarrollar la aplicación multiplataforma o hacerla nativa.

Si quieren estar al tanto del proyecto, en la pagina de FB o Twitter estaremos publicando las novedades.

¡ Saludos a todos !