Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/michelmix/football-league
O football league é uma aplicação back-end web desenvolvido em TypeScript e JavaScript que permite a visualização de uma liga de futebol com suas partidas e tabela de classificação
https://github.com/michelmix/football-league
bcryptjs-for-password-encryption chai docker express javascript mysql node sequelize sinon token typescript
Last synced: 22 days ago
JSON representation
O football league é uma aplicação back-end web desenvolvido em TypeScript e JavaScript que permite a visualização de uma liga de futebol com suas partidas e tabela de classificação
- Host: GitHub
- URL: https://github.com/michelmix/football-league
- Owner: michelmix
- Created: 2023-10-25T17:27:27.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2023-10-25T18:53:58.000Z (about 1 year ago)
- Last Synced: 2024-10-02T06:22:10.168Z (about 1 month ago)
- Topics: bcryptjs-for-password-encryption, chai, docker, express, javascript, mysql, node, sequelize, sinon, token, typescript
- Language: JavaScript
- Homepage:
- Size: 885 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Projeto Football League
Projeto desenvolvido por mim durante o curso de Desenvolvimento Web na Trybe. Divulgado aqui como portfólio de aprendizado
# Sobre o Projeto
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).
## Funcionalidades
- 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## Tecnologias e habilidades utilizadas
- 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## Instalação
- 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:
```bash
npm run compose:up
```
- Para se certificar que as dependências de cada container de serviço foram instaladas, execute o seguinte comando:```bash
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: `[email protected]` 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
## Endpoints
- GET `/teams`
Lista todos os times
- Retorna `status HTTP 200` com o seguinte resultado:
```json
[
{
"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 um `json` contendo o retorno no seguinte modelo:
```json
{
"id": 5,
"teamName": "Cruzeiro"
}
```
- POST `/login`
Realiza login no app
- O body da requisição deve conter o seguinte formato:
```json
{
"email": "string",
"password": "string"
}
```- O campo `email` deve receber um email válido. Ex: `[email protected]`;
- 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`:
```json
{
"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`:
```json
{ "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`:
```json
{ "message": "Invalid email or password" }
```- Sendo emails inválidos:
- Emails com formato inválido: `@exemplo.com`, `exemplo@exemplo`, `[email protected]`, `exemplo.exemplo.com`;
- Emails com formato válido, mas não cadastrados no banco;
- 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;
- 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:
```json
{ "message": "Token not found" }
```- Caso o token informado não seja válido, deve-se retornar, com um status `401`, a seguinte mensagem:
```json
{ "message": "Token must be a valid token" }
```- A resposta deve ser de status `200` com um `objeto` contendo a `role` do *user*:
```json
{ "role": "admin" }
```
- GET `/matches`
Lista todas as partidas
- O resultado esperado deverá ser conforme abaixo:
```json
[
{
"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:
```json
[
{
"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:
```json
{ "message": "Token not found" }
```
- Caso o token informado não seja válido, deve-se retornar, com um status `401`, a seguinte mensagem:
```json
{ "message": "Token must be a valid token" }
```
- Deve-se retornar, com um status `200`, a seguinte mensagem:
```json
{ "message": "Finished" }
```
- PATCH `/matches/:id`
Atualiza partidas em andamento
- O corpo da requisição terá o seguinte formato:
```json
{
"homeTeamGoals": 3,
"awayTeamGoals": 1
}
```- Caso o token não seja informado, deve-se retornar, com um status `401`, a seguinte mensagem:
```json
{ "message": "Token not found" }
```- Caso o token informado não seja válido, deve-se retornar, com um status `401`, a seguinte mensagem:
```json
{ "message": "Token must be a valid token" }
```
- POST `/matches`
Cadastra uma nova partida
- O corpo da requisição terá o seguinte formato:
```json
{
"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:
```json
{ "message": "Token not found" }
```- Caso o token informado não seja válido, deve-se retornar, com um status `401`, a seguinte mensagem:
```json
{ "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:
```json
{ "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`:
```json
{
"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:
```json
[
{
"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"
}
]
```
## Pastas/arquivos desenvolvidos por mim
```bash
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