Sin título-1

.marco{border: 1px solid #CCCCCC; border-radius: 5px; -moz-border-radius:5px;. -webkit-border-radius: 5px; padding: 7px;
4MB Größe 12 Downloads 35 Ansichten
Desarrollo de una red social Como sabemos, las redes sociales han tomado un protagonismo muy fuerte en el mundo de la comunicación, debido a la experiencia que generan en los usuarios de eventos en tiempo real y escalabilidad. Desarrollaremos un proyecto utilizando Node como lenguaje de programación y Redis como modelo de persistencia.



El porqué del proyecto .............. 2

Lista de amigos conectados ............. 40 Informar cuando



Definición del proyecto.............. 2

se conecta un usuario....................... 43

Creación del proyecto ........................ 3

Creación del sistema de chat............ 48

Configuración del archivo app.js......... 4

Creación del sistema de posts .......... 53

Registro y login de usuarios ............. 12

Vista de la base de datos.................. 59

Creación de la página principal ........ 25 Definición de los eventos



Resumen................................... 61



Actividades............................... 62

para un usuario logueado ................. 27 Envío de una solicitud de amistad .... 31

Servicio de atención al lector: [email protected]

2

APÉNDICE. DESARROLLO DE UNA RED SOCIAL

El porqué del proyecto La razón por la cual desarrollaremos una red social es porque reúne todas las características y condiciones que deben tener los sistemas escalables estudiados a lo largo de este libro. Una red social tiene que ser capaz de manejar eventos de interacción con los usuarios en tiempo real y debe funcionar bajo un modelo de persistencia que permita operar con millones de registros y solicitudes en el menor tiempo posible. A nuestro ejemplo lo llamaremos SocialRedis y utilizará las siguientes tecnologías:

• • • • • • •

*Node.js *Socket.IO *Express *Redis *Crypto (módulo para encriptación) *Ejs (motor de plantillas) *Session.socket.io (módulo para manejo de sesiones en Socket.IO) El sistema se encargará de funcionar de manera autónoma como

servidor propio, y ofrecerá todas las características específicas (tales como ruteo, manejo de plantillas y estilos css, eventos de tiempo real e interacción con la base de datos).

Definición del proyecto Consiste en el desarrollo de una red social similar a Facebook, donde los usuarios podrán registrarse; desde el primer momento se mostrará en tiempo real la cantidad de usuarios registrados. Consideremos que podrán enviar solicitudes de amistad y contar con un sistema de notificaciones, que permitirá aceptar o rechazar una solicitud en el mismo instante en que otro usuario la envía. Además, se podrán crear posts para compartir entre los amigos. Por último, implementaremos un sistema de chat donde los usuarios podrán tener múltiples conversaciones en forma simultánea.

www.redusers.com

3

SISTEMAS WEB ESCALABLES

Figura 1. En la pantalla principal los usuarios pueden compartir información e interactuar en tiempo real con los demás usuarios.

Instalación de herramientas previas Para comenzar, necesitamos una herramienta que nos permita administrar la base de datos. Como vamos a trabajar exclusivamente con Redis y Node, podemos utilizar el módulo Redis Commander instalándolo mediante el siguiente comando: npm install -g redis-commander

GRACIAS A NODEMON NO SERÁ NECESARIO

También es recomendable utilizar Nodemon,

REINICIAR LA

ya que nos evitaremos reiniciar la aplicación en cada cambio que hagamos. Lo podemos instalar mediante el siguiente comando:

APLICACIÓN LUEGO DE CADA CAMBIO

npm install -g nodemon

Creación del proyecto Para dar inicio a nuestro ejemplo debemos abrir una consola y dirigirnos a la unidad C:; una vez ahí, ejecutaremos los siguientes comandos para crear la aplicación e instalar los módulos necesarios:

www.redusers.com

4

APÉNDICE. DESARROLLO DE UNA RED SOCIAL

express –e SocialRedis cd SocialRedis && npm install npm install redis npm install crypto npm install socket.io npm install session.socket.io

Utilizamos el parámetro –e para crear la aplicación, con lo cual indicamos que vamos a utilizar el módulo ejs como motor de plantillas; luego instalamos los módulos redis, crypto, socket.io y sesión.socket.io. Una vez creado el entorno de trabajo, podemos ejecutar la aplicación con Nodemon y comprobar que está en funcionamiento. En la consola escribimos el siguiente comando y posteriormente abrimos un navegador para verificar su funcionamiento: nodemon app.js Por defecto, el sistema va a funcionar en la siguiente dirección: http://127.0.0.1:3000.

Configuración del archivo app.js Ahora trabajaremos directamente en la aplicación. Lo primero que haremos es configurar el archivo principal, dirigiéndonos a la raíz del proyecto y abriendo el archivo app.js. En este punto tenemos que realizar algunos cambios importantes, por lo que vamos a borrar todo el contenido del archivo e iremos agregando el código necesario. Para comenzar, vamos a incluir los módulos:

APLICACIONES DE EJEMPLO DE EXPRESS Express provee una extensa lista de ejemplos que cubren todos los aspectos del framework, como autenticación, manejo de plantillas, cookies, solución de errores, parámetros de entrada y administración de sesiones, entre otros. Podemos acceder a los ejemplos en la siguiente dirección: https://github.com/ visionmedia/express/tree/master/examples.

www.redusers.com

5

SISTEMAS WEB ESCALABLES

// definicion de modulos var express = require(‘express’) , routes

= require(‘./routes’)

, user

= require(‘./routes/user’)

, http

= require(‘http’)

, path

= require(‘path’)

, redis

= require(‘redis’)

, crypto , ssio

= require(‘crypto’) = require(‘session.socket.io’);

Inicializamos las variables agregando lo siguiente: // inicializacion de variables var app

= express()

, server

= http.createServer(app)

, io

= require(‘socket.io’).listen(server)

, sessionStore = new express.session.MemoryStore() , cookieParser = express.cookieParser(‘!@#$%^&*()1234567890qwerty’) , sessionIO

= new ssio(io, sessionStore, cookieParser);

Para continuar definimos el puerto que utilizará el sistema, el directorio y también el motor de plantillas: // configuraciones para todos los entornos app.set(‘port’, process.env.PORT || 3000); app.set(‘views’, __dirname + ‘/views’); app.set(‘view engine’, ‘ejs’);

SELECCIONAR UN FRAMEWORK Actualmente los desarrolladores nos encontramos con una gran cantidad de frameworks MVC para organizar y estructurar las aplicaciones en JS. Para ayudarnos a resolver el problema existe un proyecto llamado TodoMVC, el cual ofrece una funcionalidad similar para varios frameworks como Backbone, Ember o AngularJS.

www.redusers.com

6

APÉNDICE. DESARROLLO DE UNA RED SOCIAL

Para continuar nos encargamos de establecer las herramientas o middlewares de Express que utilizaremos: // middlewares de Express app.use(express.favicon()); app.use(express.logger(‘dev’)); app.use(express.bodyParser()); app.use(cookieParser); app.use(express.session({ store: sessionStore })); app.use(express.methodOverride()); app.use(app.router); app.use(express.static(path.join(__dirname, ‘public’)));

Posteriormente realizamos la definición del uso del manejador de errores para el entorno de desarrollo: // configuraciones para el entorno de desarrollo if (‘development’ == app.get(‘env’)) { app.use(express.errorHandler()); }

Para continuar, determinamos las variables globales que serán visibles desde todos los archivos del sistema: // definicion de variables globales global.usrEnLinea = []; global.titulo

= ‘Social Redis’;

global.autor

= ‘Carlos Alberto Benitez [2013]’;

global.db

= redis.createClient(6379, ‘localhost’);

global.io

= io;

global.sessionIO = sessionIO; global.crypto

= crypto;

La variable usrEnLinea servirá para almacenar los usuarios conectados al sistema; titulo y autor son parte de la leyenda del sitio; db es un objeto

www.redusers.com

7

SISTEMAS WEB ESCALABLES

que contendrá la conexión a la base de datos; io

LA VARIABLE CRYPTO

es también un objeto y contendrá la instancia de

PERMITIRÁ LA

Socket.io; en sessionIO almacenamos la instancia del objeto sesión.socket.io, que nos servirá para acceder

ENCRIPTACIÓN DE

a las variables de sesión desde una conexión por

LAS CLAVES

socket; por último, la variable crypto nos servirá para encriptar las claves de acceso.

DE ACCESO

A continuación debemos realizar la definición de las rutas que manejaremos en el sistema. Primero ruteamos las solicitudes por GET para el acceso y salida del sistema: // GET - definicion de las rutas app.get(‘/’, routes.index); //app.get(‘/salir’, user.logout);

Notemos que, para el ruteo de la página principal, utilizamos el método index del archivo index.js y, para la salida, el método logout del archivo user.js. Consideremos que esta última está comentada ya que aún no realizamos la definición del método. Luego, a través del método POST, determinamos el ruteo para las acciones de registro, login, creación de un post, solicitud de amistad y respuesta a una solicitud de amistad. // POST - definicion de las rutas //app.post(‘/registro’, user.registro); //app.post(‘/login’, user.login); //app.post(‘/setPost’, user.setPost); //app.post(‘/setSolicitud’, user.setSolicitud); //app.post(‘/setRespuestaSolicitud’, user.setRespuestaSolicitud);

Todas las acciones definidas utilizarán los métodos del archivo user.js, pero como todavía no las hemos creado están comentadas. A medida que vayamos creando los métodos iremos descomentando el ruteo para cada una de ellas. Por último, ponemos en marcha el servidor indicándole el puerto que vamos a utilizar:

www.redusers.com

8

APÉNDICE. DESARROLLO DE UNA RED SOCIAL

// listen server.listen(app.get(‘port’), function(){ console.log(‘Express server listening on port ‘ + app.get(‘port’)); });

Definición de la apariencia del sistema Vamos a establecer la apariencia del sistema. Primero debemos dirigirnos al directorio public/ y renombrar las carpetas images por img, javascripts por js y stylesheets por css; haremos estos cambios únicamente para simplificar la legibilidad de los archivos. Dentro de la carpeta css/, vamos a crear un archivo con el nombre style.css, con el siguiente código: body{background: url(“/img/bg.jpg”) repeat scroll 0 0 transparent; font: 12px/1.5em Arial,Helvetica,sans-serif; margin: 0; padding: 0; text-align: center; color: #444444;} header{height: 50px; padding: 20px 5px;} header #buscador{width: 370px; float: left;} header #buscador #tBuscar{width: 270px;} header #buscador #bBuscar{height: 28px;} header #notificacion{float: right; padding: 9px 0 0; width: 203px;} header #notificacion #usr{float: left; font-size: 14px; height: 100%; margin: 5px 0; width: 150px;} h1{background: url(“/img/icon-profile.png”) no-repeat scroll left top transparent; color: #666; float: left; font-size: 29px; line-height: 55px; margin: 0; padding: 0 10px 0 42px; text-align: left; text-shadow: 1px 1px 0 #FFF; width: 175px;} input[type=”text”],

www.redusers.com

9

SISTEMAS WEB ESCALABLES

input[type=”password”]{margin: 7px 0 0;} a{color: #444444; display: block; text-decoration: none;} nav{clear: both; float: left; width: 140px; padding: 0 10px;} nav ul{list-style: none outside none; padding: 0; margin: 0;} nav ul li{border: 1px solid transparent; cursor: pointer; font-size: 15px; height: 100%; line-height: 46px; padding: 0; width: auto;} nav ul li:hover,nav ul .active {background-color: #EEEEEE; backgroundimage: -moz-linear-gradient(center top , #EEEEEE, #E0E0E0); border: 1px solid #CCCCCC; box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) inset; color: #333333;} input[type=”submit”]{background: url(“/img/bg-verde.png”) repeat-x left top #47C516; border: 1px solid #149E1F; border-radius: 2px 2px 2px 2px; color: #FFFFFF; padding: 5px; text-align: center;} footer{clear: both; height: 80px; padding: 10px; text-align: center;} footer p{margin: 0 auto; padding: 30px 0; width: 300px;} .contenedor{background: url(“/img/bg-sombras.png”) no-repeat scroll center top transparent; margin: 0 auto; min-height: 600px; width: 850px;} .marco{border: 1px solid #CCCCCC; border-radius: 5px; -moz-border-radius:5px; -webkit-border-radius: 5px; padding: 7px;} .marco.blanco{background-color: #FFFFFF;} .marco.gris{background-color: #F8F8F8;} .marco.celeste{background-color: #BAD9F1;} .login{float: right; padding: 5px 15px; text-align: right; width: 401px; font-size: 15px;}

www.redusers.com

10

APÉNDICE. DESARROLLO DE UNA RED SOCIAL

.tecnologias {float: left; height: 400px; width: 400px;} .tecnologias img{margin: 20px 0;} .registro{min-height: 215px; padding: 5px 15px;} .registro input{display: block; width: 395px;} .error {background: url(“/img/icono_error.gif”) no-repeat 10px 50% #FAEBE7; border: 1px solid #F16048; color: #DF280A; padding: 3px 3px 3px 30px; textalign: justify;} .columna_derecha{float: right; margin: 0 5px; width: 431px;} .usuarios_registrados{font-size: 15px; margin: 15px 0; padding: 5px 15px; width: 399px;} .usuario{text-align: right; min-width: 140px; float: left; padding: 11px 10px 0; font-size: 14px;} .contenido{width: 438px; float: left;} .usuarios{float: left; margin: 0 5px; width: 180px; text-align: left;} .usuarios span{font-size: 13px; font-weight: bold;} #notificacion ul, .usuarios ul{margin: 0; min-height: 40px; list-style: none; padding: 0;} #notificacion ul li, .usuarios ul li{border-bottom: 1px solid #CCCCCC; margin: 10px 0; cursor: pointer; padding: 5px;} #notificacion ul li:hover, .usuarios ul li:hover{background-color: #47C516;} .chat{clear: both; height: 181px; bottom: 0; margin: 0 5px; padding: 0 3px; width: 828px; text-align: left; position: absolute; z-index: 1000;}

www.redusers.com

11

SISTEMAS WEB ESCALABLES

.ventana{margin: 0 2px; float: left; width: 186px;} .ventana input[type=”text”]{margin: 0 2px; width: 179px;} .usuariosLista{min-height: 400px;} .avatar{float: left; width: 30px; height: 30px;} .avatar img{width: 30px; height: 30px;} .bloque{text-align: left; margin-bottom: 5px; float: left; width: 421px;} .bloque .header{line-height: 45px; height: 30px; border-bottom: 1px solid #CCC; text-align: left; margin-bottom: 10px;} .bloque .header img{margin-right: 10px; float: left; width: 30px; height: 30px;} .bloque .header .bloque_usuario{font-weight: bold; margin: 0 5px 0 0;} .bloque .header .bloque_fecha{float: right;} .contador{background: url(“/img/bg-azul.png”) repeat-x left top #53AFEC; border: 1px solid #315EA8; border-radius: 2px 2px 2px 2px; color: #FFFFFF; padding: 5px; text-align: center; display: block; float: left; font-size: 18px; fontweight: bold; height: 17px; width: 30px;} #postFrm{width: 436px;} #postFrm textarea {display: block; height: 63px; width: 422px;} #postFrm input[type=”submit”]{margin-right: 0; float: right;} #solicitudes{height: 200px; width: 200px; background-color: #F8F8F8; position: absolute; z-index: 100; display: none; top: 64px;} .bSalir{display: inline-block; position: relative; cursor: pointer; text-shadow: 1px 1px 0 #FFF; top: -57px; width: 81px; right: -106px;}

www.redusers.com

12

APÉNDICE. DESARROLLO DE UNA RED SOCIAL

Dado que solo hemos definido los aspectos visuales, no se va a explicar el código en detalle.

Registro y login de usuarios Ahora vamos a trabajar sobre la primera pantalla. Para esto, primero crearemos las plantillas que se reutilizarán en la aplicación. Generaremos un archivo nuevo llamado header.ejs, que guardaremos en el directorio views/ con el siguiente contenido:

Simplemente hemos creado una estructura HTML básica que luego incluiremos en las otras plantillas. El elemento contendrá la variable titulo, que hemos definido anteriormente en el archivo app.js. Vamos a crear un nuevo archivo llamado footer.ejs con código que sigue:

-



www.redusers.com

13

SISTEMAS WEB ESCALABLES

Ya se registraron 0 usuarios

0 Podes chatear con:

No hay amigos conectados...



www.redusers.com

26

APÉNDICE. DESARROLLO DE UNA RED SOCIAL

Podes ser amigo de:

No hay usuarios nuevos...



En este código hemos incluido el archivo header.ejs y definimos el título con el contenido de la variable global y los elementos para el buscador. Luego determinamos el área para las notificaciones y la lista de solicitudes, y definimos el enlace para salir del sistema y el contenedor donde el usuario verá la lista de amigos conectados. Creamos un contenedor para el formulario y el listado de posts. Definimos el contenedor para los usuarios que no son amigos del usuario actual. Generamos el contenedor de las ventanas de chat e incluimos footer.ejs. Una vez creada la plantilla podemos acceder para ver su estructura.

www.redusers.com

27

SISTEMAS WEB ESCALABLES

Figura 6. Cuando el usuario inicie sesión, verá la lista de amigos, el área central para los posts y la columna con usuarios nuevos.

Definición de los eventos para un usuario logueado A continuación vamos a crear los eventos que emitirá y recibirá el usuario cuando inicia sesión a través de Socket.IO. Para esto, abrimos el archivo index.js y agregamos lo siguiente en la primera línea para tener acceso a las funciones definidas en el archivo user.js: var user = require(‘./user.js’); Luego, dentro de la función index(), vamos a utilizar el objeto sessionIO para acceder a la variable de sesión y desencadenar los eventos. Para esto escribimos lo siguiente:

sessionIO.on(‘connection’, function (err, socket, session) { //usuarios logueados if(session.usr != ‘’){ // guardamos el socket.id del usuario actual

www.redusers.com

28

APÉNDICE. DESARROLLO DE UNA RED SOCIAL

usrEnLinea[session.usr.uid] = socket.id; // obtenemos los usuarios que no son amigos user.getUsuariosNuevos(session.usr.uid); }else{ // obtenemos la cantidad de usuarios user.getTotalUsuarios(); } });

En este código, capturamos la conexión y verificamos si la variable session.usr contiene información. En el caso de que un usuario se haya logueado, almacenamos el identificador del socket en el array global usrEnLinea con su uid como identificador, lo que nos permitirá identificar el socket mediante el cual está conectado cada usuario para poder enviarle mensajes privados. Luego, vamos a invocar a la función getUsuariosNuevos() del archivo user.js, al cual le pasaremos como parámetro el uid del usuario actual. Por lo tanto, debemos crear esta función en el archivo user.js: // obtenemos los usuarios que no son amigos var getUsuariosNuevos = function(uid){ db.sdiff(‘usuarios’, ‘uid:’ + uid + ‘:amigos’, function (err, usuarios) { if (usuarios) { var usrNuevos = []; usuarios.forEach(function(id){ // verificamos que no sea el usuario actual if (uid != id){ // verificamos que no tenga una solicitud pendiente db.sismember(‘uid:’ + id + ‘:solicitudes’, uid, function (err, solicitudEnviada) { if(solicitudEnviada == 0){ // obtenemos la informacion del usuario db.hgetall(‘uid:’ + id, function (err, usuario) { usrNuevos.push({‘uid’: id,

www.redusers.com

29

SISTEMAS WEB ESCALABLES

‘nombre’ : usuario.nombre, ‘apellido’ : usuario.apellido }); // emitimos los usuarios if (usrNuevos.length == usuarios.length - 1) io.sockets.socket(usrEnLinea[uid]).emit(‘setUsuariosNuevos’, usrNuevos); }); } }); } }); } }); }

En este código utilizamos el comando sdiff de Redis para obtener los usuarios que no son amigos del usuario actual; luego, con cada uno verificamos que no sea el uid del usuario conectado, y comprobamos que no tenga una solicitud de amistad pendiente. Obtenemos los datos y los almacenamos en un array. Cuando finaliza la lista, emitimos el evento setUsuariosNuevos al usuario actual a través del array usrEnLinea con el índice identificado por el uid pasado como parámetro de la función. Para que la función esté disponible desde cualquier lugar, agregamos lo siguiente al final del archivo: exports.getUsuariosNuevos = getUsuariosNuevos; Ahora necesitamos capturar el evento del lado del cliente. Para esto, agregamos lo siguiente al método $(document).ready() del archivo script.js: //io - mostramos los usuarios que no son amigos sockets.on(‘setUsuariosNuevos’, mostrarUsuariosNuevos);

En el código, recibimos el evento y ejecutamos la función mostrarUsuariosNuevos(), que definiremos de la siguiente manera:

www.redusers.com

30

APÉNDICE. DESARROLLO DE UNA RED SOCIAL

// muestra los usuarios nuevos function mostrarUsuariosNuevos(data){ if (data.length) { $(‘#usuariosNuevos’).html(‘
    ’); data.forEach(function (usuario) { var nombreUsuario = usuario.nombre + ‘ ‘ + usuario.apellido ; $(‘#usuariosNuevos ul’).append(‘
  • ’ + nombreUsuario + ‘
  • ’); }); $(‘#usuariosNuevos’).append(‘
’); }; }

En el código que acabamos de presentar agregamos al elemento #usuariosNuevos, de esta manera podremos acceder a la lista recibida de los usuarios que aún no son amigos del que se encuentra conectado actualmente. Podemos efectuar la prueba de esta funcionalidad registrando varios usuarios.

Figura 7. Cuando el usuario inicie sesión obtendrá una lista con los usuarios que no son amigos, a quienes les podrá enviar una solicitud. www.redusers.com

31

SISTEMAS WEB ESCALABLES

Envío de una solicitud de amistad Vamos a desarrollar la funcionalidad que nos permitará que el usuario envíe una solicitud de amistad.

Figura 8. Para enviar una solicitud de amistad solo será necesario hacer clic en algún nombre de la lista de usuarios. Necesitamos capturar el evento click de la lista. Para esto, agregamos lo siguiente en el método $(document).ready() del archivo script.js: // se envia una solicitud de amistad $(document).on(‘click’, ‘#usuariosNuevos ul li’, function(){ setSolicitud($(this).attr(‘uid’)); });

INSTALAR EXPRESS CON EJS Por defecto, Express usa el motor de plantillas Jade, el cual es muy bueno pero abstrae de una manera significante la utilización de elementos HTML. Una alternativa es utilizar EJS, más amigable y simple al crear estructuras complejas. Para instalarlo cuando creamos una aplicación, debemos escribir el siguiente comando: express -e nombreDeLaApp.

www.redusers.com

32

APÉNDICE. DESARROLLO DE UNA RED SOCIAL

En este código hemos capturado el evento click de la lista de usuarios y ejecutamos la función setSolicitud(), que definimos en el mismo archivo de la siguiente manera: // se envia una solicitud de amistad function setSolicitud(uid) { $.post(“/setSolicitud”, ‘uid=’ + uid, function(respuesta){ if (respuesta.codigo === 201) $(‘#usuariosNuevos ul li[uid=”’ + uid + ‘”]’).fadeOut(1000); else $(‘#usuariosNuevos ul li[uid=”’ + uid + ‘”]’).html(respuesta.mensaje); } ); }

Aquí hicimos un POST por Ajax a la ruta /

CON UN CÓDIGO 201 EN LA RESPUESTA QUITAMOS AL USUARIO DE

setSolicitud, enviando el uid del usuario a que se le mandará la solicitud. En el caso de tener como respuesta el código 201, quitamos al usuario de la lista, y en el caso contrario, mostramos el mensaje en el mismo lugar. Para continuar es necesario que descomentemos el ruteo para la petición /

LA LISTA

setSolicitud en el archivo principal, para lo cual abriremos y cambiaremos el archivo app.js. Pasaremos de esto: //app.post(‘/setSolicitud’, user.setSolicitud); a esto: app.post(‘/setSolicitud’, user.setSolicitud); Ya sabemos qué hacer en caso de recibir un POST a esta ruta. Lo

siguiente es definir la función setSolicitud() en el archivo user.js, del modo que mostramos a continuación:

www.redusers.com

33

SISTEMAS WEB ESCALABLES

// registramos y envia una solicitud de amistad exports.setSolicitud = function(req, res){ var uid = req.param(‘uid’); if (uid.length < 1){ res.send({codigo: 204, mensaje: ‘Usuario Invalido’ }); }else{ // agregamos el usuario a la lista de solicitudes db.sadd(‘uid:’ + uid + ‘:solicitudes’, req.session.usr.uid); // obtenemos la solicitudes y la enviamos al usuario getSolicitudes(uid); res.send({codigo: 201, mensaje: ‘’ }); } }

En el código, recibimos el uid del usuario receptor, al que le agregamos el usuario actual en la clave solicitudes. Luego invocamos el método getSolicitudes() con el uid del usuario receptor como parámetro para obtener las solicitudes pendientes del usuario. Crearemos la función getSolicitudes()en el mismo archivo de la siguiente manera:

// obtenemos la cantidad de solicitudes pendientes var getSolicitudes = function(uid){ db.smembers(‘uid:’ + uid + ‘:solicitudes’, function (err, solicitudesRecibidas) { if (solicitudesRecibidas) { var solicitudes = []; // obtenemos los datos de cada uid solicitudesRecibidas.forEach(function(id){ db.hgetall(‘uid:’ + id, function (err, usuario) { solicitudes.push({‘uid’: id, ‘nombre’ : usuario.nombre, ‘apellido’ : usuario.apellido });

www.redusers.com

34

APÉNDICE. DESARROLLO DE UNA RED SOCIAL

// emitimos las solicitudes if (solicitudes.length == solicitudesRecibidas.length) io.sockets.socket(usrEnLinea[uid]).emit(‘getSolicitudes’, solicitudes); }); }); } }); }

En el código anterior verificamos las solicitudes pendientes del uid pasado por parámetro y, por cada una de ellas, obtenemos la información. Al obtener todas las solicitudes, emitimos el evento getSolicitudes con la lista completa al usuario recibido. Luego, al final del archivo, exportamos la función: exports.getSolicitudes

= getSolicitudes;

Necesitamos capturar el evento getSolicitudes del lado del cliente; por lo tanto, agregamos lo siguiente a la función $(document).ready() del archivo script.js: // io - cuando se recibe una solicitud sockets.on(‘getSolicitudes’, mostrarSolicitudes);

Cuando ocurra getSolicitudes, vamos a ejecutar la función mostrarSolicitudes(), que debemos definir como sigue:

DOCUMENTACIÓN DE EXPRESS 2X Si bien actualmente Express se encuentra en la versión 3, debido a que se han creado miles de aplicaciones con la versión 2 fue necesario dejar la documentación accesible para consultas de desarrolladores que necesitan mantener sistemas escritos con esta versión. Para ver la documentación correspondiente a Express en su versión 2, deberemos acceder a la página que se encuentra en la dirección http://expressjs.com/2x.

www.redusers.com

35

SISTEMAS WEB ESCALABLES

// se muestran las solicitudes de amistad function mostrarSolicitudes(solicitudes) { if (solicitudes.length) { $(‘#solicitudes’).html(‘’); solicitudes.forEach(function (usuario) { var contenido = ‘’; contenido += ‘
  • ’; contenido += ‘ ’ + usuario.nombre + ‘ ‘ + usuario.apellido + ‘ quiere ser tu amigo!’ + ‘’; contenido += ‘ ’; contenido += ‘

    ’; contenido += ‘

    ’; contenido += ‘ ’; contenido += ‘
  • ’; $(‘#solicitudes’).prepend(contenido); }); $(‘#valorContador’).fadeOut(500, function() { $(this).html(solicitudes.length).fadeIn(500) }); } }

    En el código que mostramos arriba recibimos las solicitudes y, por cada una de ellas, creamos un ítem en la lista con el nombre del usuario que ha enviado la solicitud original. Además, se procede a agregar los botones que necesitamos para aceptarla y también para cancelarla. Posteriormente, incrementamos el valor del contador con la

    POR CADA SOLICITUD DE AMISTAD RECIBIDA CREAMOS UN ÍTEM EN LA LISTA DEL USUARIO CONECTADO

    cantidad de solicitudes que han sido recibidas. Cuando un usuario reciba una solicitud de amistad, verá incrementado el contador.

    www.redusers.com

    36

    APÉNDICE. DESARROLLO DE UNA RED SOCIAL

    Figura 9. Por cada solicitud de amistad recibida, el contador incrementará su valor. Debemos lograr que, cuando el usuario haga clic en el contador, se muestre la ventana con las solicitudes; para lograrlo, agregamos lo siguiente al método $(document).ready() del archivo script.js: // contador de solicitudes $(document).on(‘click’, ‘.contador’, function(){ $(‘#solicitudes’).fadeToggle(500); });

    En el código, mostramos la lista de solicitudes con un efecto de aparición suave y, en caso de que la lista ya esté visible, la ocultamos.

    SOCKET.IO EN OTROS LENGUAJES Sockt.IO es una librería en lenguaje JavaScript, pero también es posible utilizar otras versiones, escritas en diferentes lenguajes, como Java, Objective-C, C, C++, Go, Python y PHP, entre otros. Esto es, sin duda, una gran ventaja, ya que es posible desarrollar sistemas basados en lenguajes como PHP en tiempo real. Podemos ver la lista en https://github.com/learnboost/socket.io/wiki.

    www.redusers.com

    37

    SISTEMAS WEB ESCALABLES

    Figura 10. Al recibir una solicitud el usuario puede optar por aceptarla o cancelarla. Antes definimos que, cuando se envía una solicitud de amistad, el usuario la recibe en tiempo real. Ahora debemos asegurarnos de que el usuario no conectado la reciba al conectarse. Para esto, agregamos el código siguiente en el archivo index.js, en el método sessionIO.on(), dentro del condicional de sesión: // obtenemos las solicitudes pendientes user.getSolicitudes(session.usr.uid);

    Con este código, cada vez que se conecta, el usuario puede obtener el listado de las solicitudes pendientes.

    PROYECTOS USANDO SOCKET.IO En el repositorio oficial de Socket.IO hay una lista con varios sistemas que pueden servirnos como base para el desarrollo de algún proyecto en tiempo real. Un ejemplo muy útil es collabshot, una herramienta colaborativa de edición de imágenes, notas y chat. Podemos acceder a la lista de proyectos a través del siguiente enlace: https://github.com/LearnBoost/Socket.IO/wiki/Projects-using-Socket.IO.

    www.redusers.com

    38

    APÉNDICE. DESARROLLO DE UNA RED SOCIAL

    Respuesta a una solicitud de amistad Vamos a definir las acciones que puede tomar el usuario cuando recibe una solicitud de amistad. Primero, agregamos lo siguiente al método $(document).ready() del archivo script.js: // boton de aceptar/cancelar solicitudes $(document).on(‘click’, ‘#solicitudes input[type=”button”]’, function(){ setRespuestaSolicitud($(this)); });

    En este código capturamos el evento click para aceptar o cancelar una solicitud, mediante el cual ejecutamos la función setRespuestaSolicitud(), que debemos definir en el mismo archivo del siguiente modo: // se responde una solicitud de amistad function setRespuestaSolicitud(boton) { $.post(“/setRespuestaSolicitud”, ‘uid=’ + boton.attr(‘uid’) + ‘&accion=’ + boton. attr(‘value’), function(respuesta){ if (respuesta.codigo === 201){ $(‘#solicitudes li[uid=”’ + boton.attr(‘uid’) + ‘”]’).fadeOut(1000); $(‘#valorContador’).fadeOut(500, function() { $(this).html(parseInt($(this).html()) - 1).fadeIn(500) }); }else $(‘#solicitudes li[uid=”’ + boton.attr(‘uid’) + ‘”]’).html(respuesta.mensaje); } ); }

    En el código anterior, enviamos un POST por Ajax a la ruta /setRespuestaSolicitud, con el uid del usuario al cual se responde la solicitud y con la acción (es decir, Aceptar o Cancelar). Cuando se recibe la respuesta, si se obtiene el código 201 se elimina el nombre del usuario y se descuenta el contador. De ocurrir lo contrario, se muestra el mensaje.

    www.redusers.com

    39

    SISTEMAS WEB ESCALABLES

    Lo siguiente es habilitar el ruteo para /setRespuestaSolicitud, para lo cual descomentamos la siguiente línea en el archivo app.js, pasando de esto: //app.post(‘/setRespuestaSolicitud’, user.setRespuestaSolicitud); al código que presentamos a continuación: app.post(‘/setRespuestaSolicitud’, user.setRespuestaSolicitud); Necesitaremos definir la función setRespuestaSolicitud en el archivo user.js, de la siguiente manera:

    // respondemos la solicitud de amistad exports.setRespuestaSolicitud = function(req, res){ var uid

    = req.param(‘uid’),

    accion = req.param(‘accion’); db.get(‘uid:’ + uid , function (err, reply) { if (reply === null){ res.send({codigo: 204, mensaje: ‘Usuario Invalido’ }); }else{ if (accion == ‘Aceptar’){ // agregamos como amigo a ambos usuarios db.sadd(‘uid:’ + uid + ‘:amigos’, req.session.usr.uid); db.sadd(‘uid:’ + req.session.usr.uid + ‘:amigos’, uid); } // eliminamos la solicitud actual db.srem(‘uid:’ + req.session.usr.uid + ‘:solicitudes’, uid); res.send({codigo: 201, mensaje: ‘’ }); } }); }

    En este código, verificamos si existe el usuario recibido y, en caso de que se haya aceptado la solicitud, se agregan como amigos

    www.redusers.com

    40

    APÉNDICE. DESARROLLO DE UNA RED SOCIAL

    mutuamente a través de la clave amigos. Luego se elimina la solicitud en ambos casos, es decir, tanto si se ha aceptado la solicitud como si no se ha hecho.

    Figura 11. Una solicitud de amistad permanecerá en la lista hasta que el usuario realice una de las dos acciones definidas.

    Lista de amigos conectados Vamos a crear una función para obtener los amigos que están conectados cuando un usuario inicia sesión. Para hacerlo, agregamos el código siguiente en el archivo llamado index.js, en la función sessionIO.on(), dentro del condicional de sesión:

    // obtenemos los usuarios conectados user.getAmigosConectados(session.usr.uid);

    En el código definimos que, cuando un usuario se conecta, se ejecuta la función getAmigosConectados(). Por lo tanto, vamos a definirla en el archivo user.js de la siguiente manera:

    www.redusers.com

    41

    SISTEMAS WEB ESCALABLES

    // obtenemos los amigos conectados var getAmigosConectados = function(uid){ db.smembers(‘uid:’ + uid + ‘:amigos’, function (err, amigos) { if (amigos) { var usrConectados = []; var i = 0; amigos.forEach(function(id){ i++; // verificamos que el usuario se encuentre conectado if (usrEnLinea[id]){ // obtenemos la informacion del usuario db.hgetall(‘uid:’ + id, function (err, usuario) { usrConectados.push({‘uid’: id, ‘nombre’ : usuario.nombre, ‘apellido’ : usuario.apellido }); // emitimos los amigos if (i == amigos.length) io.sockets.socket(usrEnLinea[uid]).emit(‘setAmigosConectados’, usrConectados); }); } }); } }); }

    HERRAMIENTAS PARA FRONTEND Fred Sarmento ha creado un portal con recursos para los frontend, donde se incluyen librerías y plugins como jQuery, Normalize.css, herramientas de debug y testeo como Firebug y Chrome Developer Tools, tutoriales en línea, y editores de código como Sublime Text3. Podemos ver la lista completa en el siguiente enlace: http://fredsarmento.me/frontend-tools.

    www.redusers.com

    42

    APÉNDICE. DESARROLLO DE UNA RED SOCIAL

    En el código anterior hemos obtenido los amigos del usuario actual y, para cada uno, verificamos si se encontraba conectado, es decir que existiera en el array usrEnLinea. De estos usuarios hemos obtenido el uid, el nombre y el apellido. Cuando terminamos de obtener los amigos, emitimos el evento setAmigosConectados con la lista. En este punto debemos exportar la función para que esté disponible desde cualquier lugar del sistema. Para ello, agregamos el siguiente código al final del archivo llamado user.js: exports.getAmigosConectados = getAmigosConectados; Vamos a capturar este evento del lado del cliente, definiendo lo que sigue en el archivo script.js dentro del método $(document).ready():

    // io - mostramos los amigos conectados sockets.on(‘setAmigosConectados’, mostrarAmigosConectados);

    Hasta este momento hemos definido que, cuando se recibe el evento setAmigosConectados, se ejecuta la función mostrarAmigosConectados(); por lo tanto, debemos crearla en el mismo archivo: // muestra los amigos conectados function mostrarAmigosConectados(data){ if (data.length) { $(‘#usuariosAmigos’).html(‘
      ’); data.forEach(function (usuario) { var nombreUsuario = usuario.nombre + ‘ ‘ + usuario.apellido; $(‘#usuariosAmigos ul’).prepend(‘
    • ’ + nombreUsuario + ‘
    • ’); }); }; }

      Con este código obtenemos la lista de amigos conectados, agregando a cada uno de ellos en el contenedor #usuariosAmigos.

      www.redusers.com

      43

      SISTEMAS WEB ESCALABLES

      Figura 12. Cuando los usuarios inicien sesión verán en la columna izquierda la lista de los amigos conectados.

      Informar cuando se conecta un usuario Vamos a crear una función para informar a los amigos, en tiempo real, que un usuario se ha conectado. Primero nos encargamos de agregar el siguiente código en el archivo denominado index.js, en la función sessionIO.on(), dentro del condicional de sesión: // informamos a los amigos que se ha conectado el usuario user.setAmigoConectado(session.usr);

      INTERNET Según los últimos informes de Cisco, para el año 2017 habrá cerca de 3.600 millones de usuarios de Internet, esto se puede resumir como casi el 50% de la población mundial. Implicaría un aumento del tráfico mundial por tres, donde el servicio será accesible desde notebooks, netbooks, smartphones, tablets y televisores inteligentes.

      www.redusers.com

      44

      APÉNDICE. DESARROLLO DE UNA RED SOCIAL

      En el código anterior definimos que, cuando un usuario se conecte, se ejecutará la función setAmigoConectado(), a la que le pasamos como parámetro todos los datos del usuario actual. En este momento hacemos la definición de la función setAmigoConectado() en el archivo user.js:

      // informamos a los amigos que se ha conectado el usuario var setAmigoConectado = function(usr){ // obtenemos todos los amigos db.smembers(‘uid:’ + usr.uid + ‘:amigos’, function (err, amigos) { amigos.forEach(function(id){ // verificamos que el usuario se encuentre conectado if (usrEnLinea[id]) io.sockets.socket(usrEnLinea[id]).emit(‘setAmigoConectado’, usr); }); }); }

      En el código anterior, obtenemos los amigos del usuario actual de la base de datos y, a cada uno de ellos, le informamos los datos del usuario mediante el evento setAmigoConectado. Luego, exportamos la función para que esté disponible en todo el sistema: exports.setAmigoConectado

      = setAmigoConectado;

      Vamos a capturar el evento del lado del cliente mediante el siguiente código, en el $(document).ready() del archivo script.js:

      // io - mostramos cuando se conecta un amigo sockets.on(‘setAmigoConectado’, mostrarAmigoConectado);

      En el código anterior, definimos que se ejecutará la función mostrarAmigoConectado() cuando se reciba el evento setAmigoConectado. Por lo tanto, la definimos en el mismo archivo:

      www.redusers.com

      45

      SISTEMAS WEB ESCALABLES

      // agrega un usuario a la lista de amigos cuando se conecta function mostrarAmigoConectado(data){ if ($(‘#usuariosAmigos ul li[uid=”’ + data.uid + ‘”]’).size() === 0){ // creamos la lista si no existe if($(‘#usuariosAmigos p’).size()){ $(‘#usuariosAmigos p’).remove(); $(‘#usuariosAmigos’).append(‘
        ’); } var nombreUsuario = data.nombre + ‘ ‘ + data.apellido; $(‘#usuariosAmigos ul’).prepend(‘
      • ’ + nombreUsuario + ‘
      • ’); } }

        En este código nos encargamos de verificar que el usuario recibido no exista en la lista de amigos y lo agregamos. También podemos corroborar si existe el elemento

        , para eliminarlo en caso de que sea el primer amigo que se muestre en la lista.

        Informar cuando se desconecta un usuario Vamos a crear una función para informar a los amigos cuando un usuario se desconecta. Si observamos home.ejs veremos que hemos definido el enlace salir y apunta a la ruta /salir. Para que nuestro sistema pueda efectuar alguna acción cuando recibe esta ruta, necesitaremos descomentar la siguiente línea del archivo app.js, pasando de esto: //app.get(‘/salir’, user.logout); a esto: app.get(‘/salir’, user.logout);

        www.redusers.com

        46

        APÉNDICE. DESARROLLO DE UNA RED SOCIAL

        En este código pudimos definimos que, cuando se solicite la ruta /salir, se deberá ejecutar la función logout(). Por lo tanto, debemos proceder a crearla en el archivo llamado user.js:

        // logout exports.logout = function(req, res){ // informamos a los amigos que se ha desconectado el usuario setAmigoDesconectado(req.session.usr.uid); // eliminamos el uid del array de usuarios y la session delete usrEnLinea[req.session.usr.uid]; // eliminamos la clave se session req.session.usr = ‘’; // redireccionamos res.redirect(‘/’); }

        En el código nos encargamos de ejecutar la función setAmigoDesconectado(), que vamos a crear a continuación; luego, borramos el usuario del array de usuarios en línea, vaciamos la variable de sesión y redirigimos al usuario a la pagina inicial. A continuación vamos a definir la función setAmigoDesconectado() en el mismo archivo con el que estamos trabajando:

        // informamos a los amigos que se ha desconectado el usuario var setAmigoDesconectado = function(uid){ // obtenemos todos los amigos db.smembers(‘uid:’ + uid + ‘:amigos’, function (err, amigos) { amigos.forEach(function(id){ // verificamos que el usuario se encuentre conectado if (usrEnLinea[id]) io.sockets.socket(usrEnLinea[id]).emit(‘setAmigoDesconectado’, uid); }); }); }

        www.redusers.com

        47

        SISTEMAS WEB ESCALABLES

        Aquí hemos obtenido todos los amigos del usuario y, para cada uno de ellos, hemos verificado si estaba conectado, emitiendo el evento setAmigoDesconectado con el uid del usuario actual. Para continuar nos encargamos de exportar la función para que esté disponible desde cualquier lugar del sistema: exports.setAmigoDesconectado = setAmigoDesconectado; Ahora necesitaremos realizar la captura del evento desde el lado del cliente. Para efectuar esta tarea debemos proceder a escribir lo que mostramos a continuación, en el método denominado $(document). ready() dentro del archivo script.js:

        // io - mostramos cuando se desconecta un amigo sockets.on(‘setAmigoDesconectado’, mostrarAmigoDesconectado);

        Con el código establecemos que, cuando se reciba el evento setAmigoDesconectado, se deberá ejecutar la función mostrarAmigoDesconectado(), que definiremos en el mismo archivo:

        // elimina un usuario de la lista de amigos cuando se desconecta function mostrarAmigoDesconectado(data){ // eliminamos el amigo de la lista $(‘#usuariosAmigos ul li[uid=”’ + data + ‘”]’).remove(); // si no hay amigos mostramos el mensaje if ($(‘#usuariosAmigos ul li’).size() == 0){ $(‘#usuariosAmigos ul’).remove(); $(‘#usuariosAmigos’).prepend(‘

        No hay amigos conectados...

        ’); } // eliminamos la ventana de chat si existe if ($(‘#ventana-’ + data).size()) $(‘#ventana-’ + data).remove(); }

        www.redusers.com

        48

        APÉNDICE. DESARROLLO DE UNA RED SOCIAL

        Eliminamos el ítem de la lista de amigos y verificamos si existen otros conectados; en el caso contrario mostramos un elemento

        con el mensaje de que no hay amigos conectados y, luego, verificamos si existe una ventana de chat abierta y la borramos. A continuación trabajaremos en el chat con amigos.

        Creación del sistema de chat Para continuar vamos a desarrollar el sistema de chat entre los amigos que están conectados. La idea es que, al hacer clic en un amigo de la lista, se abra una ventana típica de chat. Primero vamos a capturar el evento click. En el método $(document). ready(), en el archivo script.js, agregamos lo siguiente: // abre ventanas de chat $(document).on(‘click’, ‘#usuariosAmigos ul li’, function(){ abrirVentanaChat($(this).attr(‘uid’)); });

        Así, definimos que, cuando se haga clic en un elemento de la lista, se ejecutará la función abrirVentanaChat(), y pasamos como parámetro el atributo uid del usuario de la lista. A continuación debemos proceder a definir la función denominada abrirVentanaChat() en el mismo archivo:

        // abre una ventana de chat function abrirVentanaChat(uid) { if (!$(‘#ventana-’ + uid).size()){ var nombre = $(‘#usuariosAmigos ul li[uid=”’ + uid + ‘”]’).html(); var ventana = ‘’; ventana += ‘

        ’+ nombre + ‘’;

        ventana += ‘

        ’;

        www.redusers.com

        49

        SISTEMAS WEB ESCALABLES

        ventana += ‘

        ’;

        ventana += ‘’; $(‘.chat’).append(ventana); } }

        En el código, obtenemos el uid del usuario con el que se va a chatear, y se crea una ventana que contendrá un elemento para escribir y un

        LUEGO DE OBTENER EL UID DEL USUARIO

        elemento para mostrar los mensajes;

        SE CREA UNA

        agregamos toda la ventana al elemento chat. Vamos a definir el evento que va a capturar el mensaje para enviarlo al receptor. Para hacerlo, agregamos el siguiente código en el método

        VENTANA APTA PARA UNA SESIÓN DE CHAT

        $(document).ready() del mismo archivo: // cuando el usuario presiona enter emite el mensaje $(document).on(‘keypress’, ‘.chat-text’, function(e){ if (e.which == 13) enviarMensaje($(this).parent().attr(‘uid’), $(this).val()); });

        En este código simplemente ejecutamos la función enviarMensaje() cuando se presiona la tecla ENTER, pasándole como parámetros el uid del usuario receptor y el mensaje. Debemos definir la función enviarMensaje() en el mismo archivo:

        EXTENSIONES PARA EXPRESS Además de las herramientas que vienen integradas por defecto en Express, integrantes de la comunidad oficial han creado decenas de extensiones muy útiles para ser utilizadas en los desarrollos y ahorrar tiempo de programación. Podemos conocerlas accediendo al siguiente enlace: https://github.com/ senchalabs/connect/wiki.

        www.redusers.com

        50

        APÉNDICE. DESARROLLO DE UNA RED SOCIAL

        // se envia un mensaje en el chat function enviarMensaje(uid, msg) { var data = { para : uid, mensaje: msg, fecha : new Date() }; // emitimos el mensaje sockets.emit(‘enviarMensaje’, data); // agregamos el mensaje al textarea $(‘#ventana-’+ uid +’ textarea’).val($(‘#ventana-’+ uid +’ textarea’).val() + ‘yo: ‘ + msg + ‘\r\n’); // limpiamos la caja de texto $(‘#ventana-’+ uid +’ .chat-text’).val(‘’); }

        En el código, creamos un objeto con la clave para (donde le asignamos el uid receptor, el mensaje y la fecha de emisión) y luego emitimos el mensaje enviarMensaje al servidor; por último, agregamos a el mensaje y limpiamos la caja de texto. Ahora necesitamos capturar el evento del lado del servidor. Debemos agregar lo que sigue en el archivo index.js, en la función sessionIO.on() dentro del condicional de sesión: // Emitimos el mensaje al usuario socket.on(‘enviarMensaje’, function (data){ data.de

        = session.usr.uid;

        data.nombre = session.usr.nombre + ‘ ‘ + session.usr.apellido; user.enviarMensaje(data); });

        En el código hemos capturado el evento enviarMensaje y hemos agregado la clave de con el uid del usuario actual, y la clave nombre con los atributos nombre y apellido concatenados. Ejecutamos la función enviarMensaje() pasándole como parámetro el objeto recibido. Lo que sigue es crear la función enviarMensaje() en el archivo user.js:

        www.redusers.com

        51

        SISTEMAS WEB ESCALABLES

        // se envia el mensaje del chat var enviarMensaje = function (data){ io.sockets.socket(usrEnLinea[data.para]).emit(‘mensajeRecibido’, data); }

        Simplemente, enviamos el objeto recibido al usuario receptor a través del evento mensajeRecibido. Luego, exportamos la función para que esté disponible desde cualquier lugar del sistema: exports.enviarMensaje

        = enviarMensaje;

        Necesitamos capturar este evento del lado del cliente, dentro de la función $(document).ready(): // io - mostramos el mensaje recibido del chat sockets.on(‘mensajeRecibido’, mostrarMensajeRecibido);

        En este código hemos definido que, cuando se recibe el evento mensajeRecibido, se ejecuta la función mostrarMensajeRecibido(), por lo que debemos definirla en el mismo archivo. Antes de la función vamos a establecer una variable global llamada ultimaFechaMsg: var ultimaFechaMsg = 0; // se muestra un mensaje recibido del chat function mostrarMensajeRecibido(data){ // si se recibe mensajes duplicados if (ultimaFechaMsg == data.fecha) return; else{ // actualizamos la fecha del ultimo mensahe recibido ultimaFechaMsg = data.fecha; // si no existe la ventana la creamos if ($(‘#ventana-’ + data.de).size() == 0) abrirVentanaChat(data.de);

        www.redusers.com

        52

        APÉNDICE. DESARROLLO DE UNA RED SOCIAL

        // agregamos la informacion del mensaje $(‘#ventana-’ + data.de + ‘ span’).html(data.nombre); $(‘#ventana-’ + data.de + ‘ textarea’).val($(‘#ventana-’ + data.de + ‘ textarea’).val() + data.nombre + ‘: ‘ + data.mensaje + ‘\r\n’); } }

        En el código anterior, declaramos una variable global que utilizaremos en la función para comparar la fecha de recepción de mensajes en el caso de que existan mensajes duplicados. Luego, si la ventana de chat no existe, la abrimos y agregamos el mensaje.

        Figura 13. En la imagen podemos ver la ventana de chat con amigos.

        ESTADÍSTICAS MEDIANTE STATCOUNTER StatCounter es un sitio que publica estadísticas globales acerca de diferentes tecnologías. Entre las opciones que ofrece se encuentra la posibilidad de ver qué tecnología comparar, permite seleccionar el tipo de gráfico (líneas, barras o mapa) y además permite descargar el gráfico en formato JPG o CSV. Podemos conocerlo mejor a través del siguiente enlace: http://gs.statcounter.com.

        www.redusers.com

        53

        SISTEMAS WEB ESCALABLES

        Debemos destacar que el sistema de chat desarrollado permite al usuario tener múltiples conversaciones en simultáneo, ya que en el servidor mantenemos el id del Socket que identifica a cada uno.

        Figura 14. Mediante el sistema de chat desarrollado, los usuarios pueden mantener varias conversaciones a la vez.

        Creación del sistema de posts Vamos a desarrollar el sistema de publicación de post con amigos. Primero necesitamos poder enviar al servidor los posts escritos, para lo cual debemos capturar el contenido del formulario. En el archivo script.js, dentro del método $(document).ready(), escribiremos lo siguiente: // nuevo post $(document).on(‘submit’, ‘#postFrm’, function(e){ e.preventDefault(); setPost($(this)); });

        Así, definimos que, cuando se envía el formulario, se ejecuta la función setPost() enviando como parámetro el mismo objeto. A continuación definimos la función setPost() en el mismo archivo:

        www.redusers.com

        54

        APÉNDICE. DESARROLLO DE UNA RED SOCIAL

        // metodo POST cuando se escrine un post nuevo function setPost(form) { $.post(“/setPost”, form.serialize(), function(respuesta){ if (respuesta.codigo === 201){ var bloque = ‘’; bloque += ‘ ’; bloque += ‘

        ’;

        bloque += ‘

        yo ’;

        bloque += ‘

        hace unos segundos...’; bloque += ‘ ’; bloque += ‘ ’ + $(‘#postFrm textarea’).val() + ‘’; bloque += ‘’; $(‘#posts’).prepend(bloque); $(‘#postFrm textarea’).val(‘’); }else mostrarMensajeFormulario(form, respuesta.mensaje); } ); }

        En el código hacemos un POST por Ajax a la ruta /setPost con los elementos del formulario serializado. Si la respuesta es 201, creamos un bloque con el post escrito y se lo mostramos al mismo usuario. En el caso contrario, mostramos el mensaje de error. Como estamos haciendo un POST descomentamos la siguiente línea en app.js. De esto: //app.post(‘/setPost’, user.setPost); debemos pasar a lo que mostramos a continuación: app.post(‘/setPost’, user.setPost);

        www.redusers.com

        55

        SISTEMAS WEB ESCALABLES

        En este código nos encargamos de definir que el ruteo deberá ejecutar la función setPost(). Por esta razón, tendremos que realizar esta definición en el archivo denominado user.js:

        // registramos el post para el usuario actual y los amigos exports.setPost = function(req, res){ var post = req.param(‘post’); if (post.length < 1){ res.send({codigo: 204, mensaje: ‘Mensaje Invalido’ }); return; }else{ db.incr(‘global:ultimoPid’, function (err, pid) { var fecha = formatearFecha(); var uid = req.session.usr.uid; // seteamos el post y la fecha/hora actual db.hmset(‘post:’ + pid, {‘uid’ : uid, ‘fecha’ : fecha, ‘post’ : post } ); // incremento la cantidad de posts para el usuario actual db.incr(‘uid:’ + uid + ‘:nposts’); var postID = pid; // obtenemos los amigos db.smembers(“uid:” + uid + “:amigos”, function (err, amigos) { // agrego el usuario actual amigos.push(uid); // agrego el id del post a cada amigo amigos.forEach(function(sid){ db.lpush(‘uid:’ + sid + ‘:posts’, postID); }); });

        www.redusers.com

        56

        APÉNDICE. DESARROLLO DE UNA RED SOCIAL

        res.send({codigo: 201, mensaje: ‘Mensaje publicado’}); }); } };

        En el código anterior, recibimos el post y verificamos que tenga contenido; luego, incrementamos la clave ultimoPid y llamamos a la función formatearFecha() para obtener la fecha actual formateada. Después, obtenemos el usuario actual y guadamos en la base de datos el post con la información obtenida. Seguidamente, nos encargamos de incrementar la cantidad de posts para este usuario y la guardamos en simultáneo para cada uno de los amigos. A continuación vamos a definir la función formatearFecha():

        // funcion para formatear la fecha function formatearFecha(fecha) { var d dia

        = new Date(fecha || Date.now()), = d.getDate(),

        mes

        = (d.getMonth() + 1),

        anio

        = d.getFullYear(),

        hora

        = d.getHours(),

        minuto = d.getMinutes(), segundo = d.getSeconds(); if (mes.length < 2) mes += ‘0’; if (dia.length < 2) dia += ‘0’; return [dia, mes, anio].join(‘-’) + ‘ ‘ + [hora, minuto, segundo].join(‘:’); }

        En el código anterior hemos obtenido la fecha actual, en el caso de que no la pasemos por parámetro, y la formateamos de manera legible. Lo que nos queda, ahora, es obtener los posts cuando el usuario inicia sesión. Para esto, agregamos el siguiente código en el archivo index.js, en la función sessionIO.on(), dentro del condicional de sesión:

        www.redusers.com

        57

        SISTEMAS WEB ESCALABLES

        // obtenemos los posts user.getPosts(session.usr.uid);

        De este modo definimos que, al conectarse un usuario, se ejecutará la función getPosts(), a la cual le pasaremos como parámetro el uid actual. Debemos definir la función que corresponde en el archivo llamado user.js de la siguiente manera: var getPosts = function(uid){ db.lrange(‘uid:’ + uid + ‘:posts’, 0, 10, function (err, posts) { if (posts) { var arrayPosts = []; var i = 0; // obtenemos los atributos de cada post posts.forEach(function(pid) { db.hgetall(‘post:’ + pid, function (err, post) { var usuarioNombre; // obtenemos los atributos del usuario db.hgetall(‘uid:’ + post.uid, function (err, usuario) { i++; arrayPosts.push({‘uid’

        : post.uid,

        ‘nombre’ : usuario.nombre, ‘apellido’: usuario.apellido, ‘fecha’ : post.fecha, ‘mensaje’ : post.post }); // al final de la lista de post se emite al usuario if (i == posts.length) io.sockets.socket(usrEnLinea[uid]).emit(‘setPosts’, arrayPosts); }); }); }); } }); }

        www.redusers.com

        58

        APÉNDICE. DESARROLLO DE UNA RED SOCIAL

        Hemos obtenido los últimos diez posts del usuario actual y, de cada uno de ellos, la información completa y del usuario que la escribió, que almacenamos en un array. Por último, debemos realizar la emisión del evento llamado setPosts con el array creado. A continuación debemos capturar el evento setPosts del lado del cliente. Para realizar esto agregaremos lo que sigue en el archivo script.js en el método $(document).ready():

        // io - mostramos los posts sockets.on(‘setPosts’, mostrarPosts);

        Así determinamos que, cuando ocurra el evento setPosts, ejecutaremos la función mostrarPosts(), que definiremos en el mismo archivo tal como mostramos: // muestra los posts en el contenedor function mostrarPosts(data){ if (data.length) { $(‘#posts’).html(‘’); data.forEach(function (post) { var nombreUsuario = post.nombre + ‘ ‘ + post.apellido; var bloque = ‘’; bloque += ‘ ’; bloque += ‘

        ’;

        bloque += ‘

        ’ + nombreUsuario + ‘’; bloque += ‘ ’; bloque += ‘ ’ + post.mensaje + ‘’; bloque += ‘’; $(‘#posts’).append(bloque); }); }; }

        www.redusers.com

        59

        SISTEMAS WEB ESCALABLES

        En el código nos hemos encargado de obtener

        POR CADA POST SE

        los posts correspondientes y, por cada uno de

        CREA UN BLOQUE

        ellos, creamos un bloque con el usuario que lo escribió, la fecha y el mensaje, y lo agregamos al contenedor #posts. Por último, recordemos que había quedado pendiente explicar la llamada a la función user. getTotalUsuarios() del archivo index.js, en caso de

        CON EL USUARIO QUE LO ESCRIBIÓ Y OTROS DATOS IMPORTANTES

        que el usuario haya iniciado sesión. Ya hemos desarrollado esta función, que simplemente realiza la devolución del total de usuarios registrados en el sistema (debemos considerar que serán mostrados en la parte inferior de la pantalla principal del sistema).

        Figura 15. En la imagen podemos observar la apariencia de los posts y que el usuario tiene acceso a los posts de sus amigos.

        Vista de la base de datos Inspeccionando la base de datos de nuestro sistema podemos ver la estructura generada; por ejemplo, los usuarios registrados y conectados, las listas de amigos generadas para cada usuario, los posts creados por los usuarios y también los contadores.

        www.redusers.com

        60

        APÉNDICE. DESARROLLO DE UNA RED SOCIAL

        Figura 16. Cada usuario contiene, además de sus datos, una lista de amigos, una lista de posts y un contador de posts. También podemos observar qué información tenemos de cada post. Entre los datos que es posible identificar para los post escritos encontramos el autor, la fecha en que se creó y también el mensaje que corresponde, así como el identificador del usuario.

        Figura 17. En la base de datos guardamos la fecha, el mensaje y el identificador del usuario que lo escribió. www.redusers.com

        61

        SISTEMAS WEB ESCALABLES

        Hasta aquí hemos realizado el desarrollo completo de un sistema medianamente complejo, que nos sirve como base para cualquier tipo de sistema escalable con funciones y características en tiempo real. Como sabemos, una red social cumple los requisitos mencionados, lo que la convierte en el ejemplo ideal para el tema que hemos tratado a lo largo de los capítulos que componen esta obra.

        RESUMEN Hemos aplicado todos los conocimientos y temas tratados a lo largo del libro mediante el desarrollo de una red social que propone un gran cambio en la manera de pensar las acciones, ya que la mayoría de las interacciones deben ser reflejadas en tiempo real y desencadenan un efecto en los demás usuarios. Las bases de datos NoSQL, como Redis, cuentan con ventaja en la disponibilidad para grandes volúmenes de datos, ofreciendo un gran desempeño en el funcionamiento de cualquier sistema; al mismo tiempo, mediante Socket.IO es posible emitir y recibir eventos completamente personalizados, que entregan un aspecto único a la interacción de los usuarios con los sistemas.

        www.redusers.com

        62

        APÉNDICE. DESARROLLO DE UNA RED SOCIAL

        Actividades TEST DE AUTOEVALUACIÓN 1

        ¿Express ofrece la posibilidad de definir un sistema autosuficiente?

        2

        ¿Es posible definir diferentes entornos de funcionamiento en Express?

        3

        ¿Con qué característica debe contar una variable para tener un alcance global?

        4

        ¿ Qué característica debe tener una función para tener un alcance global?

        5

        ¿Socket.IO maneja sesiones? Justificar.

        6

        ¿Es posible utilizar un mecanismo de cookies, en vez de sesiones, para manejar usuarios conectados?

        7

        ¿on() y emit() son métodos de Socket.IO de Express?

        8

        ¿Qué utilidad tiene la exportación de las funciones?

        9

        ¿Los métodos on() y emit() pueden ser utilizados en cliente y en servidor indistintamente?

        10

        ¿Un sistema desarrollado con Node y Express necesita de un servidor web como Apache?

        EJERCICIOS PRÁCTICOS 1

        Implemente un buscador para localizar posts, un sistema de comentario con Socket.IO para notificar a los amigos y un sistema de puntuación para los posts.

        2

        Instale el módulo nodemailer para enviar correos electrónicos.

        3

        Genere la función Ver y editar el perfil actual y Acceder al perfil de los amigos.

        4

        Implemente un sistema de cierre de sesión y notificación cuando el usuario cierra el navegador.

        PROFESOR EN LÍNEA Si tiene alguna consulta técnica relacionada con el contenido, puede contactarse con nuestros expertos: [email protected]

        www.redusers.com