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

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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:

      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


Endpoints

  • 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 um json 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;
  • 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:

    { "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 um objeto contendo a role 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"
        }
      ]

Pastas/arquivos desenvolvidos por mim

  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

About

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


Languages

Language:JavaScript 45.0%Language:TypeScript 39.8%Language:CSS 12.0%Language:HTML 1.4%Language:Shell 0.9%Language:Dockerfile 0.9%