Neste projeto eu desenvolvi a minha primeira API utilizando a arquitetura MSC!
A API construída trata-se de um sistema de gerenciamento de vendas, onde é possível criar, visualizar, deletar e atualizar produtos e vendas.
Nesse projeto, eu fui capaz de:
- Entender o funcionamento da camada de Model;
- Delegar responsabilidades específicas para essa camada;
- Conectar sua aplicação com diferentes bancos de dados;
- Estruturar uma aplicação em camadas;
- Delegar responsabilidades específicas para cada parte do seu app;
- Melhorar manutenibilidade e reusabilidade do seu código;
- Entender e aplicar os padrões REST;
- Escrever assinaturas para APIs intuitivas e facilmente entendíveis.
Após cada um dos passos, haverá um exemplo do comando a ser digitado para fazer o que está sendo pedido, caso tenha dificuldades, mande mensagem para o meu e-mail [email protected].
- Abra o terminal e crie um diretório no local de sua preferência com o comando mkdir:
mkdir projetos-humberto
- Entre no diretório que acabou de criar e depois clone o projeto:
cd projetos-humberto
git clone git@github.com:Humberto-Bonadiman/Store-Manager.git
- Abra o diretório do projeto clonado, depois entre nos diretórios controllers, middlewares, models, services, test/unit e dê duplo clique nos arquivos que se encontram nos diretórios para visualizar os códigos do projeto. Também no mesmo diretório do projeto clonado é possível visualizar os arquivos no arquivo index.js após o duplo clique. Caso você tenha a disposição o VsCode em sua máquina, você pode visualizar os códigos digitando o comando abaixo e abrindo os arquivos que estão dentro dos diretórios controllers, middlewares, models, services, test/unit e/ou no mesmo diretório do projeto clonado no arquivo index.js. Não é necessário instalar nada mais.
code .
const connection = mysql.createPool({
host: process.env.MYSQL_HOST,
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
});
Para os testes rodarem corretamente, na raiz do projeto renomeie o arquivo .env.example
para .env
com as variáveis de ambiente. Por exemplo, caso o seu usuário SQL seja nome
e a senha 1234
seu arquivo ficará desta forma:
MYSQL_HOST=localhost
MYSQL_USER=nome
MYSQL_PASSWORD=1234
PORT=3000
Com essas configurações, enquanto estiver na máquina local, o banco será executado normalmente via localhost.
Na raiz do projeto existe o arquivo StoreManager.sql
que foi usado para rodar os testes. Você pode importá-lo localmente para testar o comportamento da sua aplicação.
O banco tem três tabelas: products
, sales
e sales_products
.
A tabela products
tem o seguinte formato:
(O id será gerado automaticamente)
A tabela sales
tem o seguinte formato:
(O id e date são gerados automaticamente)
A tabela sales_products
, é a tabela que faz o relacionamento N:N
entre products
e sales
e tem o seguinte formato:
-
O endpoint deve ser acessível através do caminho (
/products
); -
Os produtos enviados devem ser salvos na tabela
products
do Banco de Dados; -
O endpoint deve receber a seguinte estrutura:
{
"name": "product_name",
"quantity": "product_quantity"
}
O que foi validado
👉 Para o endpoint
POST /products
, o camponame
deve ser uma string com 5 ou mais caracteres e deve ser único.
-
Quando a requisição é feita sem o atributo
name
:{ "quantity": 100 }
- sua API deve responder com status http
400
e o seguintebody
:
{ "message": "\"name\" is required" }
- sua API deve responder com status http
-
Quando a requisição é feita e contém o seguinte
body
:{ "name": "pro", "quantity": 100 }
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "\"name\" length must be at least 5 characters long" }
- sua API deve responder com status http
-
Quando a requisição é feita com o atributo
name
igual um já cadastrado:{ "name": "produto", "quantity": 100 }
- sua API deve responder com status http
409
e o seguintebody
:
{ "message": "Product already exists" }
- sua API deve responder com status http
👉 Para o endpoint
POST /products
, o campoquantity
deve ser um número inteiro maior que 0.
-
Quando a requisição é feita sem o atributo
quantity
:{ "name": "produto" }
- sua API deve responder com status http
400
e o seguintebody
:{ "message": "\"quantity\" is required" }
- sua API deve responder com status http
-
Quando a requisição é feita e contém os seguintes
body
:{ "name": "produto", "quantity": "string" }
{ "name": "produto", "quantity": -1 }
{ "name": "produto", "quantity": 0 }
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "\"quantity\" must be a number larger than or equal to 1" }
- sua API deve responder com status http
👉 Para o endpoint
POST /products
, quando a requisição é feita corretamente, o produto deve ser cadastrado.
- Quando a requisição é feita e contém o seguinte
body
:{ "name": "produto", "quantity": 10 }
- sua API deve responder com status http
201
e o seguintebody
:
{ "id": 1, "name": "produto", "quantity": 10 }
- sua API deve responder com status http
-
O endpoint deve ser acessível através do caminho (
/products
) ou (/products/:id
); -
Através do caminho
/products
, todos os produtos devem ser retornados; -
Através do caminho
/products/:id
, apenas o produto com oid
presente na URL deve ser retornado;
O que foi validado
👉 Para o endpoint
GET /products
, será validado que todos produtos estão sendo retornados.
- sua API deve responder com status http
200
e o seguintebody
:
[
{
"id": 1,
"name": "produto A",
"quantity": 10
},
{
"id": 2,
"name": "produto B",
"quantity": 20
}
]
👉 Para o endpoint
GET /products/:id
, será validado que é possível listar um determinado produto.
- sua API deve responder com status http
200
e o seguintebody
:{ "id": 1, "name": "produto A", "quantity": 10 }
👉 Para o endpoint
GET /products/:id
, será validado que não é possível listar um produto que não existe.
- sua API deve responder com status http
404
e o seguintebody
:{ "message": "Product not found" }
-
O endpoint deve ser acessível através do caminho (
/products/:id
); -
O corpo da requisição deve seguir a mesma estrutura do método responsável por adicionar um produto;
-
Apenas o produto com o
id
presente na URL deve ser atualizado; -
O corpo da requisição deve receber a seguinte estrutura:
{
"name": "new_product_name",
"quantity": "new_product_quantity"
}
O que foi validado
👉 Para o endpoint
PUT /products/:id
, o camponame
deve ser uma string com 5 ou mais caracteres e deve ser único.
- Quando a requisição é feita e contém o seguinte
body
:{ "name": "pro", "quantity": 15 }
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "\"name\" length must be at least 5 characters long" }
- sua API deve responder com status http
👉 Para o endpoint
PUT /products/:id
, o campoquantity
deve ser um número inteiro maior que 0.
- Quando a requisição é feita e contém os seguintes
body
:{ "name": "produto", "quantity": "string" }
{ "name": "produto", "quantity": -1 }
{ "name": "produto", "quantity": 0 }
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "\"quantity\" must be a number larger than or equal to 1" }
- sua API deve responder com status http
👉 Para o endpoint
PUT /products/:id
, quando a requisição é feita corretamente, o produto deve ser alterado.
- Quando a requisição é feita e contém o seguinte
body
:{ "name": "produto", "quantity": 15 }
- sua API deve responder com status http
200
e o seguintebody
:
{ "id": 1, "name": "produto", "quantity": 15 }
- sua API deve responder com status http
👉 Para o endpoint
PUT /products/:id
, será validado que não é possível alterar um produto que não existe.
- sua API deve responder com status http
404
e o seguintebody
:{ "message": "Product not found" }
-
O endpoint deve ser acessível através do caminho (
/products/:id
); -
Apenas o produto com o
id
presente na URL deve ser deletado;
O que foi validado
👉 Para o endpoint
DELETE /products/:id
, será validado que é possível deletar um produto com sucesso.
- sua API deve responder com status http
200
e o seguintebody
:
{
"id": 1,
"name": "produto A",
"quantity": 10
}
👉 Para o endpoint
DELETE /products/:id
, será validado que não é possível deletar um produto que não existe.
- sua API deve responder com status http
404
e o seguintebody
:{ "message": "Product not found" }
-
O endpoint deve ser acessível através do caminho (
/sales
); -
As vendas enviadas devem ser salvas na tabela
sales
esales_products
do Banco de dados; -
Deve ser possível cadastrar a venda de vários produtos através da uma mesma requisição;
-
O endpoint deve receber a seguinte estrutura:
[
{
"product_id": "product_id",
"quantity": "product_quantity",
}
]
O que foi validado
👉 Para o endpoint
POST /sales
, o campoproduct_id
deve ser um id de um produto anteriormente cadastrado.
- Quando a requisição é feita sem o atributo
product_id
:[ { "quantity": 1 } ]
- sua API deve responder com status http
400
e o seguintebody
:
{ "message": "\"product_id\" is required" }
- sua API deve responder com status http
👉 Para o endpoint
POST /sales
, o campoquantity
deve ser um número inteiro maior que 0.
-
Quando a requisição é feita sem o atributo
quantity
:[ { "product_id": 1 } ]
- sua API deve responder com status http
400
e o seguintebody
:{ "message": "\"quantity\" is required" }
- sua API deve responder com status http
-
Quando a requisição é feita e contém os seguintes
body
:[ { "product_id": 1, "quantity": -1 } ]
[ { "product_id": 1, "quantity": 0 } ]
[ { "product_id": 1, "quantity": "string" } ]
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "\"quantity\" must be a number larger than or equal to 1" }
- sua API deve responder com status http
👉 Para o endpoint
POST /sales
, quando a requisição é feita corretamente, o produto deve ser cadastrado.
- Quando a requisição é feita e contém o seguinte
body
:[ { "product_id": 1, "quantity": 3 } ]
- sua API deve responder com status http
201
e o seguintebody
:
{ "id": 1, "itemsSold": [ { "product_id": 1, "quantity": 3 } ] }
- sua API deve responder com status http
👉 Para o endpoint
POST /sales
, quando a requisição é feita corretamente, a venda deve ser cadastrada.
- Quando a requisição é feita e contém o seguinte
body
:[ { "product_id": 1, "quantity": 2 }, { "product_id": 2, "quantity": 5 } ]
- sua API deve responder com status http
201
e o seguintebody
:
{ "id": 1, "itemsSold": [ { "product_id": 1, "quantity": 2 }, { "product_id": 2, "quantity": 5 } ] }
- sua API deve responder com status http
-
O endpoint deve ser acessível através do caminho (
/sales
) ou (/sales/:id
); -
Através do caminho
/sales
, todas as vendas devem ser retornadas; -
Através do caminho
/sales/:id
, apenas a venda com oid
presente na URL deve ser retornada;
O que foi validado
👉 Para o endpoint
GET /sales
, será validado que todas vendas estão sendo retornados.
- sua API deve responder com status http
200
e o seguintebody
:
[
{
"saleId": 1,
"date": "2021-09-09T04:54:29.000Z",
"product_id": 1,
"quantity": 2
},
{
"saleId": 1,
"date": "2021-09-09T04:54:54.000Z",
"product_id": 2,
"quantity": 2
}
]
👉 Para o endpoint
GET /sales/:id
, será validado que é possível listar uma determinada venda.
- sua API deve responder com status http
200
e o seguintebody
:[ { "date": "2021-09-09T04:54:29.000Z", "product_id": 1, "quantity": 2 }, { "date": "2021-09-09T04:54:54.000Z", "product_id": 2, "quantity": 2 } ]
👉 Para o endpoint
GET /sales/:id
, será validado que não é possível listar uma venda que não existe.
- sua API deve responder com status http
404
e o seguintebody
:{ "message": "Sale not found" }
-
O endpoint deve ser acessível através do caminho (
/sales/:id
); -
quantity
deve ser um número inteiro maior que 0; -
Apenas a venda com o
id
presente na URL deve ser atualizada; -
O corpo da requisição deve receber a seguinte estrutura:
[
{
"product_id": "id_change",
"quantity": "new_quantity"
}
]
O que foi validado
👉 Para o endpoint
PUT /sales/:id
, o campoproduct_id
deve ser um id de um produto anteriormente cadastrado.
- Quando a requisição é feita sem o atributo
product_id
:[ { "quantity": 10 } ]
- sua API deve responder com status http
400
e o seguintebody
:
{ "message": "\"product_id\" is required" }
- sua API deve responder com status http
👉 Para o endpoint
PUT /sales/:id
, o campoquantity
deve ser um número inteiro maior que 0.
-
Quando a requisição é feita sem o atributo
quantity
:[ { "product_id": 1 } ]
- sua API deve responder com status http
400
e o seguintebody
:
{ "message": "\"quantity\" is required" }
- sua API deve responder com status http
-
Quando a requisição é feita e contém os seguintes
body
:[ { "product_id": 1, "quantity": -1 } ]
[ { "product_id": 1, "quantity": 0 } ]
[ { "product_id": 1, "quantity": "string" } ]
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "\"quantity\" must be a number larger than or equal to 1" }
- sua API deve responder com status http
👉 Para o endpoint
PUT /sales/:id
, quando a requisição é feita corretamente, a venda deve ser alterada.
- Quando a requisição é feita e contém o seguinte
body
:[ { "product_id": 1, "quantity": 6 } ]
- sua API deve responder com status http
200
e o seguintebody
:
{ "saleId": 1, "itemUpdated": [ { "product_id": 1, "quantity": 6 } ] }
- sua API deve responder com status http
-
Seus arquivos de teste devem ficar no diretório
test/unit
, como citado aqui; -
Seus testes da
model
devem fazer mock do banco de dados obrigatóriamente; -
Opcionalmente você pode parar o serviço do
MYSQL
em sua máquina. Para rodar seus teste utilizenpm run test:mocha
;
O que foi validado
👉 Será validado que a cobertura total das linhas dos arquivos nas pastas
models
,services
econtrollers
é maior ou igual a 35%.
👉 Será validado que ao menos 24 linhas são cobertas pelos testes.
-
Seus arquivos de teste devem ficar no diretório
test/unit
, como citado aqui -
Seus testes da
model
devem fazer mock do banco de dados obrigatóriamente; -
Opcionalmente você pode parar o serviço do
MYSQL
em sua máquina. Para rodar seus teste utilizenpm run test:mocha
;
O que foi validado
👉 Será validado que a cobertura total das linhas dos arquivos nas pastas
models
,services
econtrollers
é maior ou igual a 40%.
👉 Será validado que ao menos 24 linhas são cobertas pelos testes.
-
O endpoint deve ser acessível através do caminho (
/sales/:id
); -
Apenas a venda com o
id
presente na URL deve ser deletado;
O que foi validado
👉 Para o endpoint
DELETE /sales/:id
, será validado que é possível deletar uma venda com sucesso.
- sua API deve responder com status http
200
e o seguintebody
:
[
{
"date": "2021-09-09T04:54:29.000Z",
"product_id": 1,
"quantity": 2
},
{
"date": "2021-09-09T04:54:54.000Z",
"product_id": 2,
"quantity": 2
}
]
👉 Para o endpoint
DELETE /sales/:id
, será validado que não é possível deletar uma venda que não existe.
- sua API deve responder com status http
404
e o seguintebody
:
{ "message": "Sale not found" }
-
Ao realizar uma venda, atualizá-la ou deletá-la, você deve também atualizar a quantidade do produto em questão presente na tabela responsável pelos produtos;
- Exemplo 1: suponha que haja um produto chamado Bola de Futebol e a sua propriedade
quantity
tenha o valor 10. Caso seja feita uma venda com 8 unidades desse produto, a quantidade do produto deve ser atualizada para 2 , pois 10 - 8 = 2; - Exemplo 2: Suponha que esta venda tenha sido deletada, logo estas 8 unidades devem voltar ao
quantity
e seu valor voltará a 10, pois 2 + 8 = 10;
- Exemplo 1: suponha que haja um produto chamado Bola de Futebol e a sua propriedade
O que não foi validado
👉 Será validado que ao fazer uma determinada venda, a quantidade do produto deverá ser atualizada também na tabela responsável pelos produtos.
👉 Será validado que ao deletar uma determinada venda, a quantidade do produto deverá ser atualizada também na tabela responsável pelos produtos;.
-
Um produto nunca deve ter a quantidade em estoque menor que 0;
-
Quando uma venda for realizada, garanta que a quantidade sendo vendida está disponível no estoque
O que não foi validado
👉 Para o endpoint
POST /sales
, será validado que a quantidade de produtos em estoque nunca seja menor que 0 (zero).
- Quando a requisição é feita com uma quantidade superior a quantidade em estoque:
[ { "product_id": 1, "quantity": 100 } ]
- sua API deve responder com status http
422
e o seguintebody
:
{ "message": "Such amount is not permitted to sell" }
- sua API deve responder com status http
-
Seus arquivos de teste devem ficar no diretório
test/unit
, como citado aqui; -
Seus testes da
model
devem fazer mock do banco de dados obrigatóriamente; -
Opcionalmente você pode parar o serviço do
MYSQL
em sua máquina. Para rodar seus teste utilizenpm run test:mocha
;
O que não foi validado
👉 Será validado que a cobertura total das linhas dos arquivos nas pastas
models
,services
econtrollers
é maior ou igual a 50%.
👉 Será validado que ao menos 24 linhas são cobertas pelos testes.
-
Seus arquivos de teste devem ficar no diretório
test/unit
, como citado aqui; -
Seus testes da
model
devem fazer mock do banco de dados obrigatóriamente; -
Opcionalmente você pode parar o serviço do
MYSQL
em sua máquina. Para rodar seus teste utilizenpm run test:mocha
;
O que não foi validado
👉 Será validado que a cobertura total das linhas dos arquivos nas pastas
models
,services
econtrollers
é maior ou igual a 60%.
👉 Será validado que ao menos 24 linhas são cobertas pelos testes.
Os requisitos 11 ao 14 não foram finalizados e no momento não tenho a intenção de trabalhar neles.