Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
fb43672
shares handlers, model, routes, schemas, fixtures and tests
FelipeMonobe Nov 15, 2016
73fdb89
user_id index
FelipeMonobe Nov 15, 2016
2abea76
standard fix & minor fixes
FelipeMonobe Nov 15, 2016
c262652
Implementa a sessão
alanhoff Nov 15, 2016
0e25ad2
Adiciona a variável de ambiente para os cookies
alanhoff Nov 15, 2016
c6df11f
migrations & first review fixes
FelipeMonobe Nov 16, 2016
416c37f
Load environment variables from `.env` with `dotenv` module
Nov 15, 2016
46087d8
Add steps necessary to run the app to the README
Nov 15, 2016
f2e1cbb
add `morgan` to print request logs
Nov 15, 2016
a533583
Add `users` resource
Nov 15, 2016
2e44a70
lint
Nov 15, 2016
3293096
Arruma o truncate para operar com múltiplas tabelas
alanhoff Nov 15, 2016
447c7e3
Arruma as configurações do CircleCI
alanhoff Nov 15, 2016
f1d1cab
Adequando às especificoes
thebergamo Nov 15, 2016
471ff27
Removendo workarround baseado no PR do alan
thebergamo Nov 15, 2016
c1f1caf
Removendo setup do readme.md em prol da wiki
thebergamo Nov 15, 2016
6a2f0f1
Trocando nome da rota para pt-br
thebergamo Nov 15, 2016
e64bbbf
Trocando nome da rota para plural
thebergamo Nov 15, 2016
73be47a
chore stuff - linting, remove blank lines etc
Nov 15, 2016
0c6bb3d
test duplicate user insertion
Nov 15, 2016
98918a2
rename migration
FelipeMonobe Nov 16, 2016
f262253
standard fix
FelipeMonobe Nov 16, 2016
6714d7b
standard fix #2
FelipeMonobe Nov 16, 2016
e7edd57
Modificações adicionais para o code review
alanhoff Nov 16, 2016
d957869
Merge pull request #70 from nodebr/feat/sessions
alanhoff Nov 16, 2016
db0270e
shares handlers, model, routes, schemas, fixtures and tests
FelipeMonobe Nov 15, 2016
c11e80e
user_id index
FelipeMonobe Nov 15, 2016
fc53d00
standard fix & minor fixes
FelipeMonobe Nov 15, 2016
a47c0bd
migrations & first review fixes
FelipeMonobe Nov 16, 2016
c2c1113
Add steps necessary to run the app to the README
Nov 15, 2016
3bb678e
Removendo setup do readme.md em prol da wiki
thebergamo Nov 15, 2016
504f39d
rename migration
FelipeMonobe Nov 16, 2016
4f656bb
standard fix
FelipeMonobe Nov 16, 2016
c344fbe
standard fix #2
FelipeMonobe Nov 16, 2016
6be9fa1
share - tests failing
FelipeMonobe Nov 16, 2016
8eac3a9
merge
FelipeMonobe Nov 16, 2016
ded0f5c
1 test removed, some tests fixed, pending timeout & session failures …
FelipeMonobe Dec 18, 2016
f50970c
catches
FelipeMonobe Dec 30, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
NODE_ENV=development
DATABASE_URL=mysql://nodebr:nodebr@localhost/nodebr
PORT=8080
COOKIE_SECRET=here_be_dragons
1 change: 1 addition & 0 deletions circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ machine:
environment:
DATABASE_URL: mysql://ubuntu@localhost/circle_test
NODE_ENV: test
COOKIE_SECRET: here_be_dragons
node:
version: v6.9
test:
Expand Down
12 changes: 12 additions & 0 deletions lib/async-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const co = require('co')

/**
* Um helper para registrar handlers assíncronos
* @todo Implementar funcionalidade para Promises
* @param {Function} handler Um generator que irá receber a requisição
* @return {Function} Uma função que pode ser usada como handler no Express
*/
module.exports = handler => (req, res, next) => {
co(handler.bind(null, req, res, next))
.catch(err => next(err))
}
28 changes: 28 additions & 0 deletions lib/db/drop-dabase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/* $lab:coverage:off$ */
const co = require('co')

/**
* Cria uma função no Bookshelf para zerar o banco de dados
* @param {Object} bookshelf Uma instância do Bookshelf
*/
module.exports = bookshelf => {
const { knex } = bookshelf

bookshelf.dropDatabase = co.wrap(function * () {
// Desabilita este comando em qualquer outro ambiante que não seja desenvolvimento
if (process.env.NODE_ENV === 'production') {
return Promise.reject(new Error('Você não pode executar o dropDatabase neste ambiente'))
}

// Pega todas as tabelas no nosso banco de dados
const result = yield knex.raw('SHOW TABLES;')
const tables = result[0].map(table => table[Object.keys(table)[0]])

yield knex.transaction(co.wrap(function * (trx) {
yield knex.raw('SET FOREIGN_KEY_CHECKS = 0;')
yield Promise.all(tables.map(table => knex.raw(`DROP TABLE ${table};`)))
yield knex.raw('SET FOREIGN_KEY_CHECKS = 1;')
}))
})
}
/* $lab:coverage:on$ */
1 change: 1 addition & 0 deletions lib/db/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ bookshelf.plugin(require('bookshelf-modelbase').pluggable)
bookshelf.plugin(require('bookshelf-bcrypt'))
bookshelf.plugin(require('./base'))
bookshelf.plugin(require('./truncate'))
bookshelf.plugin(require('./drop-dabase'))

const modelsPath = path.resolve(__dirname, '../../resources')

Expand Down
11 changes: 11 additions & 0 deletions lib/db/migrations/20161115211528_shares.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@

exports.up = knex => knex.schema.createTableIfNotExists('shares', table => {
table.uuid('id').primary()
table.uuid('user_id').index().references('id').inTable('users')
table.string('title', 120).notNullable()
table.string('thumbnail', 255)
table.string('link', 255).notNullable()
table.timestamps()
})

exports.down = knex => knex.schema.dropTableIfExists('shares')
6 changes: 4 additions & 2 deletions lib/error-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ module.exports = (err, req, res, next) => {

// Verifica os tipos de erros que podemos ter
if (err.isJoi) {
res.status(422).send({
return res.status(422).send({
error: 'ValidationError',
message: err.details[0].message,
path: err.details[0].path,
type: err.details[0].type
})
} else if (err.isBoom) {
return res.status(err.output.statusCode).send(err.output.payload)
} else {
// Caso nenhum tipo de erro seja encontrado então é um erro no servidor
res.status(500).send({ error: 'InternalServerError' })
console.error(err.stack)
return res.status(500).send({ error: 'InternalServerError' })
}
}
40 changes: 40 additions & 0 deletions lib/session.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const cookieSession = require('cookie-session')

/**
* Um middleware para checagem de sessão
* @param {Object} [config] Configurações da sessão
* @param {Boolean} [config.restrict=true] Deixar que apenas usuários logados
* acessem o handler
* @return {Function} Uma função que serve de middleware
*/
module.exports = (config = {
restrict: true
}) => {
const session = cookieSession({
name: 'session',
keys: [process.env.COOKIE_SECRET],
maxAge: 7 * 24 * 60 * 60 * 1000, // Uma semana
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
signed: true,
overwrite: true
})

return (req, res, next) => {
// Checa a sessão utilizando o middleware cookie-session
session(req, res, err => {
if (err) {
return next(err)
}

// Caso o acesso seja restrito à usuários logados precisamos verificar
// se a sessão foi lida com sucesso
if ((!req.session || !req.session.user_id) && config.restrict) {
return res.status(401).send({ error: 'Unauthorized' })
}

// Tudo sob controle, podemos executar o handler
next()
})
}
}
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
"test": "standard && lab --verbose --colors --assert code --ignore __core-js_shared__",
"test-cov": "npm test -- -r console -o stdout -r html -o coverage/coverage.html -r lcov -o coverage/lcov.info",
"knex": "knex --knexfile ./lib/db/knexfile.js",
"start": "node index.js"
"start": "node index.js",
"migrate": "npm run knex migrate:latest",
"reset": "node ./scripts/drop-database; npm run migrate"
},
"repository": {
"type": "git",
Expand All @@ -26,7 +28,9 @@
"bookshelf-bcrypt": "^2.1.0",
"bookshelf-modelbase": "^2.10.1",
"bookshelf-uuid": "^1.0.0",
"boom": "^4.2.0",
"co": "^4.6.0",
"cookie-session": "^2.0.0-alpha.2",
"express": "^4.14.0",
"glob": "^7.1.1",
"helmet": "^3.1.0",
Expand All @@ -41,6 +45,7 @@
"eslint-plugin-standard": "^2.0.1",
"lab": "^11.2.1",
"standard": "^8.5.0",
"supertest": "^2.0.1"
"supertest": "^2.0.1",
"uuid": "^2.0.3"
}
}
2 changes: 2 additions & 0 deletions resources/hello-world/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ router.delete('/hello-world/:id', handlers.remove)

// Somente exportamos esta rota caso o ambiente for de desenvolvimento
// pois não queremos que a mesma esteja disponível em produção
/* $lab:coverage:off$ */
if (process.env.NODE_ENV !== 'production') {
module.exports = router
}
/* $lab:coverage:on$ */
36 changes: 36 additions & 0 deletions resources/sessions/handlers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const Boom = require('boom')

const db = require('../../lib/db')
const User = db.model('User')

exports.create = function * (req, res) {
// Verifica se o usuário já está logado
if (req.session && req.session.user_id) {
throw Boom.badData('Você já está logado')
}

// Pega no banco de dados o usuário que precisamos
const user = yield User.findOne({ username: req.body.username })
.catch(User.NotFoundError, () => {
throw Boom.badData('Não foi possível encontrar o usuário')
})

// Verifica se a senha está ok
if (!(yield user.compare(req.body.password))) {
throw Boom.badData('Sua senha está incorreta')
}

// Seta a sessão e retorna sucesso
req.session.user_id = user.id
res.send({ success: true })
}

exports.findOne = (req, res) => {
User.findById(req.session.user_id)
.then((user) => res.send(user))
}

exports.remove = (req, res) => {
req.session = null
res.send({ success: true })
}
24 changes: 24 additions & 0 deletions resources/sessions/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const router = require('express').Router()

const handlers = require('./handlers')
const schemas = require('./schemas')
const validator = require('../../lib/validator')
const session = require('../../lib/session')
const asyncHandler = require('../../lib/async-handler')
const bodyParser = require('body-parser')

router.post('/sessions',
session({ restrict: false }),
bodyParser.json(),
validator({ body: schemas.create }),
asyncHandler(handlers.create))

router.get('/sessions',
session(),
handlers.findOne)

router.delete('/sessions',
session(),
handlers.remove)

module.exports = router
6 changes: 6 additions & 0 deletions resources/sessions/schemas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const Joi = require('joi')

exports.create = Joi.object({
username: Joi.string().token().min(3).max(20).required(),
password: Joi.string().min(8).max(120).required()
})
37 changes: 37 additions & 0 deletions resources/shares/handlers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
const boom = require('boom')
const db = require('../../lib/db')

const Shares = db.model('Share')

exports.findAll = (req, res) =>
Shares
.query(qb => qb.limit(req.query.limit).offset(req.query.offset))
.orderBy('created_at')
.fetchAll({}, { withRelated: ['user'] })
.then(collection => res.send(collection))
.catch(() => { throw boom.badRequest('Ocorreu um erro na consulta') })

exports.findOne = (req, res) =>
Shares
.findById(req.params.id, { withRelated: ['user'] })
.then(share => {
if (!share) return res.status(404).end()
return res.send(share)
})
.catch(() => { throw boom.badRequest('Ocorreu um erro na consulta') })

exports.remove = (req, res) => {
if (req.params.id !== req.session.user_id) return res.status(403).end()

return Shares
.forge({ id: req.params.id })
.destroy()
.then(() => res.send({ success: true }))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check this, you must implement an catch for this and other promises. Probably the error is generated for an error in constraint.

Copy link
Copy Markdown
Author

@FelipeMonobe FelipeMonobe Dec 30, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dude, I'm not sure that's the problem, since the sessions handler also has an uncaught function:

exports.findOne = (req, res) => {
  User.findById(req.session.user_id)
  .then((user) => res.send(user))
}

I've put catches to all my functions and they didn't work

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to look more closely. Will do some tests in the week

.catch(() => { throw boom.badRequest('Ocorreu um erro na exclusão') })
}

exports.create = (req, res) =>
Shares
.create(req.body)
.then(share => res.send(share))
.catch(() => { throw boom.badRequest('Ocorreu um erro na criação') })
10 changes: 10 additions & 0 deletions resources/shares/model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Cria o modelo Share
* @param {Function} bookshelf Uma instância do Bookshelf
*/
module.exports = bookshelf => bookshelf.model('Share', {
tableName: 'shares',
user: function () {
return this.belongsTo('User')
}
})
25 changes: 25 additions & 0 deletions resources/shares/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
const express = require('express')
const bodyParser = require('body-parser')

const validator = require('../../lib/validator')
const schemas = require('./schemas')
const handlers = require('./handlers')
const router = express.Router()
const session = require('../../lib/session')

router.get('/compartilhamentos/',
validator({ query: schemas.query }),
handlers.findAll)

router.get('/compartilhamentos/:id', handlers.findOne)

router.post('/compartilhamentos',
bodyParser.json(),
validator({ body: schemas.create }),
handlers.create)

router.delete('/compartilhamentos/:id',
session(),
handlers.remove)

module.exports = router
22 changes: 22 additions & 0 deletions resources/shares/schemas.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const Joi = require('joi')

exports.model = Joi.object({
id: Joi.string().uuid(),
user_id: Joi.string().uuid(),
title: Joi.string(),
thumbnail: Joi.string(),
link: Joi.string()
})

exports.create = exports.model.concat(Joi.object({
id: Joi.forbidden(),
user_id: Joi.string().uuid(),
title: Joi.string().min(10).max(120).required(),
thumbnail: Joi.string().uri().max(255).optional(),
link: Joi.string().uri().max(255).required()
}))

exports.query = Joi.object({
limit: Joi.number().integer().positive().max(50).default(15),
offset: Joi.number().integer().greater(-1).default(0)
})
8 changes: 8 additions & 0 deletions scripts/drop-database.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const db = require('../lib/db')

db.dropDatabase()
.then(() => process.exit(0))
.catch(err => {
console.error(err.stack)
process.exit(1)
})
15 changes: 15 additions & 0 deletions test/fixtures/sessions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const server = require('../../lib/server')
const request = require('supertest')

/**
* Cria um cookie compatível com a header Cookie para ser usado com o supertest
* @param {String} username O nome do usuário
* @param {String} password A senha do usuário
* @return {Promise} Uma promise que resolve com o cookie de autenticação
*/
exports.cookie = (username, password) => request(server)
.post('/sessions')
.send({ username, password })
.then(res => res.header['set-cookie']
.map(e => e.split(';')[0])
.join(';'))
Loading