Desarrollo de una red social - …premium.redusers.com.s3.amazonaws.com/LIBROS/Sistemas Web... ·...

62
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. Desarrollo de una red social El porqué del proyecto .............. 2 Definición del proyecto.............. 2 Creación del proyecto ........................ 3 Configuración del archivo app.js......... 4 Registro y login de usuarios ............. 12 Creación de la página principal ........ 25 Definición de los eventos para un usuario logueado ................. 27 Envío de una solicitud de amistad .... 31 Lista de amigos conectados ............. 40 Informar cuando se conecta un usuario....................... 43 Creación del sistema de chat............ 48 Creación del sistema de posts .......... 53 Vista de la base de datos.................. 59 Resumen................................... 61 Actividades............................... 62

Transcript of Desarrollo de una red social - …premium.redusers.com.s3.amazonaws.com/LIBROS/Sistemas Web... ·...

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

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.

Desarrollode una red social

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

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

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

Confi guración del archivo app.js ......... 4

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

Creación de la página principal ........ 25

Defi nición de los eventos

para un usuario logueado ................. 27

Envío de una solicitud de amistad .... 31

Lista de amigos conectados ............. 40

Informar cuando

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

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

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

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

▼ Resumen ...................................61

▼ Actividades ...............................62

APÉNDICE. DESARROLLO DE UNA RED SOCIAL2

www.redusers.com

El porqué del proyectoLa 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ífi cas (tales

como ruteo, manejo de plantillas y estilos css, eventos de tiempo

real e interacción con la base de datos).

Defi nición del proyectoConsiste 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

notifi caciones, 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.

SISTEMAS WEB ESCALABLES 3

www.redusers.com

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

Instalación de herramientas previasPara 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

También es recomendable utilizar Nodemon,

ya que nos evitaremos reiniciar la aplicación en

cada cambio que hagamos. Lo podemos instalar

mediante el siguiente comando:

npm install -g nodemon

Creación del proyectoPara 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:

GRACIAS A NODEMON

NO SERÁ NECESARIO

REINICIAR LA

APLICACIÓN LUEGO

DE CADA CAMBIO

APÉNDICE. DESARROLLO DE UNA RED SOCIAL4

www.redusers.com

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 verifi car su funcionamiento:

nodemon app.js

Por defecto, el sistema va a funcionar en la siguiente dirección:

http://127.0.0.1:3000.

Confi guración del archivo app.jsAhora trabajaremos directamente en la aplicación. Lo primero que

haremos es confi gurar 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:

Express provee una extensa lista de ejemplos que cubren todos los aspectos del framework, como auten-

ticació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.

APLICACIONES DE EJEMPLO DE EXPRESS

SISTEMAS WEB ESCALABLES 5

www.redusers.com

// defi nicion de modulos

var express = require(‘express’)

, routes = require(‘./routes’)

, user = require(‘./routes/user’)

, http = require(‘http’)

, path = require(‘path’)

, redis = require(‘redis’)

, crypto = require(‘crypto’)

, ssio = 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 defi nimos el puerto que utilizará el sistema, el

directorio y también el motor de plantillas:

// confi guraciones para todos los entornos

app.set(‘port’, process.env.PORT || 3000);

app.set(‘views’, __dirname + ‘/views’);

app.set(‘view engine’, ‘ejs’);

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.

SELECCIONAR UN FRAMEWORK

APÉNDICE. DESARROLLO DE UNA RED SOCIAL6

www.redusers.com

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 defi nición del uso del manejador de

errores para el entorno de desarrollo:

// confi guraciones 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:

// defi nicion 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

SISTEMAS WEB ESCALABLES 7

www.redusers.com

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

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

Socket.io; en sessionIO almacenamos la instancia del

objeto sesión.socket.io, que nos servirá para acceder

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

socket; por último, la variable crypto nos servirá

para encriptar las claves de acceso.

A continuación debemos realizar la defi nición

de las rutas que manejaremos en el sistema.

Primero ruteamos las solicitudes por GET para el

acceso y salida del sistema:

// GET - defi nicion 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 defi nició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 - defi nicion 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 defi nidas 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:

LA VARIABLE CRYPTO

PERMITIRÁ LA

ENCRIPTACIÓN DE

LAS CLAVES

DE ACCESO

APÉNDICE. DESARROLLO DE UNA RED SOCIAL8

www.redusers.com

// listen

server.listen(app.get(‘port’), function(){

console.log(‘Express server listening on port ‘ + app.get(‘port’));

});

Defi nición de la apariencia del sistemaVamos 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 simplifi car 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; fl oat: left;}

header #buscador #tBuscar{width: 270px;}

header #buscador #bBuscar{height: 28px;}

header #notifi cacion{fl oat: right; padding: 9px 0 0; width: 203px;}

header #notifi cacion #usr{fl oat: left; font-size: 14px; height: 100%; margin:

5px 0; width: 150px;}

h1{background: url(“/img/icon-profi le.png”) no-repeat scroll left top transparent;

color: #666; fl oat: 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”],

SISTEMAS WEB ESCALABLES 9

www.redusers.com

input[type=”password”]{margin: 7px 0 0;}

a{color: #444444; display: block; text-decoration: none;}

nav{clear: both; fl oat: 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; background-

image: -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{fl oat: right; padding: 5px 15px; text-align: right; width: 401px; font-size:

15px;}

APÉNDICE. DESARROLLO DE UNA RED SOCIAL10

www.redusers.com

.tecnologias {fl oat: 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; text-

align: justify;}

.columna_derecha{fl oat: 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; fl oat: left; padding: 11px 10px 0;

font-size: 14px;}

.contenido{width: 438px; fl oat: left;}

.usuarios{fl oat: left; margin: 0 5px; width: 180px; text-align: left;}

.usuarios span{font-size: 13px; font-weight: bold;}

#notifi cacion ul, .usuarios ul{margin: 0; min-height: 40px; list-style: none; pad-

ding: 0;}

#notifi cacion ul li, .usuarios ul li{border-bottom: 1px solid #CCCCCC; margin:

10px 0; cursor: pointer; padding: 5px;}

#notifi cacion 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;}

SISTEMAS WEB ESCALABLES 11

www.redusers.com

.ventana{margin: 0 2px; fl oat: left; width: 186px;}

.ventana input[type=”text”]{margin: 0 2px; width: 179px;}

.usuariosLista{min-height: 400px;}

.avatar{fl oat: left; width: 30px; height: 30px;}

.avatar img{width: 30px; height: 30px;}

.bloque{text-align: left; margin-bottom: 5px; fl oat: 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; fl oat: left; width: 30px; height: 30px;}

.bloque .header .bloque_usuario{font-weight: bold; margin: 0 5px 0 0;}

.bloque .header .bloque_fecha{fl oat: 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; fl oat: left; font-size: 18px; font-

weight: bold; height: 17px; width: 30px;}

#postFrm{width: 436px;}

#postFrm textarea {display: block; height: 63px; width: 422px;}

#postFrm input[type=”submit”]{margin-right: 0; fl oat: right;}

#solicitudes{height: 200px; width: 200px; background-color: #F8F8F8; posi-

tion: 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;}

APÉNDICE. DESARROLLO DE UNA RED SOCIAL12

www.redusers.com

Dado que solo hemos defi nido los aspectos visuales, no se va a

explicar el código en detalle.

Registro y login de usuariosAhora 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:

<!DOCTYPE html>

<html lang=”es”>

<head>

<meta charset=”utf-8” />

<title><%= titulo %></title>

<link rel=”stylesheet” href=”/css/style.css” />

<link rel=”shortcut icon” href=”/img/favicon.png” >

</head>

<body>

<div class=”contenedor”>

Simplemente hemos creado una estructura HTML básica que luego

incluiremos en las otras plantillas. El elemento <title> contendrá la

variable titulo, que hemos defi nido anteriormente en el archivo app.js.

Vamos a crear un nuevo archivo llamado footer.ejs con código que sigue:

<footer>

<p>

<b><%= titulo %></b> - <%= autor %>

</p>

</footer>

</div>

<script type=”text/javascript” src=”/socket.io/socket.io.js”></script>

<script type=”text/javascript” src=”http://ajax.googleapis.com/ajax/libs/jque-

ry/1.9.0/jquery.min.js”></script>

<script type=”text/javascript” src=”/js/script.js”></script>

SISTEMAS WEB ESCALABLES 13

www.redusers.com

<!--[if lt IE 9]><script src=”http://html5shiv.googlecode.com/svn/trunk/html5.

js”></script><![endif]-->

</body>

</html>

Al igual que header.ejs, este archivo contendrá las variables globales

titulo y autor defi nidas en el archivo app.js; además, se incluyen los

archivos JavaScript que se necesitarán del lado del cliente. El archivo

html5.js permite utilizar elementos HTML5 en versiones de Internet

Explorer menores a 9.

Ya estamos en condiciones de crear la pantalla de acceso.

Generaremos un archivo dentro del directorio views/ con el nombre

index.ejs y el siguiente contenido:

<!-- inluimos el archivo header.ejs -->

<% include header %>

<header>

<h1><%= titulo %></h1>

<!-- formulario de login -->

<div class=”login marco blanco”>

<form id=”loginFrm” action=””>

<input type=”text” name=”usuario” value=”” placeholder=”Usuario”/>

<input type=”password” name=”clave” value=”” placeholder=”Clave”/>

<input type=”submit” id=”loginBtn” value=”Entrar” />

</form>

</div>

Como hemos visto, Express ofrece un entorno en el cual podemos, entre otras cosas, manejar el ruteo,

y con Node es posible crear nuestro propio servidor web. Una interesante propuesta es desarrollar el

mismo sistema utilizando PHP y Apache para identifi car las ventajas y desventajas de cada uno y defi nir

cuándo utilizar una arquitectura u otra.

REPLICAR EL EJEMPLO UTILIZANDO PHP

APÉNDICE. DESARROLLO DE UNA RED SOCIAL14

www.redusers.com

</header>

<!-- logos de Redis y Node -->

<section class=”tecnologias”>

<img src=”img/bg_redis.png” alt=”Redis”/>

<img src=”img/bg_nodejs.png” alt=”Node.js”/>

</section>

<!-- formulario de registro -->

<section class=”columna_derecha”>

<article class=”registro marco blanco”>

<h2>Usuario Nuevo:</h2>

<div>

<form id=”nuevoFrm” action=””>

<input type=”text” name=”nombre” value=”” placeholder=”Nombre” />

<input type=”text” name=”apellido” value=”” placeholder=”Apellido” />

<input type=”text” name=”usuario” value=”” placeholder=”Usuario” />

<input type=”password” name=”clave” value=”” placeholder=”Clave” />

<input type=”text” name=”correo” value=”” placeholder=”Correo” />

<input type=”submit” id=”nuevoBtn” value=”Registrar” />

</form>

</div>

</article>

<!-- elemento para mostrar los usuarios registrados -->

<aside class=”usuarios_registrados marco blanco”>

<p>Ya se registraron <span id=”totalUsuarios”>0</span> usuarios</p>

</aside>

</section>

<!-- inluimos el archivo footer.ejs -->

<% include footer %>

En este código, primero incluimos la plantilla header y defi nimos el

título mediante la variable global. Luego, creamos un archivo llamado

home.ejs y lo guardamos en el mismo directorio que index.ejs, por el

momento sin contenido. También generamos el formulario de login

y, más abajo, agregamos los logos de Redis y Node para lograr una

apariencia más atractiva. Creamos el formulario de registro de usuarios

y, debajo, un elemento que permite mostrar la cantidad de usuarios

registrados en el sistema. Por último, incluimos la plantilla footer.

SISTEMAS WEB ESCALABLES 15

www.redusers.com

Una vez defi nida la plantilla de inicio, necesitamos crear la función

que se encargará de renderizar el contenido cuando se accede al

sistema. Para esto, abrimos el archivo index.js dentro del directorio

routes/ y reemplazamos todo el contenido por lo siguiente:

exports.index = function(req, res){

// inicializamos la variable se sesion de usuario

req.session.usr = req.session.usr || ‘’;

// defi nimos la plantilla a renderizar

plantilla = req.session.usr === ‘’ ? ‘index’ : ‘home’;

// renderizamos la palntilla

res.render(plantilla,

{ titulo: titulo,

autor: autor,

usuario: req.session.usr

}

);

};

En este código exportamos la función index para que sea accesible

desde cualquier archivo y, dentro de ella, verifi camos si la variable req.

session.ur tiene algún contenido: como aún no la hemos defi nido, está

vacía. Por lo tanto, renderizaremos la plantilla index.ejs pasándole las

variables globales titulo y autor; la variable usuario, en esta instancia,

está vacía. Si ejecutamos la aplicación y accedemos a la dirección

http://127.0.0.1:3000 deberemos ver lo que muestra la Figura 2.

Siempre es buena idea formar parte de los grupos de usuarios de las tecnologías que nos interesan.

Express posee una lista que actualmente cuenta con alrededor de dos mil usuarios, discutiendo cerca de

cinco mil temas. El grupo utiliza la plataforma Google Groups y podemos unirnos a través del siguiente

enlace: https://groups.google.com/forum/#!forum/express-js.

GRUPO DE USUARIOS DE EXPRESS

APÉNDICE. DESARROLLO DE UNA RED SOCIAL16

www.redusers.com

Figura 2. Al ingresar al sistema, los usuarios podránregistrarse o iniciar sesión para poder interactuar con amigos.

A continuación defi niremos el archivo que manejará las acciones

del usuario, creando un archivo llamado script.js, que guardaremos

en el directorio js/ con el siguiente contenido:

$(document).ready(function(){

// registro de usuario

$(document).on(‘submit’, ‘#nuevoFrm’, function(e){

e.preventDefault();

setUsuario($(this));

});

});

// funcion para mostrar el mensaje de error en los formularios

function mostrarMensajeFormulario(form, mensaje){

if (form.fi nd(‘p’).size())

form.fi nd(‘p’).show().html(mensaje);

else

SISTEMAS WEB ESCALABLES 17

www.redusers.com

form.prepend(‘<p class=”error”>’ + mensaje + ‘</p>’);

form.fi nd(‘p’).fadeOut(7000);

}

// metodo POST de registro

function setUsuario(form) {

$.post(“/registro”, form.serialize(),

function(respuesta){

if (respuesta.codigo === 201)

window.location = respuesta.mensaje;

else

mostrarMensajeFormulario(form, respuesta.mensaje);

}

);

}

En el código defi nimos la acción para el formulario de registro,

donde ejecutamos la función setUsuario()

para enviar los datos al servidor. Si la

respuesta del servidor es 201, redirigimos al

usuario a la pantalla principal del sistema;

en el caso contrario, ejecutamos la función

mostrarMensajeFormulario(), que creará un elemento

<p> para mostrar el mensaje recibido.

En la función setUsuario() hacemos un POST

por Ajax a la ruta /registro (en el archivo app.js

tenemos comentado el ruteo para esta solicitud).

Ahora simplemente tenemos que descomentarla,

pasando de esto:

//app.post(‘/registro’, user.registro);

a esto:

app.post(‘/registro’, user.registro);

UNA RESPUESTA 201

REDIRIGE AL USUARIO

A LA PANTALLA

PRINCIPAL

DEL SISTEMA

APÉNDICE. DESARROLLO DE UNA RED SOCIAL18

www.redusers.com

Debemos, ahora, defi nir el método registro, para lo cual abrimos

el archivo user.js (ubicado en el directorio routes/), borramos todo el

contenido y escribimos lo siguiente:

// registramos el usuario

exports.registro = function(req, res) {

var nombre = req.param(‘nombre’),

apellido = req.param(‘apellido’),

usuario = req.param(‘usuario’),

clave = req.param(‘clave’),

correo = req.param(‘correo’);

if (nombre.length < 1 || apellido.length < 1 || usuario.length < 1 || clave.length <

1 || correo.length < 1 ){

res.send({codigo: 204, mensaje: ‘Complete todos los campos’ });

return;

}else{

// guardar en DB

db.get(‘usuario:’ + usuario + ‘:uid’, function (err, reply) {

if (reply === null){

// obtenemos el proximo uid

db.incr(‘global:ultimoUid’, function (err, uid) {

// seteamos el uid al usuario

db.set(‘usuario:’ + usuario + ‘:uid’, uid);

// encripto las claves

var hashClave = crypto.createHash(‘sha256’).update(clave).digest(‘hex’);

// seteamos los campos al usuario

db.hmset(‘uid:’ + uid, {‘nombre’ : nombre,

‘apellido’ : apellido,

‘usuario’ : usuario,

‘clave’ : hashClave,

‘correo’ : correo

}

);

// incremento la cantidad de usuarios (ver si dejar o no)

db.sadd(‘usuarios’,uid);

SISTEMAS WEB ESCALABLES 19

www.redusers.com

// seteamos las variables de session

req.session.usr = {‘uid’ : uid,

‘nombre’ : nombre,

‘apellido’ : apellido,

‘usuario’ : usuario

};

// emitimos el total de usuarios

getTotalUsuarios();

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

});

}else

res.send({codigo: 204, mensaje: ‘Error: El usuario ya existe’ });

});

}

};

En este código obtenemos los parámetros

enviados del formulario y verifi camos que ya

no esté registrado el usuario; incrementamos la

clave global:ultimoUid y creamos un usuario con

este valor. Encriptamos la clave y guardamos los

atributos en la base de datos, agregamos el uid a

usuarios y creamos la variable de sesión con uid,

nombre, apellido y usuario. Hacemos una llamada

a la función getTotalUsuarios(), y retornamos el

código 201 con la ruta para la redirección.

A continuación, debemos defi nir la función getTotalUsuarios() que

hemos utilizado anteriormente:

// emitimos el total de usuarios

var getTotalUsuarios = function () {

db.scard(‘usuarios’, function (err, cant) {

io.sockets.emit(“setTotalUsuarios”, cant);

});

}

LUEGO DE ENCRIPTAR

LA CLAVE DEBEMOS

GUARDAR LOS

ATRIBUTOS DE LA

BASE DE DATOS

APÉNDICE. DESARROLLO DE UNA RED SOCIAL20

www.redusers.com

Utilizaremos la función getTotalUsuarios() para obtener la cantidad de

usuarios registrados y emitir el resultado a través del evento llamado

setTotalUsuarios, utilizando Socket.IO. Al fi nal del archivo exportamos la

función para que pueda ser invocada desde cualquier archivo del sistema:

// exportamos las funciones

exports.getTotalUsuarios = getTotalUsuarios;

En síntesis, cuando se registra un usuario, emitimos un evento para

informar a todos los miembros la cantidad de usuarios registrados. El

paso siguiente es retornar al archivo script.js para capturar el evento

setTotalUsuarios. Primero nos conectamos al socket, incluyendo la

siguiente línea al principio del archivo:

// nos conectamos al socket

var sockets = io.connect(‘http://127.0.0.1:3000’);

Luego, dentro de la función $(document).ready(), capturamos el evento:

// io - cuando se registra un usuario muestro el total

sockets.on(‘setTotalUsuarios’, mostrarTotalUsuarios);

En este código hemos indicado que, cuando se reciba el evento, se

ejecutará la función mostrarTotalUsuarios(), que defi nimos de la siguiente

manera:

// cuando recibe el total de usuarios lo muestra en el contenedor

function mostrarTotalUsuarios(data){

$(‘#totalUsuarios’).html(data);

}

En este código obtenemos la cantidad de usuarios y los mostramos

en el elemento. Podemos probar cómo se ve el proyecto abriendo un

navegador y registrando un usuario. Si todo salió bien, el usuario

SISTEMAS WEB ESCALABLES 21

www.redusers.com

nuevo será redirigido, por ahora, a una página en blanco o, en caso

contrario, se mostrará un error.

Figura 3. Para que un usuario pueda registrarse debe completartodos los campos; de otra manera, se mostrará un mensaje de error.

A continuación vamos a crear el login de usuario. En el archivo script.

js, dentro de la función $(document).ready(), agregamos lo siguiente:

// login

$(document).on(‘submit’, ‘#loginFrm’, function(e){

e.preventDefault();

login($(this));

});

Redis provee una vía para testear, agregar y mejorar características de la base de datos: solo es necesa-

rio clonar el repositorio ofi cial de Github. Otra alternativa para contribuir con Redis es reportar bugs o re-

solver los existentes. Ambas opciones son defi nidas en detalle en el enlace http://redis.io/community.

CONTRIBUIR CON REDIS

APÉNDICE. DESARROLLO DE UNA RED SOCIAL22

www.redusers.com

En el mismo archivo agregamos la función login():

// metodo POST de login

function login(form) {

$.post(“/login”, form.serialize(),

function(respuesta){

if (respuesta.codigo === 201)

window.location = respuesta.mensaje;

else

mostrarMensajeFormulario(form, respuesta.mensaje);

}

);

}

En el código anterior hicimos un POST por Ajax a la ruta /login,

en el cual enviamos los datos del formulario al igual que el registro.

En el caso de que la respuesta del servidor sea 201, redireccionamos

al usuario a la ruta indicada; en el caso contrario, mostramos el

mensaje mediante la función mostrarMensajeFormulario(). Ahora

descomentamos el ruteo para /login en el archivo principal (es decir,

en app.js), pasando de esto:

//app.post(‘/login’, user.login);

a esto:

app.post(‘/login’, user.login);

Necesitamos crear la función login(). Escribiremos lo siguiente en el

archivo user.js:

// verifi camos el usuario y creamos la variable de sesion

exports.login = function(req, res){

var usuario = req.param(‘usuario’),

clave = req.param(‘clave’);

SISTEMAS WEB ESCALABLES 23

www.redusers.com

if (usuario.length < 1 || clave.length < 1){

res.send({codigo: 204, mensaje: ‘Complete todos los campos’ });

return;

}else{

db.get(‘usuario:’ + usuario + ‘:uid’, function (err, uid) {

if (uid === null)

res.send({codigo: 204, mensaje: ‘Error: El Usuario no existe’ });

else{

// obtenemos todos los atributos del usuario

db.hgetall(‘uid:’ + uid, function (err, usuario) {

// encriptamos la clave

var hashClave = crypto.createHash(‘sha256’).update(clave).digest(‘hex’);

if (hashClave == usuario.clave) {

// creamos la variable se sesion

req.session.usr = {‘uid’ : uid,

‘nombre’ : usuario.nombre,

‘apellido’ : usuario.apellido,

‘usuario’ : usuario.usuario

};

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

}else

res.send({codigo: 204, mensaje: ‘Error: Usuario o clave incorrectos’ });

});

}

});

}

}

En este código, primero verifi camos que los parámetros sean

diferentes a vacío y luego comprobamos que el usuario exista: en caso

de existir obtenemos los atributos del usuario y comparamos la clave.

Si la clave almacenada en la base de datos coincide con la ingresada,

creamos la variable de sesión con los datos del usuario, devolvemos el

código 201 y la ruta para redireccionar. En el caso contrario, retornamos

el código 204 y generamos un mensaje de error.

APÉNDICE. DESARROLLO DE UNA RED SOCIAL24

www.redusers.com

Figura 4. Cuando un usuario intente accedersin completar los campos, se mostrará una advertencia.

Es importante considerar que cada vez que se intente acceder con

un nombre de usuario que no se encuentre registrado en el sistema, se

procederá a mostrar un mensaje de carácter informativo a los usuarios

que están intentando conectarse.

Figura 5. En el caso de que un usuario no existaen la base de datos, simplemente mostramos otro mensaje.

SISTEMAS WEB ESCALABLES 25

www.redusers.com

Creación de la página principalUna vez que el usuario se ha registrado o ha iniciado sesión, es

redireccionado a la página principal con la variable de sesión creada.

Por lo tanto, renderizamos la plantilla home.ejs. A continuación

abriremos este archivo y agregaremos el siguiente código:

<!-- inluimos el archivo header.ejs -->

<% include header %>

<header>

<h1><%= titulo %></h1>

<!-- formulario de busqeda -->

<div id=”buscador”>

<input type=”text” id=”tBuscar” placeholder=”Buscar...” class=”marco” />

<input type=”submit” id=”bBuscar” value=”Buscar” />

</div>

<!-- elemento para mostrar las notifi caciones -->

<div id=”notifi cacion”>

<span class=”usuario” ><%= usuario.nombre %> <%= usuario.apellido

%></span>

<span class=”contador”>

<span id=”valorContador”>0</span>

</span>

<!-- elemento para mostrar la lista de solicitudes -->

<ul id=”solicitudes” class=”marco blanco”></ul>

</div>

<a href=”/salir” class=”bSalir”>Salir</a>

</header>

<!-- columna para la lista de amigos -->

<aside class=”usuarios marco blanco”>

<span>Podes chatear con:</span>

<div id=”usuariosAmigos” class=”usuariosLista”>

<p>No hay amigos conectados...</p>

APÉNDICE. DESARROLLO DE UNA RED SOCIAL26

www.redusers.com

</div>

</aside>

<!-- formulario para crear y mostrar los post -->

<section class=”contenido”>

<article class=”bloque”>

<form id=”postFrm” action=””>

<textarea name=”post” placeholder=”Contale al mundo...”

class=”marco”></textarea>

<input type=”submit” id=”postBtn” value=”Postear” />

</form>

</article>

<div id=”posts”></div>

</section>

<!-- columna para usuarios nuevos -->

<aside class=”usuarios marco blanco”>

<span>Podes ser amigo de:</span>

<div id=”usuariosNuevos” class=”usuariosLista”>

<p>No hay usuarios nuevos...</p>

</div>

</aside>

<!-- contenedor de las ventanas de chat -->

<aside class=”chat”></aside>

<!-- inluimos el archivo footer.ejs -->

<% include footer %>

En este código hemos incluido el archivo header.ejs y defi nimos el título

con el contenido de la variable global y los elementos para el buscador.

Luego determinamos el área para las notifi caciones y la lista de

solicitudes, y defi nimos 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. Defi nimos 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.

SISTEMAS WEB ESCALABLES 27

www.redusers.com

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

Defi nición de los eventospara 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 defi nidas 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

APÉNDICE. DESARROLLO DE UNA RED SOCIAL28

www.redusers.com

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 verifi camos si la variable

session.usr contiene información. En el caso de que un usuario se

haya logueado, almacenamos el identifi cador del socket en el array

global usrEnLinea con su uid como identifi cador, lo que nos permitirá

identifi car 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){

// verifi camos que no sea el usuario actual

if (uid != id){

// verifi camos 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,

SISTEMAS WEB ESCALABLES 29

www.redusers.com

‘nombre’ : usuario.nombre,

‘apellido’ : usuario.apellido

});

// emitimos los usuarios

if (usrNuevos.length == usuarios.length - 1)

io.sockets.socket(usrEnLinea[uid]).emit(‘setUsuariosNuevos’, usr-

Nuevos);

});

}

});

}

});

}

});

}

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

verifi camos 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 fi naliza la lista, emitimos el

evento setUsuariosNuevos al usuario actual a través del array usrEnLinea

con el índice identifi cado 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 fi nal 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 defi niremos de la siguiente manera:

APÉNDICE. DESARROLLO DE UNA RED SOCIAL30

www.redusers.com

// muestra los usuarios nuevos

function mostrarUsuariosNuevos(data){

if (data.length) {

$(‘#usuariosNuevos’).html(‘<ul>’);

data.forEach(function (usuario) {

var nombreUsuario = usuario.nombre + ‘ ‘ + usuario.apellido ;

$(‘#usuariosNuevos ul’).append(‘<li uid=”’ + usuario.uid +’”>’ + nombreUsu-

ario + ‘</li>’);

});

$(‘#usuariosNuevos’).append(‘</ul>’);

};

}

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 iniciesesión obtendrá una lista con los usuarios que no son amigos,

a quienes les podrá enviar una solicitud.

SISTEMAS WEB ESCALABLES 31

www.redusers.com

Envío de una solicitud de amistadVamos 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’));

});

Por defecto, Express usa el motor de plantillas Jade, el cual es muy bueno pero abstrae de una manera

signifi cante 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 siguien-

te comando: express -e nombreDeLaApp.

INSTALAR EXPRESS CON EJS

APÉNDICE. DESARROLLO DE UNA RED SOCIAL32

www.redusers.com

En este código hemos capturado el evento click de la lista de

usuarios y ejecutamos la función setSolicitud(), que defi nimos 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 /

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 /

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 defi nir la función setSolicitud() en el archivo user.js, del

modo que mostramos a continuación:

CON UN CÓDIGO 201

EN LA RESPUESTA

QUITAMOS AL

USUARIO DE

LA LISTA

SISTEMAS WEB ESCALABLES 33

www.redusers.com

// 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

});

APÉNDICE. DESARROLLO DE UNA RED SOCIAL34

www.redusers.com

// emitimos las solicitudes

if (solicitudes.length == solicitudesRecibidas.length)

io.sockets.socket(usrEnLinea[uid]).emit(‘getSolicitudes’, solicitudes);

});

});

}

});

}

En el código anterior verifi camos 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 fi nal

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 defi nir como sigue:

Si bien actualmente Express se encuentra en la versión 3, debido a que se han creado miles de apli-

caciones con la versión 2 fue necesario dejar la documentación accesible para consultas de desarro-

lladores que necesitan mantener sistemas escritos con esta versión. Para ver la documentación corres-

pondiente a Express en su versión 2, deberemos acceder a la página que se encuentra en la dirección

http://expressjs.com/2x.

DOCUMENTACIÓN DE EXPRESS 2X

SISTEMAS WEB ESCALABLES 35

www.redusers.com

// se muestran las solicitudes de amistad

function mostrarSolicitudes(solicitudes) {

if (solicitudes.length) {

$(‘#solicitudes’).html(‘’);

solicitudes.forEach(function (usuario) {

var contenido = ‘’;

contenido += ‘<li uid=”’ + usuario.uid +’” >’;

contenido += ‘ <span>’ + usuario.nombre + ‘ ‘ + usuario.apellido + ‘ quiere

ser tu amigo!’ + ‘</span>’;

contenido += ‘ <div>’;

contenido += ‘ <input type=”button” value=”Aceptar” uid=”’ + usuario.

uid +’” />’;

contenido += ‘ <input type=”button” value=”Cancelar” uid=”’ + usu-

ario.uid +’” />’;

contenido += ‘ </div>’;

contenido += ‘</li>’;

$(‘#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

cantidad de solicitudes que han sido recibidas.

Cuando un usuario reciba una solicitud de

amistad, verá incrementado el contador.

POR CADA SOLICITUD

DE AMISTAD RECIBIDA

CREAMOS UN ÍTEM

EN LA LISTA DEL

USUARIO CONECTADO

APÉNDICE. DESARROLLO DE UNA RED SOCIAL36

www.redusers.com

Figura 9. Por cada solicitud de amistadrecibida, 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.

Sockt.IO es una librería en lenguaje JavaScript, pero también es posible utilizar otras versiones, escri-

tas 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.

SOCKET.IO EN OTROS LENGUAJES

SISTEMAS WEB ESCALABLES 37

www.redusers.com

Figura 10. Al recibir una solicitudel usuario puede optar por aceptarla o cancelarla.

Antes defi nimos 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.

En el repositorio ofi cial 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.

PROYECTOS USANDO SOCKET.IO

APÉNDICE. DESARROLLO DE UNA RED SOCIAL38

www.redusers.com

Respuesta a una solicitud de amistadVamos a defi nir 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 defi nir 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.

SISTEMAS WEB ESCALABLES 39

www.redusers.com

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 defi nir 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, verifi camos si existe el usuario recibido y, en

caso de que se haya aceptado la solicitud, se agregan como amigos

APÉNDICE. DESARROLLO DE UNA RED SOCIAL40

www.redusers.com

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 amistadpermanecerá en la lista hasta que el usuario realice

una de las dos acciones defi nidas.

Lista de amigos conectadosVamos 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 defi nimos que, cuando un usuario se conecta, se ejecuta

la función getAmigosConectados(). Por lo tanto, vamos a defi nirla en el

archivo user.js de la siguiente manera:

SISTEMAS WEB ESCALABLES 41

www.redusers.com

// 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++;

// verifi camos 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’, usrCo-

nectados);

});

}

});

}

});

}

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 siguien-

te enlace: http://fredsarmento.me/frontend-tools.

HERRAMIENTAS PARA FRONTEND

APÉNDICE. DESARROLLO DE UNA RED SOCIAL42

www.redusers.com

En el código anterior hemos obtenido los amigos del usuario actual

y, para cada uno, verifi camos 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 fi nal del archivo llamado user.js:

exports.getAmigosConectados = getAmigosConectados;

Vamos a capturar este evento del lado del cliente, defi niendo 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 defi nido 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(‘<ul></ul>’);

data.forEach(function (usuario) {

var nombreUsuario = usuario.nombre + ‘ ‘ + usuario.apellido;

$(‘#usuariosAmigos ul’).prepend(‘<li uid=”’ + usuario.uid +’”>’ + nombreUs-

uario + ‘</li>’);

});

};

}

Con este código obtenemos la lista de amigos conectados, agregando

a cada uno de ellos en el contenedor #usuariosAmigos.

SISTEMAS WEB ESCALABLES 43

www.redusers.com

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

Informar cuandose 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);

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áfi co mundial por tres, donde el servicio será accesible desde notebooks, netbooks, smartphones,

tablets y televisores inteligentes.

INTERNET

APÉNDICE. DESARROLLO DE UNA RED SOCIAL44

www.redusers.com

En el código anterior defi nimos 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 defi nició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){

// verifi camos 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, defi nimos que se ejecutará la función

mostrarAmigoConectado() cuando se reciba el evento setAmigoConectado.

Por lo tanto, la defi nimos en el mismo archivo:

SISTEMAS WEB ESCALABLES 45

www.redusers.com

// 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(‘<ul></ul>’);

}

var nombreUsuario = data.nombre + ‘ ‘ + data.apellido;

$(‘#usuariosAmigos ul’).prepend(‘<li uid=”’ + data.uid +’”>’ + nombreUsuario

+ ‘</li>’);

}

}

En este código nos encargamos de verifi car que el usuario recibido

no exista en la lista de amigos y lo agregamos. También podemos

corroborar si existe el elemento <p>, para eliminarlo en caso de que

sea el primer amigo que se muestre en la lista.

Informar cuandose 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

defi nido 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);

APÉNDICE. DESARROLLO DE UNA RED SOCIAL46

www.redusers.com

En este código pudimos defi nimos 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 defi nir 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){

// verifi camos que el usuario se encuentre conectado

if (usrEnLinea[id])

io.sockets.socket(usrEnLinea[id]).emit(‘setAmigoDesconectado’, uid);

});

});

}

SISTEMAS WEB ESCALABLES 47

www.redusers.com

Aquí hemos obtenido todos los amigos del usuario y, para cada uno

de ellos, hemos verifi cado 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 defi niremos 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(‘<p>No hay amigos conectados...</p>’);

}

// eliminamos la ventana de chat si existe

if ($(‘#ventana-’ + data).size())

$(‘#ventana-’ + data).remove();

}

APÉNDICE. DESARROLLO DE UNA RED SOCIAL48

www.redusers.com

Eliminamos el ítem de la lista de amigos y verifi camos si existen

otros conectados; en el caso contrario mostramos un elemento <p>

con el mensaje de que no hay amigos conectados y, luego, verifi camos

si existe una ventana de chat abierta y la borramos. A continuación

trabajaremos en el chat con amigos.

Creación del sistema de chatPara 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í, defi nimos 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 defi nir 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 = ‘<div class=”ventana marco celeste” id=”ventana-’ + uid + ‘”

uid=”’ + uid + ‘”>’;

ventana += ‘ <span>’+ nombre + ‘</span>’;

ventana += ‘ <textarea cols=”20” rows=”7” readonly=”true”></tex-

tarea>’;

SISTEMAS WEB ESCALABLES 49

www.redusers.com

ventana += ‘ <input type=”text” class=”chat-text” maxlength=”20” />’;

ventana += ‘</div>’;

$(‘.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 <input> para escribir y un

elemento <textarea> para mostrar los mensajes;

agregamos toda la ventana al elemento chat.

Vamos a defi nir el evento que va a capturar el

mensaje para enviarlo al receptor. Para hacerlo,

agregamos el siguiente código en el método

$(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 defi nir la función enviarMensaje() en el mismo archivo:

Además de las herramientas que vienen integradas por defecto en Express, integrantes de la comunidad

ofi cial 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.

EXTENSIONES PARA EXPRESS

LUEGO DE OBTENER

EL UID DEL USUARIO

SE CREA UNA

VENTANA APTA PARA

UNA SESIÓN DE CHAT

APÉNDICE. DESARROLLO DE UNA RED SOCIAL50

www.redusers.com

// 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 <textarea> 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:

SISTEMAS WEB ESCALABLES 51

www.redusers.com

// 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 defi nido que, cuando se recibe el evento

mensajeRecibido, se ejecuta la función mostrarMensajeRecibido(), por lo

que debemos defi nirla 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);

APÉNDICE. DESARROLLO DE UNA RED SOCIAL52

www.redusers.com

// agregamos la informacion del mensaje

$(‘#ventana-’ + data.de + ‘ span’).html(data.nombre);

$(‘#ventana-’ + data.de + ‘ textarea’).val($(‘#ventana-’ + data.de + ‘ textar-

ea’).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.

StatCounter es un sitio que publica estadísticas globales acerca de diferentes tecnologías. Entre las opcio-

nes que ofrece se encuentra la posibilidad de ver qué tecnología comparar, permite seleccionar el tipo de

gráfi co (líneas, barras o mapa) y además permite descargar el gráfi co en formato JPG o CSV. Podemos

conocerlo mejor a través del siguiente enlace: http://gs.statcounter.com.

ESTADÍSTICAS MEDIANTE STATCOUNTER

SISTEMAS WEB ESCALABLES 53

www.redusers.com

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 identifi ca 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 postsVamos 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í, defi nimos que, cuando se envía el formulario, se ejecuta la

función setPost() enviando como parámetro el mismo objeto.

A continuación defi nimos la función setPost() en el mismo archivo:

APÉNDICE. DESARROLLO DE UNA RED SOCIAL54

www.redusers.com

// metodo POST cuando se escrine un post nuevo

function setPost(form) {

$.post(“/setPost”, form.serialize(),

function(respuesta){

if (respuesta.codigo === 201){

var bloque = ‘<article class=”bloque marco blanco”>’;

bloque += ‘ <div class=”header”>’;

bloque += ‘ <img src=”/img/icon-profi le.png” />’;

bloque += ‘ <span class=”bloque_usuario”>yo </span>’;

bloque += ‘ <span class=”bloque_fecha”> hace unos segundos...</

span>’;

bloque += ‘ </div>’;

bloque += ‘ <div class=”value”>’ + $(‘#postFrm textarea’).val() + ‘</

div>’;

bloque += ‘</article>’;

$(‘#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);

SISTEMAS WEB ESCALABLES 55

www.redusers.com

En este código nos encargamos de defi nir que el ruteo deberá

ejecutar la función setPost(). Por esta razón, tendremos que realizar

esta defi nició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);

});

});

APÉNDICE. DESARROLLO DE UNA RED SOCIAL56

www.redusers.com

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

});

}

};

En el código anterior, recibimos el post y verifi camos 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 defi nir la función formatearFecha():

// funcion para formatear la fecha

function formatearFecha(fecha) {

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

dia = 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:

SISTEMAS WEB ESCALABLES 57

www.redusers.com

// obtenemos los posts

user.getPosts(session.usr.uid);

De este modo defi nimos que, al conectarse un usuario, se ejecutará

la función getPosts(), a la cual le pasaremos como parámetro el uid

actual. Debemos defi nir 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 fi nal de la lista de post se emite al usuario

if (i == posts.length)

io.sockets.socket(usrEnLinea[uid]).emit(‘setPosts’, arrayPosts);

});

});

});

}

});

}

APÉNDICE. DESARROLLO DE UNA RED SOCIAL58

www.redusers.com

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 defi niremos 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 = ‘<article class=”bloque marco blanco”>’;

bloque += ‘ <div class=”header”>’;

bloque += ‘ <img src=”/img/icon-profi le.png” />’;

bloque += ‘ <span class=”bloque_usuario”>’ + nombreUsuario + ‘</

span>’;

bloque += ‘ <span class=”bloque_fecha”>’+ post.fecha + ‘</span>’;

bloque += ‘ </div>’;

bloque += ‘ <div class=”value”>’ + post.mensaje + ‘</div>’;

bloque += ‘</article>’;

$(‘#posts’).append(bloque);

});

};

}

SISTEMAS WEB ESCALABLES 59

www.redusers.com

En el código nos hemos encargado de obtener

los posts correspondientes y, por cada uno de

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

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 podemosobservar la apariencia de los posts y que el usuario

tiene acceso a los posts de sus amigos.

Vista de la base de datosInspeccionando 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.

POR CADA POST SE

CREA UN BLOQUE

CON EL USUARIO QUE

LO ESCRIBIÓ Y OTROS

DATOS IMPORTANTES

APÉNDICE. DESARROLLO DE UNA RED SOCIAL60

www.redusers.com

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 identifi car para los post escritos

encontramos el autor, la fecha en que se creó y también el mensaje

que corresponde, así como el identifi cador del usuario.

Figura 17. En la base de datos guardamos la fecha,el mensaje y el identifi cador del usuario que lo escribió.

SISTEMAS WEB ESCALABLES 61

www.redusers.com

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.

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 refl ejadas 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.

RESUMEN

APÉNDICE. DESARROLLO DE UNA RED SOCIAL62

www.redusers.com

Actividades

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

PROFESOR EN LÍNEA

TEST DE AUTOEVALUACIÓN

1 ¿Express ofrece la posibilidad de defi nir un sistema autosufi ciente?

2 ¿Es posible defi nir 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? Justifi car.

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 notifi car 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 perfi l actual y Acceder al perfi l de los amigos.

4 Implemente un sistema de cierre de sesión y notifi cación cuando el usuario cierra el navegador.