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 +1 -1
- package/src/controllers/produtos-controller.js +3 -5
- package/src/controllers/usuarios-controller.js +3 -4
- package/src/data/carrinhos.db +8 -0
- package/src/data/produtos.db +6 -0
- package/src/data/usuarios.db +2 -0
- package/src/middlewares/error-handler.js +6 -0
- package/src/services/carrinhos-service.js +30 -22
- package/src/services/produtos-service.js +84 -0
- package/src/services/usuarios-service.js +6 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serverest",
|
|
3
|
-
"version": "3.
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
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.
|
|
31
|
-
|
|
32
|
-
|
|
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
|
package/src/data/carrinhos.db
CHANGED
|
@@ -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"}}
|
package/src/data/produtos.db
CHANGED
|
@@ -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"}}
|
package/src/data/usuarios.db
CHANGED
|
@@ -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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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(
|
|
84
|
-
return
|
|
85
|
-
},
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
97
|
-
|
|
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.
|
|
29
|
-
return JSON.parse(resultado
|
|
32
|
+
const resultado = await datastore.findOne({ email, password })
|
|
33
|
+
return JSON.parse(resultado.administrador)
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
exports.createUser = async body => {
|