/TrybeSmith

Primary LanguageTypeScript

Trybe Smith ⚔️

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

⚠️ O conteúdo do aqruivo Trybesmith.sql é responsável pela criação do banco de dados usado pela API e foi fornecido pela Trybe.

Tecnologias

As tecnologias utilizadas para o desenvolvimento da aplicação foram:

  • Node.js
  • TypeScript
  • MySQL
  • Express
  • Json Web Token
  • Dotenv
  • Joi

Executando o projeto

🐳 Rodando no Docker vs Localmente

Com Docker

Rode os serviços node e db com o comando docker-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 chamado trybesmith_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.

Para executar o projeto localmente, é necessário:

  • 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;

⚠️ É essencial configurar essas 3 variáveis de ambiente para testar o projeto localmente: ⚠️

  host: process.env.MYSQL_HOST
  user: process.env.MYSQL_USER
  password: process.env.MYSQL_PASSWORD

⚠️ Existe um arquivo já criado chamado .env.example onde estão listadas as variáveis de ambiente esperadas no projeto. Variáveis de ambiente além das especificadas no arquivo mencionado não são suportadas, pois não são esperadas pelo avaliador do projeto. ⚠️

⚠️ É essencial que seu arquivo tenha o nome 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

⚠️ Leia as informações abaixo atentamente e siga à risca o que for pedido. ⚠️

👀 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 arquivo app.ts;


Todos os seus endpoints devem estar no padrão REST

  • 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.

Requisitos

1 - Crie um endpoint para o cadastro de produtos

  • 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",
      }

2 - Crie um endpoint para a listagem de produtos

  • 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
      }
    ]

3 - Crie um endpoint para o cadastro de pessoas usuárias

  • 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"
    }

4 - Crie um endpoint para listar todos os pedidos

  • O endpoint deve ser acessível através do caminho (/orders).
  • Essa rota deve retornar todos os pedidos e os ids 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 ids 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]
        }
      ]

5 - Crie um endpoint para o login de pessoas usuárias

  • O endpoint deve ser acessível através do caminho (/login).

  • A rota deve receber os campos username e password, 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"
  }

⚠️ Na configuração do 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" }
  • [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" }
  • [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" }
  • [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" }

👉 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"
    }

Requisitos Bônus

6 - Crie as validações dos produtos

  • 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" }
  • [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 http 422 e
      { "message": "\"name\" must be a string" }
  • [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" }

👉 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" }
  • [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 http 422 e
      { "message": "\"amount\" must be a string" }
  • [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" }


7 - Crie as validações para as pessoas usuárias

  • 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" }
  • [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 http 422 e
      { "message": "\"username\" must be a string" }
  • [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 http 422 e
      { "message": "\"username\" length must be at least 3 characters long" }

👉 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" }
  • [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 http 422 e
      { "message": "\"vocation\" must be a string" }
  • [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 http 422 e
      { "message": "\"vocation\" length must be at least 3 characters long" }

👉 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" }
  • [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 http 422 e
      { "message": "\"level\" must be a number" }
  • [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 http 422 e
      { "message": "\"level\" must be greater than or equal to 1" }

👉 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" }
  • [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 http 422 e
      { "message": "\"password\" must be a string" }
  • [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 http 422 e
      { "message": "\"password\" length must be at least 8 characters long" }


8 - Crie um endpoint para o cadastro de um pedido

  • 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, salvando id da pessoa usuária da aplicação que fez esse pedido.

  • A tabela products também deve ser alterada, atualizando todos os produtos com os id incluídos na chave productsIds da requisição, e adicionando nesses produtos o orderId do pedido recém criado;

  • O endpoint deve receber a seguinte estrutura:

  {
    "productsIds": [1, 2]
  }

⚠️ Ao cadastrar um pedido, lembre-se de atualizar os respectivos produtos no banco de dados, incluindo neles o número do pedido criado.

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" }
  • [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" }

👉 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" }
  • [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" }
  • [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" }

👉 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],
      }
  • [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]
      }