serverest 2.25.4 → 2.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,31 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.26.2](https://github.com/ServeRest/ServeRest/compare/v2.26.1...v2.26.2) (2022-08-18)
4
+
5
+ ## [2.26.1](https://github.com/ServeRest/ServeRest/compare/v2.26.0...v2.26.1) (2022-07-08)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * show user that request ended with timeout ([1f432c4](https://github.com/ServeRest/ServeRest/commit/1f432c429647c871b2075dc8a4259ab52e714e04))
11
+
12
+ # [2.26.0](https://github.com/ServeRest/ServeRest/compare/v2.25.4...v2.26.0) (2022-06-19)
13
+
14
+
15
+ ### Chores
16
+
17
+ * avoid action unpublished version ([15fab7f](https://github.com/ServeRest/ServeRest/commit/15fab7f4565ef46350cc8959669e1430f02aec03)), closes [#310](https://github.com/ServeRest/ServeRest/issues/310)
18
+
19
+
20
+ ### Continuous Integration
21
+
22
+ * run mutation test only when files in src/ dir change ([0ed60ad](https://github.com/ServeRest/ServeRest/commit/0ed60ad04bf0db573c7ba23c1ba8826708f29711))
23
+
24
+
25
+ ### Features
26
+
27
+ * allow load test and route /status to get infos ([7098bff](https://github.com/ServeRest/ServeRest/commit/7098bffed401ec001a1d5eef0406a406a6fd6e97))
28
+
3
29
  ## [2.25.4](https://github.com/ServeRest/ServeRest/compare/v2.25.3...v2.25.4) (2022-06-19)
4
30
 
5
31
 
package/README.md CHANGED
@@ -25,6 +25,7 @@
25
25
 
26
26
  _ServeRest_ permite o estudo de:
27
27
  - Verbos *GET, POST, PUT* e *DELETE* com persistência de dados
28
+ - [Teste de carga](#teste-de-carga)
28
29
  - Autenticação no header
29
30
  - Query string
30
31
  - Teste de schema json
@@ -124,6 +125,28 @@ Em ambos os comandos de subida de ambiente local será utilizado a última vers
124
125
 
125
126
  Você pode encontrar as versões disponíveis na [lista de tags no Docker Hub](https://hub.docker.com/r/paulogoncalvesbh/serverest/tags) e na [lista de versões do NPM](https://www.npmjs.com/package/serverest).
126
127
 
128
+ ## Teste de carga
129
+
130
+ ### IMPORTANTE
131
+
132
+ 1. É obrigatório enviar o header `monitor: false` em todas as requisições do seu teste de carga.
133
+ 2. O teste de carga deve ser executado apenas em ambiente local (disponibilizado via [NPM](#localmente-com-npm) ou [Docker](#localmente-com-docker) e acessível via <http://localhost:3000>).
134
+
135
+ > O não seguimento dos 2 tópicos acimas vai acarretar em prejuízo para o projeto open source e gratuito e irá impactar o estudo de outras pessoas.
136
+
137
+ ### Acesso ao status
138
+
139
+ Para acompanhar o comportamento do ServeRest diante dos seus testes você pode acessar a página <http://localhost:3000/status>, que contém informações como:
140
+
141
+ - Uso de CPU.
142
+ - Uso da memória.
143
+ - Tempo de resposta.
144
+ - RPS (Requisições por segundo).
145
+
146
+ A página de status (_/status_) está disponível apenas localmente.
147
+
148
+ > Fez teste de carga? O que acha de compartilhar com o autor do projeto o repositório e o relatório final contendo dados de RPS para auxiliar o ServeRest a entender o comportamento de sua infra?
149
+
127
150
  ## Badge
128
151
 
129
152
  Criou repositório utilizando o ServeRest? Adicione o código abaixo no topo do README.md para ter a badge do projeto.
@@ -155,7 +178,15 @@ Sua empresa usa o ServeRest? Pergunte ao seu gerente ou equipe de marketing se s
155
178
  1. Subdomínio próprio (_nome-escolhido.serverest.dev_)
156
179
  1. Acesso a todas as requests e respostas feitas nos últimos 7 dias no subdomínio
157
180
 
158
- [![Apoiador individual - Open Collective](https://opencollective.com/serverest/tiers/patrocinador.svg)](https://opencollective.com/serverest)
181
+ [![Empresas - Open Collective](https://opencollective.com/serverest/tiers/patrocinador.svg)](https://opencollective.com/serverest)
182
+
183
+ Empresas que apoiam o ServeRest:
184
+
185
+ <p align="center">
186
+ <img alt="Logo da EBAC" src="https://user-images.githubusercontent.com/29241659/177436481-2a6a3324-1b0e-4d28-8a40-d885f54291c0.png#gh-light-mode-only" height="120">
187
+ <img alt="Logo da EBAC" src="https://user-images.githubusercontent.com/29241659/177436489-5d2f50f8-2fb3-4091-b822-446d24c83722.png#gh-dark-mode-only" height="120">
188
+ <img alt="Logo da Agilizei" src="https://user-images.githubusercontent.com/29241659/177436678-8187f90f-bb4a-4978-87ab-a03f2f80820f.png" height="124">
189
+ </p>
159
190
 
160
191
  ### Individuais
161
192
 
@@ -187,6 +218,8 @@ Veja aqui [como você pode contribuir](https://github.com/ServeRest/ServeRest/bl
187
218
  <td align="center"><a href="https://about.me/rustnnes"><img src="https://avatars1.githubusercontent.com/u/638445?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Diego Bandeira</b></sub></a><br /><a href="#infra-rustnnes" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
188
219
  <td align="center"><a href="https://github.com/maximilianoalves"><img src="https://avatars3.githubusercontent.com/u/11561118?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Maximiliano Alves</b></sub></a><br /><a href="#talk-maximilianoalves" title="Talks">📢</a></td>
189
220
  <td align="center"><a href="https://github.com/murilomaiaa"><img src="https://avatars.githubusercontent.com/u/56596799?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Murilo Maia</b></sub></a><br /><a href="https://github.com/ServeRest/ServeRest/commits?author=murilomaiaa" title="Code">💻</a></td>
221
+ <td align="center"><a href="https://github.com/crisnazario"><img src="https://avatars.githubusercontent.com/u/37200398?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Cristina Nazário</b></sub></a><br /><a href="#ideas-crisnazario" title="Ideas, Planning, & Feedback">🤔</a></td>
222
+ <td align="center"><a href="http://www.eduardosantos.dev"><img src="https://avatars.githubusercontent.com/u/10568807?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Eduardo Santos</b></sub></a><br /><a href="https://github.com/ServeRest/ServeRest/commits?author=edumaxsantos" title="Code">💻</a></td>
190
223
  </tr>
191
224
  </table>
192
225
 
package/docs/swagger.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "swagger": "2.0",
3
3
  "info": {
4
- "description": "**O ServeRest é uma API REST gratuita que simula uma loja virtual com intuito de servir de material de estudos de testes de API.**\n\n**Não deixe de seguir o [autor do projeto](https://github.com/PauloGoncalvesBH) e deixar um star no repositório: [github.com/ServeRest/ServeRest](https://github.com/ServeRest/ServeRest)**\n\nEssa página documenta todas as rotas e como acessá-las. Para mais detalhes do ServeRest (como executar localmente utilizando Docker ou NPM, alterar timeout de autenticação, etc) acesse [o repositório do ServeRest](https://github.com/serverest/serverest).\n\nEstá utilizando Postman? Importe o [JSON do Swagger](https://raw.githubusercontent.com/ServeRest/ServeRest/trunk/docs/swagger.json) para ter acesso às collections.\n\n\nMuito obrigado ♥ a todos que apoiam o projeto [financeiramente](https://opencollective.com/serverest#section-contributors) ou [com código, ideias e divulgação](https://github.com/ServeRest/ServeRest#contribuidores-), graças a vocês **mais de R$ 2000,00 foram doados para a ONG [Todas as letras](https://todasasletras.org/)** até o momento.\n\nO ServeRest possui um front, com status em beta, não deixe de conhecer: [front.serverest.dev](https://front.serverest.dev).\n\nPrecisa de apoio? [Abra uma issue](https://github.com/ServeRest/ServeRest/issues) ou contate o mantenedor do projeto:\n",
4
+ "description": "**O ServeRest é uma API REST gratuita que simula uma loja virtual com intuito de servir de material de estudos de testes de API.**\n\n**Não deixe de seguir o [autor do projeto](https://github.com/PauloGoncalvesBH) e deixar um star no repositório: [github.com/ServeRest/ServeRest](https://github.com/ServeRest/ServeRest)**\n\nEssa página documenta todas as rotas e como acessá-las. Para mais detalhes do ServeRest (como executar localmente utilizando Docker ou NPM, alterar timeout de autenticação, etc) acesse [o repositório do ServeRest](https://github.com/serverest/serverest).\n\nEstá utilizando Postman? Importe o [JSON do Swagger](https://raw.githubusercontent.com/ServeRest/ServeRest/trunk/docs/swagger.json) para ter acesso às collections.\n\nVai fazer teste de carga? Leia a seção '[Teste de Carga](https://github.com/ServeRest/ServeRest#teste-de-carga)'.\n\n\nMuito obrigado ♥ a todos que apoiam o projeto [financeiramente](https://opencollective.com/serverest#section-contributors) ou [com código, ideias e divulgação](https://github.com/ServeRest/ServeRest#contribuidores-), graças a vocês **mais de R$ 2000,00 foram doados para a ONG [Todas as letras](https://todasasletras.org/)** até o momento.\n\nO ServeRest possui um front, com status em beta, não deixe de conhecer: [front.serverest.dev](https://front.serverest.dev).\n\nPrecisa de apoio? [Abra uma issue](https://github.com/ServeRest/ServeRest/issues) ou contate o mantenedor do projeto:\n",
5
5
  "version": "2.X.X",
6
6
  "title": "ServeRest",
7
7
  "contact": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverest",
3
- "version": "2.25.4",
3
+ "version": "2.26.2",
4
4
  "description": "Servidor REST local de forma rápida e simples para estudo de testes de API",
5
5
  "author": "Paulo Gonçalves <paulorochag@hotmail.com> (https://www.linkedin.com/in/paulo-goncalves/)",
6
6
  "license": "GPL-3.0",
@@ -34,7 +34,7 @@
34
34
  "dev": "nodemon --legacy-watch -e json,js ./src/server.js --nodoc",
35
35
  "test:mutation": "stryker run ./test/stryker.conf.js",
36
36
  "test:mutation:diff": "stryker-diff-runner --path ./test/stryker.conf.js --branch trunk",
37
- "test:unit": "nyc --report-dir ./coverage-unit mocha --config test/unit/.mocharc.js",
37
+ "test:unit": "nyc --report-dir ./coverage-unit --check-coverage false mocha --config test/unit/.mocharc.js",
38
38
  "test:integration": "nyc --report-dir ./coverage-integration mocha --config test/integration/.mocharc.js",
39
39
  "test:e2e": "mocha --config test/integration/.mocharc.js --fgrep @skipE2E --invert",
40
40
  "test:smoke": "mocha --config test/integration/.mocharc.js --grep @smokeE2E",
@@ -52,6 +52,7 @@
52
52
  "express": "^4.17.1",
53
53
  "express-async-errors": "^3.1.1",
54
54
  "express-query-int": "^3.0.0",
55
+ "express-status-monitor": "1.3.4",
55
56
  "express-validation": "^3.0.8",
56
57
  "is-ci": "^3.0.0",
57
58
  "jsonwebtoken": "^8.5.1",
package/src/app.js CHANGED
@@ -10,7 +10,7 @@ const timeout = require('connect-timeout')
10
10
  const { join } = require('path')
11
11
  const swaggerUi = require('swagger-ui-express')
12
12
 
13
- const { formaDeExecucao, urlDocumentacao } = require('./utils/ambiente')
13
+ const { aplicacaoExecutandoLocalmente, formaDeExecucao, urlDocumentacao } = require('./utils/ambiente')
14
14
  const { conf } = require('./utils/conf')
15
15
  const errorHandler = require('./middlewares/error-handler')
16
16
  const logger = require('./utils/logger')
@@ -47,6 +47,11 @@ if (!conf.semHeaderDeSeguranca) {
47
47
  })
48
48
  }
49
49
 
50
+ /* istanbul ignore next */
51
+ if (aplicacaoExecutandoLocalmente() && !ehAmbienteDeTestes) {
52
+ app.use(require('express-status-monitor')({ title: 'ServeRest Status' }))
53
+ }
54
+
50
55
  logger(app)
51
56
 
52
57
  /* istanbul ignore next */
@@ -1,10 +1,8 @@
1
1
  'use strict'
2
2
 
3
- const authService = require('../services/auth-service')
4
3
  const constant = require('../utils/constants')
5
4
  const produtosService = require('../services/produtos-service')
6
5
  const service = require('../services/carrinhos-service')
7
- const usuariosService = require('../services/usuarios-service')
8
6
 
9
7
  exports.get = async (req, res) => {
10
8
  const carrinhos = await service.getAll(req.query)
@@ -15,59 +13,46 @@ exports.getOne = async (req, res) => {
15
13
  const { id } = req.params
16
14
  const carrinho = await service.getOne(id)
17
15
  if (!carrinho) {
18
- return res.status(400).json({ message: constant.CARRINHO_NAO_ENCONTRADO })
16
+ return res.status(400).json({ message: constant.CART_NOT_FOUND })
19
17
  }
20
18
  return res.status(200).send(carrinho)
21
19
  }
22
20
 
23
21
  exports.post = async (req, res) => {
24
- const { email, password } = authService.verifyToken(req.headers.authorization)
25
- const { _id } = await usuariosService.getDadosDoUsuario({ email, password })
26
- const usuarioJaPossuiCarrinho = await service.existeCarrinho({ idUsuario: _id })
27
- if (usuarioJaPossuiCarrinho) {
28
- return res.status(400).send({ message: constant.LIMITE_1_CARRINHO })
22
+ const { idUsuario, possuiCarrinho } = await service.usuarioJaPossuiCarrinho(req.headers.authorization)
23
+ if (possuiCarrinho) {
24
+ return res.status(400).send({ message: constant.LIMIT_JUST_ONE_CART })
29
25
  }
30
26
 
31
27
  const idProdutosDuplicados = service.extrairProdutosDuplicados(req.body.produtos)
32
- const temProdutosDuplicados = typeof idProdutosDuplicados[0] !== 'undefined'
28
+ const temProdutosDuplicados = isNotUndefined(idProdutosDuplicados[0])
33
29
  if (temProdutosDuplicados) {
34
- return res.status(400).send({ message: constant.CARRINHO_COM_PRODUTO_DUPLICADO, idProdutosDuplicados })
30
+ return res.status(400).send({ message: constant.CART_WITH_DUPLICATE_PRODUCT, idProdutosDuplicados })
35
31
  }
36
32
 
37
- const produtos = req.body.produtos
38
- for (let index = 0; index < produtos.length; index++) {
39
- produtos[index].quantidade = parseInt(produtos[index].quantidade)
40
- const { idProduto, quantidade } = produtos[index]
41
- if (!await produtosService.existeProduto({ _id: idProduto })) {
42
- return res.status(400).send({ message: constant.IDPRODUTO_INVALIDO, item: { index, idProduto, quantidade } })
33
+ const { produtos } = req.body
34
+ const produtosComPrecoUnitario = []
35
+ for (const produto of produtos) {
36
+ const { precoUnitario: preco, error } = await produtosService.getPrecoUnitarioOuErro(produto)
37
+ if (error) {
38
+ const index = produtos.indexOf(produto)
39
+ const item = { ...error.item, index }
40
+ return res.status(error.statusCode).send({ message: error.message, item })
43
41
  }
44
-
45
- const { quantidade: quantidadeEstoque, preco } = await produtosService.getDadosDoProduto({ _id: idProduto })
46
- if (quantidade > quantidadeEstoque) {
47
- return res.status(400).send({ message: constant.ESTOQUE_INSUFICIENTE, item: { index, idProduto, quantidade, quantidadeEstoque } })
48
- }
49
- Object.assign(produtos[index], { precoUnitario: preco })
50
- }
51
- let precoTotal = 0
52
- let quantidadeTotal = 0
53
- for (let index = 0; index < produtos.length; index++) {
54
- const { idProduto, quantidade } = produtos[index]
55
- const { quantidade: quantidadeEmEstoque, preco } = await produtosService.getDadosDoProduto({ _id: idProduto })
56
- const novaQuantidade = quantidadeEmEstoque - quantidade
57
- await produtosService.updateById(idProduto, { $set: { quantidade: novaQuantidade } })
58
- precoTotal += preco * quantidade
59
- quantidadeTotal += quantidade
42
+ produtosComPrecoUnitario.push({ ...produto, precoUnitario: preco })
60
43
  }
44
+ const precoTotal = await service.precoTotal(produtosComPrecoUnitario)
45
+ const quantidadeTotal = await service.quantidadeTotal(produtosComPrecoUnitario)
61
46
 
62
- Object.assign(req.body, { precoTotal, quantidadeTotal, idUsuario: _id })
47
+ const carrinho = { produtos: produtosComPrecoUnitario, precoTotal, quantidadeTotal, idUsuario }
63
48
 
64
- const dadosCadastrados = await service.criarCarrinho(req.body)
65
- res.status(201).send({ message: constant.POST_SUCESS, _id: dadosCadastrados._id })
49
+ const dadosCadastrados = await service.criarCarrinho(carrinho)
50
+ res.status(201).send({ message: constant.POST_SUCCESS, _id: dadosCadastrados._id })
66
51
  }
67
52
 
68
53
  exports.cancelarCompra = async (req, res) => {
69
54
  const carrinhoDoUsuario = await service.getCarrinhoDoUsuario(req.headers.authorization)
70
- const usuarioTemCarrinho = typeof carrinhoDoUsuario[0] !== 'undefined'
55
+ const usuarioTemCarrinho = isNotUndefined(carrinhoDoUsuario[0])
71
56
 
72
57
  if (usuarioTemCarrinho) {
73
58
  const produtos = carrinhoDoUsuario[0].produtos
@@ -79,18 +64,22 @@ exports.cancelarCompra = async (req, res) => {
79
64
  })
80
65
 
81
66
  await service.deleteById(carrinhoDoUsuario[0]._id)
82
- return res.status(200).send({ message: `${constant.DELETE_SUCESS}. ${constant.ESTOQUE_REABASTECIDO}` })
67
+ return res.status(200).send({ message: `${constant.DELETE_SUCCESS}. ${constant.REPLENISHED_STOCK}` })
83
68
  }
84
69
 
85
- res.status(200).send({ message: constant.SEM_CARRINHO })
70
+ res.status(200).send({ message: constant.NO_CART })
86
71
  }
87
72
 
88
73
  exports.concluirCompra = async (req, res) => {
89
74
  const carrinhoDoUsuario = await service.getCarrinhoDoUsuario(req.headers.authorization)
90
- const usuarioTemCarrinho = typeof carrinhoDoUsuario[0] !== 'undefined'
75
+ const usuarioTemCarrinho = isNotUndefined(carrinhoDoUsuario[0])
91
76
  if (usuarioTemCarrinho) {
92
77
  await service.deleteById(carrinhoDoUsuario[0]._id)
93
- return res.status(200).send({ message: constant.DELETE_SUCESS })
78
+ return res.status(200).send({ message: constant.DELETE_SUCCESS })
94
79
  }
95
- res.status(200).send({ message: constant.SEM_CARRINHO })
80
+ res.status(200).send({ message: constant.NO_CART })
96
81
  }
82
+
83
+ const isUndefined = (object) => typeof object === 'undefined'
84
+
85
+ const isNotUndefined = (object) => !isUndefined(object)
@@ -10,7 +10,7 @@ exports.post = async (req, res) => {
10
10
  const token = authService.createToken(req.body)
11
11
  /* istanbul ignore next */
12
12
  const authorization = conf.semBearer ? token : `Bearer ${token}`
13
- return res.status(200).send({ message: constant.LOGIN_SUCESS, authorization })
13
+ return res.status(200).send({ message: constant.LOGIN_SUCCESS, authorization })
14
14
  }
15
15
  res.status(401).send({ message: constant.LOGIN_FAIL })
16
16
  }
@@ -13,17 +13,17 @@ exports.getOne = async (req, res) => {
13
13
  const { id } = req.params
14
14
  const produto = await service.getOne(id)
15
15
  if (!produto) {
16
- return res.status(400).json({ message: constant.PRODUTO_NAO_ENCONTRADO })
16
+ return res.status(400).json({ message: constant.PRODUCT_NOT_FOUND })
17
17
  }
18
18
  return res.status(200).send(produto)
19
19
  }
20
20
 
21
21
  exports.post = async (req, res) => {
22
22
  if (await service.existeProduto({ nome: req.body.nome.trim() })) {
23
- return res.status(400).send({ message: constant.NOME_JA_USADO })
23
+ return res.status(400).send({ message: constant.NAME_ALREADY_USED })
24
24
  }
25
25
  const dadosCadastrados = await service.criarProduto(req.body)
26
- res.status(201).send({ message: constant.POST_SUCESS, _id: dadosCadastrados._id })
26
+ res.status(201).send({ message: constant.POST_SUCCESS, _id: dadosCadastrados._id })
27
27
  }
28
28
 
29
29
  exports.delete = async (req, res) => {
@@ -31,19 +31,19 @@ exports.delete = async (req, res) => {
31
31
  const usuarioTemCarrinho = typeof carrinhoDoUsuario[0] !== 'undefined'
32
32
  if (usuarioTemCarrinho) {
33
33
  const idCarrinhos = carrinhoDoUsuario.map((carrinhos) => { return carrinhos._id })
34
- return res.status(400).send({ message: constant.EXCLUIR_PRODUTO_COM_CARRINHO, idCarrinhos })
34
+ return res.status(400).send({ message: constant.DELETE_PRODUCT_WITH_CART, idCarrinhos })
35
35
  }
36
36
  const quantidadeRegistrosExcluidos = await service.deleteById(req.params.id)
37
- const message = quantidadeRegistrosExcluidos === 0 ? constant.DELETE_NONE : constant.DELETE_SUCESS
37
+ const message = quantidadeRegistrosExcluidos === 0 ? constant.DELETE_NONE : constant.DELETE_SUCCESS
38
38
  res.status(200).send({ message })
39
39
  }
40
40
 
41
41
  exports.put = async (req, res) => {
42
42
  const idInexistenteENomeRepetido = await service.existeProduto({ nome: req.body.nome.trim(), $not: { _id: req.params.id } })
43
43
  if (idInexistenteENomeRepetido) {
44
- return res.status(400).send({ message: constant.NOME_JA_USADO })
44
+ return res.status(400).send({ message: constant.NAME_ALREADY_USED })
45
45
  }
46
46
  const registroCriado = await service.createOrUpdateById(req.params.id, req.body)
47
- if (registroCriado._id !== req.params.id) { return res.status(201).send({ message: constant.POST_SUCESS, _id: registroCriado._id }) }
48
- res.status(200).send({ message: constant.PUT_SUCESS })
47
+ if (registroCriado._id !== req.params.id) { return res.status(201).send({ message: constant.POST_SUCCESS, _id: registroCriado._id }) }
48
+ res.status(200).send({ message: constant.PUT_SUCCESS })
49
49
  }
@@ -13,36 +13,36 @@ exports.getOne = async (req, res) => {
13
13
  const { id } = req.params
14
14
  const usuario = await service.getOne(id)
15
15
  if (!usuario) {
16
- return res.status(400).json({ message: constant.USUARIO_NAO_ENCONTRADO })
16
+ return res.status(400).json({ message: constant.USER_NOT_FOUND })
17
17
  }
18
18
  return res.status(200).send(usuario)
19
19
  }
20
20
 
21
21
  exports.post = async (req, res) => {
22
22
  if (await service.existeUsuario({ email: req.body.email })) {
23
- return res.status(400).send({ message: constant.EMAIL_JA_USADO })
23
+ return res.status(400).send({ message: constant.EMAIL_ALREADY_USED })
24
24
  }
25
25
  const dadosCadastrados = await service.createUser(req.body)
26
- res.status(201).send({ message: constant.POST_SUCESS, _id: dadosCadastrados._id })
26
+ res.status(201).send({ message: constant.POST_SUCCESS, _id: dadosCadastrados._id })
27
27
  }
28
28
 
29
29
  exports.delete = async (req, res) => {
30
30
  const carrinhoDoUsuario = await carrinhosService.getAll({ idUsuario: req.params.id })
31
31
  const usuarioTemCarrinho = typeof carrinhoDoUsuario[0] !== 'undefined'
32
32
  if (usuarioTemCarrinho) {
33
- return res.status(400).send({ message: constant.EXCLUIR_USUARIO_COM_CARRINHO, idCarrinho: carrinhoDoUsuario[0]._id })
33
+ return res.status(400).send({ message: constant.DELETE_USER_WITH_CART, idCarrinho: carrinhoDoUsuario[0]._id })
34
34
  }
35
35
  const quantidadeRegistrosExcluidos = await service.deleteById(req.params.id)
36
- const message = quantidadeRegistrosExcluidos === 0 ? constant.DELETE_NONE : constant.DELETE_SUCESS
36
+ const message = quantidadeRegistrosExcluidos === 0 ? constant.DELETE_NONE : constant.DELETE_SUCCESS
37
37
  res.status(200).send({ message })
38
38
  }
39
39
 
40
40
  exports.put = async (req, res) => {
41
41
  const idInexistenteEEmailRepetido = await service.existeUsuario({ email: req.body.email, $not: { _id: req.params.id } })
42
42
  if (idInexistenteEEmailRepetido) {
43
- return res.status(400).send({ message: constant.EMAIL_JA_USADO })
43
+ return res.status(400).send({ message: constant.EMAIL_ALREADY_USED })
44
44
  }
45
45
  const registroCriado = await service.createOrUpdateById(req.params.id, req.body)
46
- if (registroCriado._id !== req.params.id) { return res.status(201).send({ message: constant.POST_SUCESS, _id: registroCriado._id }) }
47
- res.status(200).send({ message: constant.PUT_SUCESS })
46
+ if (registroCriado._id !== req.params.id) { return res.status(201).send({ message: constant.POST_SUCCESS, _id: registroCriado._id }) }
47
+ res.status(200).send({ message: constant.PUT_SUCCESS })
48
48
  }
@@ -6,18 +6,18 @@ const usuariosService = require('../services/usuarios-service')
6
6
 
7
7
  exports.checkAdm = async (req, res, next) => {
8
8
  if (!await tokenValido(req.headers)) {
9
- return res.status(401).send({ message: constant.TOKEN_INVALID })
9
+ return res.status(401).send({ message: constant.INVALID_TOKEN })
10
10
  }
11
11
  const tokenDecodificado = authService.verifyToken(req.headers.authorization)
12
12
  if (!await usuariosService.usuarioEhAdministrador(tokenDecodificado)) {
13
- return res.status(403).send({ message: constant.NECESSARIO_ADM })
13
+ return res.status(403).send({ message: constant.REQUIRED_ADMIN })
14
14
  }
15
15
  next()
16
16
  }
17
17
 
18
18
  exports.checkToken = async (req, res, next) => {
19
19
  if (!await tokenValido(req.headers)) {
20
- return res.status(401).send({ message: constant.TOKEN_INVALID })
20
+ return res.status(401).send({ message: constant.INVALID_TOKEN })
21
21
  }
22
22
  next()
23
23
  }
@@ -1,4 +1,4 @@
1
- const { INTERNAL_ERROR } = require('../utils/constants')
1
+ const { INTERNAL_ERROR, TIMEOUT } = require('../utils/constants')
2
2
  const montarMensagemDeErroDeSchema = require('../utils/montarMensagemDeErroDeSchema')
3
3
 
4
4
  function errorHandler (error, _req, res, _next) {
@@ -10,9 +10,13 @@ function errorHandler (error, _req, res, _next) {
10
10
  if (error.type === 'entity.parse.failed') {
11
11
  console.log('Erro 500')
12
12
  return res.status(500).json({
13
- message: 'Adicione aspas em todos os valores. Esse problema está sendo investigado na issue https://github.com/ServeRest/ServeRest/issues/225'
13
+ message: 'Adicione aspas em todos os valores. Para mais informações acesse a issue https://github.com/ServeRest/ServeRest/issues/225'
14
14
  })
15
15
  }
16
+ /* istanbul ignore if */
17
+ if (error.code === 'ETIMEDOUT') {
18
+ return res.status(408).json({ message: TIMEOUT })
19
+ }
16
20
  return res.status(500).json({ message: INTERNAL_ERROR, error })
17
21
  }
18
22
 
@@ -2,17 +2,36 @@
2
2
 
3
3
  const { RateLimiterMemory } = require('rate-limiter-flexible')
4
4
 
5
+ const { aplicacaoExecutandoLocalmente } = require('../utils/ambiente')
6
+
5
7
  const rateLimiter = new RateLimiterMemory({
6
- points: 150, // requests
7
- duration: 1 // segundo por IP
8
+ points: 250, // requests
9
+ duration: 2 // segundo por IP
8
10
  })
9
11
 
10
- // Adicionar header 'monitor: false' quando atingir o limite definido para não enviar informações para o moesif.
12
+ // Irá retornar mensagem de erro se o teste de carga:
13
+ // 1. Estiver sendo executado em localhost sem o header 'monitor'.
14
+ // 2. Estiver sendo executado fora de localhost.
11
15
  module.exports = async (req, res, next) => {
16
+ const reqContainsHeaderMonitor = req.rawHeaders.includes('monitor')
17
+
18
+ const messageLoadTest = 'Foi detectado comportamento equivalente a teste de carga'
19
+ const messageAdjust = 'Leia a seção sobre teste de carga https://github.com/ServeRest/ServeRest#teste-de-carga e faça o ajuste necessário.'
20
+
12
21
  await rateLimiter.consume(req.ip)
13
22
  .then(() => next())
14
23
  .catch(() => {
15
- req.headers.monitor = false
24
+ if (aplicacaoExecutandoLocalmente()) {
25
+ if (!reqContainsHeaderMonitor) {
26
+ return res.status(429).send({
27
+ message: `${messageLoadTest} com ausência do header 'monitor'. ${messageAdjust}`
28
+ })
29
+ }
30
+ } else {
31
+ return res.status(429).send({
32
+ message: `${messageLoadTest}. ${messageAdjust}`
33
+ })
34
+ }
16
35
  next()
17
36
  })
18
37
  }
@@ -6,6 +6,7 @@ const { join } = require('path')
6
6
  const alterarValoresParaRegex = require('../utils/alterarValoresParaRegex')
7
7
  const authService = require('../services/auth-service')
8
8
  const usuariosService = require('../services/usuarios-service')
9
+ const produtosService = require('../services/produtos-service')
9
10
 
10
11
  const datastore = Datastore.create({ filename: join(__dirname, '../data/carrinhos.db'), autoload: true })
11
12
 
@@ -42,7 +43,33 @@ exports.deleteById = async id => {
42
43
  }
43
44
 
44
45
  exports.getCarrinhoDoUsuario = async (authorization) => {
46
+ const _id = await idUsuario(authorization)
47
+ return this.getAll({ idUsuario: _id })
48
+ }
49
+
50
+ exports.usuarioJaPossuiCarrinho = async (authorization) => {
51
+ const _id = await idUsuario(authorization)
52
+ return {
53
+ idUsuario: _id,
54
+ possuiCarrinho: await this.existeCarrinho({ idUsuario: _id })
55
+ }
56
+ }
57
+
58
+ exports.precoTotal = async (produtos) => {
59
+ return produtos.reduce(async (precoAnterior, produto) => {
60
+ return (await precoAnterior) + produto.precoUnitario * produto.quantidade
61
+ }, Promise.resolve(0))
62
+ }
63
+
64
+ exports.quantidadeTotal = async (produtos) => {
65
+ return produtos.reduce(async (quantidadeAnterior, produto) => {
66
+ await produtosService.updateQuantidade(produto)
67
+ return (await quantidadeAnterior) + produto.quantidade
68
+ }, Promise.resolve(0))
69
+ }
70
+
71
+ const idUsuario = async (authorization) => {
45
72
  const { email, password } = authService.verifyToken(authorization)
46
- const { _id: idUsuario } = await usuariosService.getDadosDoUsuario({ email, password })
47
- return this.getAll({ idUsuario })
73
+ const { _id } = await usuariosService.getDadosDoUsuario({ email, password })
74
+ return _id
48
75
  }
@@ -1,5 +1,6 @@
1
1
  'use strict'
2
2
 
3
+ const constant = require('../utils/constants')
3
4
  const Datastore = require('nedb-promises')
4
5
  const { join } = require('path')
5
6
 
@@ -24,11 +25,32 @@ exports.existeProduto = pesquisa => {
24
25
  return datastore.count(pesquisa)
25
26
  }
26
27
 
28
+ exports.updateQuantidade = async ({ idProduto, quantidade }) => {
29
+ const { quantidade: quantidadeEmEstoque } = await this.getDadosDoProduto({ _id: idProduto })
30
+ const novaQuantidade = quantidadeEmEstoque - quantidade
31
+ await this.updateById(idProduto, { $set: { quantidade: novaQuantidade } })
32
+ }
33
+
27
34
  exports.criarProduto = async body => {
28
35
  body = formatarValores(body)
29
36
  return datastore.insert(body)
30
37
  }
31
38
 
39
+ exports.getPrecoUnitarioOuErro = async (produto) => {
40
+ const quantidade = parseInt(produto.quantidade)
41
+ const { idProduto } = produto
42
+ const existePedido = await this.existeProduto({ _id: idProduto })
43
+ if (!existePedido) {
44
+ return { error: { statusCode: 400, message: constant.PRODUCT_NOT_FOUND, item: { idProduto, quantidade } } }
45
+ }
46
+
47
+ const { quantidade: quantidadeEstoque, preco } = await this.getDadosDoProduto({ _id: idProduto })
48
+ if (quantidade > quantidadeEstoque) {
49
+ return { error: { statusCode: 400, message: constant.INSUFFICIENT_STOCK, item: { idProduto, quantidade, quantidadeEstoque } } }
50
+ }
51
+ return { precoUnitario: preco }
52
+ }
53
+
32
54
  exports.deleteById = async id => {
33
55
  return datastore.remove({ _id: id }, {})
34
56
  }
@@ -12,6 +12,10 @@ function formaDeExecucao () {
12
12
  return 'npm'
13
13
  }
14
14
 
15
+ const aplicacaoExecutandoLocalmente = () => {
16
+ return (formaDeExecucao() === 'npm' || formaDeExecucao() === 'docker')
17
+ }
18
+
15
19
  function urlDocumentacao () {
16
20
  switch (formaDeExecucao()) {
17
21
  case 'serverest.dev':
@@ -26,6 +30,7 @@ function urlDocumentacao () {
26
30
  }
27
31
 
28
32
  module.exports = {
33
+ aplicacaoExecutandoLocalmente,
29
34
  formaDeExecucao,
30
35
  urlDocumentacao
31
36
  }
@@ -2,25 +2,25 @@
2
2
 
3
3
  module.exports = {
4
4
  INTERNAL_ERROR: 'Abra uma issue informando essa resposta. https://github.com/ServeRest/ServeRest/issues',
5
- POST_SUCESS: 'Cadastro realizado com sucesso',
6
- DELETE_SUCESS: 'Registro excluído com sucesso',
5
+ TIMEOUT: 'Timeout da requisição atingido',
6
+ POST_SUCCESS: 'Cadastro realizado com sucesso',
7
+ DELETE_SUCCESS: 'Registro excluído com sucesso',
7
8
  DELETE_NONE: 'Nenhum registro excluído',
8
- PUT_SUCESS: 'Registro alterado com sucesso',
9
- LOGIN_SUCESS: 'Login realizado com sucesso',
9
+ PUT_SUCCESS: 'Registro alterado com sucesso',
10
+ LOGIN_SUCCESS: 'Login realizado com sucesso',
10
11
  LOGIN_FAIL: 'Email e/ou senha inválidos',
11
- EMAIL_JA_USADO: 'Este email já está sendo usado',
12
- NOME_JA_USADO: 'Já existe produto com esse nome',
13
- NECESSARIO_ADM: 'Rota exclusiva para administradores',
14
- TOKEN_INVALID: 'Token de acesso ausente, inválido, expirado ou usuário do token não existe mais',
15
- LIMITE_1_CARRINHO: 'Não é permitido ter mais de 1 carrinho',
16
- IDPRODUTO_INVALIDO: 'Produto não encontrado',
17
- ESTOQUE_INSUFICIENTE: 'Produto não possui quantidade suficiente',
18
- EXCLUIR_USUARIO_COM_CARRINHO: 'Não é permitido excluir usuário com carrinho cadastrado',
19
- EXCLUIR_PRODUTO_COM_CARRINHO: 'Não é permitido excluir produto que faz parte de carrinho',
20
- CARRINHO_COM_PRODUTO_DUPLICADO: 'Não é permitido possuir produto duplicado',
21
- SEM_CARRINHO: 'Não foi encontrado carrinho para esse usuário',
22
- ESTOQUE_REABASTECIDO: 'Estoque dos produtos reabastecido',
23
- USUARIO_NAO_ENCONTRADO: 'Usuário não encontrado',
24
- CARRINHO_NAO_ENCONTRADO: 'Carrinho não encontrado',
25
- PRODUTO_NAO_ENCONTRADO: 'Produto não encontrado'
12
+ EMAIL_ALREADY_USED: 'Este email já está sendo usado',
13
+ NAME_ALREADY_USED: 'Já existe produto com esse nome',
14
+ REQUIRED_ADMIN: 'Rota exclusiva para administradores',
15
+ INVALID_TOKEN: 'Token de acesso ausente, inválido, expirado ou usuário do token não existe mais',
16
+ LIMIT_JUST_ONE_CART: 'Não é permitido ter mais de 1 carrinho',
17
+ INSUFFICIENT_STOCK: 'Produto não possui quantidade suficiente',
18
+ DELETE_USER_WITH_CART: 'Não é permitido excluir usuário com carrinho cadastrado',
19
+ DELETE_PRODUCT_WITH_CART: 'Não é permitido excluir produto que faz parte de carrinho',
20
+ CART_WITH_DUPLICATE_PRODUCT: 'Não é permitido possuir produto duplicado',
21
+ NO_CART: 'Não foi encontrado carrinho para esse usuário',
22
+ REPLENISHED_STOCK: 'Estoque dos produtos reabastecido',
23
+ USER_NOT_FOUND: 'Usuário não encontrado',
24
+ CART_NOT_FOUND: 'Carrinho não encontrado',
25
+ PRODUCT_NOT_FOUND: 'Produto não encontrado'
26
26
  }