Projeto desenvolvido por mim durante o curso de Desenvolvimento Web na Trybe. Divulgado aqui como portfólio de aprendizado
O Football League é um site informativo sobre partidas e classificações de futebol. Trata-se de uma aplicação full stack web, utilizando TypeScript e JavaScript. Desenvolvi somente o back-end (front-end já veio pronto).
- Contruir api RESTful completa
- Banco de dados com um container docker MySQL
- Validação da rota /login com token
- Criptografia de senhas
- Desenvolver um middleware de validação de token para role
- Criação de migrations e models com sequelize
- Filtragem de partidas no front-end, podendo escolher partidas em andamento ou finalizadas
- Finalizar uma partida no banco de dados
- Atualizar partidas em andamento
- Tabela de classificação relacionada aos atributos dos times
- JavaScript e TypeScript
- Docker
- Sequelize para modelagem de dados
- Container docker MySQL
- bcryptjs para criptografia de senhas
- Node e Express
- Sinon e Chai para testes
- joi
-
Na raiz do projeto, você deverá subir os containers dos serviços de backend, frontend e db através do docker-compose, executando o seguinte comando:
npm run compose:up
-
Para se certificar que as dependências de cada container de serviço foram instaladas, execute o seguinte comando:
npm run install:apps
-
Agora basta acessar o endereço
localhost:3000/login
onde está mapeado o Frontend da aplicação e logar com o usuário:user@user.com
e senha:secret_user
, para assim ter acesso a rotas que necessitam autenticação.
ATENÇÃO: Apenas algumas funcionalidades estão implementadas no front-end
- GET
/teams
Lista todos os times
- Retorna
status HTTP 200
com o seguinte resultado:[ { "id": 1, "teamName": "Avaí/Kindermann" }, { "id": 2, "teamName": "Bahia" }, { "id": 3, "teamName": "Botafogo" }, ... ]
- GET
teams/:id
Lista um time pelo seu id
- Retorna resposta com status
200
e com umjson
contendo o retorno no seguinte modelo:
{
"id": 5,
"teamName": "Cruzeiro"
}
- POST
/login
Realiza login no app
- O body da requisição deve conter o seguinte formato:
{
"email": "string",
"password": "string"
}
-
O campo
email
deve receber um email válido. Ex:tfc@projeto.com
; -
O campo
password
deve ter mais de 6 caracteres. -
Além de válidos, é necessário que o email e a senha estejam cadastrados no banco para ser feito o login;
-
Se o login foi feito com sucesso, o resultado retornado deverá ser similar ao exibido abaixo, com um status http
200
:{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwicm9sZSI6ImFkbWluIiwiaWF0IjoxNjU0NTI3MTg5fQ.XS_9AA82iNoiVaASi0NtJpqOQ_gHSHhxrpIdigiT-fc" // Aqui deve ser o token gerado pelo backend. }
-
Se o login não tiver o campo "email" ou "password", o resultado retornado deverá ser a mensagem abaixo, com um status http
400
:{ "message": "All fields must be filled" }
-
Se o login tiver o "email" inválido ou a "senha" inválida, o resultado retornado será similar ao exibido abaixo, com um status http
401
:
{ "message": "Invalid email or password" }
- Sendo emails inválidos:
- Emails com formato inválido:
@exemplo.com
,exemplo@exemplo
,exemplo@.com
,exemplo.exemplo.com
; - Emails com formato válido, mas não cadastrados no banco;
- Emails com formato inválido:
- Sendo senhas inválidas:
- Senhas com formato inválido: com um tamanho menor do que
6 caracteres
; - Senhas com formato válido, mas não cadastradas no banco;
- Senhas com formato inválido: com um tamanho menor do que
- GET
/login/role
Mostra o tipo de usuário
-
Caso o token não seja informado, deve-se retornar, com um status
401
, a seguinte mensagem:{ "message": "Token not found" }
-
Caso o token informado não seja válido, deve-se retornar, com um status
401
, a seguinte mensagem:{ "message": "Token must be a valid token" }
-
A resposta deve ser de status
200
com umobjeto
contendo arole
do user:{ "role": "admin" }
- GET
/matches
Lista todas as partidas
- O resultado esperado deverá ser conforme abaixo:
[ { "id": 1, "homeTeamId": 16, "homeTeamGoals": 1, "awayTeamId": 8, "awayTeamGoals": 1, "inProgress": false, "homeTeam": { "teamName": "São Paulo" }, "awayTeam": { "teamName": "Grêmio" } }, ... { "id": 41, "homeTeamId": 16, "homeTeamGoals": 2, "awayTeamId": 9, "awayTeamGoals": 0, "inProgress": true, "homeTeam": { "teamName": "São Paulo" }, "awayTeam": { "teamName": "Internacional" } } ]
- GET
matches?inProgress=true
Lista todas as partidas em andamento
Exemplo de retorno da requisição:
json [ { "id": 41, "homeTeamId": 16, "homeTeamGoals": 2, "awayTeamId": 9, "awayTeamGoals": 0, "inProgress": true, "homeTeam": { "teamName": "São Paulo" }, "awayTeam": { "teamName": "Internacional" } }, { "id": 42, "homeTeamId": 6, "homeTeamGoals": 1, "awayTeamId": 1, "awayTeamGoals": 0, "inProgress": true, "homeTeam": { "teamName": "Ferroviária" }, "awayTeam": { "teamName": "Avaí/Kindermann" } } ]
- GET
matches?inProgress=false
Lista todas as partidas finalizadas
Exemplo de retorno da requisição:
[
{
"id": 1,
"homeTeamId": 16,
"homeTeamGoals": 1,
"awayTeamId": 8,
"awayTeamGoals": 1,
"inProgress": false,
"homeTeam": {
"teamName": "São Paulo"
},
"awayTeam": {
"teamName": "Grêmio"
}
},
{
"id": 2,
"homeTeamId": 9,
"homeTeamGoals": 1,
"awayTeamId": 14,
"awayTeamGoals": 1,
"inProgress": false,
"homeTeam": {
"teamName": "Internacional"
},
"awayTeam": {
"teamName": "Santos"
}
}
]
- PATCH
/matches/:id/finish
Finaliza uma partida em andamento
-
Caso o token não seja informado, deve-se retornar, com um status
401
, a seguinte mensagem:{ "message": "Token not found" }
-
Caso o token informado não seja válido, deve-se retornar, com um status
401
, a seguinte mensagem:{ "message": "Token must be a valid token" }
-
Deve-se retornar, com um status
200
, a seguinte mensagem:{ "message": "Finished" }
- PATCH
/matches/:id
Atualiza partidas em andamento
-
O corpo da requisição terá o seguinte formato:
{ "homeTeamGoals": 3, "awayTeamGoals": 1 }
-
Caso o token não seja informado, deve-se retornar, com um status
401
, a seguinte mensagem:{ "message": "Token not found" }
-
Caso o token informado não seja válido, deve-se retornar, com um status
401
, a seguinte mensagem:{ "message": "Token must be a valid token" }
- POST
/matches
Cadastra uma nova partida
-
O corpo da requisição terá o seguinte formato:
{ "homeTeamId": 16, // O valor deve ser o id do time "awayTeamId": 8, // O valor deve ser o id do time "homeTeamGoals": 2, "awayTeamGoals": 2, }
-
Caso o token não seja informado, deve-se retornar, com um status
401
, a seguinte mensagem:{ "message": "Token not found" }
-
Caso o token informado não seja válido, deve-se retornar, com um status
401
, a seguinte mensagem:{ "message": "Token must be a valid token" }
-
Caso tente-se inserir uma partida entre o time e ele mesmo, deve-se retornar, com um status
422
, a seguinte mensagem:
{ "message": "It is not possible to create a match with two equal teams" }
-
Caso a partida seja inserida com sucesso, deve-se retornar os dados da partida, com status
201
:{ "id": 1, "homeTeamId": 16, "homeTeamGoals": 2, "awayTeamId": 8, "awayTeamGoals": 2, "inProgress": true, }
- GET
/leaderboard/home
Retorna a classificação dos times da casa
-
Exemplo de retorno:
[ { "name": "Santos", "totalPoints": 9, "totalGames": 3, "totalVictories": 3, "totalDraws": 0, "totalLosses": 0, "goalsFavor": 9, "goalsOwn": 3, "goalsBalance": 6, "efficiency": "100.00" }, { "name": "Palmeiras", "totalPoints": 7, "totalGames": 3, "totalVictories": 2, "totalDraws": 1, "totalLosses": 0, "goalsFavor": 10, "goalsOwn": 5, "goalsBalance": 5, "efficiency": "77.78" }, { "name": "Corinthians", "totalPoints": 6, "totalGames": 2, "totalVictories": 2, "totalDraws": 0, "totalLosses": 0, "goalsFavor": 6, "goalsOwn": 1, "goalsBalance": 5, "efficiency": "100.00" }, { "name": "Grêmio", "totalPoints": 6, "totalGames": 2, "totalVictories": 2, "totalDraws": 0, "totalLosses": 0, "goalsFavor": 4, "goalsOwn": 1, "goalsBalance": 3, "efficiency": "100.00" }, { "name": "Real Brasília", "totalPoints": 6, "totalGames": 2, "totalVictories": 2, "totalDraws": 0, "totalLosses": 0, "goalsFavor": 2, "goalsOwn": 0, "goalsBalance": 2, "efficiency": "100.00" }, { "name": "São Paulo", "totalPoints": 4, "totalGames": 2, "totalVictories": 1, "totalDraws": 1, "totalLosses": 0, "goalsFavor": 4, "goalsOwn": 1, "goalsBalance": 3, "efficiency": "66.67" }, { "name": "Internacional", "totalPoints": 4, "totalGames": 3, "totalVictories": 1, "totalDraws": 1, "totalLosses": 1, "goalsFavor": 4, "goalsOwn": 6, "goalsBalance": -2, "efficiency": "44.44" }, { "name": "Botafogo", "totalPoints": 4, "totalGames": 3, "totalVictories": 1, "totalDraws": 1, "totalLosses": 1, "goalsFavor": 2, "goalsOwn": 4, "goalsBalance": -2, "efficiency": "44.44" }, { "name": "Ferroviária", "totalPoints": 3, "totalGames": 2, "totalVictories": 1, "totalDraws": 0, "totalLosses": 1, "goalsFavor": 3, "goalsOwn": 2, "goalsBalance": 1, "efficiency": "50.00" }, { "name": "Napoli-SC", "totalPoints": 2, "totalGames": 2, "totalVictories": 0, "totalDraws": 2, "totalLosses": 0, "goalsFavor": 2, "goalsOwn": 2, "goalsBalance": 0, "efficiency": "33.33" }, { "name": "Cruzeiro", "totalPoints": 1, "totalGames": 2, "totalVictories": 0, "totalDraws": 1, "totalLosses": 1, "goalsFavor": 2, "goalsOwn": 3, "goalsBalance": -1, "efficiency": "16.67" }, { "name": "Flamengo", "totalPoints": 1, "totalGames": 2, "totalVictories": 0, "totalDraws": 1, "totalLosses": 1, "goalsFavor": 1, "goalsOwn": 2, "goalsBalance": -1, "efficiency": "16.67" }, { "name": "Minas Brasília", "totalPoints": 1, "totalGames": 3, "totalVictories": 0, "totalDraws": 1, "totalLosses": 2, "goalsFavor": 3, "goalsOwn": 6, "goalsBalance": -3, "efficiency": "11.11" }, { "name": "Avaí/Kindermann", "totalPoints": 1, "totalGames": 3, "totalVictories": 0, "totalDraws": 1, "totalLosses": 2, "goalsFavor": 3, "goalsOwn": 7, "goalsBalance": -4, "efficiency": "11.11" }, { "name": "São José-SP", "totalPoints": 0, "totalGames": 3, "totalVictories": 0, "totalDraws": 0, "totalLosses": 3, "goalsFavor": 2, "goalsOwn": 5, "goalsBalance": -3, "efficiency": "0.00" }, { "name": "Bahia", "totalPoints": 0, "totalGames": 3, "totalVictories": 0, "totalDraws": 0, "totalLosses": 3, "goalsFavor": 0, "goalsOwn": 4, "goalsBalance": -4, "efficiency": "0.00" } ]
app/backend/Dockerfile
app/backend/src/api/
app/backend/src/database/migrations/
app/backend/src/database/migrations/models/
app/backend/src/middlewares/
app/backend/src/middlewares/
app/backend/src/tests/
app/frontend/Dockerfile