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 +26 -0
- package/README.md +34 -1
- package/docs/swagger.json +1 -1
- package/package.json +3 -2
- package/src/app.js +6 -1
- package/src/controllers/carrinhos-controller.js +30 -41
- package/src/controllers/login-controller.js +1 -1
- package/src/controllers/produtos-controller.js +8 -8
- package/src/controllers/usuarios-controller.js +8 -8
- package/src/middlewares/authentication-middleware.js +3 -3
- package/src/middlewares/error-handler.js +6 -2
- package/src/middlewares/rate-limiter.js +23 -4
- package/src/services/carrinhos-service.js +29 -2
- package/src/services/produtos-service.js +22 -0
- package/src/utils/ambiente.js +5 -0
- package/src/utils/constants.js +19 -19
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
|
-
[](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.
|
|
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.
|
|
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 {
|
|
25
|
-
|
|
26
|
-
|
|
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 =
|
|
28
|
+
const temProdutosDuplicados = isNotUndefined(idProdutosDuplicados[0])
|
|
33
29
|
if (temProdutosDuplicados) {
|
|
34
|
-
return res.status(400).send({ message: constant.
|
|
30
|
+
return res.status(400).send({ message: constant.CART_WITH_DUPLICATE_PRODUCT, idProdutosDuplicados })
|
|
35
31
|
}
|
|
36
32
|
|
|
37
|
-
const produtos = req.body
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const {
|
|
41
|
-
if (
|
|
42
|
-
|
|
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
|
-
|
|
47
|
+
const carrinho = { produtos: produtosComPrecoUnitario, precoTotal, quantidadeTotal, idUsuario }
|
|
63
48
|
|
|
64
|
-
const dadosCadastrados = await service.criarCarrinho(
|
|
65
|
-
res.status(201).send({ message: constant.
|
|
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 =
|
|
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.
|
|
67
|
+
return res.status(200).send({ message: `${constant.DELETE_SUCCESS}. ${constant.REPLENISHED_STOCK}` })
|
|
83
68
|
}
|
|
84
69
|
|
|
85
|
-
res.status(200).send({ message: constant.
|
|
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 =
|
|
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.
|
|
78
|
+
return res.status(200).send({ message: constant.DELETE_SUCCESS })
|
|
94
79
|
}
|
|
95
|
-
res.status(200).send({ message: constant.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
48
|
-
res.status(200).send({ message: constant.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
47
|
-
res.status(200).send({ message: constant.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
7
|
-
duration:
|
|
8
|
+
points: 250, // requests
|
|
9
|
+
duration: 2 // segundo por IP
|
|
8
10
|
})
|
|
9
11
|
|
|
10
|
-
//
|
|
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
|
-
|
|
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
|
|
47
|
-
return
|
|
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
|
}
|
package/src/utils/ambiente.js
CHANGED
|
@@ -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
|
}
|
package/src/utils/constants.js
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
}
|