Nota: Este texto forma parte de la Guía de Express.js:el libro completo sobre Express.js.
El proyecto HackHall se escribió utilizando Backbone.js y Underscore para la aplicación de front-end, y Express.js, MongoDB a través de Mongoose para el servidor API REST de back-end.
Ejemplo :El código fuente de HackHall está en el repositorio público de GitHub.
Se puede acceder a la demostración en vivo en hackhall.com, ya sea con AngelList o correo electrónico precargado ([email protected]) y contraseña (1).
Qué es HackHall
HackHall (ex-Accelerator.IO) es una red social de código abierto y una herramienta de colaboración solo para invitados para piratas informáticos, hipsters, empresarios y piratas (es broma). HackHall es similar a Reddit, más Hacker News, más grupos de Facebook con selección.
El proyecto HackHall se encuentra en su etapa inicial y es aproximadamente una versión beta. Planeamos ampliar la base de código en el futuro y atraer a más personas para compartir habilidades, sabiduría y pasión por la programación.
En este capítulo, cubriremos la versión 1.0 que tiene:
- OAuth 1.0 con módulos OAuth y API AngelList
- Autenticación de correo electrónico y contraseña
- Modelos y esquemas de Mongoose
- Estructura Express.js con rutas en módulos
- API REST de JSON
- Manejo de errores de Express.js
- Aplicación Front-end Client Backbone.js (para obtener más información sobre Backbone.js, descargue o lea en línea nuestros tutoriales de creación rápida de prototipos con JS)
- Variables ambientales con
.env
de Foreman - TDD con moca
- Configuración básica de Makefile
Ejecutar HackHall
Para obtener el código fuente, puede navegar hasta hackhall
carpeta o clon de GitHub:
$ git clone [email protected]:azat-co/hackhall
$ git checkout 1.0
$ npm install
Si planea probar una AngelList (opcional), HackHall está usando la configuración de Heroku y Foreman para las claves API de AngelList almacenándolas en variables ambientales, por lo que debemos agregar .evn
archivo como este (abajo hay valores falsos):
ANGELLIST_CLIENT_ID=254C0335-5F9A-4607-87C0
ANGELLIST_CLIENT_SECRET=99F5C1AC-C5F7-44E6-81A1-8DF4FC42B8D9
Las claves se pueden obtener en angel.co/api después de que alguien cree y registre su aplicación AngelList.
Descargue e instale MongoDB si aún no lo tiene. Las bases de datos y las bibliotecas de terceros están fuera del alcance de este libro. Sin embargo, puede encontrar suficientes materiales en línea y en Prototipos rápidos con JS.
Para iniciar el servidor MongoDB, abra una nueva ventana de terminal y ejecute:
$ mongod
Vuelva a la carpeta del proyecto y ejecute:
$ foreman start
Después de que MongoDB se ejecuta en localhost con el puerto predeterminado 27017, es posible inicializar la base de datos hackhall
con el usuario administrador predeterminado ejecutando seed.js
secuencia de comandos mongo:
[Nota al margen]
Leer publicaciones de blog es bueno, pero ver cursos en video es aún mejor porque son más atractivos.
Muchos desarrolladores se quejaron de la falta de material de video de calidad asequible en Node. Es una distracción ver videos de YouTube y una locura pagar $ 500 por un curso de video de Node.
Visite Node University, que tiene cursos de video GRATUITOS en Node:node.university.
[Fin de la nota al margen]
$ mongo localhost:27017/hackhall seed.js
Siéntase libre de modificar seed.js
a tu gusto (¡ojo que borra todos los datos anteriores!):
db.dropDatabase();
var seedUser ={
firstName:'Azat',
lastName:"Mardanov",
displayName:"Azat Mardanov",
password:'1',
email:'[email protected]',
role:'admin',
approved: true,
admin: true
};
db.users.save(seedUser);
Si abre su navegador en http://localhost:5000, debería ver la pantalla de inicio de sesión.
Introduce usuario y contraseña para entrar (los del seed.js
archivo).
Después de una autenticación exitosa, los usuarios son redirigidos a la página de Publicaciones:
donde pueden crear una publicación (por ejemplo, una pregunta):
Guarda la publicación:
Me gusta publicaciones:
Visita los perfiles de otros usuarios:
Si tienen derechos de administrador, los usuarios pueden aprobar candidatos:
y administrar su cuenta en la página de perfil:
Estructura
Esto es lo que tiene cada una de las carpetas y archivos:
/api
:rutas compartidas por la aplicación/models
:Modelos de mangosta/public
:aplicación Backbone, archivos estáticos como JavaScript front-end, CSS, HTML/routes
:rutas API REST/tests
:Pruebas de moca.gitignore
:lista de archivos que git debería ignorarMakefile
:crea un archivo para ejecutar pruebasProcfile
:Archivo de pila de cedro necesario para la implementación de Herokupackage.json
:dependencias de NPM y metadatos de HackHallreadme.md
:descripciónserver.js
:archivo principal del servidor HackHall
Aplicación Express.js
Vayamos directamente al server.js
y aprenda cómo se implementa. En primer lugar, declaramos dependencias:
var express = require('express'),
routes = require('./routes'),
http = require('http'),
util = require('util'),
oauth = require('oauth'),
querystring = require('querystring');
Luego, inicializamos la aplicación y configuramos los middlewares. El process.env.PORT
se completa con Heroku y, en caso de una configuración local, recurra a 3000.
var app = express();
app.configure(function(){
app.set('port', process.env.PORT || 3000 );
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
Los valores pasados a cookieParser y los middlewares de sesión son necesarios para la autenticación. Obviamente, se supone que los secretos de sesión son privados:
app.use(express.cookieParser('asd;lfkajs;ldfkj'));
app.use(express.session({
secret: '<h1>WHEEYEEE</h1>',
key: 'sid',
cookie: {
secret: true,
expires: false
}
}));
Así es como servimos nuestra aplicación Backbone.js de cliente front-end y otros archivos estáticos como CSS:
app.use(express.static(__dirname + '/public'));
app.use(app.router);
});
El manejo de errores se divide en tres funciones con clientErrorHandler
dedicado a las solicitudes AJAX/XHR de la aplicación Backbone.js (responde con JSON):
app.configure(function() {
app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);
});
function logErrors(err, req, res, next) {
console.error('logErrors', err.toString());
next(err);
}
function clientErrorHandler(err, req, res, next) {
console.error('clientErrors ', err.toString());
res.send(500, { error: err.toString()});
if (req.xhr) {
console.error(err);
res.send(500, { error: err.toString()});
} else {
next(err);
}
}
function errorHandler(err, req, res, next) {
console.error('lastErrors ', err.toString());
res.send(500, {error: err.toString()});
}
De la misma manera que determinamos process.env.PORT
y recurrimos al valor de configuración local 3000, hacemos algo similar con la cadena de conexión MongoDB:
var dbUrl = process.env.MONGOHQ_URL
|| 'mongodb://@127.0.0.1:27017/hackhall';
var mongoose = require('mongoose');
var connection = mongoose.createConnection(dbUrl);
connection.on('error', console.error.bind(console,
'connection error:'));
En algún momento es una buena idea iniciar sesión en el evento de apertura de conexión:
connection.once('open', function () {
console.info('connected to database')
});
Los modelos Mongoose viven en el models
carpeta:
var models = require('./models');
Este middleware proporcionará acceso a dos colecciones dentro de nuestros métodos de rutas:
function db (req, res, next) {
req.db = {
User: connection.model('User', models.User, 'users'),
Post: connection.model('Post', models.Post, 'posts')
};
return next();
}
Solo un nuevo nombre para las funciones de autenticación importadas:
checkUser = routes.main.checkUser;
checkAdmin = routes.main.checkAdmin;
checkApplicant = routes.main.checkApplicant;
Rutas AngelList OAuth:
app.get('/auth/angellist', routes.auth.angelList);
app.get('/auth/angellist/callback',
routes.auth.angelListCallback,
routes.auth.angelListLogin,
db,
routes.users.findOrAddUser);
Principales rutas de aplicación, incluido api/profile
devolver sesión de usuario si el usuario ha iniciado sesión:
//MAIN
app.get('/api/profile', checkUser, db, routes.main.profile);
app.del('/api/profile', checkUser, db, routes.main.delProfile);
app.post('/api/login', db, routes.main.login);
app.post('/api/logout', routes.main.logout);
Las solicitudes POST para crear usuarios y publicaciones:
//POSTS
app.get('/api/posts', checkUser, db, routes.posts.getPosts);
app.post('/api/posts', checkUser, db, routes.posts.add);
app.get('/api/posts/:id', checkUser, db, routes.posts.getPost);
app.put('/api/posts/:id', checkUser, db, routes.posts.updatePost);
app.del('/api/posts/:id', checkUser, db, routes.posts.del);
//USERS
app.get('/api/users', checkUser, db, routes.users.getUsers);
app.get('/api/users/:id', checkUser, db,routes.users.getUser);
app.post('/api/users', checkAdmin, db, routes.users.add);
app.put('/api/users/:id', checkAdmin, db, routes.users.update);
app.del('/api/users/:id', checkAdmin, db, routes.users.del);
Estas rutas son para nuevos miembros que aún no han sido aprobados:
//APPLICATION
app.post('/api/application',
checkAdmin,
db,
routes.application.add);
app.put('/api/application',
checkApplicant,
db,
routes.application.update);
app.get('/api/application',
checkApplicant,
db,
routes.application.get);
La ruta de captura de todo lo demás:
app.get('*', function(req, res){
res.send(404);
});
El require.main === module
es un truco inteligente para determinar si este archivo se está ejecutando de forma independiente o como un módulo importado:
http.createServer(app);
if (require.main === module) {
app.listen(app.get('port'), function(){
console.info('Express server listening on port '
+ app.get('port'));
});
}
else {
console.info('Running app as a module')
exports.app = app;
}
El código fuente completo para hackhall/server.js
:
var express = require('express'),
routes = require('./routes'),
http = require('http'),
util = require('util'),
oauth = require('oauth'),
querystring = require('querystring');
var app = express();
app.configure(function(){
app.set('port', process.env.PORT || 3000 );
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(express.cookieParser('asd;lfkajs;ldfkj'));
app.use(express.session({
secret: '<h1>WHEEYEEE</h1>',
key: 'sid',
cookie: {
secret: true,
expires: false
}
}));
// app.use(express.csrf());
// app.use(function(req, res, next) {
// res.locals.csrf = req.session._cstf;
// return next();
// });
app.use(express.static(__dirname + '/public'));
app.use(app.router);
});
app.configure(function() {
app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);
});
function logErrors(err, req, res, next) {
console.error('logErrors', err.toString());
next(err);
}
function clientErrorHandler(err, req, res, next) {
console.error('clientErrors ', err.toString());
res.send(500, { error: err.toString()});
if (req.xhr) {
console.error(err);
res.send(500, { error: err.toString()});
} else {
next(err);
}
}
function errorHandler(err, req, res, next) {
console.error('lastErrors ', err.toString());
res.send(500, {error: err.toString()});
}
var dbUrl = process.env.MONGOHQ_URL || 'mongodb://@127.0.0.1:27017/hackhall';
var mongoose = require('mongoose');
var connection = mongoose.createConnection(dbUrl);
connection.on('error', console.error.bind(console, 'connection error:'));
connection.once('open', function () {
console.info('connected to database')
});
var models = require('./models');
function db (req, res, next) {
req.db = {
User: connection.model('User', models.User, 'users'),
Post: connection.model('Post', models.Post, 'posts')
};
return next();
}
checkUser = routes.main.checkUser;
checkAdmin = routes.main.checkAdmin;
checkApplicant = routes.main.checkApplicant;
app.get('/auth/angellist', routes.auth.angelList);
app.get('/auth/angellist/callback',
routes.auth.angelListCallback,
routes.auth.angelListLogin,
db,
routes.users.findOrAddUser);
//MAIN
app.get('/api/profile', checkUser, db, routes.main.profile);
app.del('/api/profile', checkUser, db, routes.main.delProfile);
app.post('/api/login', db, routes.main.login);
app.post('/api/logout', routes.main.logout);
//POSTS
app.get('/api/posts', checkUser, db, routes.posts.getPosts);
app.post('/api/posts', checkUser, db, routes.posts.add);
app.get('/api/posts/:id', checkUser, db, routes.posts.getPost);
app.put('/api/posts/:id', checkUser, db, routes.posts.updatePost);
app.del('/api/posts/:id', checkUser, db, routes.posts.del);
//USERS
app.get('/api/users', checkUser, db, routes.users.getUsers);
app.get('/api/users/:id', checkUser, db,routes.users.getUser);
app.post('/api/users', checkAdmin, db, routes.users.add);
app.put('/api/users/:id', checkAdmin, db, routes.users.update);
app.del('/api/users/:id', checkAdmin, db, routes.users.del);
//APPLICATION
app.post('/api/application', checkAdmin, db, routes.application.add);
app.put('/api/application', checkApplicant, db, routes.application.update);
app.get('/api/application', checkApplicant, db, routes.application.get);
app.get('*', function(req, res){
res.send(404);
});
http.createServer(app);
if (require.main === module) {
app.listen(app.get('port'), function(){
console.info('Express server listening on port ' + app.get('port'));
});
}
else {
console.info('Running app as a module')
exports.app = app;
}
Rutas
Las rutas HackHall residen en hackhall/routes
carpeta y se agrupan en varios módulos:
hackhall/routes/index.js
:puente entreserver.js
y otras rutas en la carpetahackhall/routes/auth.js
:rutas que manejan OAuth dance con AngelList APIhackhall/routes/main.js
:inicio de sesión, cierre de sesión y otras rutashackhall/routes/users.js
:rutas relacionadas con la API REST de los usuarioshackhall/routes/application.js
:presentación de la solicitud para convertirse en usuariohackhall/routes/posts.js
:rutas relacionadas con publicaciones REST API
index.js
Lleguemos a hackhall/routes/index.js
donde incluimos otros módulos:
exports.posts = require('./posts');
exports.main = require('./main');
exports.users = require('./users');
exports.application = require('./application');
exports.auth = require('./auth');
autorización.js
En este módulo, manejaremos OAuth dance con la API AngelList. Para hacerlo, tendremos que confiar en https
biblioteca:
var https = require('https');
El ID de cliente de AngelList API y el secreto del cliente se obtienen en el sitio web angel.co/api y se almacenan en las variables de entorno:
var angelListClientId = process.env.ANGELLIST_CLIENT_ID;
var angelListClientSecret = process.env.ANGELLIST_CLIENT_SECRET;
El método redirigirá a los usuarios al sitio web angel.co para la autenticación:
exports.angelList = function(req, res) {
res.redirect('https://angel.co/api/oauth/authorize?client_id=' + angelListClientId + '&scope=email&response_type=code');
}
Después de que los usuarios permiten que nuestra aplicación acceda a su información, AngelList los envía de vuelta a esta ruta para que hagamos una nueva solicitud (HTTPS) para recuperar el token:
exports.angelListCallback = function(req, res, next) {
var token;
var buf = '';
var data;
// console.log('/api/oauth/token?client_id='
//+ angelListClientId
//+ '&client_secret='
//+ angelListClientSecret
//+ '&code='
//+ req.query.code
//+ '&grant_type=authorization_code');
var angelReq = https.request({
host: 'angel.co',
path: '/api/oauth/token?client_id='
+ angelListClientId
+ '&client_secret='
+ angelListClientSecret
+ '&code='
+ req.query.code
+ '&grant_type=authorization_code',
port: 443,
method: 'POST',
headers: {
'content-length': 0
}
},
function(angelRes) {
angelRes.on('data', function(buffer) {
buf += buffer;
});
angelRes.on('end', function() {
try {
data = JSON.parse(buf.toString('utf-8'));
} catch (e) {
if (e) return res.send(e);
}
if (!data || !data.access_token) return res.send(500);
token = data.access_token;
req.session.angelListAccessToken = token;
if (token) next();
else res.send(500);
});
});
angelReq.end();
angelReq.on('error', function(e) {
console.error(e);
next(e);
});
}
Llame directamente a la API AngleList con el token del middleware anterior para obtener información del usuario:
exports.angelListLogin = function(req, res, next) {
token = req.session.angelListAccessToken;
httpsRequest = https.request({
host: 'api.angel.co',
path: '/1/me?access_token=' + token,
port: 443,
method: 'GET'
},
function(httpsResponse) {
httpsResponse.on('data', function(buffer) {
data = JSON.parse(buffer.toString('utf-8'));
if (data) {
req.angelProfile = data;
next();
}
});
}
);
httpsRequest.end();
httpsRequest.on('error', function(e) {
console.error(e);
});
};
El código fuente completo de hackhall/routes/auth.js
archivos:
var https = require('https');
var angelListClientId = process.env.ANGELLIST_CLIENT_ID;
var angelListClientSecret = process.env.ANGELLIST_CLIENT_SECRET;
exports.angelList = function(req, res) {
res.redirect('https://angel.co/api/oauth/authorize?client_id=' + angelListClientId + '&scope=email&response_type=code');
}
exports.angelListCallback = function(req, res, next) {
var token;
var buf = '';
var data;
// console.log('/api/oauth/token?client_id=' + angelListClientId + '&client_secret=' + angelListClientSecret + '&code=' + req.query.code + '&grant_type=authorization_code');
var angelReq = https.request({
host: 'angel.co',
path: '/api/oauth/token?client_id=' + angelListClientId + '&client_secret=' + angelListClientSecret + '&code=' + req.query.code + '&grant_type=authorization_code',
port: 443,
method: 'POST',
headers: {
'content-length': 0
}
},
function(angelRes) {
angelRes.on('data', function(buffer) {
buf += buffer;
});
angelRes.on('end', function() {
try {
data = JSON.parse(buf.toString('utf-8'));
} catch (e) {
if (e) return res.send(e);
}
if (!data || !data.access_token) return res.send(500);
token = data.access_token;
req.session.angelListAccessToken = token;
if (token) next();
else res.send(500);
});
});
angelReq.end();
angelReq.on('error', function(e) {
console.error(e);
next(e);
});
}
exports.angelListLogin = function(req, res, next) {
token = req.session.angelListAccessToken;
httpsRequest = https.request({
host: 'api.angel.co',
path: '/1/me?access_token=' + token,
port: 443,
method: 'GET'
},
function(httpsResponse) {
httpsResponse.on('data', function(buffer) {
data = JSON.parse(buffer.toString('utf-8'));
if (data) {
req.angelProfile = data;
next();
}
});
}
);
httpsRequest.end();
httpsRequest.on('error', function(e) {
console.error(e);
});
};
principal.js
El hackhall/routes/main.js
El archivo también puede ser interesante.
El checkAdmin()
La función realiza la autenticación para los privilegios de administrador. Si el objeto de la sesión no tiene el indicador adecuado, llamamos a Express.js next()
función con un objeto de error:
exports.checkAdmin = function(request, response, next) {
if (request.session
&& request.session.auth
&& request.session.userId
&& request.session.admin) {
console.info('Access ADMIN: ' + request.session.userId);
return next();
} else {
next('User is not an administrator.');
}
};
De manera similar, podemos verificar solo el usuario sin verificar los derechos de administrador:
exports.checkUser = function(req, res, next) {
if (req.session && req.session.auth && req.session.userId
&& (req.session.user.approved || req.session.admin)) {
console.info('Access USER: ' + req.session.userId);
return next();
} else {
next('User is not logged in.');
}
};
La aplicación es solo un objeto de usuario no aprobado y también podemos comprobarlo:
exports.checkApplicant = function(req, res, next) {
if (req.session && req.session.auth && req.session.userId
&& (!req.session.user.approved || req.session.admin)) {
console.info('Access USER: ' + req.session.userId);
return next();
} else {
next('User is not logged in.');
}
};
En la función de inicio de sesión, buscamos coincidencias de correo electrónico y contraseña en la base de datos. En caso de éxito, almacenamos el objeto de usuario en la sesión y procedemos; de lo contrario, la solicitud falla:
exports.login = function(req, res, next) {
req.db.User.findOne({
email: req.body.email,
password: req.body.password
},
null, {
safe: true
},
function(err, user) {
if (err) return next(err);
if (user) {
req.session.auth = true;
req.session.userId = user._id.toHexString();
req.session.user = user;
if (user.admin) {
req.session.admin = true;
}
console.info('Login USER: ' + req.session.userId);
res.json(200, {
msg: 'Authorized'
});
} else {
next(new Error('User is not found.'));
}
});
};
El proceso de cierre de sesión elimina cualquier información de la sesión:
exports.logout = function(req, res) {
console.info('Logout USER: ' + req.session.userId);
req.session.destroy(function(error) {
if (!error) {
res.send({
msg: 'Logged out'
});
}
});
};
Esta ruta se utiliza tanto para la página de perfil como para Backbone.js para la autenticación de usuarios:
exports.profile = function(req, res, next) {
req.db.User.findById(req.session.userId, 'firstName lastName'
+ 'displayName headline photoUrl admin'
+ 'approved banned role angelUrl twitterUrl'
+ 'facebookUrl linkedinUrl githubUrl', function(err, obj) {
if (err) next(err);
if (!obj) next(new Error('User is not found'));
req.db.Post.find({
author: {
id: obj._id,
name: obj.displayName
}
}, null, {
sort: {
'created': -1
}
}, function(err, list) {
if (err) next(err);
obj.posts.own = list || [];
req.db.Post.find({
likes: obj._id
}, null, {
sort: {
'created': -1
}
Esta lógica encuentra publicaciones y comentarios realizados por el usuario:
}, function(err, list) {
if (err) next(err);
obj.posts.likes = list || [];
req.db.Post.find({
watches: obj._id
}, null, {
sort: {
'created': -1
}
}, function(err, list) {
if (err) next(err);
obj.posts.watches = list || [];
req.db.Post.find({
'comments.author.id': obj._id
}, null, {
sort: {
'created': -1
}
}, function(err, list) {
if (err) next(err);
obj.posts.comments = [];
list.forEach(function(value, key, list) {
obj.posts.comments.push(
value.comments.filter(
function(el, i, arr) {
return (el.author.id.toString() == obj._id.toString());
}
)
);
});
res.json(200, obj);
});
});
});
});
});
};
Es importante permitir que los usuarios eliminen sus perfiles:
exports.delProfile = function(req, res, next) {
console.log('del profile');
console.log(req.session.userId);
req.db.User.findByIdAndRemove(req.session.user._id, {},
function(err, obj) {
if (err) next(err);
req.session.destroy(function(error) {
if (err) {
next(err)
}
});
res.json(200, obj);
}
);
};
El código fuente completo de hackhall/routes/main.js
archivos:
exports.checkAdmin = function(request, response, next) {
if (request.session && request.session.auth && request.session.userId && request.session.admin) {
console.info('Access ADMIN: ' + request.session.userId);
return next();
} else {
next('User is not an administrator.');
}
};
exports.checkUser = function(req, res, next) {
if (req.session && req.session.auth && req.session.userId && (req.session.user.approved || req.session.admin)) {
console.info('Access USER: ' + req.session.userId);
return next();
} else {
next('User is not logged in.');
}
};
exports.checkApplicant = function(req, res, next) {
if (req.session && req.session.auth && req.session.userId && (!req.session.user.approved || req.session.admin)) {
console.info('Access USER: ' + req.session.userId);
return next();
} else {
next('User is not logged in.');
}
};
exports.login = function(req, res, next) {
req.db.User.findOne({
email: req.body.email,
password: req.body.password
},
null, {
safe: true
},
function(err, user) {
if (err) return next(err);
if (user) {
req.session.auth = true;
req.session.userId = user._id.toHexString();
req.session.user = user;
if (user.admin) {
req.session.admin = true;
}
console.info('Login USER: ' + req.session.userId);
res.json(200, {
msg: 'Authorized'
});
} else {
next(new Error('User is not found.'));
}
});
};
exports.logout = function(req, res) {
console.info('Logout USER: ' + req.session.userId);
req.session.destroy(function(error) {
if (!error) {
res.send({
msg: 'Logged out'
});
}
});
};
exports.profile = function(req, res, next) {
req.db.User.findById(req.session.userId, 'firstName lastName displayName headline photoUrl admin approved banned role angelUrl twitterUrl facebookUrl linkedinUrl githubUrl', function(err, obj) {
if (err) next(err);
if (!obj) next(new Error('User is not found'));
req.db.Post.find({
author: {
id: obj._id,
name: obj.displayName
}
}, null, {
sort: {
'created': -1
}
}, function(err, list) {
if (err) next(err);
obj.posts.own = list || [];
req.db.Post.find({
likes: obj._id
}, null, {
sort: {
'created': -1
}
}, function(err, list) {
if (err) next(err);
obj.posts.likes = list || [];
req.db.Post.find({
watches: obj._id
}, null, {
sort: {
'created': -1
}
}, function(err, list) {
if (err) next(err);
obj.posts.watches = list || [];
req.db.Post.find({
'comments.author.id': obj._id
}, null, {
sort: {
'created': -1
}
}, function(err, list) {
if (err) next(err);
obj.posts.comments = [];
list.forEach(function(value, key, list) {
obj.posts.comments.push(value.comments.filter(function(el, i, arr) {
return (el.author.id.toString() == obj._id.toString());
}));
});
res.json(200, obj);
});
});
});
});
});
};
exports.delProfile = function(req, res, next) {
console.log('del profile');
console.log(req.session.userId);
req.db.User.findByIdAndRemove(req.session.user._id, {}, function(err, obj) {
if (err) next(err);
req.session.destroy(function(error) {
if (err) {
next(err)
}
});
res.json(200, obj);
});
};
usuarios.js
El código fuente completo para hackhall/routes/users.js
archivos:
objectId = require('mongodb').ObjectID;
exports.getUsers = function(req, res, next) {
if (req.session.auth && req.session.userId) {
req.db.User.find({}, 'firstName lastName displayName headline photoUrl admin approved banned role angelUrl twitterUrl facebookUrl linkedinUrl githubUrl', function(err, list) {
if (err) next(err);
res.json(200, list);
});
} else {
next('User is not recognized.')
}
}
exports.getUser = function(req, res, next) {
req.db.User.findById(req.params.id, 'firstName lastName displayName headline photoUrl admin approved banned role angelUrl twitterUrl facebookUrl linkedinUrl githubUrl', function(err, obj) {
if (err) next(err);
if (!obj) next(new Error('User is not found'));
req.db.Post.find({
author: {
id: obj._id,
name: obj.displayName
}
}, null, {
sort: {
'created': -1
}
}, function(err, list) {
if (err) next(err);
obj.posts.own = list || [];
req.db.Post.find({
likes: obj._id
}, null, {
sort: {
'created': -1
}
}, function(err, list) {
if (err) next(err);
obj.posts.likes = list || [];
req.db.Post.find({
watches: obj._id
}, null, {
sort: {
'created': -1
}
}, function(err, list) {
if (err) next(err);
obj.posts.watches = list || [];
req.db.Post.find({
'comments.author.id': obj._id
}, null, {
sort: {
'created': -1
}
}, function(err, list) {
if (err) next(err);
obj.posts.comments = [];
list.forEach(function(value, key, list) {
obj.posts.comments.push(value.comments.filter(function(el, i, arr) {
return (el.author.id.toString() == obj._id.toString());
}));
});
res.json(200, obj);
});
});
});
});
});
};
exports.add = function(req, res, next) {
var user = new req.db.User(req.body);
user.save(function(err) {
if (err) next(err);
res.json(user);
});
};
exports.update = function(req, res, next) {
var obj = req.body;
obj.updated = new Date();
delete obj._id;
req.db.User.findByIdAndUpdate(req.params.id, {
$set: obj
}, {
new: true
}, function(err, obj) {
if (err) next(err);
res.json(200, obj);
});
};
exports.del = function(req, res, next) {
req.db.User.findByIdAndRemove(req.params.id, function(err, obj) {
if (err) next(err);
res.json(200, obj);
});
};
exports.findOrAddUser = function(req, res, next) {
data = req.angelProfile;
req.db.User.findOne({
angelListId: data.id
}, function(err, obj) {
console.log('angelListLogin4');
if (err) next(err);
console.warn(obj);
if (!obj) {
req.db.User.create({
angelListId: data.id,
angelToken: token,
angelListProfile: data,
email: data.email,
firstName: data.name.split(' ')[0],
lastName: data.name.split(' ')[1],
displayName: data.name,
headline: data.bio,
photoUrl: data.image,
angelUrl: data.angellist_url,
twitterUrl: data.twitter_url,
facebookUrl: data.facebook_url,
linkedinUrl: data.linkedin_url,
githubUrl: data.github_url
}, function(err, obj) { //remember the scope of variables!
if (err) next(err);
console.log(obj);
req.session.auth = true;
req.session.userId = obj._id;
req.session.user = obj;
req.session.admin = false; //assing regular user role by default
res.redirect('/#application');
// }
});
} else { //user is in the database
req.session.auth = true;
req.session.userId = obj._id;
req.session.user = obj;
req.session.admin = obj.admin; //false; //assing regular user role by default
if (obj.approved) {
res.redirect('/#posts');
} else {
res.redirect('/#application');
}
}
})
}
aplicaciones.js
En la versión actual, enviar y aprobar una solicitud no generará una notificación por correo electrónico. Por lo tanto, los usuarios deben regresar al sitio web para verificar su estado.
Simplemente agregue un objeto de usuario (con aprobado=falso por defecto) a la base de datos:
exports.add = function(req, res, next) {
req.db.User.create({
firstName: req.body.firstName,
lastName: req.body.lastName,
displayName: req.body.displayName,
headline: req.body.headline,
photoUrl: req.body.photoUrl,
password: req.body.password,
email: req.body.email,
angelList: {
blah: 'blah'
},
angelUrl: req.body.angelUrl,
twitterUrl: req.body.twitterUrl,
facebookUrl: req.body.facebookUrl,
linkedinUrl: req.body.linkedinUrl,
githubUrl: req.body.githubUrl
}, function(err, obj) {
if (err) next(err);
if (!obj) next('Cannot create.')
res.json(200, obj);
})
};
Deje que los usuarios actualicen la información en sus aplicaciones:
exports.update = function(req, res, next) {
var data = {};
Object.keys(req.body).forEach(function(k) {
if (req.body[k]) {
data[k] = req.body[k];
}
});
delete data._id;
req.db.User.findByIdAndUpdate(req.session.user._id, {
$set: data
}, function(err, obj) {
if (err) next(err);
if (!obj) next('Cannot save.')
res.json(200, obj);
});
};
Seleccione un objeto en particular con el get()
función:
exports.get = function(req, res, next) {
req.db.User.findById(req.session.user._id,
'firstName lastName photoUrl headline displayName'
+ 'angelUrl facebookUrl twitterUrl linkedinUrl'
+ 'githubUrl', {}, function(err, obj) {
if (err) next(err);
if (!obj) next('cannot find');
res.json(200, obj);
})
};
El código fuente completo de hackhall/routes/applications.js
archivos:
***Error:** File "applications.js" does not exist at this path*
publicaciones.js
El último módulo de rutas que dividimos es hackhall/routes/posts.js
. Se encarga de agregar, editar y eliminar publicaciones, así como de comentar, ver y dar me gusta.
Usamos ID de objeto para la conversión de cadenas HEX a objetos adecuados:
objectId = require('mongodb').ObjectID;
La coloración es agradable para iniciar sesión pero, por supuesto, es opcional. Lo logramos con secuencias de escape:
var red, blue, reset;
red = '\u001b[31m';
blue = '\u001b[34m';
reset = '\u001b[0m';
console.log(red + 'This is red' + reset + ' while ' + blue + ' this is blue' + reset);
Los valores predeterminados para la paginación de las publicaciones:
var LIMIT = 10;
var SKIP = 0;
El add()
función maneja la creación de nuevas publicaciones:
exports.add = function(req, res, next) {
if (req.body) {
req.db.Post.create({
title: req.body.title,
text: req.body.text || null,
url: req.body.url || null,
author: {
id: req.session.user._id,
name: req.session.user.displayName
}
}, function(err, docs) {
if (err) {
console.error(err);
next(err);
} else {
res.json(200, docs);
}
});
} else {
next(new Error('No data'));
}
};
Para recuperar la lista de publicaciones:
exports.getPosts = function(req, res, next) {
var limit = req.query.limit || LIMIT;
var skip = req.query.skip || SKIP;
req.db.Post.find({}, null, {
limit: limit,
skip: skip,
sort: {
'_id': -1
}
}, function(err, obj) {
if (!obj) next('There are not posts.');
obj.forEach(function(item, i, list) {
if (req.session.user.admin) {
item.admin = true;
} else {
item.admin = false;
}
if (item.author.id == req.session.userId) {
item.own = true;
} else {
item.own = false;
}
if (item.likes
&& item.likes.indexOf(req.session.userId) > -1) {
item.like = true;
} else {
item.like = false;
}
if (item.watches
&& item.watches.indexOf(req.session.userId) > -1) {
item.watch = true;
} else {
item.watch = false;
}
});
var body = {};
body.limit = limit;
body.skip = skip;
body.posts = obj;
req.db.Post.count({}, function(err, total) {
if (err) next(err);
body.total = total;
res.json(200, body);
});
});
};
Para la página de publicación individual, necesitamos el getPost()
método:
exports.getPost = function(req, res, next) {
if (req.params.id) {
req.db.Post.findById(req.params.id, {
title: true,
text: true,
url: true,
author: true,
comments: true,
watches: true,
likes: true
}, function(err, obj) {
if (err) next(err);
if (!obj) {
next('Nothing is found.');
} else {
res.json(200, obj);
}
});
} else {
next('No post id');
}
};
El del()
La función elimina publicaciones específicas de la base de datos. El findById()
y remove()
En este fragmento se utilizan métodos de Mongoose. Sin embargo, se puede lograr lo mismo con solo remove()
.
exports.del = function(req, res, next) {
req.db.Post.findById(req.params.id, function(err, obj) {
if (err) next(err);
if (req.session.admin || req.session.userId === obj.author.id) {
obj.remove();
res.json(200, obj);
} else {
next('User is not authorized to delete post.');
}
})
};
Para dar me gusta a la publicación, actualizamos el elemento de la publicación anteponiendo post.likes
array con el ID del usuario:
function likePost(req, res, next) {
req.db.Post.findByIdAndUpdate(req.body._id, {
$push: {
likes: req.session.userId
}
}, {}, function(err, obj) {
if (err) {
next(err);
} else {
res.json(200, obj);
}
});
};
Asimismo, cuando un usuario realiza la acción de vigilancia, el sistema agrega una nueva ID al post.watches
matriz:
function watchPost(req, res, next) {
req.db.Post.findByIdAndUpdate(req.body._id, {
$push: {
watches: req.session.userId
}
}, {}, function(err, obj) {
if (err) next(err);
else {
res.json(200, obj);
}
});
};
El updatePost()
es lo que llama a las funciones "me gusta" o "mirar" según el indicador de acción enviado con la solicitud. Además, el updatePost()
procesa los cambios en la publicación y los comentarios:
exports.updatePost = function(req, res, next) {
var anyAction = false;
if (req.body._id && req.params.id) {
if (req.body && req.body.action == 'like') {
anyAction = true;
likePost(req, res);
}
if (req.body && req.body.action == 'watch') {
anyAction = true;
watchPost(req, res);
}
if (req.body && req.body.action == 'comment'
&& req.body.comment && req.params.id) {
anyAction = true;
req.db.Post.findByIdAndUpdate(req.params.id, {
$push: {
comments: {
author: {
id: req.session.userId,
name: req.session.user.displayName
},
text: req.body.comment
}
}
}, {
safe: true,
new: true
}, function(err, obj) {
if (err) throw err;
res.json(200, obj);
});
}
if (req.session.auth && req.session.userId && req.body
&& req.body.action != 'comment' &&
req.body.action != 'watch' && req.body != 'like' &&
req.params.id && (req.body.author.id == req.session.user._id
|| req.session.user.admin)) {
req.db.Post.findById(req.params.id, function(err, doc) {
if (err) next(err);
doc.title = req.body.title;
doc.text = req.body.text || null;
doc.url = req.body.url || null;
doc.save(function(e, d) {
if (e) next(e);
res.json(200, d);
});
})
} else {
if (!anyAction) next('Something went wrong.');
}
} else {
next('No post ID.');
}
};
El código fuente completo del hackhall/routes/posts.js
archivo:
objectId = require('mongodb').ObjectID;
var red, blue, reset;
red = '\u001b[31m';
blue = '\u001b[34m';
reset = '\u001b[0m';
console.log(red + 'This is red' + reset + ' while ' + blue + ' this is blue' + reset);
var LIMIT = 10;
var SKIP = 0;
exports.add = function(req, res, next) {
if (req.body) {
req.db.Post.create({
title: req.body.title,
text: req.body.text || null,
url: req.body.url || null,
author: {
id: req.session.user._id,
name: req.session.user.displayName
}
}, function(err, docs) {
if (err) {
console.error(err);
next(err);
} else {
res.json(200, docs);
}
});
} else {
next(new Error('No data'));
}
};
exports.getPosts = function(req, res, next) {
var limit = req.query.limit || LIMIT;
var skip = req.query.skip || SKIP;
req.db.Post.find({}, null, {
limit: limit,
skip: skip,
sort: {
'_id': -1
}
}, function(err, obj) {
if (!obj) next('There are not posts.');
obj.forEach(function(item, i, list) {
if (req.session.user.admin) {
item.admin = true;
} else {
item.admin = false;
}
if (item.author.id == req.session.userId) {
item.own = true;
} else {
item.own = false;
}
if (item.likes && item.likes.indexOf(req.session.userId) > -1) {
item.like = true;
} else {
item.like = false;
}
if (item.watches && item.watches.indexOf(req.session.userId) > -1) {
item.watch = true;
} else {
item.watch = false;
}
});
var body = {};
body.limit = limit;
body.skip = skip;
body.posts = obj;
req.db.Post.count({}, function(err, total) {
if (err) next(err);
body.total = total;
res.json(200, body);
});
});
};
exports.getPost = function(req, res, next) {
if (req.params.id) {
req.db.Post.findById(req.params.id, {
title: true,
text: true,
url: true,
author: true,
comments: true,
watches: true,
likes: true
}, function(err, obj) {
if (err) next(err);
if (!obj) {
next('Nothing is found.');
} else {
res.json(200, obj);
}
});
} else {
next('No post id');
}
};
exports.del = function(req, res, next) {
req.db.Post.findById(req.params.id, function(err, obj) {
if (err) next(err);
if (req.session.admin || req.session.userId === obj.author.id) {
obj.remove();
res.json(200, obj);
} else {
next('User is not authorized to delete post.');
}
})
};
function likePost(req, res, next) {
req.db.Post.findByIdAndUpdate(req.body._id, {
$push: {
likes: req.session.userId
}
}, {}, function(err, obj) {
if (err) {
next(err);
} else {
res.json(200, obj);
}
});
};
function watchPost(req, res, next) {
req.db.Post.findByIdAndUpdate(req.body._id, {
$push: {
watches: req.session.userId
}
}, {}, function(err, obj) {
if (err) next(err);
else {
res.json(200, obj);
}
});
};
exports.updatePost = function(req, res, next) {
var anyAction = false;
if (req.body._id && req.params.id) {
if (req.body && req.body.action == 'like') {
anyAction = true;
likePost(req, res);
}
if (req.body && req.body.action == 'watch') {
anyAction = true;
watchPost(req, res);
}
if (req.body && req.body.action == 'comment' && req.body.comment && req.params.id) {
anyAction = true;
req.db.Post.findByIdAndUpdate(req.params.id, {
$push: {
comments: {
author: {
id: req.session.userId,
name: req.session.user.displayName
},
text: req.body.comment
}
}
}, {
safe: true,
new: true
}, function(err, obj) {
if (err) throw err;
res.json(200, obj);
});
}
if (req.session.auth && req.session.userId && req.body && req.body.action != 'comment' &&
req.body.action != 'watch' && req.body != 'like' &&
req.params.id && (req.body.author.id == req.session.user._id || req.session.user.admin)) {
req.db.Post.findById(req.params.id, function(err, doc) {
if (err) next(err);
doc.title = req.body.title;
doc.text = req.body.text || null;
doc.url = req.body.url || null;
doc.save(function(e, d) {
if (e) next(e);
res.json(200, d);
});
})
} else {
if (!anyAction) next('Something went wrong.');
}
} else {
next('No post ID.');
}
};
Modelos de mangosta
Idealmente, en una aplicación grande, dividiríamos cada modelo en un archivo separado. Ahora mismo en la aplicación HackHall, los tenemos todos en hackhall/models/index.js
.
Como siempre, nuestras dependencias se ven mejor en la parte superior:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var roles = 'user staff mentor investor founder'.split(' ');
El modelo de publicación representa la publicación con sus Me gusta, comentarios y relojes.
exports.Post = new Schema ({
title: {
required: true,
type: String,
trim: true,
// match: /^([[:alpha:][:space:][:punct:]]{1,100})$/
match: /^([\w ,.!?]{1,100})$/
},
url: {
type: String,
trim: true,
max: 1000
},
text: {
type: String,
trim: true,
max: 2000
},
comments: [{
text: {
type: String,
trim: true,
max:2000
},
author: {
id: {
type: Schema.Types.ObjectId,
ref: 'User'
},
name: String
}
}],
watches: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
likes: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
author: {
id: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
name: {
type: String,
required: true
}
},
created: {
type: Date,
default: Date.now,
required: true
},
updated: {
type: Date,
default: Date.now,
required: true
},
own: Boolean,
like: Boolean,
watch: Boolean,
admin: Boolean,
action: String
});
El modelo de Usuario también puede servir como un objeto de aplicación (cuando approved=false
):
exports.User = new Schema({
angelListId: String,
angelListProfile: Schema.Types.Mixed,
angelToken: String,
firstName: {
type: String,
required: true,
trim: true
},
lastName: {
type: String,
required: true,
trim: true
},
displayName: {
type: String,
required: true,
trim: true
},
password: String,
email: {
type: String,
required: true,
trim: true
},
role: {
type:String,
enum: roles,
required: true,
default: roles[0]
},
approved: {
type: Boolean,
default: false
},
banned: {
type: Boolean,
default: false
},
admin: {
type: Boolean,
default: false
},
headline: String,
photoUrl: String,
angelList: Schema.Types.Mixed,
created: {
type: Date,
default: Date.now
},
updated: {
type: Date, default: Date.now
},
angelUrl: String,
twitterUrl: String,
facebookUrl: String,
linkedinUrl: String,
githubUrl: String,
own: Boolean,
posts: {
own: [Schema.Types.Mixed],
likes: [Schema.Types.Mixed],
watches: [Schema.Types.Mixed],
comments: [Schema.Types.Mixed]
}
});
El código fuente completo para hackhall/models/index.js
:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var roles = 'user staff mentor investor founder'.split(' ');
exports.Post = new Schema ({
title: {
required: true,
type: String,
trim: true,
// match: /^([[:alpha:][:space:][:punct:]]{1,100})$/
match: /^([\w ,.!?]{1,100})$/
},
url: {
type: String,
trim: true,
max: 1000
},
text: {
type: String,
trim: true,
max: 2000
},
comments: [{
text: {
type: String,
trim: true,
max:2000
},
author: {
id: {
type: Schema.Types.ObjectId,
ref: 'User'
},
name: String
}
}],
watches: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
likes: [{
type: Schema.Types.ObjectId,
ref: 'User'
}],
author: {
id: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
name: {
type: String,
required: true
}
},
created: {
type: Date,
default: Date.now,
required: true
},
updated: {
type: Date,
default: Date.now,
required: true
},
own: Boolean,
like: Boolean,
watch: Boolean,
admin: Boolean,
action: String
});
exports.User = new Schema({
angelListId: String,
angelListProfile: Schema.Types.Mixed,
angelToken: String,
firstName: {
type: String,
required: true,
trim: true
},
lastName: {
type: String,
required: true,
trim: true
},
displayName: {
type: String,
required: true,
trim: true
},
password: String,
email: {
type: String,
required: true,
trim: true
},
role: {
type:String,
enum: roles,
required: true,
default: roles[0]
},
approved: {
type: Boolean,
default: false
},
banned: {
type: Boolean,
default: false
},
admin: {
type: Boolean,
default: false
},
headline: String,
photoUrl: String,
angelList: Schema.Types.Mixed,
created: {
type: Date,
default: Date.now
},
updated: {
type: Date, default: Date.now
},
angelUrl: String,
twitterUrl: String,
facebookUrl: String,
linkedinUrl: String,
githubUrl: String,
own: Boolean,
posts: {
own: [Schema.Types.Mixed],
likes: [Schema.Types.Mixed],
watches: [Schema.Types.Mixed],
comments: [Schema.Types.Mixed]
}
});
Pruebas de moca
Uno de los beneficios de usar la arquitectura del servidor REST API es que cada ruta y la aplicación en su conjunto se vuelven muy comprobables. La seguridad de las pruebas aprobadas es un maravilloso complemento durante el desarrollo:el llamado enfoque de desarrollo basado en pruebas.
Para ejecutar pruebas, utilizamos Makefile:
REPORTER = list
MOCHA_OPTS = --ui tdd --ignore-leaks
test:
clear
echo Starting test *********************************************************
./node_modules/mocha/bin/mocha \
--reporter $(REPORTER) \
$(MOCHA_OPTS) \
tests/*.js
echo Ending test
test-w:
./node_modules/mocha/bin/mocha \
--reporter $(REPORTER) \
--growl \
--watch \
$(MOCHA_OPTS) \
tests/*.js
users:
mocha tests/users.js --ui tdd --reporter list --ignore-leaks
posts:
clear
echo Starting test *********************************************************
./node_modules/mocha/bin/mocha \
--reporter $(REPORTER) \
$(MOCHA_OPTS) \
tests/posts.js
echo Ending test
application:
mocha tests/application.js --ui tdd --reporter list --ignore-leaks
.PHONY: test test-w posts application
Por lo tanto, podemos comenzar las pruebas con:$ make
comando.
Todas las 20 pruebas deben pasar:
Las pruebas de HackHall en vivo en tests
carpeta y consta de:
hackhall/tests/application.js
:pruebas funcionales para información de usuarios no aprobadoshackhall/tests/posts.js
:pruebas funcionales para publicacioneshackhall/tests/users.js
:pruebas funcionales para usuarios
Pruebe usar una biblioteca llamada superagent (GitHub).
El contenido completo para hackhall/tests/application.js
:
var app = require ('../server').app,
assert = require('assert'),
request = require('superagent');
app.listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
});
var user1 = request.agent();
var port = 'http://localhost:'+app.get('port');
var userId;
suite('APPLICATION API', function (){
suiteSetup(function(done){
done();
});
test('log in as admin', function(done){
user1.post(port+'/api/login').send({email:'[email protected]',password:'1'}).end(function(res){
assert.equal(res.status,200);
done();
});
});
test('get profile for admin',function(done){
user1.get(port+'/api/profile').end(function(res){
assert.equal(res.status,200);
done();
});
});
test('submit applicaton for user [email protected]', function(done){
user1.post(port+'/api/application').send({
firstName: 'Dummy',
lastName: 'Application',
displayName: 'Dummy Application',
password: '3',
email: '[email protected]',
headline: 'Dummy Appliation',
photoUrl: '/img/user.png',
angelList: {blah:'blah'},
angelUrl: 'http://angel.co.com/someuser',
twitterUrl: 'http://twitter.com/someuser',
facebookUrl: 'http://facebook.com/someuser',
linkedinUrl: 'http://linkedin.com/someuser',
githubUrl: 'http://github.com/someuser'
}).end(function(res){
assert.equal(res.status,200);
userId = res.body._id;
done();
});
});
test('logout admin',function(done){
user1.post(port+'/api/logout').end(function(res){
assert.equal(res.status,200);
done();
});
});
test('get profile again after logging out',function(done){
user1.get(port+'/api/profile').end(function(res){
assert.equal(res.status,500);
done();
});
});
test('log in as user3 - unapproved', function(done){
user1.post(port+'/api/login').send({email:'[email protected]',password:'3'}).end(function(res){
assert.equal(res.status,200);
done();
});
});
test('get user application', function(done){
user1.get(port+'/api/application/').end(function(res){
// console.log(res.body)
assert.equal(res.status, 200);
done();
});
});
test('update user application', function(done){
user1.put(port+'/api/application/').send({
firstName: 'boo'}).end(function(res){
// console.log(res.body)
assert.equal(res.status, 200);
done();
});
});
test('get user application', function(done){
user1.get(port+'/api/application/').end(function(res){
// console.log(res.body)
assert.equal(res.status, 200);
done();
});
});
test('check for posts - fail (unapproved?)', function(done){
user1.get(port+'/api/posts/').end(function(res){
// console.log(res.body)
assert.equal(res.status, 500);
done();
});
});
test('logout user',function(done){
user1.post(port+'/api/logout').end(function(res){
assert.equal(res.status,200);
done();
});
});
test('log in as admin', function(done){
user1.post(port+'/api/login').send({email:'[email protected]',password:'1'}).end(function(res){
assert.equal(res.status,200);
done();
});
});
test('delete user3', function(done){
user1.del(port+'/api/users/'+userId).end(function(res){
assert.equal(res.status, 200);
done();
});
});
test('logout admin',function(done){
user1.post(port+'/api/logout').end(function(res){
assert.equal(res.status,200);
done();
});
});
test('log in as user - should fail', function(done){
user1.post(port+'/api/login').send({email:'[email protected]',password:'3'}).end(function(res){
// console.log(res.body)
assert.equal(res.status,500);
done();
});
});
test('check for posts - must fail', function(done){
user1.get(port+'/api/posts/').end(function(res){
// console.log(res.body)
assert.equal(res.status, 500);
done();
});
});
suiteTeardown(function(done){
done();
});
});
El contenido completo para hackhall/tests/posts.js
:
var app = require('../server').app,
assert = require('assert'),
request = require('superagent');
app.listen(app.get('port'), function() {
console.log('Express server listening on port ' + app.get('port'));
});
var user1 = request.agent();
var port = 'http://localhost:' + app.get('port');
var postId;
suite('POSTS API', function() {
suiteSetup(function(done) {
done();
});
test('log in', function(done) {
user1.post(port + '/api/login').send({
email: '[email protected]',
password: '1'
}).end(function(res) {
assert.equal(res.status, 200);
done();
});
});
test('add post', function(done) {
user1.post(port + '/api/posts').send({
title: 'Yo Test Title',
text: 'Yo Test text',
url: ''
}).end(function(res) {
assert.equal(res.status, 200);
postId = res.body._id;
done();
});
});
test('get profile', function(done) {
user1.get(port + '/api/profile').end(function(res) {
assert.equal(res.status, 200);
done();
});
});
test('post get', function(done) {
// console.log('000'+postId);
user1.get(port + '/api/posts/' + postId).end(function(res) {
assert.equal(res.status, 200);
assert.equal(res.body._id, postId);
done();
});
});
test('delete post', function(done) {
user1.del(port + '/api/posts/' + postId).end(function(res) {
assert.equal(res.status, 200);
done();
});
});
test('check for deleted post', function(done) {
user1.get(port + '/api/posts/' + postId).end(function(res) {
// console.log(res.body)
assert.equal(res.status, 500);
done();
});
});
suiteTeardown(function(done) {
done();
});
});
El contenido completo para hackhall/tests/users.js
:
var app = require('../server').app,
assert = require('assert'),
request = require('superagent');
// http = require('support/http');
var user1 = request.agent();
var port = 'http://localhost:' + app.get('port');
app.listen(app.get('port'), function() {
console.log('Express server listening on port ' + app.get('port'));
});
suite('Test root', function() {
setup(function(done) {
console.log('setup');
done();
});
test('check /', function(done) {
request.get('http://localhost:3000').end(function(res) {
assert.equal(res.status, 200);
done();
});
});
test('check /api/profile', function(done) {
request.get('http://localhost:' + app.get('port') + '/api/profile').end(function(res) {
assert.equal(res.status, 500);
done();
});
});
test('check /api/users', function(done) {
user1.get('http://localhost:' + app.get('port') + '/api/users').end(function(res) {
assert.equal(res.status, 500);
// console.log(res.text.length);
done();
});
// done();
});
test('check /api/posts', function(done) {
user1.get('http://localhost:' + app.get('port') + '/api/posts').end(function(res) {
assert.equal(res.status, 500);
// console.log(res.text.length);
done();
});
// done();
});
teardown(function(done) {
console.log('teardown');
done();
});
});
suite('Test log in', function() {
setup(function(done) {
console.log('setup');
done();
});
test('login', function(done) {
user1.post('http://localhost:3000/api/login').send({
email: '[email protected]',
password: '1'
}).end(function(res) {
assert.equal(res.status, 200);
done();
});
});
test('check /api/profile', function(done) {
user1.get('http://localhost:' + app.get('port') + '/api/profile').end(function(res) {
assert.equal(res.status, 200);
// console.log(res.text.length);
done();
});
// done();
});
test('check /api/users', function(done) {
user1.get('http://localhost:' + app.get('port') + '/api/users').end(function(res) {
assert.equal(res.status, 200);
// console.log(res.text);
done();
});
// done();
});
test('check /api/posts', function(done) {
user1.get('http://localhost:' + app.get('port') + '/api/posts').end(function(res) {
assert.equal(res.status, 200);
// console.log(res.text.length);
done();
});
// done();
});
teardown(function(done) {
console.log('teardown');
done();
});
});
suite('User control', function() {
var user2 = {
firstName: 'Bob',
lastName: 'Dilan',
displayName: 'Bob Dilan',
email: '[email protected]'
};
suiteSetup(function(done) {
user1.post('http://localhost:3000/api/login').send({
email: '[email protected]',
password: '1'
}).end(function(res) {
assert.equal(res.status, 200);
// done();
});
user1.get('http://localhost:' + app.get('port') + '/api/profile').end(function(res) {
assert.equal(res.status, 200);
// console.log(res.text.length);
// done();
});
done();
})
test('new user POST /api/users', function(done) {
user1.post(port + '/api/users')
.send(user2)
.end(function(res) {
assert.equal(res.status, 200);
// console.log(res.text.length);
user2 = res.body;
// console.log(user2)
done();
})
});
test('get user list and check for new user GET /api/users', function(done) {
user1.get('http://localhost:' + app.get('port') + '/api/users').end(function(res) {
assert.equal(res.status, 200);
// console.log(res.body)
var user3 = res.body.filter(function(el, i, list) {
return (el._id == user2._id);
});
assert(user3.length === 1);
// assert(res.body.indexOf(user2)>-1);
// console.log(res.body.length)
done();
})
});
test('Approve User: PUT /api/users/' + user2._id, function(done) {
assert(user2._id != '');
user1.put(port + '/api/users/' + user2._id)
.send({
approved: true
})
.end(function(res) {
assert.equal(res.status, 200);
// console.log(res.text.length);
assert(res.body.approved);
user1.get(port + '/api/users/' + user2._id).end(function(res) {
assert(res.status, 200);
assert(res.body.approved);
done();
})
})
});
test('Banned User: PUT /api/users/' + user2._id, function(done) {
assert(user2._id != '');
user1.put(port + '/api/users/' + user2._id)
.send({
banned: true
})
.end(function(res) {
assert.equal(res.status, 200);
// console.log(res.text.length);
assert(res.body.banned);
user1.get(port + '/api/users/' + user2._id).end(function(res) {
assert(res.status, 200);
assert(res.body.banned);
done();
})
})
});
test('Promote User: PUT /api/users/' + user2._id, function(done) {
assert(user2._id != '');
user1.put(port + '/api/users/' + user2._id)
.send({
admin: true
})
.end(function(res) {
assert.equal(res.status, 200);
// console.log(res.text.length);
assert(res.body.admin);
user1.get(port + '/api/users/' + user2._id).end(function(res) {
assert(res.status, 200);
assert(res.body.admin);
done();
})
})
});
test('Delete User: DELETE /api/users/:id', function(done) {
assert(user2._id != '');
user1.del(port + '/api/users/' + user2._id)
.end(function(res) {
assert.equal(res.status, 200);
// console.log('id:' + user2._id)
user1.get(port + '/api/users').end(function(res) {
assert.equal(res.status, 200);
var user3 = res.body.filter(function(el, i, list) {
return (el._id === user2._id);
});
// console.log('***');
// console.warn(user3);
assert(user3.length === 0);
done();
});
});
});
});
// app.close();
// console.log(app)
Advertencia: No almacene contraseñas/claves simples en la base de datos. Cualquier aplicación de producción seria debería al menos saltear las contraseñas antes de almacenarlas.
Conclusión
HackHall aún está en desarrollo, pero hay importantes componentes de aplicaciones de producción real, como la arquitectura REST API, OAuth, Mongoose y sus modelos, la estructura MVC de las aplicaciones Express.js, el acceso a las variables de entorno, etc.