Projeto de uma API de loja de itens medievais, utilizando um banco de dados SQL realizando cadastro, busca, login, auth.
A API foi construída utilizando os princípios REST e seguindo a arquitetura MSC (Model, Service, Controller), aplicando, pela primeira vez, o conceito de classe
.
Suas principais funcionalidades são:
- Cadastro, login e autenticação de pessoas usuárias
- Busca e cadastro de productos
- Busca e cadastro de pedidos
Trybesmith.sql
é responsável pela criação do banco de dados usado pela API e foi fornecido pela Trybe.
As tecnologias utilizadas para o desenvolvimento da aplicação foram:
- Node.js
- TypeScript
- MySQL
- Express
- Json Web Token
- Dotenv
- Joi
🐳 Rodando no Docker vs Localmente
Rode os serviços
node
edb
com o comandodocker-compose up -d
.
- Lembre-se de parar o
mysql
se estiver usando localmente na porta padrão (3306
), ou adapte, caso queria fazer uso da aplicação em containers - Esses serviços irão inicializar um container chamado
trybesmith
e outro chamadotrybesmith_db
. - A partir daqui você pode rodar o container
trybesmith
via CLI ou abri-lo no VS Code.
Use o comando
docker exec -it trybesmith bash
.
- Ele te dará acesso ao terminal interativo do container criado pelo compose, que está rodando em segundo plano.
Instale as dependências [Caso existam] com
npm install
⚠ Atenção ⚠ Caso opte por utilizar o Docker, TODOS os comandos disponíveis no package.json
(npm start, npm test, npm run dev, ...) devem ser executados DENTRO do container, ou seja, no terminal que aparece após a execução do comando docker exec
citado acima.
-
Clonar este repositório
git clone git@github.com:leandrcosta/TrybeSmith.git
-
cd TrybeSmith
npm install
-
Ter um servidor MySQL no computador
-
Ter um cliente MySQL e conectá-lo ao servidor
-
Executar as queries do arquivo Trybesmith.sql no cliente MySQL para criar o banco de dados
-
Iniciar o servidor da aplicação
npm start
🏦 Conexão com o Banco
A conexão do banco local deverá conter os seguintes parâmetros:
import dotenv from 'dotenv';
import mysql from 'mysql2/promise';
dotenv.config();
const connection = mysql.createPool({
host: process.env.MYSQL_HOST,
user: process.env.MYSQL_USER,
password: process.env.MYSQL_PASSWORD,
}); // sua conexão NÃO deve ter o database, este deve ser especificado em cada query
export default connection;
host: process.env.MYSQL_HOST
user: process.env.MYSQL_USER
password: process.env.MYSQL_PASSWORD
connection.ts
e esteja no diretório src/models
🪑 Tabelas
O banco terá três tabelas: pessoas usuárias, produtos e pedidos.
DROP SCHEMA IF EXISTS Trybesmith;
CREATE SCHEMA IF NOT EXISTS Trybesmith;
CREATE TABLE Trybesmith.users (
id INTEGER AUTO_INCREMENT PRIMARY KEY NOT NULL,
username TEXT NOT NULL,
vocation TEXT NOT NULL,
level INTEGER NOT NULL,
password TEXT NOT NULL
);
CREATE TABLE Trybesmith.orders (
id INTEGER AUTO_INCREMENT PRIMARY KEY NOT NULL,
user_id INTEGER,
FOREIGN KEY (user_id) REFERENCES Trybesmith.users (id)
);
CREATE TABLE Trybesmith.products (
id INTEGER AUTO_INCREMENT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
amount TEXT NOT NULL,
order_id INTEGER,
FOREIGN KEY (order_id) REFERENCES Trybesmith.orders (id)
);
O arquivo Trybesmith.sql
contém as queries que criam e populam o banco como o teste faz, e os testes restauram o banco de dados após sua execução.
Para que o avaliador funcione corretamente, tanto local quanto remoto, sua connection.ts
não deve conter o database e suas queries devem conter o banco de dados explicitamente como o exemplo abaixo:
SELECT * FROM Trybesmith.products;
🍪 Informações sobre a API
👀 Observações importantes:
-
O não cumprimento de um requisito, total ou parcialmente, impactará em sua avaliação;
-
O projeto deve rodar na porta 3001;
-
O arquivo
index.ts
existe para rodar corretamente os testes. Toda a chamada de rotas do projeto deverá ser feita dentro do arquivoapp.ts
;
-
Use os verbos
HTTP
adequados para cada operação; -
Agrupe e padronize suas URL em cada recurso;
-
Garanta que seus endpoints sempre retornem uma resposta, havendo sucesso nas operações ou não;
-
Retorne os códigos de status corretos (recurso criado, erro de validação, etc).
Há dois arquivos no diretório ./src/
: index.ts
e app.ts
, ambos não devem ser renomeados ou apagados.
Você poderá fazer modificações em ambos os arquivos, porém no arquivo app.ts
o seguinte trecho de código não deve ser removido:
import express from 'express';
const app = express();
app.use(express.json());
export default app;
Isso está configurado para o avaliador funcionar corretamente.
-
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": "Espada longa",
"amount": "30 peças de ouro"
}
Além disso, as seguintes verificações serão feitas:
👉 Para caso os dados sejam enviados corretamente
- [Será validado que é possível cadastrar um produto com sucesso]
- O resultado retornado para cadastrar o produto com sucesso deverá ser conforme exibido abaixo, com um status http
201
:
{ "id": 6, "name": "Espada longa", "amount": "30 peças de ouro", }
- O resultado retornado para cadastrar o produto com sucesso deverá ser conforme exibido abaixo, com um status http
- O endpoint deve ser acessível através do caminho (
/products
);
Além disso, as seguintes verificações serão feitas:
👉 Para caso os dados sejam enviados corretamente
- [Será validado que é possível listar todos os produtos com sucesso]
- O resultado retornado para listar produtos com sucesso deverá ser conforme exibido abaixo, com um status http
200
:
[ { "id": 1, "name": "Poção de cura", "amount": "20 gold", "orderId": null }, { "id": 2, "name": "Escudo do Herói", "amount": "100 diamond", "orderId": 1 } ]
- O resultado retornado para listar produtos com sucesso deverá ser conforme exibido abaixo, com um status http
-
O endpoint deve ser acessível através do caminho (
/users
); -
As informações de pessoas usuárias cadastradas devem ser salvas na tabela
users
do banco de dados; -
O endpoint deve receber a seguinte estrutura:
{
"username": "MAX",
"vocation": "swordsman",
"level": 10,
"password": "SavingPeople"
}
Além disso, as seguintes verificações serão feitas:
👉 Para caso os dados sejam enviados corretamente
- [Será validado que é possível cadastrar a pessoa usuária com sucesso]
- Se a pessoa usuária for cadastrada com sucesso, o resultado deverá ser conforme o exibido abaixo, com um status http
201
e retornando um token:
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" }
- Se a pessoa usuária for cadastrada com sucesso, o resultado deverá ser conforme o exibido abaixo, com um status http
- O endpoint deve ser acessível através do caminho (
/orders
). - Essa rota deve retornar todos os pedidos e os
id
s dos produtos associados a estes.
✨ Dica: Todos os produtos são itens artesanais, portanto, únicos. Por isso são os produtos que contêm os id
s dos pedidos.
✨ Dica: Pesquise na documentação oficial do MySQL sobre a função de agregação JSON_ARRAYAGG
, ela pode ser bem útil. 😉
Além disso, as seguintes verificações serão feitas:
👉 Para orders
- [Será validado que é possível listar todos os pedidos com sucesso]
- Quando houver mais de um pedido, o resultado retornado para listar pedidos com sucesso deverá ser conforme exibido abaixo, com um status http
200
:
[ { "id": 1, "userId": 2, "productsIds": [1, 2] }, { "id": 2, "userId": 1, "productsIds": [3, 4] } ]
- Quando houver mais de um pedido, o resultado retornado para listar pedidos com sucesso deverá ser conforme exibido abaixo, com um status http
-
O endpoint deve ser acessível através do caminho (
/login
). -
A rota deve receber os campos
username
epassword
, e esses campos devem ser validados no banco de dados. -
Um token
JWT
deve ser gerado e retornado caso haja sucesso no login. No seu payload deve estar presente o id e username. -
O endpoint deve receber a seguinte estrutura:
{
"username": "string",
"password": "string"
}
JWT
não use variáveis de ambientes para não ter conflito com o avaliador.
Além disso, as seguintes verificações serão feitas:
👉 Para caso haja problemas no login
-
[Será validado que o campo "username" é enviado]
- Se o login não tiver o campo "username", o resultado retornado deverá ser um status http
400
e
{ "message": "\"username\" is required" }
- Se o login não tiver o campo "username", o resultado retornado deverá ser um status http
-
[Será validado que o campo "password" é enviado]
- Se o login não tiver o campo "password", o resultado retornado deverá ser um status http
400
{ "message": "\"password\" is required" }
- Se o login não tiver o campo "password", o resultado retornado deverá ser um status http
-
[Será validado que não é possível fazer login com um username inválido]
- Se o login tiver o username inválido, o resultado retornado deverá ser um status http
401
e
{ "message": "Username or password invalid" }
- Se o login tiver o username inválido, o resultado retornado deverá ser um status http
-
[Será validado que não é possível fazer login com uma senha inválida]
- Se o login tiver a senha inválida, o resultado retornado deverá ser um status http
401
e
{ "message": "Username or password invalid" }
- Se o login tiver a senha inválida, o resultado retornado deverá ser um status http
👉 Para caso os dados sejam enviados corretamente
- [Será validado que é possível fazer login com sucesso]
- Se o login foi feito com sucesso, o resultado deverá ser um status http
200
e deverá retornar um token:
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" }
- Se o login foi feito com sucesso, o resultado deverá ser um status http
-
Vamos realizar as validações referentes a criação do endpont do requisito 1?
-
Neste requisito de validação, não é necessário conectar com o banco de dados
As seguintes validações deverão ser realizadas:
👉 Para name
-
[Será validado que o campo "name" é obrigatório]
- Se o campo "name" não for informado, o resultado retornado deverá ser um status http
400
e
{ "message": "\"name\" is required" }
- Se o campo "name" não for informado, o resultado retornado deverá ser um status http
-
[Será validado que o campo "name" tem o tipo string]
- Se o campo "name" não for do tipo
string
, o resultado retornado deverá ser um status http422
e
{ "message": "\"name\" must be a string" }
- Se o campo "name" não for do tipo
-
[Será validado que o campo "name" é uma string com mais de 2 caracteres]
- Se o campo "name" não for uma string com mais de 2 caracteres, o resultado retornado deverá ser um status http
422
e
{ "message": "\"name\" length must be at least 3 characters long" }
- Se o campo "name" não for uma string com mais de 2 caracteres, o resultado retornado deverá ser um status http
👉 Para amount
-
[Será validado que o campo "amount" é obrigatório]
- Se o campo "amount" não for informado, o resultado retornado deverá ser um status http
400
e
{ "message": "\"amount\" is required" }
- Se o campo "amount" não for informado, o resultado retornado deverá ser um status http
-
[Será validado que o campo "amount" tem o tipo string]
- Se o campo "amount" não for do tipo
string
, o resultado retornado deverá ser um status http422
e
{ "message": "\"amount\" must be a string" }
- Se o campo "amount" não for do tipo
-
[Será validado que o campo "amount" é uma string com mais de 2 caracteres]
- Se o campo "amount" não for uma string com mais de 2 caracteres, o resultado retornado deverá ser um status http
422
e
{ "message": "\"amount\" length must be at least 3 characters long" }
- Se o campo "amount" não for uma string com mais de 2 caracteres, o resultado retornado deverá ser um status http
-
Vamos realizar as validações referentes a criação do endpont do requisito 3?
-
Neste requisito de validação, não é necessário conectar com o banco de dados
As seguintes validações deverão ser realizadas:
👉 Para username
-
[Será validado que o campo "username" é obrigatório]
- Se a requisição não tiver o campo "username", o resultado retornado deverá ser um status http
400
e
{ "message": "\"username\" is required" }
- Se a requisição não tiver o campo "username", o resultado retornado deverá ser um status http
-
[Será validado que o campo "username" tem o tipo string]
- Se o campo "username" não for do tipo
string
, o resultado retornado deverá ser um status http422
e
{ "message": "\"username\" must be a string" }
- Se o campo "username" não for do tipo
-
[Será validado que o campo "username" é uma string com mais de 2 caracteres]
- Se o campo "username" não for do tipo
string
com mais de 2 caracteres, o resultado retornado deverá ser um status http422
e
{ "message": "\"username\" length must be at least 3 characters long" }
- Se o campo "username" não for do tipo
👉 Para vocation
-
[Será validado que o campo "vocation" é obrigatório]
- Se a requisição não tiver o campo "vocation", o resultado retornado deverá ser um status http
400
e
{ "message": "\"vocation\" is required" }
- Se a requisição não tiver o campo "vocation", o resultado retornado deverá ser um status http
-
[Será validado que o campo "vocation" tem o tipo string]
- Se o campo "vocation" não for do tipo
string
, o resultado retornado deverá ser um status http422
e
{ "message": "\"vocation\" must be a string" }
- Se o campo "vocation" não for do tipo
-
[Será validado que o campo "vocation" é uma string com mais de 2 caracteres]
- Se o campo "vocation" não for do tipo
string
com mais de 2 caracteres, o resultado retornado deverá ser um status http422
e
{ "message": "\"vocation\" length must be at least 3 characters long" }
- Se o campo "vocation" não for do tipo
👉 Para level
-
[Será validado que o campo "level" é obrigatório]
- Se a pessoa usuária não tiver o campo "level", o resultado retornado deverá ser um status http
400
e
{ "message": "\"level\" is required" }
- Se a pessoa usuária não tiver o campo "level", o resultado retornado deverá ser um status http
-
[Será validado que o campo "level" tem o tipo number]
- Se o campo "level" não for do tipo
number
, o resultado retornado deverá ser um status http422
e
{ "message": "\"level\" must be a number" }
- Se o campo "level" não for do tipo
-
[Será validado que o campo "level" deve ser um número maior que 0]
- Se o campo "level" não for do tipo
number
maior que 0, o resultado retornado deverá ser um status http422
e
{ "message": "\"level\" must be greater than or equal to 1" }
- Se o campo "level" não for do tipo
👉 Para password
-
[Será validado que o campo "password" é obrigatório]
- Se a requisição não tiver o campo "password", o resultado retornado deverá ser um status http
400
e
{ "message": "\"password\" is required" }
- Se a requisição não tiver o campo "password", o resultado retornado deverá ser um status http
-
[Será validado que o campo "password" tem o tipo string]
- Se o campo "password" não for do tipo
string
, o resultado retornado deverá ser um status http422
e
{ "message": "\"password\" must be a string" }
- Se o campo "password" não for do tipo
-
[Será validado que o campo "password" é uma string com 8 ou mais caracteres]
- Se o campo "password" não for do tipo
string
com mais de 8 caracteres, o resultado retornado deverá ser um status http422
e
{ "message": "\"password\" length must be at least 8 characters long" }
- Se o campo "password" não for do tipo
-
O endpoint deve ser acessível através do caminho (
/orders
); -
Um pedido só pode ser criado caso a pessoa usuária esteja logada e o token
JWT
validado; -
Os pedidos enviados devem ser salvos na tabela
orders
do banco de dados, salvandoid
da pessoa usuária da aplicação que fez esse pedido. -
A tabela
products
também deve ser alterada, atualizando todos os produtos com osid
incluídos na chaveproductsIds
da requisição, e adicionando nesses produtos oorderId
do pedido recém criado; -
O endpoint deve receber a seguinte estrutura:
{
"productsIds": [1, 2]
}
Além disso, as seguintes verificações serão feitas:
👉 Para token
-
[Será validado que não é possível cadastrar pedidos sem token]
- Se o token não for informado, o resultado retornado deverá ser um status http
401
e
{ "message": "Token not found" }
- Se o token não for informado, o resultado retornado deverá ser um status http
-
[Será validado que não é possível cadastrar um pedido com token inválido]
- Se o token informado não for válido, o resultado retornado deverá ser um status http
401
e
{ "message": "Invalid token" }
- Se o token informado não for válido, o resultado retornado deverá ser um status http
👉 Para products
-
[Será validado que o campo "productsIds" é obrigatório]
- Se o corpo da requisição não possuir o campo "productsIds", o resultado retornado deverá ser um status http
400
e
{ "message": "\"productsIds\" is required" }
- Se o corpo da requisição não possuir o campo "productsIds", o resultado retornado deverá ser um status http
-
[Será validado que não é possível criar um pedido com o campo "productsIds" não sendo um array]
- Se o valor do campo "productsIds" não for um array, o resultado retornado deverá ser um status http
422
e
{ "message": "\"productsIds\" must be an array" }
- Se o valor do campo "productsIds" não for um array, o resultado retornado deverá ser um status http
-
[Será validado que não é possível cadastrar um pedido se o campo "productsIds" for um array vazio]
- Se o campo "productsIds" possuir um array vazio, o resultado retornado deverá ser um status http
422
e
{ "message": "\"productsIds\" must include only numbers" }
- Se o campo "productsIds" possuir um array vazio, o resultado retornado deverá ser um status http
👉 Para caso os dados sejam enviados corretamente
-
[Será validado que é possível criar um pedido com sucesso com 1 item]
- O resultado retornado para cadastrar um pedido com sucesso deverá ser conforme exibido abaixo, com um status http
201
:
{ "userId": 1, "productsIds": [1], }
- O resultado retornado para cadastrar um pedido com sucesso deverá ser conforme exibido abaixo, com um status http
-
[Será validado que é possível criar um pedido com sucesso com vários itens]
- O resultado retornado para cadastrar um pedido com sucesso deverá ser conforme exibido abaixo, com um status http
201
:
{ "userId": 1, "productsIds": [1, 2] }
- O resultado retornado para cadastrar um pedido com sucesso deverá ser conforme exibido abaixo, com um status http