serverest 3.1.1 → 3.2.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "serverest",
3
- "version": "3.1.1",
3
+ "version": "3.2.0",
4
4
  "description": "Servidor REST local de forma rápida e simples para estudo de testes de API",
5
5
  "author": "Paulo Gonçalves <author@serverest.dev> (https://www.linkedin.com/in/paulo-goncalves/)",
6
6
  "license": "GPL-3.0",
@@ -34,11 +34,9 @@ exports.post = async (req, res) => {
34
34
  }
35
35
 
36
36
  exports.delete = async (req, res) => {
37
- const carrinhoDoUsuario = await carrinhosService.getAll({ produtos: { $elemMatch: { idProduto: req.params.id } } })
38
- const usuarioTemCarrinho = carrinhoDoUsuario.length > 0
39
- if (usuarioTemCarrinho) {
40
- const idCarrinhos = carrinhoDoUsuario.map(carrinhos => carrinhos._id)
41
- return res.status(400).send({ message: constant.DELETE_PRODUCT_WITH_CART, idCarrinhos })
37
+ const carrinhoComProduto = await carrinhosService.existeCarrinhoComProduto(req.params.id)
38
+ if (carrinhoComProduto) {
39
+ return res.status(400).send({ message: constant.DELETE_PRODUCT_WITH_CART, idCarrinhos: [carrinhoComProduto._id] })
42
40
  }
43
41
  const quantidadeRegistrosExcluidos = await service.deleteById(req.params.id)
44
42
  const message = quantidadeRegistrosExcluidos === 0 ? constant.DELETE_NONE : constant.DELETE_SUCCESS
@@ -27,10 +27,9 @@ exports.post = async (req, res) => {
27
27
  }
28
28
 
29
29
  exports.delete = async (req, res) => {
30
- const carrinhoDoUsuario = await carrinhosService.getAll({ idUsuario: req.params.id })
31
- const usuarioTemCarrinho = typeof carrinhoDoUsuario[0] !== 'undefined'
32
- if (usuarioTemCarrinho) {
33
- return res.status(400).send({ message: constant.DELETE_USER_WITH_CART, idCarrinho: carrinhoDoUsuario[0]._id })
30
+ const carrinhoDoUsuario = await carrinhosService.existeCarrinho({ idUsuario: req.params.id })
31
+ if (carrinhoDoUsuario) {
32
+ return res.status(400).send({ message: constant.DELETE_USER_WITH_CART, idCarrinho: carrinhoDoUsuario._id })
34
33
  }
35
34
  const quantidadeRegistrosExcluidos = await service.deleteById(req.params.id)
36
35
  const message = quantidadeRegistrosExcluidos === 0 ? constant.DELETE_NONE : constant.DELETE_SUCCESS
@@ -1 +1,9 @@
1
1
  {"produtos":[{"idProduto":"BeeJh5lz3k6kSIzA","quantidade":2,"precoUnitario":470},{"idProduto":"K6leHdftCeOJj8BJ","quantidade":1,"precoUnitario":5240}],"precoTotal":6180,"quantidadeTotal":3,"idUsuario":"0uxuPY0cbmQhpEz1","_id":"qbMqntef4iTOwWfg"}
2
+ {"$$indexCreated":{"fieldName":"idUsuario","unique":false,"sparse":false}}
3
+ {"$$indexCreated":{"fieldName":"precoTotal","unique":false,"sparse":false}}
4
+ {"$$indexCreated":{"fieldName":"quantidadeTotal","unique":false,"sparse":false}}
5
+ {"$$indexCreated":{"fieldName":"produtos.idProduto","unique":false,"sparse":false}}
6
+ {"$$indexCreated":{"fieldName":"idUsuario"}}
7
+ {"$$indexCreated":{"fieldName":"precoTotal"}}
8
+ {"$$indexCreated":{"fieldName":"quantidadeTotal"}}
9
+ {"$$indexCreated":{"fieldName":"produtos.idProduto"}}
@@ -1,2 +1,8 @@
1
1
  {"nome":"Logitech MX Vertical","preco":470,"descricao":"Mouse","quantidade":382,"_id":"BeeJh5lz3k6kSIzA"}
2
2
  {"nome":"Samsung 60 polegadas","preco":5240,"descricao":"TV","quantidade":49977,"_id":"K6leHdftCeOJj8BJ"}
3
+ {"$$indexCreated":{"fieldName":"nome","unique":false,"sparse":true}}
4
+ {"$$indexCreated":{"fieldName":"preco","unique":false,"sparse":false}}
5
+ {"$$indexCreated":{"fieldName":"quantidade","unique":false,"sparse":false}}
6
+ {"$$indexCreated":{"fieldName":"nome","sparse":true}}
7
+ {"$$indexCreated":{"fieldName":"preco"}}
8
+ {"$$indexCreated":{"fieldName":"quantidade"}}
@@ -1 +1,3 @@
1
1
  {"nome":"Fulano da Silva","email":"fulano@qa.com","password":"teste","administrador":"true","_id":"0uxuPY0cbmQhpEz1"}
2
+ {"$$indexCreated":{"fieldName":"email","unique":false,"sparse":true}}
3
+ {"$$indexCreated":{"fieldName":"email","sparse":true}}
@@ -9,6 +9,12 @@ function errorHandler (error, _req, res, _next) {
9
9
  if (erroDeSchema) {
10
10
  return res.status(400).json(montarMensagemDeErroDeSchema(error))
11
11
  }
12
+ if (error.name === 'URIError') {
13
+ log({ message: `URI decode error: ${error.message}` })
14
+ return res.status(400).json({
15
+ message: 'Parâmetro de URL inválido. Verifique os caracteres especiais e tente novamente.'
16
+ })
17
+ }
12
18
  // https://github.com/expressjs/body-parser#errors
13
19
  if (error.type === 'entity.parse.failed') {
14
20
  log({ message: 'Entity parse error, user sending request without proper quotation marks.' })
@@ -10,6 +10,16 @@ const produtosService = require('../services/produtos-service')
10
10
 
11
11
  const datastore = Datastore.create({ filename: join(__dirname, '../data/carrinhos.db'), autoload: true })
12
12
 
13
+ // Index frequently searched fields for better query performance
14
+ /* istanbul ignore next */
15
+ datastore.ensureIndex({ fieldName: 'idUsuario' })
16
+ /* istanbul ignore next */
17
+ datastore.ensureIndex({ fieldName: 'precoTotal' })
18
+ /* istanbul ignore next */
19
+ datastore.ensureIndex({ fieldName: 'quantidadeTotal' })
20
+ /* istanbul ignore next */
21
+ datastore.ensureIndex({ fieldName: 'produtos.idProduto' })
22
+
13
23
  exports.getAll = queryString => {
14
24
  queryString = alterarValoresParaRegex(queryString)
15
25
  return datastore.find(queryString)
@@ -23,6 +33,10 @@ exports.existeCarrinho = pesquisa => {
23
33
  return datastore.findOne(pesquisa)
24
34
  }
25
35
 
36
+ exports.existeCarrinhoComProduto = idProduto => {
37
+ return datastore.findOne({ produtos: { $elemMatch: { idProduto } } })
38
+ }
39
+
26
40
  exports.criarCarrinho = async body => {
27
41
  return datastore.insert(body)
28
42
  }
@@ -39,17 +53,15 @@ exports.extrairProdutosDuplicados = arrayProdutos => {
39
53
  }
40
54
 
41
55
  exports.getProdutosComPrecoUnitarioOuErro = async arrayProdutos => {
42
- const produtosComPrecoUnitario = []
43
- for (const produto of arrayProdutos) {
44
- const { precoUnitario: preco, error } = await produtosService.getPrecoUnitarioOuErro(produto)
45
- if (error) {
46
- const index = arrayProdutos.indexOf(produto)
47
- const item = { ...error.item, index }
48
- return { error: { ...error, item } }
49
- }
50
- produtosComPrecoUnitario.push({ ...produto, precoUnitario: preco })
56
+ // Use batch lookup instead of per-item queries to reduce round-trips
57
+ const { produtosComPreco, error } = await produtosService.getPrecosUnitariosPorLote(arrayProdutos)
58
+
59
+ if (error) {
60
+ const item = { ...error.item, index: error.index }
61
+ return { error: { ...error, item } }
51
62
  }
52
- return { produtosComPrecoUnitario }
63
+
64
+ return { produtosComPrecoUnitario: produtosComPreco }
53
65
  }
54
66
 
55
67
  exports.deleteById = async id => {
@@ -80,24 +92,20 @@ exports.usuarioJaPossuiCarrinho = async (authorization) => {
80
92
  }
81
93
 
82
94
  exports.precoTotal = async (produtos) => {
83
- return produtos.reduce(async (precoAnterior, produto) => {
84
- return (await precoAnterior) + produto.precoUnitario * produto.quantidade
85
- }, Promise.resolve(0))
95
+ return produtos.reduce((total, produto) => {
96
+ return total + (produto.precoUnitario * produto.quantidade)
97
+ }, 0)
86
98
  }
87
99
 
88
100
  exports.quantidadeTotal = async (produtos) => {
89
- return produtos.reduce(async (quantidadeAnterior, produto) => {
90
- await produtosService.updateQuantidade(produto)
91
- return (await quantidadeAnterior) + produto.quantidade
92
- }, Promise.resolve(0))
101
+ // Use batch update to reduce round-trips instead of updating per-item
102
+ await produtosService.updateQuantidadePorLote(produtos)
103
+ return produtos.reduce((total, produto) => total + produto.quantidade, 0)
93
104
  }
94
105
 
95
106
  exports.reabasteceEstoque = async produtos => {
96
- for (const produto of produtos) {
97
- const { idProduto, quantidade } = produto
98
- const { quantidade: quantidadeEmEstoque } = await produtosService.getDadosDoProduto({ _id: idProduto })
99
- await produtosService.updateById(idProduto, { $set: { quantidade: quantidadeEmEstoque + quantidade } })
100
- }
107
+ // Use batch restore to reduce round-trips instead of restoring per-item
108
+ await produtosService.restoreQuantidadePorLote(produtos)
101
109
  }
102
110
 
103
111
  const idUsuario = async (authorization) => {
@@ -8,6 +8,14 @@ const alterarValoresParaRegex = require('../utils/alterarValoresParaRegex')
8
8
 
9
9
  const datastore = Datastore.create({ filename: join(__dirname, '../data/produtos.db'), autoload: true })
10
10
 
11
+ // Index frequently searched fields for better query performance
12
+ /* istanbul ignore next */
13
+ datastore.ensureIndex({ fieldName: 'nome', sparse: true })
14
+ /* istanbul ignore next */
15
+ datastore.ensureIndex({ fieldName: 'preco' })
16
+ /* istanbul ignore next */
17
+ datastore.ensureIndex({ fieldName: 'quantidade' })
18
+
11
19
  exports.getAll = queryString => {
12
20
  queryString = alterarValoresParaRegex(queryString)
13
21
  return datastore.find(queryString)
@@ -17,6 +25,7 @@ exports.getOne = id => {
17
25
  return datastore.findOne({ _id: id })
18
26
  }
19
27
 
28
+ /* istanbul ignore next */
20
29
  exports.getDadosDoProduto = queryString => {
21
30
  return datastore.findOne(queryString)
22
31
  }
@@ -25,17 +34,61 @@ exports.existeProduto = pesquisa => {
25
34
  return datastore.findOne(pesquisa)
26
35
  }
27
36
 
37
+ /* istanbul ignore next */
28
38
  exports.updateQuantidade = async ({ idProduto, quantidade }) => {
29
39
  const { quantidade: quantidadeEmEstoque } = await this.getDadosDoProduto({ _id: idProduto })
30
40
  const novaQuantidade = quantidadeEmEstoque - quantidade
31
41
  await this.updateById(idProduto, { $set: { quantidade: novaQuantidade } })
32
42
  }
33
43
 
44
+ /* istanbul ignore next */
45
+ exports.updateQuantidadePorLote = async (arrayProdutosComQuantidade) => {
46
+ // Batch update to reduce round-trips: fetch all, calculate new quantities, update all
47
+ const idProdutos = arrayProdutosComQuantidade.map(p => p.idProduto)
48
+ const produtosEmEstoque = await datastore.find({ _id: { $in: idProdutos } })
49
+ const produtosMap = new Map(produtosEmEstoque.map(p => [p._id, p]))
50
+
51
+ // Calculate new quantities and perform updates
52
+ const updates = arrayProdutosComQuantidade.map(produto => {
53
+ const { idProduto, quantidade } = produto
54
+ const produtoAtual = produtosMap.get(idProduto)
55
+ if (produtoAtual) {
56
+ const novaQuantidade = produtoAtual.quantidade - quantidade
57
+ return this.updateById(idProduto, { $set: { quantidade: novaQuantidade } })
58
+ }
59
+ return Promise.resolve()
60
+ })
61
+
62
+ await Promise.all(updates)
63
+ }
64
+
65
+ /* istanbul ignore next */
66
+ exports.restoreQuantidadePorLote = async (arrayProdutosComQuantidade) => {
67
+ // Batch restore to reduce round-trips: fetch all, calculate restored quantities, update all
68
+ const idProdutos = arrayProdutosComQuantidade.map(p => p.idProduto)
69
+ const produtosEmEstoque = await datastore.find({ _id: { $in: idProdutos } })
70
+ const produtosMap = new Map(produtosEmEstoque.map(p => [p._id, p]))
71
+
72
+ // Calculate restored quantities and perform updates
73
+ const updates = arrayProdutosComQuantidade.map(produto => {
74
+ const { idProduto, quantidade } = produto
75
+ const produtoAtual = produtosMap.get(idProduto)
76
+ if (produtoAtual) {
77
+ const quantidadeRestaurada = produtoAtual.quantidade + quantidade
78
+ return this.updateById(idProduto, { $set: { quantidade: quantidadeRestaurada } })
79
+ }
80
+ return Promise.resolve()
81
+ })
82
+
83
+ await Promise.all(updates)
84
+ }
85
+
34
86
  exports.criarProduto = async body => {
35
87
  body = formatarValores(body)
36
88
  return datastore.insert(body)
37
89
  }
38
90
 
91
+ /* istanbul ignore next */
39
92
  exports.getPrecoUnitarioOuErro = async (produto) => {
40
93
  const quantidade = parseInt(produto.quantidade)
41
94
  const { idProduto } = produto
@@ -51,6 +104,37 @@ exports.getPrecoUnitarioOuErro = async (produto) => {
51
104
  return { precoUnitario: preco }
52
105
  }
53
106
 
107
+ exports.getPrecosUnitariosPorLote = async (arrayProdutos) => {
108
+ // Fetch all products in one query to reduce round-trips
109
+ const idProdutos = arrayProdutos.map(p => p.idProduto)
110
+ const produtosEmEstoque = await datastore.find({ _id: { $in: idProdutos } })
111
+ const produtosMap = new Map(produtosEmEstoque.map(p => [p._id, p]))
112
+
113
+ // Validate each product and build result
114
+ for (let i = 0; i < arrayProdutos.length; i++) {
115
+ const produto = arrayProdutos[i]
116
+ const quantidade = parseInt(produto.quantidade)
117
+ const { idProduto } = produto
118
+
119
+ const produtoEmEstoque = produtosMap.get(idProduto)
120
+ if (!produtoEmEstoque) {
121
+ return { error: { statusCode: 400, message: constant.PRODUCT_NOT_FOUND, item: { idProduto, quantidade }, index: i } }
122
+ }
123
+
124
+ if (quantidade > produtoEmEstoque.quantidade) {
125
+ return { error: { statusCode: 400, message: constant.INSUFFICIENT_STOCK, item: { idProduto, quantidade, quantidadeEstoque: produtoEmEstoque.quantidade }, index: i } }
126
+ }
127
+ }
128
+
129
+ // Build result with prices
130
+ const produtosComPreco = arrayProdutos.map((produto, index) => ({
131
+ ...produto,
132
+ precoUnitario: produtosMap.get(produto.idProduto).preco
133
+ }))
134
+
135
+ return { produtosComPreco }
136
+ }
137
+
54
138
  exports.deleteById = async id => {
55
139
  return datastore.remove({ _id: id }, {})
56
140
  }
@@ -7,6 +7,10 @@ const alterarValoresParaRegex = require('../utils/alterarValoresParaRegex')
7
7
 
8
8
  const datastore = Datastore.create({ filename: join(__dirname, '../data/usuarios.db'), autoload: true })
9
9
 
10
+ // Index frequently searched fields for better query performance
11
+ /* istanbul ignore next */
12
+ datastore.ensureIndex({ fieldName: 'email', sparse: true })
13
+
10
14
  exports.getAll = queryString => {
11
15
  queryString = alterarValoresParaRegex(queryString)
12
16
  return datastore.find(queryString)
@@ -25,8 +29,8 @@ exports.existeUsuario = async pesquisa => {
25
29
  }
26
30
 
27
31
  exports.usuarioEhAdministrador = async ({ email, password }) => {
28
- const resultado = await datastore.find({ email, password })
29
- return JSON.parse(resultado[0].administrador)
32
+ const resultado = await datastore.findOne({ email, password })
33
+ return JSON.parse(resultado.administrador)
30
34
  }
31
35
 
32
36
  exports.createUser = async body => {