Esse projeto foi desenvolvido durante o módulo de Backend na Trybe! #vqv
Aqui você vai encontrar os detalhes de como foi o desenvolvimento do projeto e quais foram os requisitos técnicos necessários para a entrega do desafio.
Neste projeto, fui capaz de:
- Utilizar TypeScript;
- Utilizar os princípios do POO para criar uma estrutura de um campeonato de futebol;
- Utilizar os princípios da arquitetura SOLID para organizar o projeto e deixá-lo com uma manutenibilidade muito maior;
- Construir um backend dockerizado utilizando modelagem de dados através do ORM Sequelize;
- CRUD para criação, leitura, atualização e/ou remoção de usuários, times, partidas e tabela (placar do campeonato);
- Organização do código respeitando também o modelo MSC (Model-Service-Controller), de forma a dividir a responsabilidade do código e das funções/métodos de acordo com suas propostas;
- Construção de testes de integração utilizando Mocha, Chai e Sinon.
CRUD é um acrônimo para Create, Read, Update and Delete. Em português seria Criar, Ler, Atualizar e Deletar registros. Nesse projeto trabalhamos diretamente com a manipulação no banco de dados MySQL, utilizando do ORM Sequelize para a manipulação do banco.
MSC é um acrônimo para Model, Services e Controller. A utilização dessas camadas facilita a manutenção e legibilidade no código, uma vez que cada camada é responsável por apenas uma função. A camada Controller é responsável por retornar as requisições e respostas de nossa API para o usuário, enquanto que a camada Model faz as queries necessárias diretamente ao banco de dados. Já o Service é responsável por fazer a intermediação entre as duas camadas, podendo agir como regulador das regras de negócio da aplicação e lançar erros em caso de algum problema na requisição ou query.
Os ORMs ou Object-Relational Mappers visam diminuir o uso dos comandos e consultas SQL nas tabelas do banco de dados. Utilizando um framework baseado em ORM, conseguimos utilizar comandos SQL sem utilizar a linguagem do mySQL para tal. Nesse projeto utilizamos o Sequelize.
O Sequelize é um ORM baseado em Promises para Node.js e pode ser utilizado para diversos bancos de dados. Neste projeto, utilizei em conjunto com o MySQL.
O paradigma da POO (Programação Orientada a Objetos) é um modelo de análise, projeto e programação baseado na aproximação entre o mundo real e o mundo virtual, através da criação e interação entre objetos, atributos, códigos, métodos, entre outros.
O SOLID é um facilitador que torna o código mais coeso, além de mais fácil de manter, estender, adaptar e ajustar conforme alterações de escopo. Além disso, ele faz com que o código seja testável e de fácil entendimento, extensível e forneça o máximo de reaproveitamento. O termo SOLID é um acrônimo que representa cinco ideias, originadas pelo famoso Robert Cecil Martin, e significam:
- Single Responsability Principle (Princípio da Responsabilidade Única);
- Open/Closed Principle (Princípio Aberto/Fechado);
- Liskov Substitution Principle (Princípio da substituição de Liskov);
- Interface Segregation Principle (Princípio da Segregação de Interface);
- Dependency Inversion Principle (Princípio da Inversão de Dependência).
Para mais detalhes, sugiro acessar documentações oficiais.
Para rodar está aplicação é necessário ter Git, Docker, Node e o Docker Compose instalados no seu computador. O Docker Compose precisa estar na versão 1.29 ou superior e o Node na versão 16.
git clone git@github.com:GJMKauer/trybe-futebol-clube.git && cd trybe-futebol-clube
npm run compose:up
- email: admin@admin.com
- password: secret_admin
- email: user@user.com
- password: secret_user
O projeto trata-se de um desafio para consolidar todo o aprendizado até então em backend. Sendo o projeto mais desafiador da Trybe até o momento, tivemos que utilizar todos os conceitos ensinados e praticados desde então - utilização de HOFs, CRUD, Sequelize, manipulação do banco de dados, criação e validação de tokens JWT para login/cadastro de usuários, validação/criptografia de senhas com o BCrypt e muito mais.
Com isso, o projeto trata-se de um sistema de gerenciamento de campeonato do Trybe Futebol Clube (vide nome do projeto). Nele, é possível realizar login como um usuário comum ou como um administrador; visualizar partidas em andamento e já finalizadas; alterar o placar das partidas em andamento; finalizar partidas em andamento; adicionar novas partidas e visualizar o placar geral do campeonato.
A parte do frontend da aplicação já veio pronta pela Trybe. No entanto, todo o Backend e validações foram realizadas por mim. Além de tudo isso, implementei testes de integração que garantem o funcionamento do código.
Execute o comando abaixo no diretório raíz do projeto para verificar a cobertura de testes.
cd app/backend/ && npm install && npm run test:coverage
Construí a cobertura de pouco mais de 99% das funções do código, porém no futuro pretendo atingir a cobertura da totalidade das linhas.
Método | Funcionalidade | URL |
---|---|---|
POST |
Realiza o login no Backend da aplicação | http://localhost:3001/login |
A estrutura do body
da requisição deverá seguir o padrão abaixo:
{
"email": "admin@admin.com",
"password": "secret_admin"
}
A resposta da requisição é a seguinte, com status 200:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6ImFkbWluIiwiaWF0IjoxNjU0NTI3MTg5fQ.XS_9AA82iNoiVaASi0NtJpqOQ_gHSHhxrpIdigiT-fc"
}
A requisição irá falhar nos seguintes casos:
- A rota retorna o código400
, com a mensagem All fields must be filled
caso o campo email não seja informado no body da requisição;- A rota retorna o código
400
, com a mensagem All fields must be filled
caso o campo email não seja informado no body da requisição;- A rota retorna o código
400
, com a mensagem Incorrect email or password
caso o campo email seja inválido no body da requisição;- A rota retorna o código
400
, com a mensagem Incorrect email or password
caso o campo password seja inválido no body da requisição;Método | Funcionalidade | URL |
---|---|---|
GET |
Valida o login no Backend da aplicação | http://localhost:3001/login/validate |
Essa requisição deve, obrigatoriamente, ter um token de autenticação
nos headers, no campo authorization
(obtido após realizar o login).
A resposta da requisição é a seguinte, com status 200:
{ "role": "admin" }
Método | Funcionalidade | URL |
---|---|---|
GET |
Retorna uma lista dos times cadastrados | http://localhost:3001/teams |
A resposta da requisição é a seguinte, com status 200:
[
{
"id": 1,
"teamName": "Avaí/Kindermann"
},
{
"id": 2,
"teamName": "Bahia"
},
{
"id": 3,
"teamName": "Botafogo"
},
// ...
]
Método | Funcionalidade | URL |
---|---|---|
GET |
Retorna um time no banco de dados (substitua :id por um número) |
http://localhost:3001/teams/:id |
A resposta da requisição é a seguinte, com status 200:
[
{
"id": 1,
"teamName": "Avaí/Kindermann"
},
{
"id": 2,
"teamName": "Bahia"
},
{
"id": 3,
"teamName": "Botafogo"
},
// ...
]
Método | Funcionalidade | URL |
---|---|---|
GET |
Retorna uma lista das partidas cadastradas | http://localhost:3001/matches |
A resposta da requisição é a seguinte, com status 200:
[
{
"id": 1,
"homeTeam": 16,
"homeTeamGoals": 1,
"awayTeam": 8,
"awayTeamGoals": 1,
"inProgress": false,
"teamHome": {
"teamName": "São Paulo"
},
"teamAway": {
"teamName": "Grêmio"
}
},
// ...
{
"id": 41,
"homeTeam": 16,
"homeTeamGoals": 2,
"awayTeam": 9,
"awayTeamGoals": 0,
"inProgress": true,
"teamHome": {
"teamName": "São Paulo"
},
"teamAway": {
"teamName": "Internacional"
}
}
]
Método | Funcionalidade | URL |
---|---|---|
GET |
Retorna uma lista das partidas em andamento | http://localhost:3001/matches?inProgress=true |
A resposta da requisição é a seguinte, com status 200:
[
{
"id": 41,
"homeTeam": 16,
"homeTeamGoals": 2,
"awayTeam": 9,
"awayTeamGoals": 0,
"inProgress": true,
"teamHome": {
"teamName": "São Paulo"
},
"teamAway": {
"teamName": "Internacional"
}
},
{
"id": 42,
"homeTeam": 6,
"homeTeamGoals": 1,
"awayTeam": 1,
"awayTeamGoals": 0,
"inProgress": true,
"teamHome": {
"teamName": "Ferroviária"
},
"teamAway": {
"teamName": "Avaí/Kindermann"
}
}
]
Método | Funcionalidade | URL |
---|---|---|
GET |
Retorna uma lista das partidas finalizadas | http://localhost:3001/matches?inProgress=false |
A resposta da requisição é a seguinte, com status 200:
[
{
"id": 1,
"homeTeam": 16,
"homeTeamGoals": 1,
"awayTeam": 8,
"awayTeamGoals": 1,
"inProgress": false,
"teamHome": {
"teamName": "São Paulo"
},
"teamAway": {
"teamName": "Grêmio"
}
},
{
"id": 2,
"homeTeam": 9,
"homeTeamGoals": 1,
"awayTeam": 14,
"awayTeamGoals": 1,
"inProgress": false,
"teamHome": {
"teamName": "Internacional"
},
"teamAway": {
"teamName": "Santos"
}
}
]
Método | Funcionalidade | URL |
---|---|---|
POST |
Adiciona uma partida em andamento ao banco de dados | http://localhost:3001/matches |
Essa requisição deve, obrigatoriamente, ter um token de autenticação
nos headers, no campo authorization
(obtido após realizar o login).
A estrutura do body
da requisição deverá seguir o padrão abaixo:
{
"homeTeam": 16, // O valor deve ser o id do time
"awayTeam": 8, // O valor deve ser o id do time
"homeTeamGoals": 2,
"awayTeamGoals": 2,
"inProgress": true
}
A resposta da requisição é a seguinte, com status 201:
{
"id": 1,
"homeTeam": 16,
"homeTeamGoals": 2,
"awayTeam": 8,
"awayTeamGoals": 2,
"inProgress": true,
}
A requisição irá falhar nos seguintes casos:
- A rota retorna o código401
, com a mensagem It is not possible to create a match with two equal teams
caso informe o mesmo valor para ambos os campos homeTeam e awayTeam body da requisição;- A rota retorna o código
404
, com a mensagem There is no team with such id!
caso informe um id de time inválido no body da requisição;- A rota retorna o código
401
, com a mensagem Token must be a valid token
caso informe um token de autenticação inválido no campo authorization dos headers da requisição;Método | Funcionalidade | URL |
---|---|---|
PATCH |
Finaliza uma partida que está em andamento (substitua id por um número) |
http://localhost:3001/matches/:id/finish |
A resposta da requisição é a seguinte, com status 200:
{ "message": "Finished" }
Método | Funcionalidade | URL |
---|---|---|
PATCH |
Altera os dados de uma partida em andamento no banco de dados (substitua id por um número) |
http://localhost:3001/matches/:id |
A estrutura do body
da requisição deverá seguir o padrão abaixo:
{
"homeTeamGoals": 3,
"awayTeamGoals": 1
}
A resposta da requisição é a seguinte, com status 200:
{
"id": 1,
"homeTeam": 16,
"homeTeamGoals": 3,
"awayTeam": 8,
"awayTeamGoals": 1,
"inProgress": true,
}
Método | Funcionalidade | URL |
---|---|---|
GET |
Retorna o placar geral do campeonato, ordenado | http://localhost:3001/leaderboard |
A resposta da requisição é a seguinte, com status 200:
[
{
"name": "Palmeiras",
"totalPoints": 13,
"totalGames": 5,
"totalVictories": 4,
"totalDraws": 1,
"totalLosses": 0,
"goalsFavor": 17,
"goalsOwn": 5,
"goalsBalance": 12,
"efficiency": "86.67"
},
// ...
{
"name": "Napoli-SC",
"totalPoints": 2,
"totalGames": 5,
"totalVictories": 0,
"totalDraws": 2,
"totalLosses": 3,
"goalsFavor": 3,
"goalsOwn": 12,
"goalsBalance": -9,
"efficiency": "13.33"
}
]
Método | Funcionalidade | URL |
---|---|---|
GET |
Retorna o placar dos jogos da casa do campeonato, ordenado |
http://localhost:3001/leaderboard/home |
A resposta da requisição é a seguinte, com status 200:
[
{
"name": "Santos",
"totalPoints": 9,
"totalGames": 3,
"totalVictories": 3,
"totalDraws": 0,
"totalLosses": 0,
"goalsFavor": 9,
"goalsOwn": 3,
"goalsBalance": 6,
"efficiency": "100.00"
},
// ...
{
"name": "Bahia",
"totalPoints": 0,
"totalGames": 3,
"totalVictories": 0,
"totalDraws": 0,
"totalLosses": 3,
"goalsFavor": 0,
"goalsOwn": 4,
"goalsBalance": -4,
"efficiency": "0.00"
}
]
Método | Funcionalidade | URL |
---|---|---|
GET |
Retorna o placar dos jogos fora de casa do campeonato, ordenado |
http://localhost:3001/leaderboard/away |
A resposta da requisição é a seguinte, com status 200:
[
{
"name": "Palmeiras",
"totalPoints": 6,
"totalGames": 2,
"totalVictories": 2,
"totalDraws": 0,
"totalLosses": 0,
"goalsFavor": 7,
"goalsOwn": 0,
"goalsBalance": 7,
"efficiency": "100.00"
},
// ...
{
"name": "Napoli-SC",
"totalPoints": 0,
"totalGames": 3,
"totalVictories": 0,
"totalDraws": 0,
"totalLosses": 3,
"goalsFavor": 1,
"goalsOwn": 10,
"goalsBalance": -9,
"efficiency": "0.00"
}
]
É possível verificar todo o histórico de commits do projeto, de modo a visualizar passo-a-passo como foi desenvolvido o meu raciocínio até a finalização do projeto.
-
✅ 1. Desenvolva em /app/backend/src/database nas pastas correspondentes, uma migration e um model para a tabela users.
-
✅ 2. Desenvolva testes que cubram no mínimo 5% dos arquivos back-end em /src, com um mínimo de 7 linhas cobertas.
-
✅ 3. Desenvolva o endpoint /login no back-end de maneira que ele permita o acesso com dados válidos no front-end.
-
✅ 4. Desenvolva testes que cubram no mínimo 10% dos arquivos back-end em /src, com um mínimo de 19 linhas cobertas.
-
✅ 5. Desenvolva o endpoint /login no back-end de maneira que ele não permita o acesso sem informar um email no front-end.
-
✅ 6. Desenvolva testes que cubram no mínimo 15% dos arquivos back-end em /src, com um mínimo de 25 linhas cobertas.
-
✅ 7. Desenvolva o endpoint /login no back-end de maneira que ele não permita o acesso sem informar uma senha no front-end.
-
✅ 8. Desenvolva testes que cubram no mínimo 20% dos arquivos back-end em /src, com um mínimo de 35 linhas cobertas.
-
✅ 9. Desenvolva o endpoint /login no back-end de maneira que ele não permita o acesso com um email inválido no front-end.
-
✅ 10. Desenvolva testes que cubram no mínimo 30% dos arquivos back-end em /src, com um mínimo de 45 linhas cobertas.
-
✅ 11. Desenvolva o endpoint /login no back-end de maneira que ele não permita o acesso com uma senha inválida no front-end.
-
✅ 12. Desenvolva o endpoint /login/validate no back-end de maneira que ele retorne os dados corretamente no front-end.
-
✅ 13. Desenvolva testes que cubram no mínimo 45% dos arquivos back-end em /src, com um mínimo de 70 linhas cobertas.
-
✅ 14. Desenvolva em /app/backend/src/database nas pastas correspondentes, uma migration e um model para a tabela de teams.
-
✅ 15. Desenvolva o endpoint /teams no back-end de forma que ele possa retornar todos os times corretamente.
-
✅ 16. Desenvolva o endpoint /teams/:id no back-end de forma que ele possa retornar dados de um time específico.
-
✅ 17. Desenvolva testes que cubram no mínimo 60% dos arquivos back-end em /src, com um mínimo de 80 linhas cobertas.
-
✅ 18. Desenvolva em /app/backend/src/database nas pastas correspondentes, uma migration e um model para a tabela de matches.
-
✅ 19. Desenvolva o endpoint /matches de forma que os dados apareçam corretamente na tela de partidas no front-end..
-
✅ 20. Desenvolva o endpoint /matches de forma que seja possível filtrar as partidas em andamento na tela de partidas do front-end.
-
✅ 21. Desenvolva o endpoint /matches de forma que seja possível filtrar as partidas finalizadas na tela de partidas do front-end.
-
✅ 23. Desenvolva o endpoint /matches de modo que seja possível salvar uma partida com o status de inProgress como true no banco de dados.
-
✅ 24. Desenvolva o endpoint /matches/:id/finish de modo que seja possível alterar o status inProgress de uma partida para false no banco de dados.
-
✅ 25. Desenvolva o endpoint /matches de forma que não seja possível inserir uma partida com times iguais.
-
✅ 26. Desenvolva o endpoint /matches de forma que não seja possível inserir uma partida com um time que não existe na tabela teams.
-
✅ 27. Desenvolva o endpoint /matches de forma que não seja possível inserir uma partida sem um token válido.
-
✅ 28. Desenvolva o endpoint /matches/:id de forma que seja possível atualizar partidas em andamento.
-
✅ 29. Desenvolva o endpoint /leaderboard/home de forma que seja possível filtrar as classificações dos times da casa na tela de classificação do front-end com os dados iniciais do banco de dados.
-
✅ 30. Desenvolva o endpoint /leaderboard/home de forma que seja possível filtrar as classificações dos times da casa na tela de classificação do front-end, e atualizar a tabela ao inserir a partida Corinthians 2 X 1 Internacional.
-
✅ 31. Desenvolva o endpoint /leaderboard/away, de forma que seja possível filtrar as classificações dos times quando visitantes na tela de classificação do front-end, com os dados iniciais do banco de dados.
-
✅ 32. Desenvolva o endpoint /leaderboard/away de forma que seja possível filtrar as classificações dos times quando visitantes na tela de classificação do front-end e atualizar a tabela ao inserir a partida Corinthians 2 X 1 Internacional.
-
✅ 33. Desenvolva o endpoint /leaderboard de forma que seja possível filtrar a classificação geral dos times na tela de classificação do front-end com os dados iniciais do banco de dados.
-
✅ 34. Desenvolva o endpoint /leaderboard de forma que seja possível filtrar a classificação geral dos times na tela de classificação do front-end e atualizar a tabela ao inserir a partida Flamengo 3 X 0 Napoli-SC.
-
✅ 35. Desenvolva o endpoint /leaderboard de forma que seja possível filtrar a classificação geral dos times na tela de classificação do front-end e atualizar a tabela ao inserir a partida Minas Brasília 1 X 0 Ferroviária.
- ✅ 22. Desenvolva testes que cubram no mínimo 80% dos arquivos back-end em /src, com um mínimo de 100 linhas cobertas.