One place for hosting & domains

      Construir

      Como construir uma paginação personalizada com o React


      Introdução

      Muitas vezes, no desenvolvimento de aplicativos Web, precisamos buscar grandes conjuntos de registros de dados de um servidor remoto, API ou banco de dados. Se você estiver construindo um sistema de pagamento, por exemplo, ele poderia estar buscando milhares de transações. Se é um aplicativo de mídia social, poderia estar buscando muitos comentários de usuários, perfis ou atividades. Seja qual for o caso, existem diversas soluções para apresentar os dados de uma maneira que não sobrecarregue o usuário final que esteja interagindo com o aplicativo.

      Um dos métodos para lidar com grandes conjuntos de dados é usar a paginação. A paginação funciona de maneira eficaz quando o tamanho do conjunto de dados (o número total de registros no conjunto de dados) é conhecido previamente. Além disso, apenas a quantidade de dados necessária é carregada do conjunto total de dados com base na interação de usuários finais com o controle de paginação. Essa é a técnica usada na exibição de resultados na pesquisa do Google.

      Neste tutorial, veremos como criar um componente de paginação personalizada com o React para paginar grandes conjuntos de dados. Será construida uma visualização paginada dos países no mundo — um conjunto de dados com um tamanho conhecido.

      Aqui está uma demonstração daquilo que você construirá neste tutorial:

      Captura de tela do aplicativo de demonstração — exibindo os países no mundo

      Pré-requisitos

      Para completar este tutorial, você precisará de:

      • O Node instalado em sua máquina. Os passos para isso podem ser encontrados em Como instalar o Node.js e criar um ambiente de desenvolvimento local.
      • O pacote de linha de comando [create-react-app][`create-react-app] para criar um código boilerplate para seu aplicativo React. Caso esteja usando onpm < 5.2, pode ser necessário instalar ocreate-react-app` como uma dependência global.
      • Por fim, este tutorial assume que você já esteja familiarizado com o React. Se este não for o caso, verifique a série Como programar no React.js para aprender mais sobre o React.

      Este tutorial foi verificado com o Node v14.2.0, npm v6.14.4, react v16.13.1 e react-scripts v3.4.1.

      Passo 1 — Configurando o projeto

      Incie um novo aplicativo React usando o comando create-react-app. Sinta-se à vontade para dar o nome que preferir ao aplicativo, mas este tutorial irá chamá-lo de react-pagination:

      • npx create-react-app react-pagination

      Em seguida, você irá instalar as dependências necessárias para o seu aplicativo. Primeiro, use a janela do terminal para navegar até o diretório do projeto:

      Execute o seguinte comando para instalar as dependências necessárias:

      • npm install bootstrap@4.1.0 prop-types@15.6.1 react-flags@0.1.13 countries-api@2.0.1 node-sass@4.14.1

      Isso irá instalar o bootstrap, prop-types, react-flags, countries-api e o node-sass.

      O pacote bootstrap foi instalado como uma dependência para o seu aplicativo, pois você precisará de um estilo padrão. Além disso, você também irá utilizar estilos do componente pagination do Bootstrap.

      Para incluir o Bootstrap no aplicativo, edite o arquivo src/index.js:

      E adicione a linha a seguir antes das outras declarações import:

      src/index.js

      import "bootstrap/dist/css/bootstrap.min.css";
      

      Agora, o estilo do Bootstrap estará disponível em todo o seu aplicativo.

      Além disso, o react-flags foi instalado como uma dependência para o seu aplicativo. Para obter acesso aos ícones de bandeira a partir do seu aplicativo, será necessário copiar as imagens dos ícones para o diretório public do seu aplicativo.

      Crie um diretório img em seu diretório public:

      Copie os arquivos de imagem em flags para img:

      • cp -R node_modules/react-flags/vendor/flags public/img

      Isso gera uma cópia de todas as imagens do react-flag para o seu aplicativo.

      Agora que você incluiu algumas dependências, inicie o aplicativo executando o comando a seguir com o npm a partir do diretório de projeto react-pagination:

      Agora que o aplicativo foi iniciado, o desenvolvimento pode começar. Observe que uma guia do navegador foi aberta para você com a funcionalidade de carregamento dinâmico para manter tudo sincronizado à medida em que você avança no desenvolvimento.

      Neste ponto, a visualização do aplicativo deve se assemelhar à seguinte captura de tela:

      Visualização inicial – tela de boas-vindas ao React

      Agora, tudo está pronto para começarmos a criar os componentes.

      Passo 2 — Criando o componente ContryCard

      Neste passo, você irá criar o componente CountryCard. O componente CountryCard processa o nome, região e bandeira de um determinado país.

      Primeiro, vamos criar um diretório components dentro do diretório src:

      Em seguida, crie um novo arquivo CountryCard.js dentro do diretório src/components:

      • nano src/components/CountryCard.js

      E adicione o seguinte código a ele:

      src/components/CountryCard.js

      import React from 'react';
      import PropTypes from 'prop-types';
      import Flag from 'react-flags';
      
      const CountryCard = props => {
        const {
          cca2: code2 = '', region = null, name = {}
        } = props.country || {};
      
        return (
          <div className="col-sm-6 col-md-4 country-card">
            <div className="country-card-container border-gray rounded border mx-2 my-3 d-flex flex-row align-items-center p-0 bg-light">
              <div className="h-100 position-relative border-gray border-right px-2 bg-white rounded-left">
                <Flag country={code2} format="png" pngSize={64} basePath="./img/flags" className="d-block h-100" />
              </div>
              <div className="px-3">
                <span className="country-name text-dark d-block font-weight-bold">{ name.common }</span>
                <span className="country-region text-secondary text-uppercase">{ region }</span>
              </div>
            </div>
          </div>
        )
      }
      
      CountryCard.propTypes = {
        country: PropTypes.shape({
          cca2: PropTypes.string.isRequired,
          region: PropTypes.string.isRequired,
          name: PropTypes.shape({
            common: PropTypes.string.isRequired
          }).isRequired
        }).isRequired
      };
      
      export default CountryCard;
      

      O componente CountryCard exige uma propriedade country que contém os dados sobre o país a ser processado. Como visto nas propTypes para o componente CountryCard, a propriedade country deve conter os seguintes dados:

      • cca2 – Código de 2 dígitos do país
      • region – a região do país (por exemplo, “África”)
      • name.common – o nome comum do país (por exemplo, “Nigéria”)

      Aqui está um objeto de país de amostra:

      {
        cca2: "NG",
        region: "Africa",
        name: {
          common: "Nigeria"
        }
      }
      

      Além disso, note como a bandeira do país é renderizada usando o pacote react-flags. Verifique a documentação do react-flags para aprender mais sobre as propriedades exigidas e como usar o pacote.

      Agora, você finalizou a criação de um componente CountryCard individual. No final das contas, você usará os CountryCards várias vezes para exibir diferentes bandeiras e informações dos países em seu aplicativo.

      Passo 3 — Criando o componente Pagination

      Neste passo, você irá criar o componente Pagination. O componente Pagination contém a lógica para a construção, renderização e troca de páginas no controle de paginação.

      Crie um novo arquivo Pagination.js dentro do diretório src/components:

      • nano src/components/Pagination.js

      E adicione o seguinte código a ele:

      src/components/Pagination.js

      import React, { Component, Fragment } from 'react';
      import PropTypes from 'prop-types';
      
      class Pagination extends Component {
        constructor(props) {
          super(props);
          const { totalRecords = null, pageLimit = 30, pageNeighbours = 0 } = props;
      
          this.pageLimit = typeof pageLimit === 'number' ? pageLimit : 30;
          this.totalRecords = typeof totalRecords === 'number' ? totalRecords : 0;
      
          // pageNeighbours can be: 0, 1 or 2
          this.pageNeighbours = typeof pageNeighbours === 'number'
            ? Math.max(0, Math.min(pageNeighbours, 2))
            : 0;
      
          this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);
      
          this.state = { currentPage: 1 };
        }
      }
      
      Pagination.propTypes = {
        totalRecords: PropTypes.number.isRequired,
        pageLimit: PropTypes.number,
        pageNeighbours: PropTypes.number,
        onPageChanged: PropTypes.func
      };
      
      export default Pagination;
      

      O componente Pagination pode receber quatro propriedades especiais assim como especificado no objeto propTypes.

      • onPageChanged é uma função chamada com dados do estado atual de paginação apenas quando a página atual muda.
      • totalRecords indica o número total de registros a serem paginados. Ele é exigido.
      • pageLimit indica o número de registros a serem exibidos por página. Caso não seja especificado, seu valor padrão é 30 conforme definido no constructor().
      • pageNeighbours indica a quantidade de números de página adicionais a se exibir em cada lado da página atual. O valor mínimo é 0, e o valor máximo é 2. Caso não seja especificado, o valor padrão é 0 conforme definido no constructor().

      A imagem a seguir ilustra o efeito de diferentes valores para a propriedade pageNeighbours:

      Ilustração de pageNeighbours

      Na função constructor(), você computa o total de páginas da seguinte forma:

      this.totalPages = Math.ceil(this.totalRecords / this.pageLimit);
      

      Observe que o Math.ceil() é usado aqui para garantir que seja obtido um valor inteiro para o número total de páginas. Isso também garante que os registros em excesso sejam capturados na última página, principalmente nos casos em que o número de registros em excesso seja inferior ao número de registros a serem exibidos por página.

      Por fim, você inicializou o estado com a propriedade currentPage definida em 1. Essa propriedade de estado é necessária para manter o controle interno da página ativa atual.

      Em seguida, você irá criar o método para gerar os números de página.

      Após os imports mas antes da classe Pagination, adicione as constantes a seguir e a função range:

      src/components/Pagination.js

      // ...
      
      const LEFT_PAGE = 'LEFT';
      const RIGHT_PAGE = 'RIGHT';
      
      /**
       * Helper method for creating a range of numbers
       * range(1, 5) => [1, 2, 3, 4, 5]
       */
      const range = (from, to, step = 1) => {
        let i = from;
        const range = [];
      
        while (i <= to) {
          range.push(i);
          i += step;
        }
      
        return range;
      }
      

      Na classe Pagination, após o constructor, adicione o seguinte método fetchPageNumbers:

      src/components/Pagination.js

      class Pagination extends Component {
        // ...
      
        /**
         * Let's say we have 10 pages and we set pageNeighbours to 2
         * Given that the current page is 6
         * The pagination control will look like the following:
         *
         * (1) < {4 5} [6] {7 8} > (10)
         *
         * (x) => terminal pages: first and last page(always visible)
         * [x] => represents current page
         * {...x} => represents page neighbours
         */
        fetchPageNumbers = () => {
          const totalPages = this.totalPages;
          const currentPage = this.state.currentPage;
          const pageNeighbours = this.pageNeighbours;
      
          /**
           * totalNumbers: the total page numbers to show on the control
           * totalBlocks: totalNumbers + 2 to cover for the left(<) and right(>) controls
           */
          const totalNumbers = (this.pageNeighbours * 2) + 3;
          const totalBlocks = totalNumbers + 2;
      
          if (totalPages > totalBlocks) {
            const startPage = Math.max(2, currentPage - pageNeighbours);
            const endPage = Math.min(totalPages - 1, currentPage + pageNeighbours);
            let pages = range(startPage, endPage);
      
            /**
             * hasLeftSpill: has hidden pages to the left
             * hasRightSpill: has hidden pages to the right
             * spillOffset: number of hidden pages either to the left or to the right
             */
            const hasLeftSpill = startPage > 2;
            const hasRightSpill = (totalPages - endPage) > 1;
            const spillOffset = totalNumbers - (pages.length + 1);
      
            switch (true) {
              // handle: (1) < {5 6} [7] {8 9} (10)
              case (hasLeftSpill && !hasRightSpill): {
                const extraPages = range(startPage - spillOffset, startPage - 1);
                pages = [LEFT_PAGE, ...extraPages, ...pages];
                break;
              }
      
              // handle: (1) {2 3} [4] {5 6} > (10)
              case (!hasLeftSpill && hasRightSpill): {
                const extraPages = range(endPage + 1, endPage + spillOffset);
                pages = [...pages, ...extraPages, RIGHT_PAGE];
                break;
              }
      
              // handle: (1) < {4 5} [6] {7 8} > (10)
              case (hasLeftSpill && hasRightSpill):
              default: {
                pages = [LEFT_PAGE, ...pages, RIGHT_PAGE];
                break;
              }
            }
      
            return [1, ...pages, totalPages];
          }
      
          return range(1, totalPages);
        }
      }
      

      Aqui, você define duas constantes pela primeira vez: LEFT_PAGE e RIGHT_PAGE. Essas constantes serão usadas para indicar os pontos onde haverá controles de página para mover-se para a esquerda e direita, respectivamente.

      Você também definiu uma função auxiliar range() que pode ajudá-lo a gerar intervalos de números.

      Nota: se você usa uma biblioteca de utilidades como o Lodash em seu projeto, então pode usar a função _.range() disponibilizada pelo Lodash como alternativa. O código a seguir mostra a diferença entre a função range() que você acabou de definir e a do Lodash:

      range(1, 5); // returns [1, 2, 3, 4, 5]
      _.range(1, 5); // returns [1, 2, 3, 4]
      

      Em seguida, você definiu o método fetchPageNumbers() na classe Pagination. Esse método processa a lógica principal para a geração dos números de página a serem exibidos no controle de paginação. É desejável que a primeira página e a última sejam estejam visíveis.

      Primeiro, algumas variáveis foram definidas. totalNumbers representa os números totais de páginas que serão exibidos no controle. totalBlocks representa os números totais de página a serem exibidos com o acréscimo de dois blocos adicionais para os indicadores esquerdo e direito de página.

      Se o totalPages não for maior que totalBlocks, você retorna um intervalo de números de 1 até totalPages. Caso contrário, você retorna a matriz de números de página, com LEFT_PAGE e RIGHT_PAGE em pontos onde existem páginas sendo despejadas para a esquerda e direita, respectivamente.

      No entanto, observe que o controle de paginação garante que a primeira página e a última estejam sempre visíveis. Os controles de página da esquerda e direita aparecem na parte de dentro.

      Agora, você irá adicionar o método render() para habilitar a renderização do controle de paginação.

      Na classe Pagination, após os métodosconstructor e fetchPageNumbers, adicione o seguinte método render:

      src/components/Pagination.js

      class Pagination extends Component {
        // ...
      
        render() {
          if (!this.totalRecords || this.totalPages === 1) return null;
      
          const { currentPage } = this.state;
          const pages = this.fetchPageNumbers();
      
          return (
            <Fragment>
              <nav aria-label="Countries Pagination">
                <ul className="pagination">
                  { pages.map((page, index) => {
      
                    if (page === LEFT_PAGE) return (
                      <li key={index} className="page-item">
                        <a className="page-link" href="https://www.digitalocean.com/community/tutorials/#" aria-label="Previous" onClick={this.handleMoveLeft}>
                          <span aria-hidden="true">&laquo;</span>
                          <span className="sr-only">Previous</span>
                        </a>
                      </li>
                    );
      
                    if (page === RIGHT_PAGE) return (
                      <li key={index} className="page-item">
                        <a className="page-link" href="https://www.digitalocean.com/community/tutorials/#" aria-label="Next" onClick={this.handleMoveRight}>
                          <span aria-hidden="true">&raquo;</span>
                          <span className="sr-only">Next</span>
                        </a>
                      </li>
                    );
      
                    return (
                      <li key={index} className={`page-item${ currentPage === page ? ' active' : ''}`}>
                        <a className="page-link" href="https://www.digitalocean.com/community/tutorials/#" onClick={ this.handleClick(page) }>{ page }</a>
                      </li>
                    );
      
                  }) }
      
                </ul>
              </nav>
            </Fragment>
          );
        }
      }
      

      Aqui, você gera o array de números de página chamando o método fetchPageNumbers() que você criou anteriormente. Em seguida, você renderiza cada número de página usando o Array.prototype.map(). Observe que manipuladores de eventos de clique foram registrados em cada número de página renderizada para manipular os cliques.

      Além disso, note que o controle de paginação não será renderizado caso a propriedade totalRecords não tenha sido passada corretamente para o componente Pagination, ou nos casos em que haja apenas 1 página.

      Por fim, você irá definir os métodos do manipulador de eventos.

      Na classe Pagination, após o constructor e métodos fetchPageNumbers e render, adicione o seguinte:

      src/components/Pagination.js

      class Pagination extends Component {
        // ...
      
        componentDidMount() {
          this.gotoPage(1);
        }
      
        gotoPage = page => {
          const { onPageChanged = f => f } = this.props;
          const currentPage = Math.max(0, Math.min(page, this.totalPages));
          const paginationData = {
            currentPage,
            totalPages: this.totalPages,
            pageLimit: this.pageLimit,
            totalRecords: this.totalRecords
          };
      
          this.setState({ currentPage }, () => onPageChanged(paginationData));
        }
      
        handleClick = page => evt => {
          evt.preventDefault();
          this.gotoPage(page);
        }
      
        handleMoveLeft = evt => {
          evt.preventDefault();
          this.gotoPage(this.state.currentPage - (this.pageNeighbours * 2) - 1);
        }
      
        handleMoveRight = evt => {
          evt.preventDefault();
          this.gotoPage(this.state.currentPage + (this.pageNeighbours * 2) + 1);
        }
      }
      

      Foi definido o método gotoPage() que modifica o estado e define o currentPage para a página especificada. Ele garante que o argumento page tenha um valor mínimo de 1 e um valor máximo do número total de páginas. Por fim, a função onPageChanged() que foi passada como uma propriedade é chamada, com dados indicando o novo estado de paginação.

      Quando o componente é montado, você vai até a primeira página chamando this.gotoPage(1) como mostrado no método de ciclo de vida componentDidMount().

      Observe como o (this.pageNeighbours * 2) foi usado em handleMoveLeft() e handleMoveRight() para deslizar os números de página para a esquerda e direita, respectivamente, com base no número atual de página.

      Aqui está uma demonstração da interação dos movimentos para a esquerde e direita.

      Interação da movimentação para a esquerda e direita

      Agora, você concluiu o componente Pagination. Os usuários serão capazes de interagir com os controles de navegação neste componente para exibir diferentes páginas de bandeiras.

      Passo 4 — Construindo o componente App

      Agora que você tem um componente CountryCard e um Pagination, use-os em seu componente App.

      Modifique o arquivo App.js no diretório src:

      Substitua o conteúdo de App.js pelas linhas de código a seguir:

      src/App.js

      import React, { Component } from 'react';
      import Countries from 'countries-api';
      import './App.css';
      import Pagination from './components/Pagination';
      import CountryCard from './components/CountryCard';
      
      class App extends Component {
        state = { allCountries: [], currentCountries: [], currentPage: null, totalPages: null }
      
        componentDidMount() {
          const { data: allCountries = [] } = Countries.findAll();
          this.setState({ allCountries });
        }
      
        onPageChanged = data => {
          const { allCountries } = this.state;
          const { currentPage, totalPages, pageLimit } = data;
          const offset = (currentPage - 1) * pageLimit;
          const currentCountries = allCountries.slice(offset, offset + pageLimit);
      
          this.setState({ currentPage, currentCountries, totalPages });
        }
      }
      
      export default App;
      

      Aqui, inicializa-se o estado do componente App com os seguintes atributos:

      • allCountries – é uma matriz de todos os países em seu aplicativo. Inicializada como uma matriz vazia ([]).
      • currentCountries – é uma matriz de todos os países a serem exibidos na página atualmente ativa. Inicializada como uma matriz vazia ([]).
      • currentPage – é o número da página que está atualmente ativa. Inicializada como null.
      • totalPages – é o número total de páginas para todos os registros de país. Inicializada como null.

      Em seguida, no método de ciclo de vida componentDidMount(), busca-se todos os países usando o pacote countries-api invocando o Countries.findAll(). Depois disso, atualiza-se o estado do aplicativo, definindo allCountries para conter todos os países do mundo. Veja a documentação do countries-apipara aprender mais sobre o pacote.

      Por fim, definiu-se o método onPageChanged(), que será chamado toda vez que o usuário navegar para uma nova página a partir do controle de paginação. Esse método será passado para a propriedade onPageChanged do componente Pagination.

      Há duas linhas que merecem atenção nesse método. A primeira é esta:

      const offset = (currentPage - 1) * pageLimit;
      

      O valor offset (deslocamento) indica o índice de partida para buscar os registros para a página atual. Usar (currentPage - 1) garante que o deslocamento é baseado em zero. Vamos supor que, por exemplo, estejam sendo exibidos 25 registros por página, e o usuário esteja visualizado atualmente a página 5. Sendo assim, o offset será ((5-1) * 25 = 100).

      Exemplificando, caso esteja buscando registros sob demanda de um banco de dados, esta é uma consulta SQL de amostra para mostrar como o offset pode ser usado:

      SELECT * FROM `countries` LIMIT 100, 25
      

      Como você não está coletando registros de um banco de dados ou qualquer fonte externa, uma maneira de extrair os registros a serem exibidos na página atual é necessária.

      A segunda é esta linha:

      const currentCountries = allCountries.slice(offset, offset + pageLimit);
      

      Aqui usa-se o método Array.prototype.slice() para extrair a parte dos registros requisitada de allCountries passando o offset como o índice de partida para a fatia e (offset + pageLimit) como o índice antes do qual a fatia deve terminar.

      Nota: neste tutorial, não foram coletados registros de nenhuma fonte externa. Em um aplicativo real, você provavelmente estaria buscando registros de um banco de dados ou uma API. A lógica para coletar os registros pode entrar no método onPageChanged() do componente App.

      Vamos supor que você tenha um ponto de extremidade de API fictício /api/countries?page={current_page}&limit={page_limit}. O código a seguir mostra como é possível buscar os países sob demanda a partir da API usando o pacote HTTP [axios][“axios]:

      onPageChanged = data => {
        const { currentPage, totalPages, pageLimit } = data;
      
        axios.get(`/api/countries?page=${currentPage}&limit=${pageLimit}`)
          .then(response => {
            const currentCountries = response.data.countries;
            this.setState({ currentPage, currentCountries, totalPages });
          });
      }
      

      Agora, finalize o componente App adicionando o método render().

      Na classe App, após o componentDidMount e onPageChanged, adicione o seguinde método render:

      src/App.js

      class App extends Component {
        // ... other methods here ...
      
        render() {
          const { allCountries, currentCountries, currentPage, totalPages } = this.state;
          const totalCountries = allCountries.length;
      
          if (totalCountries === 0) return null;
      
          const headerClass = ['text-dark py-2 pr-4 m-0', currentPage ? 'border-gray border-right' : ''].join(' ').trim();
      
          return (
            <div className="container mb-5">
              <div className="row d-flex flex-row py-5">
                <div className="w-100 px-4 py-5 d-flex flex-row flex-wrap align-items-center justify-content-between">
                  <div className="d-flex flex-row align-items-center">
                    <h2 className={headerClass}>
                      <strong className="text-secondary">{totalCountries}</strong> Countries
                    </h2>
                    { currentPage && (
                      <span className="current-page d-inline-block h-100 pl-4 text-secondary">
                        Page <span className="font-weight-bold">{ currentPage }</span> / <span className="font-weight-bold">{ totalPages }</span>
                      </span>
                    ) }
                  </div>
                  <div className="d-flex flex-row py-4 align-items-center">
                    <Pagination totalRecords={totalCountries} pageLimit={18} pageNeighbours={1} onPageChanged={this.onPageChanged} />
                  </div>
                </div>
                { currentCountries.map(country => <CountryCard key={country.cca3} country={country} />) }
              </div>
            </div>
          );
        }
      }
      

      No método render(), renderiza-se o número total de países, a página atual, o número total de páginas, o controle <Pagination> e o <CountryCard> para cada país na página atual.

      Observe que o método onPageChanged() definido anteriormente na propriedade onPageChanged do controle <Pagination> foi passado. Isso é muito importante para capturar alterações de página a partir do componente Pagination. Além disso, estão sendo exibidos 18 países por página.

      Neste ponto, o aplicativo estará parecido com o mostrado na captura de tela a seguir:

      Captura de tela do aplicativo com 248 países listados e números de página no topo para percorrer cada página

      Agora, você possui um componente App que exibe vários componentes CountryCard e um componente Pagination que divide o conteúdo em páginas separadas. Em seguida, você irá explorar a estilização do seu aplicativo.

      Passo 5 — Adicionando estilos personalizados

      Você pode ter notado que algumas classes personalizadas foram sendo adicionadas aos componentes criados anteriormente. Vamos definir algumas regras de estilo para essas classes no arquivo src/App.scss.

      O arquivo App.scss ficará parecido com o seguinte código:

      src/App.scss

      /* Declare some variables */
      $base-color: #ced4da;
      $light-background: lighten(desaturate($base-color, 50%), 12.5%);
      
      .current-page {
        font-size: 1.5rem;
        vertical-align: middle;
      }
      
      .country-card-container {
        height: 60px;
        cursor: pointer;
        position: relative;
        overflow: hidden;
      }
      
      .country-name {
        font-size: 0.9rem;
      }
      
      .country-region {
        font-size: 0.7rem;
      }
      
      .current-page,
      .country-name,
      .country-region {
        line-height: 1;
      }
      
      // Override some Bootstrap pagination styles
      ul.pagination {
        margin-top: 0;
        margin-bottom: 0;
        box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
      
        li.page-item.active {
          a.page-link {
            color: saturate(darken($base-color, 50%), 5%) !important;
            background-color: saturate(lighten($base-color, 7.5%), 2.5%) !important;
            border-color: $base-color !important;
          }
        }
      
        a.page-link {
          padding: 0.75rem 1rem;
          min-width: 3.5rem;
          text-align: center;
          box-shadow: none !important;
          border-color: $base-color !important;
          color: saturate(darken($base-color, 30%), 10%);
          font-weight: 900;
          font-size: 1rem;
      
          &:hover {
            background-color: $light-background;
          }
        }
      }
      

      Modifique seu arquivo App.js para fazer referência ao App.scss ao invés do App.css.

      Nota: para mais informações sobre isso, consulte a documentação do Create React App.

      src/App.js

      import React, { Component } from 'react';
      import Countries from 'countries-api';
      import './App.scss';
      import Pagination from './components/Pagination';
      import CountryCard from './components/CountryCard';
      

      Após adicionar os estilos, o aplicativo terá um visual semelhante ao da captura de tela a seguir:

      Captura de tela do aplicativo, páginas 1 a 14, com estilos

      Agora, você possui um aplicativo completo com uma estilização personalizada adicional. É possível utilizar estilos personalizados para modificar e aprimorar todos os estilos padrão disponibilizados por bibliotecas como o Bootstrap.

      Conclusão

      Neste tutorial, você criou um widget de paginação personalizada em seu aplicativo React. Embora não tenha feito chamadas para nenhuma API, nem interagido com qualquer backend de banco de dados neste tutorial, seu aplicativo pode exigir essas interações. Não sinta-se limitado à abordagem usada neste tutorial de maneira alguma — você pode estendê-la conforme desejar para que atenda aos requisitos do seu aplicativo.

      Para o código fonte completo deste tutorial, confira o repositório build-react-pagination-demo no GitHub. Se quiser, obtenha também uma demonstração em tempo real deste tutorial no Code Sandbox.

      Se quiser aprender mais sobre o React, dê uma olhada em nossa série Como programar no React.js, ou confira nossa página do tópico React para exercícios e projetos de programação.



      Source link

      Como construir uma API REST com o Prisma e o PostgreSQL


      O autor selecionou a Diversity in Tech Fund​​​​​ para receber uma doação como parte do programa Write for DOnations.

      Introdução

      O Prisma é um conjunto de ferramentas para banco de dados de código aberto. Ele consiste em três ferramentas principais:

      • Prisma Client: um construtor de consultas gerado automaticamente e fortemente tipado para o Node.js e o TypeScript.
      • Prisma Migrate: um sistema declarativo de modelagem e migração de dados.
      • Prisma Studio: uma GUI para visualizar e editar dados em seu banco de dados

      Essas ferramentas visam aumentar a produtividade de um desenvolvedor de aplicativos em seu fluxo de trabalho com banco de dados. Um dos principais benefícios do Prisma é o nível de abstração que ele fornece: em vez de lidar com consultas SQL ou migrações de esquema complexas, os desenvolvedores de aplicativos podem trabalhar com seus dados de uma maneira mais intuitiva usando o Prisma para trabalhar com seus bancos de dados.

      Neste tutorial, você irá construir uma API REST para um pequeno aplicativo de blog no TypeScript usando o Prisma e um banco de dados PostgreSQL. Você irá configurar seu banco de dados PostgreSQL localmente com o Docker e implementar as rotas da API REST usando o Express. No final do tutorial, você terá um servidor Web sendo executado localmente em sua máquina que pode responder a vários pedidos HTTP, além de ler e escrever dados no banco de dados

      Pré-requisitos

      Este tutorial assume o seguinte:

      Possuir uma familiaridade básica com o TypeScript e as APIs REST é útil, mas não é obrigatório para este tutorial.

      Passo 1 — Criando seu projeto do TypeScript

      Neste passo, você irá configurar um projeto simples do TypeScript usando o npm. Esse projeto servirá como fundação para a API REST que você irá construir ao longo deste tutorial.

      Primeiramente, crie um novo diretório para o seu projeto:

      Em seguida, navegue até o diretório e inicialize um projeto vazio do npm. Observe que a opção -y presente aqui significa que os prompts interativos do comando estão sendo ignorados. Para que os prompts sejam executados, remova o -y do comando:

      Para mais detalhes sobre esses prompts, siga o Passo 1 em Como usar os módulos do Node.js com o npm e o package.json.

      Você receberá um resultado semelhante ao seguinte, com as respostas padrão sendo utilizadas:

      Output

      Wrote to /.../my-blog/package.json: { "name": "my-blog", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }

      Esse comando cria um arquivo package.json mínimo que você usa como o arquivo de configuração para o seu projeto do npm. Agora, você está pronto para configurar o TypeScript em seu projeto.

      Execute o comando a seguir para uma configuração simples do TypeScript:

      • npm install typescript ts-node @types/node --save-dev

      Isso instala três pacotes como dependências de desenvolvimento em seu projeto:

      • typescript: a cadeia de ferramentas do TypeScript.
      • ts-node: um pacote para executar aplicativos do TypeScript sem compilação prévia para JavaScript.
      • @types/node: as definições de tipo do TypeScript para o Node.js.

      A última coisa a ser feita é adicionar um arquivo tsconfig.json para garantir que o TypeScript esteja configurado corretamente para o aplicativo que você irá compilar.

      Primeiramente, execute o comando a seguir para criar o arquivo:

      Adicione o seguinte código JSON ao arquivo:

      my-blog/tsconfig.json

      {
        "compilerOptions": {
          "sourceMap": true,
          "outDir": "dist",
          "strict": true,
          "lib": ["esnext"],
          "esModuleInterop": true
        }
      }
      

      Salve e saia do arquivo.

      Essa é uma configuração padrão e mínima para um projeto do TypeScript. Se quiser aprender sobre as propriedades individuais do arquivo de configuração, procure por elas na documentação do TypeScript.

      Você configurou seu projeto simples do TypeScript usando o npm. Em seguida, irá configurar seu banco de dados do PostgreSQL com o Docker e conectar o Prisma a ele.

      Passo 2 — Configurando o Prisma com o PostgreSQL

      Neste passo, você irá instalar o Prisma CLI, criar seu arquivo de esquema do Prisma inicial, configurar o PostgreSQL com o Docker e conectar o Prisma a ele. O esquema do Prisma é o arquivo de configuração principal para sua configuração do Prisma e contém o esquema do seu banco de dados.

      Comece instalando o Prisma CLI com o seguinte comando:

      • npm install @prisma/cli --save-dev

      Como prática recomendada, aconselha-se instalar o Prisma CLI localmente em seu projeto (ao invés de uma instalação global). Isso ajuda a evitar conflitos de versão caso você tenha mais de um projeto Prisma em sua máquina.

      Em seguida, você irá configurar seu banco de dados do PostgreSQL usando o Docker. Crie um novo arquivo do Docker Compose com o seguinte comando:

      Agora, adicione o código a seguir ao arquivo recém-criado:

      my-blog/docker-compose.yml

      version: '3.8'
      services:
        postgres:
          image: postgres:10.3
          restart: always
          environment:
            - POSTGRES_USER=sammy
            - POSTGRES_PASSWORD=your_password
          volumes:
            - postgres:/var/lib/postgresql/data
          ports:
            - '5432:5432'
      volumes:
        postgres:
      

      Esse arquivo do Docker Compose configura um banco de dados do PostgreSQL que pode ser acessado pela porta 5432 do contêiner do Docker. Observe também que as credenciais do banco de dados estão atualmente definidas como sammy (usuário) e your_password (senha). Sinta-se livre para alterar essas credenciais para o usuário e senha de sua escolha. Salve e saia do arquivo.

      Com essa configuração ajustada, inicie o servidor do banco de dados PostgreSQL com o seguinte comando:

      O resultado deste comando será semelhante a este:

      Output

      Pulling postgres (postgres:10.3)... 10.3: Pulling from library/postgres f2aa67a397c4: Pull complete 6de83ca23e55: Pull complete . . . Status: Downloaded newer image for postgres:10.3 Creating my-blog_postgres_1 ... done

      Verifique se o banco de dados está sendo executado com o seguinte comando:

      Isso irá gerar um resultado semelhante a este:

      Output

      CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 8547f8e007ba postgres:10.3 "docker-entrypoint.s…" 3 seconds ago Up 2 seconds 0.0.0.0:5432->5432/tcp my-blog_postgres_1

      Com o servidor do banco de dados em execução, crie agora sua configuração do Prisma. Execute o comando a seguir a partir do Prisma CLI:

      Isso imprimirá o seguinte resultado:

      Output

      ✔ Your Prisma schema was created at prisma/schema.prisma. You can now open it in your favorite editor.

      Observe que como uma prática recomendada, aconselha-se prefixar todas as invocações do Prisma CLI com o npx. Isso garante que sua instalação local esteja sendo usada.

      Depois de executar o comando, o Prisma CLI criou uma nova pasta chamada prisma em seu projeto. Ela contém os dois arquivos a seguir:

      • schema.prisma: o arquivo de configuração principal para o seu projeto Prisma (incluirá o seu modelo de dados).
      • .env: um arquivo dotenv para definir a URL de conexão do seu banco de dados.

      Para garantir que o Prisma saiba a localização do seu banco de dados, abra o arquivo .env e ajuste a variável de ambiente DATABASE_URL.

      Primeiro, abra o arquivo .env:

      Agora, defina a variável de ambiente da seguinte forma:

      my-blog/prisma/.env

      DATABASE_URL="postgresql://sammy:your_password@localhost:5432/my-blog?schema=public"
      

      Certifique-se de substituir as credenciais do banco de dados por aquelas que você especificou no arquivo do Docker Compose. Para aprender mais sobre o formato da conexão de URL, visite os documentos do Prisma.

      Assim que terminar, salve e feche o arquivo.

      Neste passo, você configurou seu banco de dados do PostgreSQL de dados com o Docker, instalou o Prisma CLI e conectou o Prisma ao banco de dados através de uma variável de ambiente. Na próxima seção, você irá definir seu modelo de dados e criar as tabelas do seu banco de dados.

      Passo 3 — Definindo seu modelo de dados e criando tabelas de banco de dados

      Neste passo, você irá definir seu modelo de dados no arquivo do esquema do Prisma. Esse modelo de dados será então mapeado para o banco de dados com o Prisma Migrate, que irá gerar e enviar as instruções SQL para criar as tabelas que correspondem ao seu modelo de dados. Como você está criando um aplicativo de blog, as principais entidades do aplicativo serão usuários e postagens.

      O Prisma usa sua própria linguagem de modelagem de dados para definir a forma dos dados do seu aplicativo.

      Primeiro, abra seu arquivo schema.prisma com o seguinte comando:

      • nano prisma/schema.prisma

      Agora, adicione as seguintes definições de modelo a ele. Coloque os modelos no final do arquivo, logo após o bloco generator client:

      my-blog/prisma/schema.prisma

      . . .
      model User {
        id    Int     @default(autoincrement()) @id
        email String  @unique
        name  String?
        posts Post[]
      }
      
      model Post {
        id        Int     @default(autoincrement()) @id
        title     String
        content   String?
        published Boolean @default(false)
        author    User?   @relation(fields: [authorId], references: tag:www.digitalocean.com,2005:/community/tutorials/how-to-build-a-rest-api-with-prisma-and-postgresql-pt)
        authorId  Int?
      }
      

      Salve e saia do arquivo.

      Você está definindo dois modelos, chamados User e Post. Cada um deles tem um número de campos que representam as propriedades do modelo. Os modelos serão mapeados para as tabelas do banco de dados; os campos representam as colunas individuais.

      Observe também que existe uma relação de uma para muitos entre os dois modelos, especificada pelos campos de relação posts e author em User e Post. Isso significa que um usuário pode estar associado a muitas postagens.

      Com esses modelos posicionados, crie agora as tabelas correspondentes no banco de dados usando o Prisma Migrate. No seu terminal, execute o seguinte comando:

      • npx prisma migrate save --experimental --create-db --name "init"

      Esse comando cria uma nova migração em seu sistema de arquivos Aqui está uma visão geral resumida das três opções que são fornecidas ao comando:

      • --experimental: é necessário porque o Prisma Migrate está atualmente em um estado experimental.
      • --create-db: permite que o Prisma Migrate crie o banco de dados chamado my-blog que é especificado na URL de conexão.
      • --name "init": especifica o nome da migração (será usado para nomear a pasta de migração que será criada em seu sistema de arquivos).

      O resultado deste comando será semelhante a este:

      Output

      New datamodel: // This is your Prisma schema file, // learn more about it in the docs: https://pris.ly/d/prisma-schema datasource db { provider = "postgresql" url = env("DATABASE_URL") } generator client { provider = "prisma-client-js" } model User { id Int @default(autoincrement()) @id email String @unique name String? posts Post[] } model Post { id Int @default(autoincrement()) @id title String content String? published Boolean @default(false) author User? @relation(fields: [authorId], references: tag:www.digitalocean.com,2005:/community/tutorials/how-to-build-a-rest-api-with-prisma-and-postgresql-pt) authorId Int? } Prisma Migrate just created your migration 20200811140708-init in migrations/ └─ 20200811140708-init/ └─ steps.json └─ schema.prisma └─ README.md

      Sinta-se à vontade para explorar os arquivos de migração que foram criados no diretório prisma/migrations.

      Para executar a migração em seu banco de dados e criar as tabelas para seus modelos do Prisma, execute o seguinte comando em seu terminal:

      • npx prisma migrate up --experimental

      Você receberá o seguinte resultado:

      Output

      . . . Checking the datasource for potential data loss... Database Changes: Migration Database actions Status 20200811140708-init 2 CreateTable statements. Done 🚀 You can get the detailed db changes with prisma migrate up --experimental --verbose Or read about them here: ./migrations/20200811140708-init/README.md 🚀 Done with 1 migration in 206ms.

      O Prisma Migrate agora gera as declarações SQL que são necessárias para a migração e as envia para o banco de dados. Vê-se a seguir as instruções SQL que criaram as tabelas:

      CREATE TABLE "public"."User" (
        "id" SERIAL,
        "email" text  NOT NULL ,
        "name" text   ,
        PRIMARY KEY ("id")
      )
      
      CREATE TABLE "public"."Post" (
        "id" SERIAL,
        "title" text  NOT NULL ,
        "content" text   ,
        "published" boolean  NOT NULL DEFAULT false,
        "authorId" integer   ,
        PRIMARY KEY ("id")
      )
      
      CREATE UNIQUE INDEX "User.email" ON "public"."User"("email")
      
      ALTER TABLE "public"."Post" ADD FOREIGN KEY ("authorId")REFERENCES "public"."User"("id") ON DELETE SET NULL ON UPDATE CASCADE
      

      Neste passo, você definiu seu modelo de dados em seu esquema do Prisma e criou as respectivas tabelas de bancos de dados com o Prisma Migrate. No próximo passo, você irá instalar o Prisma Client em seu projeto para que seja possível consultar o banco de dados.

      Passo 4 — Explorando as consultas do Prisma Client em um script simples

      O Prisma Client é um construtor de consultas gerado automaticamente e fortemente tipado que pode ser usado para ler e escrever dados programaticamente em um banco de dados a partir de um aplicativo Node.js ou TypeScript. Você irá usá-lo para ter acesso ao banco de dados em suas rotas da API REST, substituindo as ORMs tradicionais, consultas SQL simples, camadas de acesso de dados personalizadas, ou qualquer outro método de comunicação com um banco de dados.

      Neste passo, você irá instalar o Prisma Client e familiarizar-se com as consultas que podem ser enviadas com ela. Antes de implementar as rotas para sua API REST nos próximos passos, primeiro irá explorar algumas das consultas do Prisma Client em um script simples e executável.

      Primeiro, instale o Prisma Client em seu projeto abrindo seu terminal e instalando o pacote npm do Prisma Client:

      • npm install @prisma/client

      Em seguida, crie um novo diretório chamado src que irá conter seus arquivos de origem:

      Agora, crie um arquivo do TypeScript dentro do novo diretório:

      Todas as consultas do Prisma Client retornam promessas que você pode await (aguardar) em seu código. Isso requer que você envie as consultas dentro de uma função async (assíncrona).

      Adicione o seguinte texto clichê com uma função async a ser executada em seu script:

      my-blog/src/index.ts

      import { PrismaClient } from '@prisma/client'
      
      const prisma = new PrismaClient()
      
      async function main() {
        // ... your Prisma Client queries will go here
      }
      
      main()
        .catch((e) => console.error(e))
        .finally(async () => await prisma.disconnect())
      

      Aqui está um rápido detalhamento do código boilerplate.

      1. Você importa o construtor PrismaClient do pacote npm @prisma/client previamente instalado.
      2. Você instancia o PrismaClient chamando o construtor e obtém uma instância chamada prisma.
      3. Você define uma função async chamada main onde irá adicionar suas consultas do Prisma Client a seguir.
      4. Você chama a função main, enquanto captura todas as exceções em potencial e certifica-se de que o Prisma Client feche todas as conexões do banco de dados abertas chamando o prisma.disconnect().

      Com a função main no lugar, comece a adicionar as consultas do Prisma Client ao script. Ajuste o index.ts para que fique parecido com o seguinte:

      my-blog/src/index.ts

      import { PrismaClient } from '@prisma/client'
      
      const prisma = new PrismaClient()
      
      async function main() {
        const newUser = await prisma.user.create({
          data: {
            name: 'Alice',
            email: '[email protected]',
            posts: {
              create: {
                title: 'Hello World',
              },
            },
          },
        })
        console.log('Created new user: ', newUser)
      
        const allUsers = await prisma.user.findMany({
          include: { posts: true },
        })
        console.log('All users: ')
        console.dir(allUsers, { depth: null })
      }
      
      main()
        .catch((e) => console.error(e))
        .finally(async () => await prisma.disconnect())
      

      Neste código, duas consultas do Prisma Client estão sendo usadas:

      • create: cria um novo registro de User. Observe que você está usando na verdade um texto aninhado, o que significa que um registro de User e Post estão sendo criados na mesma consulta.
      • findMany: lê todos os registros de User existentes no banco de dados Você está fornecendo a opção include que carrega também os registros de Post relacionados para cada registro de User.

      Agora, execute o script com o seguinte comando:

      Você verá o seguinte resultado em seu terminal:

      Output

      Created new user: { id: 1, email: '[email protected]', name: 'Alice' } [ { id: 1, email: '[email protected]', name: 'Alice', posts: [ { id: 1, title: 'Hello World', content: null, published: false, authorId: 1 } ] }

      Nota: se estiver usando a GUI de um banco de dados, é possível confirmar que os dados foram criados olhando nas tabelas User e Post. De maneira alternativa, você pode explorar os dados do Prisma Studio executando npx prisma studio --experimental.

      Até aqui, você usou o Prisma Client para ler e escrever dados em seu banco de dados. Nos passos restantes, você irá aplicar esse novo conhecimento para implementar as rotas para uma API REST amostral.

      Passo 5 — Implementando sua primeira rota da API REST

      Neste passo, você irá instalar o Express em seu aplicativo. O Express é um framework Web popular para o Node.js que será usado para implementar as rotas da sua API REST neste projeto. A primeira rota a ser implementada lhe permitirá buscar todos os usuários da API usando uma solicitação GET. Os dados do usuário serão recuperados do banco de dados usando o Prisma Client.

      Vá em frente e instale o Express com o seguinte comando:

      Como você está usando TypeScript, também será necessário instalar os respectivos tipos como dependências de desenvolvimento. Execute o comando a seguir para fazer isso:

      • npm install @types/express --save-dev

      Com as dependências no lugar, configure agora sua aplicação Express.

      Comece abrindo seu arquivo de código de origem principal novamente:

      Agora, exclua todo o código em index.ts e substitua-o pelo seguinte, de forma a iniciar sua API REST:

      my-blog/src/index.ts

      import { PrismaClient } from '@prisma/client'
      import express from 'express'
      
      const prisma = new PrismaClient()
      const app = express()
      
      app.use(express.json())
      
      // ... your REST API routes will go here
      
      app.listen(3000, () =>
        console.log('REST API server ready at: http://localhost:3000'),
      )
      

      Aqui está um rápido detalhamento do código:

      1. Você importa o PrismaClient e o express dos respectivos pacotes npm.
      2. Você instancia o PrismaClient chamando o construtor e obtém uma instância chamada prisma.
      3. Você cria seu aplicativo Express chamando o express().
      4. Você adiciona o middleware express.json() para garantir que os dados JSON sejam processados corretamente pelo Express.
      5. Você inicia o servidor na porta 3000.

      Agora, implemente sua primeira rota. Adicione o seguinte código entre as chamadas para app.use e app.listen:

      my-blog/src/index.ts

      . . .
      app.use(express.json())
      
      app.get('/users', async (req, res) => {
        const users = await prisma.user.findMany()
        res.json(users)
      })
      
      app.listen(3000, () =>
      console.log('REST API server ready at: http://localhost:3000'),
      )
      

      Depois de adicionado, salve e saia do seu arquivo. Em seguida, inicie seu servidor Web local usando o seguinte comando:

      Você receberá o seguinte resultado:

      Output

      REST API server ready at: http://localhost:3000

      Para acessar a rota /users, aponte seu navegador para http://localhost:3000/users ou qualquer outro cliente HTTP.

      Neste tutorial, você irá testar todas as rotas da API REST usando o curl, um cliente HTTP baseado em terminal.

      Nota: se preferir usar um cliente HTTP baseado em GUI, pode usar o Postwoman ou o Advanced REST Client.

      Para testar sua rota, abra uma nova janela ou guia de terminal (para que o seu servidor Web local continue sendo executado) e execute o seguinte comando:

      • curl http://localhost:3000/users

      Você receberá os dados de User que você criou no passo anterior:

      Output

      [{"id":1,"email":"[email protected]","name":"Alice"}]

      Observe que a matriz posts não foi incluída desta vez. Isso ocorre porque a opção include não está sendo passada para a chamada findMany na implementação da rota /users.

      Você implementou sua primeira rota da API REST em /users. No próximo passo, você irá implementar as rotas restantes da API REST para adicionar mais funcionalidade à sua API.

      Passo 6 — Implementando as rotas restantes da API REST

      Neste passo, você irá implementar as rotas restantes da API REST para seu aplicativo de blog. No final, seu servidor Web irá atender diversas solicitações GET, POST, PUT, e DELETE.

      Aqui está uma visão geral das diferentes rotas que serão implementadas:

      Método HTTPRotaDescrição
      GET/feedBusca todos os posts publicados.
      GET/post/:idBusca um post específico com base em seu ID.
      POST/userCria um novo usuário.
      POST/postCria um novo post (como um rascunho).
      PUT/post/publish/:idDefine o campo published (publicado) de um post como true (verdadeiro).
      DELETEpost/:idExclui um post por seu ID.

      Vá em frente e implemente as rotas GET restantes primeiro.

      Abra o index.ts com o seguinte comando:

      Em seguida, adicione o código a seguir após a implementação da rota /users:

      my-blog/src/index.ts

      . . .
      
      app.get('/feed', async (req, res) => {
        const posts = await prisma.post.findMany({
          where: { published: true },
          include: { author: true }
        })
        res.json(posts)
      })
      
      app.get(`/post/:id`, async (req, res) => {
        const { id } = req.params
        const post = await prisma.post.findOne({
          where: { id: Number(id) },
        })
        res.json(post)
      })
      
      app.listen(3000, () =>
        console.log('REST API server ready at: http://localhost:3000'),
      )
      

      Salve e saia do seu arquivo.

      Esse código implementa as rotas API para duas solicitações GET:

      • /feed: retorna uma lista de posts publicados.
      • /post/:id: retorna um post específico por seu ID.

      O Prisma Client é usado em ambas as implementações. Na implementação de rota /feed, a consulta enviada com o Prisma Client filtra por todos os registros de Post onde a coluna published contém o valor true. Além disso, a consulta do Prisma Client usa include (incluir) para também buscar as informações de author relacionadas para cada post retornado. Na implementação de rota /post/:id, o ID que é recuperado do caminho do URL é passado para ler um registro de Post específico do banco de dados.

      Você pode interromper o servidor apertando CTRL+C em seu teclado. Em seguida, reinicie o servidor usando:

      Para testar a rota /feed, use o seguinte comando curl:

      • curl http://localhost:3000/feed

      Como nenhum post ainda foi publicado, a resposta é uma matriz vazia:

      Output

      []

      Para testar a rota /post/:id, use o seguinte comando curl:

      • curl http://localhost:3000/post/1

      Isso irá retornar o post que você criou inicialmente:

      Output

      {"id":1,"title":"Hello World","content":null,"published":false,"authorId":1}

      Em seguida, implemente as duas rotas POST. Adicione o código a seguir em index.ts após as implementações das três rotas GET:

      my-blog/src/index.ts

      . . .
      
      app.post(`/user`, async (req, res) => {
        const result = await prisma.user.create({
          data: { ...req.body },
        })
        res.json(result)
      })
      
      app.post(`/post`, async (req, res) => {
        const { title, content, authorEmail } = req.body
        const result = await prisma.post.create({
          data: {
            title,
            content,
            published: false,
            author: { connect: { email: authorEmail } },
          },
        })
        res.json(result)
      })
      
      app.listen(3000, () =>
        console.log('REST API server ready at: http://localhost:3000'),
      )
      

      Assim que terminar, salve e feche o arquivo.

      Esse código implementa as rotas API para duas solicitações POST:

      • /user: cria um novo usuário no banco de dados.
      • /post: cria um novo post no banco de dados.

      Assim como antes, o Prisma Client é usado em ambas as implementações. Na implementação de rota /user, são passados os valores do corpo da solicitação HTTP para a consulta create do Prisma Client.

      A rota /post é um pouco mais confusa: aqui não é possível passar diretamente os valores do corpo da solicitação HTTP; em vez disso, é necessário primeiro extraí-los manualmente para passá-los para a consulta do Prisma Client. A razão por trás disso é que a estrutura do JSON no corpo da solicitação não corresponde à estrutura esperada pelo Prisma Client. Sendo assim, é necessário criar manualmente a estrutura esperada.

      Você pode testar as novas rotas interrompendo o servidor com CTRL+C. Em seguida, reinicie o servidor usando:

      Para criar um novo usuário com a rota /user, envie a seguinte solicitação POST com o o curl:

      • curl -X POST -H "Content-Type: application/json" -d '{"name":"Bob", "email":"[email protected]"}' http://localhost:3000/user

      Isso irá criar um novo usuário no banco de dados, imprimindo o seguinte resultado:

      Output

      {"id":2,"email":"[email protected]","name":"Bob"}

      Para criar um novo post através da rota /post, envie a seguinte solicitação POST com o curl:

      • curl -X POST -H "Content-Type: application/json" -d '{"title":"I am Bob", "authorEmail":"[email protected]"}' http://localhost:3000/post

      Isso irá criar um novo post no banco de dados e conectá-lo ao usuário com e-mail [email protected]. O seguinte resultado será impresso:

      Output

      {"id":2,"title":"I am Bob","content":null,"published":false,"authorId":2}

      Por fim, é possível implementar as rotas PUT e DELETE.

      Abra o index.ts com o seguinte comando:

      Em seguida, adicione o código destacado após a implementação das duas rotas POST:

      my-blog/src/index.ts

      . . .
      
      app.put('/post/publish/:id', async (req, res) => {
        const { id } = req.params
        const post = await prisma.post.update({
          where: { id: Number(id) },
          data: { published: true },
        })
        res.json(post)
      })
      
      app.delete(`/post/:id`, async (req, res) => {
        const { id } = req.params
        const post = await prisma.post.delete({
          where: { id: Number(id) },
        })
        res.json(post)
      })
      
      app.listen(3000, () =>
        console.log('REST API server ready at: http://localhost:3000'),
      )
      

      Salve e saia do seu arquivo.

      Esse código implementa as rotas API para uma solicitação PUT e uma DELETE:

      • /post/publish/:id (PUT): publica um post por seu ID.
      • /post/:id (DELETE): exclui um post por seu ID.

      Novamente, o Prisma Client é usado em ambas as implementações. Na implementação de rota /post/publish/:id, o ID do post a ser publicado é recuperado da URL e passado para a consulta update do Prisma Client. A implementação da rota /post/:id para excluir um post no banco de dados também recupera o ID do post da URL e o passa para a consulta delete do Prisma Client.

      Novamente, interrompa o servidor com CTRL+C em seu teclado. Em seguida, reinicie o servidor usando:

      Teste a rota PUT com o seguinte comando curl:

      • curl -X PUT http://localhost:3000/post/publish/2

      Isso irá publicar o post com um valor de ID de 2. Se você reenviar a solicitação /feed, esse post será agora incluído na resposta.

      Por fim, teste a rota DELETE com o seguinte comando curl:

      • curl -X DELETE http://localhost:3000/post/1

      Isso irá excluir o post com um valor de ID de 1. Para confirmar se o post com este ID foi excluído, reenvie uma solicitação GET para a rota /post/1.

      Neste passo, você implementou as rotas restantes da API REST para seu aplicativo de blog. A API agora responde a várias solicitações GET, POST, PUT e DELETE e implementa funcionalidades para ler e escrever dados no banco de dados.

      Conclusão

      Neste artigo, você criou um servidor da API REST com uma série de rotas distintas para criar, ler, atualizar e excluir dados de usuários e posts para um aplicativo de blog amostral. Dentro das rotas da API, você usou o Prisma Client para enviar as respectivas consultas para seu banco de dados.

      Como passos adicionais, você pode implementar rotas de API adicionais ou estender seu esquema de banco de dados com o Prisma Migrate. Certifique-se de visitar a documentação do Prisma para aprender sobre os diferentes aspectos do Prisma e explorar alguns exemplos de projetos prontos para serem executados no repositório prisma-examples. Por exemplo, como usar ferramentas como o GraphQL ou o grPC APIs.



      Source link

      Como construir um Slackbot em Python no Ubuntu 20.04


      O autor selecionou a Tech Education Fund para receber uma doação como parte do programa Write for DOnations.

      Introdução

      O Slack é uma plataforma de comunicação projetada para a produtividade no trabalho. Ele inclui recursos como mensagens diretas, canais públicos e privados, chamadas de voz e de vídeo e integrações de bot. Um Slackbot é um programa automatizado que pode executar uma variedade de funções no Slack, desde o envio de mensagens até acionar tarefas para alertar em determinados eventos.

      Neste tutorial, você irá construir um Slackbot na linguagem de programação Python. O Python é uma linguagem popular que se orgulha da simplicidade e da legibilidade. O Slack fornece uma API Python Slack rica para integração com o Slack para executar tarefas comuns, como enviar mensagens, adicionar emojis às mensagens e muito mais. O Slack também fornece uma API de eventos Slack para Python para integração com eventos no Slack, permitindo que você realize ações em eventos como mensagens e menções.

      Como uma prova de conceito divertida que irá demonstrar o poder do Python e suas APIs Slack você irá construir um CoinBot; um Slackbot que monitora um canal e, quando acionado, irá jogar uma moeda para você. Em seguida, você pode modificar seu CoinBot para atender a qualquer número de aplicações um pouco mais práticas.

      Observe que este tutorial usa o Python 3 e não é compatível com o Python 2.

      Pré-requisitos

      Para seguir este guia, você vai precisar do seguinte:

      • Um Workspace Slack em que você tenha a capacidade de instalar aplicativos. Se você criou o workspace você tem essa habilidade. Se você ainda não tiver um, você pode criar um no site do Slack.

      • (Opcional) Um servidor ou um computador com um endereço IP público para desenvolvimento. Recomendamos uma instalação nova do Ubuntu 20.04, um usuário não-root com privilégios sudo, e o SSH habilitado. Você pode seguir este guia para inicializar seu servidor e completar esses passos.

      Você pode querer testar este tutorial em um servidor que tenha um endereço IP público. O Slack precisará ser capaz de enviar eventos, como mensagens para seu bot. Se você estiver testando em uma máquina local, você precisará fazer o encaminhamento de tráfego de porta através do firewall para o sistema local. Se você estiver procurando uma maneira de desenvolver em um servidor em nuvem, confira este tutorial How To Use Visual Studio Code for Remote Development via the Remote-SSH Plugin.

      Passo 1 — Criando o Slackbot na UI do Slack

      Primeiro crie sua aplicação Slack no painel de controle da API do Slack. Faça login em seu workspace no Slack através de um navegador Web e navegue até o Painel de Controle da API. Agora, clique no botão Create an App.

      Create Your Slack App

      Em seguida, você será solicitado a inserir o nome da sua aplicação e selecionar um workspace de desenvolvimento do Slack. Para este tutorial, nomeie sua aplicação como CoinBot e selecione um workspace a que você tenha acesso como admin. Depois de ter feito isto, clique no botão Create App.

      Name Your Slack App and Select a Workspace

      Depois que sua aplicação for criada, você será apresentado ao seguinte painel padrão da aplicação. Este painel é onde você gerencia sua aplicação definindo permissões, subscrevendo a eventos, instalando a aplicação em workspaces, e muito mais.

      Default Slack App Panel

      Para que sua aplicação seja capaz de enviar mensagens em um canal, você precisa conceder permissões à aplicação para enviar mensagens. Para fazer isso, clique no botão Permissions no painel de controle.

      Select the Permissions Button in the Control Panel

      Quando você chegar à página OAuth & Permissions, desça até encontrar a seção Scopes da página. Em seguida, encontre a subseção Bot Token Scopes no escopo e clique no botão Add an OAuth Scope.

      Select the Add an OAuth Scope Button

      Clique naquele botão e então digite chat:write. Selecione essa permissão para adicioná-la ao seu bot. Isso permitirá que a aplicação poste mensagens em canais que ela possa acessar. Para mais informações sobre as permissões disponíveis, consulte a Documentação do Slack.

      Add the chat:write Permission

      Agora que você adicionou a permissão adequada, é hora de instalar sua aplicação em seu workspace do Slack. Role para trás na página OAuth & Permissions e clique no botão Install App to Workspace no topo.

      Install App to Workspace

      Clique neste botão e revise as ações que a aplicação pode executar no canal. Uma vez satisfeito, clique no botão Allow para terminar a instalação.

      Install App to Workspace

      Uma vez que o bot está instalado, você será apresentado a um Token Bot User OAuth Access para que sua aplicação use ao tentar executar ações no workspace. Vá em frente e copie este token; você precisará dele mais tarde.

      Save the Access Token

      Por fim, adicione seu bot recém-instalado em um canal dentro de seu workspace. Se você ainda não criou um canal, você pode usar o #general que é criado por padrão em seu workspace do Slack. Localize a aplicação na seção Apps da barra de navegação em seu cliente Slack e clique nela. Depois de ter feito isso, abra o menu Details no lado superior direito. Se o seu cliente Slack não estiver em tela cheia, ele se parecerá com um i em um círculo.

      Click on the App Details Icon

      Para terminar de adicionar sua aplicação a um canal, clique no botão More representado por três pontos na página de detalhes e selecione Add this app to a channel…. Digite seu canal no modal que aparece e clique em Add.

      Add App to a Channel

      Agora, você criou com sucesso sua aplicação e a adicionou a um canal dentro do seu workspace do Slack. Depois de escrever o código para sua aplicação, ele será capaz de postar mensagens naquele canal. Na próxima seção, você irá começar a escrever o código Python que irá alimentar o CoinBot.

      Passo 2 — Configurando o seu ambiente de desenvolvedor Python

      Primeiro vamos configurar seu ambiente Python para que você possa desenvolver o Slackbot.

      Abra um terminal e instale o python3 e as ferramentas relevantes em seu sistema:

      • sudo apt install python3 python3-venv

      Em seguida, você irá criar um ambiente virtual para isolar seus pacotes Python da instalação de sistema do Python. Para fazer isso, primeiro crie um diretório no qual você irá criar seu ambiente virtual. Crie um novo diretório em ~/.venvs:

      Agora, crie seu ambiente virtual Python:

      • python3 -m venv ~/.venvs/slackbot

      Em seguida, ative seu ambiente virtual para que você possa usar sua instalação do Python e instalar pacotes:

      • source ~/.venvs/slackbot/bin/activate

      Seu prompt de shell irá agora mostrar o ambiente virtual entre parênteses. Eles se parecerão com isso:

      Agora, use o pip para instalar os pacotes Python necessários em seu ambiente virtual:

      • pip install slackclient slackeventsapi Flask

      slackclient e slackeventsapi facilitam a interação do Python com as APIs do Slack. O Flask é um micro framework Web popular que você irá usar para implantar sua aplicação:

      Agora que você tem seu ambiente de desenvolvimento configurado, você pode começar a escrever seu Slackbot Python:

      Passo 3 — Criando a classe de mensagens Slackbot no Python

      As mensagens no Slack são enviadas através de um payload JSON especificamente formatado. Este é um exemplo do JSON que seu Slackbot irá criar e enviar como uma mensagem:

      {
         "channel":"channel",
         "blocks":[
            {
               "type":"section",
               "text":{
                  "type":"mrkdwn",
                  "text":"Sure! Flipping a coin....nn"
               }
            },
            {
               "type":"section",
               "text":{
                  "type":"mrkdwn",
                  "text":"*flips coin* The result is Tails."
               }
            }
         ]
      }
      

      Você pode criar manualmente este JSON e enviá-lo, mas em vez disto, vamos construir uma classe Python que não só faz a criação deste payload, mas também simula uma jogada de moeda.

      Primeiro use o comando touch para criar um arquivo chamado coinbot.py:

      Em seguida, abra o arquivo com o nano ou com o seu editor favorito:

      Agora, adicione as seguintes linhas de código para importar as bibliotecas relevantes para sua aplicação. A única biblioteca que você precisa para esta classe é a biblioteca random da Python Standard Library. Esta biblioteca nos permitirá simular uma jogada de moeda.

      Adicione as seguintes linhas ao coinbot.py para importar todas as bibliotecas necessárias:

      coinbot.py

      # import the random library to help us generate the random numbers
      import random
      

      Em seguida, crie sua classe CoinBot e uma instância desta classe para criar o payload da mensagem. Adicione as seguintes linhas ao coinbot.py para criar a classe CoinBot:

      coinbot.py

      ...
      class CoinBot:
      

      Agora, faça a indentação e crie as constantes, construtores e métodos necessários para sua classe. Primeiro vamos criar a constante que irá armazenar a base de seu payload de mensagem. Esta seção especifica que esta constante é do tipo section e que o texto é formatado através de markdown. Ele também especifica qual texto você deseja exibir. Você pode ler mais sobre as diferentes opções de payload na documentação oficial de payload de mensagem do Slack.

      Acrescente as seguintes linhas ao coinbot.py para criar o modelo base para o payload:

      coinbot.py

      ...
          # Create a constant that contains the default text for the message
          COIN_BLOCK = {
              "type": "section",
              "text": {
                  "type": "mrkdwn",
                  "text": (
                      "Sure! Flipping a coin....nn"
                  ),
              },
          }
      

      Em seguida, crie um construtor para sua classe para que você possa criar uma instância separada do seu bot para cada requisição. Não se preocupe com sobrecarga de memória aqui; o coletor de lixo do Python irá limpar essas instâncias uma vez que elas não forem mais necessárias. Este código define o canal recipiente com base em um parâmetro passado para o construtor.

      Acrescente as seguintes linhas ao coinbot.py para criar o construtor:

      coinbot.py

      ...
          # The constructor for the class. It takes the channel name as the a
          # parameter and sets it as an instance variable.
          def __init__(self, channel):
              self.channel = channel
      

      Agora escreva o código que simula jogar uma moeda. Vamos gerar aleatoriamente um ou zero, representando caras ou coroas respectivamente.

      Acrescente as seguintes linhas ao coinbot.py para simular jogar uma moeda e retornar o payload trabalhado:

      coinbot.py

      ...
          # Generate a random number to simulate flipping a coin. Then return the
          # crafted slack payload with the coin flip message.
          def _flip_coin(self):
              rand_int =  random.randint(0,1)
              if rand_int == 0:
                  results = "Heads"
              else:
                  results = "Tails"
      
              text = f"The result is {results}"
      
              return {"type": "section", "text": {"type": "mrkdwn", "text": text}},
      

      Por fim, crie um método que crie e retorne o payload completo da mensagem, incluindo os dados do seu construtor, chamando seu método _flip_coin.

      Acrescente as seguintes linhas ao coinbot.py para criar o método que irá gerar o payload finalizado:

      coinbot.py

      ...
          # Craft and return the entire message payload as a dictionary.
          def get_message_payload(self):
              return {
                  "channel": self.channel,
                  "blocks": [
                      self.COIN_BLOCK,
                      *self._flip_coin(),
                  ],
              }
      

      Agora você concluiu a classe CoinBot e ela está pronta para testes. Antes de continuar, verifique se seu arquivo finalizado, coinbot.py, contém o seguinte:

      coinbot.py

      # import the random library to help us generate the random numbers
      import random
      
      # Create the CoinBot Class
      class CoinBot:
      
          # Create a constant that contains the default text for the message
          COIN_BLOCK = {
              "type": "section",
              "text": {
                  "type": "mrkdwn",
                  "text": (
                      "Sure! Flipping a coin....nn"
                  ),
              },
          }
      
          # The constructor for the class. It takes the channel name as the a
          # parameter and then sets it as an instance variable
          def __init__(self, channel):
              self.channel = channel
      
          # Generate a random number to simulate flipping a coin. Then return the
          # crafted slack payload with the coin flip message.
          def _flip_coin(self):
              rand_int =  random.randint(0,1)
              if rand_int == 0:
                  results = "Heads"
              else:
                  results = "Tails"
      
              text = f"The result is {results}"
      
              return {"type": "section", "text": {"type": "mrkdwn", "text": text}},
      
          # Craft and return the entire message payload as a dictionary.
          def get_message_payload(self):
              return {
                  "channel": self.channel,
                  "blocks": [
                      self.COIN_BLOCK,
                      *self._flip_coin(),
                  ],
              }
      

      Salve e feche o arquivo.

      Agora que você tem uma classe Python pronta para fazer o trabalho para seu Slackbot, vamos garantir que esta classe produza um payload útil de mensagem e que você possa enviá-la para seu workspace.

      Passo 4 — Testando sua mensagem

      Agora, vamos testar se esta classe produz um payload adequado. Crie um arquivo chamado coinbot_test.py:

      Agora, adicione o código a seguir: Certifique-se de alterar o nome do canal na instanciação da classe coinbot coin_bot = coinbot("#YOUR_CHANNEL_HERE"). Este código irá criar um cliente Slack no Python que enviará uma mensagem para o canal que você especificar onde você já instalou a aplicação:

      coinbot_test.py

      from slack import WebClient
      from coinbot import CoinBot
      import os
      
      # Create a slack client
      slack_web_client = WebClient(token=os.environ.get("SLACK_TOKEN"))
      
      # Get a new CoinBot
      coin_bot = CoinBot("#YOUR_CHANNEL_HERE")
      
      # Get the onboarding message payload
      message = coin_bot.get_message_payload()
      
      # Post the onboarding message in Slack
      slack_web_client.chat_postMessage(**message)
      

      Salve e feche o arquivo.

      Antes que você possa executar este arquivo, você precisará exportar o token Slack que você salvou no Passo 1 como uma variável de ambiente:

      • export SLACK_TOKEN="your_bot_user_token"

      Agora, teste este arquivo e verifique se o payload é produzido e enviado executando o seguinte script em seu terminal. Certifique-se de que seu ambiente virtual esteja ativado. Você pode verificar isso vendo o texto (slackbot) na frente do seu prompt bash. Ao executar este comando, você receberá uma mensagem do seu Slackbot com os resultados de uma jogada de moeda:

      Verifique o canal em que você instalou sua aplicação e verifique se o bot realmente enviou a mensagem de jogada da moeda. Seu resultado será cara ou coroa.

      Coin Flip Test

      Agora que você verificou que seu Slackbot pode jogar uma moeda, criar uma mensagem, e entregar a mensagem, vamos criar um Flask para executar perpetuamente esta aplicação e fazer com que ela simule uma jogada de moeda e compartilhe os resultados sempre que ela vir certo texto nas mensagens enviadas no canal.

      Passo 5 — Criando uma aplicação Flask para executar seu Slackbot

      Agora que você tem uma aplicação em funcionamento que pode enviar mensagens para seu workspace do Slack, você precisa criar um processo de longo prazo para que seu bot possa ouvir as mensagens enviadas no canal e responder a elas se o texto cumprir determinados critérios. Você irá usar o framework Web Python Flask para executar este processo e ouvir eventos em seu canal.

      Nesta seção, você irá executar seu aplicativo Flask a partir de um servidor com um endereço IP público para que a API Slack possa lhe enviar eventos. Se você estiver executando isso localmente em sua estação de trabalho pessoal, você precisará encaminhar a porta de seu firewall pessoal para a porta que irá estar em execução em sua estação de trabalho. Essas portas podem ser as mesmas e este tutorial será configurado para usar a porta 3000.

      Primeiro ajuste suas configurações de firewall para permitir o tráfego através da porta 3000:

      Agora, verifique o status do ufw:

      Você verá uma saída como esta:

      Output

      Status: active To Action From -- ------ ---- OpenSSH ALLOW Anywhere 3000 ALLOW Anywhere OpenSSH (v6) ALLOW Anywhere (v6) 3000 (v6) ALLOW Anywhere (v6)

      Agora, crie o arquivo para sua aplicação Flask. Nomeie este arquivo app.py:

      Em seguida, abra este arquivo em seu editor de texto favorito:

      Agora, adicione as seguintes statements de importação: Você irá importar as seguintes bibliotecas pelas seguintes razões:

      • import os – Para acessar variáveis de ambiente
      • import logging – Para registrar os eventos da aplicação
      • from flask import Flask – Para criar uma aplicação Flask
      • from slack import WebClient – Para enviar mensagens via Slack
      • from slackeventsapi import SlackEventAdapter – Para receber eventos do Slack e processá-los
      • from coinbot import CoinBot – Para criar uma instância de seu CoinBot e gerar o payload da mensagem.

      Adicione as seguintes linhas ao app.py para importar todas as bibliotecas necessárias:

      app.py

      import os
      import logging
      from flask import Flask
      from slack import WebClient
      from slackeventsapi import SlackEventAdapter
      from coinbot import CoinBot
      

      Agora, crie sua aplicação Flask e registre um adaptador de eventos Slack para sua aplicação Slack no endpoint /slack/events. Isso irá criar uma rota em sua aplicação Slack onde os eventos do Slack serão enviados e ingeridos. Para fazer isso, você precisará obter outro token de sua aplicação Slack, o que você fará mais tarde no tutorial. Depois de obter esta variável, você irá exportá-la como uma variável de ambiente chamada SLACK_EVENTS_TOKEN. Vá em frente e escreva seu código para lê-lo ao criar o SlackEventAdapter, mesmo que você não tenha definido o token ainda.

      Acrescente as seguintes linhas ao app.py para criar a aplicação Flask e registrar o adaptador de eventos dentro desta aplicação:

      app.py

      ...
      # Initialize a Flask app to host the events adapter
      app = Flask(__name__)
      
      # Create an events adapter and register it to an endpoint in the slack app for event ingestion.
      slack_events_adapter = SlackEventAdapter(os.environ.get("SLACK_EVENTS_TOKEN"), "/slack/events", app)
      

      Em seguida, crie um objeto Web client que permitirá que sua aplicação execute ações no workspace, especificamente para enviar mensagens. Isso é semelhante ao que você fez quando você testou seu arquivo coinbot.py anteriormente.

      Acrescente a seguinte linha ao app.py para criar este slack_web_client:

      app.py

      ...
      # Initialize a Web API client
      slack_web_client = WebClient(token=os.environ.get("SLACK_TOKEN"))
      

      Agora, crie uma função que pode ser chamada, que irá criar uma instância do CoinBot, e então usar esta instância para criar um payload de mensagem e repassá-lo para o Slack web client para entrega. Esta função terá um único parâmetro, channel, que irá especificar qual canal recebe a mensagem.

      Acrescente as seguintes linhas ao app.py para criar esta função:

      app.py

      ...
      def flip_coin(channel):
          """Craft the CoinBot, flip the coin and send the message to the channel
          """
          # Create a new CoinBot
          coin_bot = CoinBot(channel)
      
          # Get the onboarding message payload
          message = coin_bot.get_message_payload()
      
          # Post the onboarding message in Slack
          slack_web_client.chat_postMessage(**message)
      

      Agora que você criou uma função para lidar com os aspectos de mensagens da sua aplicação, crie uma que monitora os eventos do Slack para uma determinada ação e então execute seu bot. Você irá configurar sua aplicação para responder com os resultados de uma simulação de jogada de moeda quando ela vir a frase “Hey Sammy, Flip a coin”. Você vai aceitar qualquer versão disso; caso você não queira impedir que a aplicação responda.

      Primeiro decore sua função com a sintaxe @slack_events_adapter.on que permite que sua função receba eventos. Especifique que você só deseja os eventos message e faça com que sua função aceite um parâmetro payload contendo todas as informações necessárias do Slack. Depois de ter este payload, você irá fazer um parse do texto e analisá-lo. Então, se ele receber a frase de ativação, sua aplicação irá enviar os resultados de uma simulação de jogada de moeda.

      Acrescente o seguinte código ao app.py para receber, analisar e agir sobre mensagens recebidas:

      app.py

      # When a 'message' event is detected by the events adapter, forward that payload
      # to this function.
      @slack_events_adapter.on("message")
      def message(payload):
          """Parse the message event, and if the activation string is in the text,
          simulate a coin flip and send the result.
          """
      
          # Get the event data from the payload
          event = payload.get("event", {})
      
          # Get the text from the event that came through
          text = event.get("text")
      
          # Check and see if the activation phrase was in the text of the message.
          # If so, execute the code to flip a coin.
          if "hey sammy, flip a coin" in text.lower():
              # Since the activation phrase was met, get the channel ID that the event
              # was executed on
              channel_id = event.get("channel")
      
              # Execute the flip_coin function and send the results of
              # flipping a coin to the channel
              return flip_coin(channel_id)
      

      Por fim, crie uma seção main que irá criar um logger para que você possa ver os detalhes internos da sua aplicação, bem como lançar a aplicação em seu endereço IP externo na porta 3000. Para ingerir os eventos do Slack, como quando uma nova mensagem é enviada, você deve testar sua aplicação em um endereço IP voltado para o público.

      Acrescente as seguintes linhas ao app.py para configurar sua seção main:

      app.py

      if __name__ == "__main__":
          # Create the logging object
          logger = logging.getLogger()
      
          # Set the log level to DEBUG. This will increase verbosity of logging messages
          logger.setLevel(logging.DEBUG)
      
          # Add the StreamHandler as a logging handler
          logger.addHandler(logging.StreamHandler())
      
          # Run your app on your externally facing IP address on port 3000 instead of
          # running it on localhost, which is traditional for development.
          app.run(host="0.0.0.0", port=3000)
      

      Agora você concluiu a aplicação Flask e ela está pronta para testes. Antes de seguir em frente, verifique se seu arquivo finalizado, o app.py contém o seguinte:

      app.py

      import os
      import logging
      from flask import Flask
      from slack import WebClient
      from slackeventsapi import SlackEventAdapter
      from coinbot import CoinBot
      
      # Initialize a Flask app to host the events adapter
      app = Flask(__name__)
      # Create an events adapter and register it to an endpoint in the slack app for event injestion.
      slack_events_adapter = SlackEventAdapter(os.environ.get("SLACK_EVENTS_TOKEN"), "/slack/events", app)
      
      # Initialize a Web API client
      slack_web_client = WebClient(token=os.environ.get("SLACK_TOKEN"))
      
      def flip_coin(channel):
          """Craft the CoinBot, flip the coin and send the message to the channel
          """
          # Create a new CoinBot
          coin_bot = CoinBot(channel)
      
          # Get the onboarding message payload
          message = coin_bot.get_message_payload()
      
          # Post the onboarding message in Slack
          slack_web_client.chat_postMessage(**message)
      
      
      # When a 'message' event is detected by the events adapter, forward that payload
      # to this function.
      @slack_events_adapter.on("message")
      def message(payload):
          """Parse the message event, and if the activation string is in the text,
          simulate a coin flip and send the result.
          """
      
          # Get the event data from the payload
          event = payload.get("event", {})
      
          # Get the text from the event that came through
          text = event.get("text")
      
          # Check and see if the activation phrase was in the text of the message.
          # If so, execute the code to flip a coin.
          if "hey sammy, flip a coin" in text.lower():
              # Since the activation phrase was met, get the channel ID that the event
              # was executed on
              channel_id = event.get("channel")
      
              # Execute the flip_coin function and send the results of
              # flipping a coin to the channel
              return flip_coin(channel_id)
      
      if __name__ == "__main__":
          # Create the logging object
          logger = logging.getLogger()
      
          # Set the log level to DEBUG. This will increase verbosity of logging messages
          logger.setLevel(logging.DEBUG)
      
          # Add the StreamHandler as a logging handler
          logger.addHandler(logging.StreamHandler())
      
          # Run our app on our externally facing IP address on port 3000 instead of
          # running it on localhost, which is traditional for development.
          app.run(host="0.0.0.0", port=3000)
      

      Salve e feche o arquivo.

      Agora que seu app Flask está pronto para atender sua aplicação, vamos testá-la.

      Passo 6 — Executando sua aplicação Flask

      Por fim, junte tudo e execute sua aplicação.

      Primeiro, adicione a aplicação em execução como um handler autorizado para seu Slackbot.

      Navegue até a seção Basic Information da sua aplicação na UI do Slack Desça até você encontrar a seção App Credentials.

      Slack Signing Secret

      Copie o Signing Secret e exporte-o como a variável de ambiente SLACK_EVENTS_TOKEN:

      • export SLACK_EVENTS_TOKEN="MY_SIGNING_SECRET_TOKEN"

      Com isso, você tem todos os tokens de API necessários para executar sua aplicação. Consulte o Passo 1 se você precisar relembrar como exportar seu SLACK_TOKEN. Agora, você pode iniciar sua aplicação e verificar se ela está realmente em execução. Certifique-se de que seu ambiente virtual esteja ativado e execute o seguinte comando para iniciar sua aplicação Flask:

      Você verá uma saída como esta:

      (slackbot) [20:04:03] sammy:coinbot$ python app.py
       * Serving Flask app "app" (lazy loading)
       * Environment: production
         WARNING: This is a development server. Do not use it in a production deployment.
         Use a production WSGI server instead.
       * Debug mode: off
       * Running on http://0.0.0.0:3000/ (Press CTRL+C to quit)
      

      Para verificar se sua aplicação está ativa, abra uma nova janela de terminal e faça um curl no endereço IP do seu servidor com a porta correta em /slack/events:

      • curl http://YOUR_IP_ADDRESS:3000/slack/events

      O curl retornará o seguinte:

      Output

      These are not the slackbots you're looking for.

      Receber a mensagem These are not the slackbots you're looking for. indica que sua aplicação está funcionando normalmente.

      Agora, deixe esta aplicação Flask em execução enquanto você termina de configurar sua aplicação na UI do Slack.

      Primeiro conceda à sua aplicação as permissões apropriadas para que ela possa ouvir mensagens e responder de acordo. Clique em Event Subscriptions na barra lateral da UI e alterne o botão de opção Enable Events.

      Enable Events Button

      Depois de ter feito isso, digite seu endereço IP, porta e o endpoint /slack/events no campo Request URL. Não se esqueça do prefixo do protocolo HTTP. O Slack fará uma tentativa de se conectar ao seu endpoint. Depois de ter feito isso com sucesso você verá uma marca de verificação verde com a palavra Verified ao lado dela.

      Event Subscriptions Request URL

      Em seguida, expanda Subscribe to bot events e adicione a permissão message.channels à sua aplicação. Isso permitirá que sua aplicação receba mensagens do seu canal e as processe.

      Subscribe to bot events permissions

      Depois de ter feito isso, você verá o evento listado em sua seção Subscribe to bot events. Em seguida, clique no botão verde Save Changes no canto inferior direito.

      Confirm and Save changes

      Depois de fazer isso, você verá uma faixa amarela no topo da tela lhe informando que você precisa reinstalar sua aplicação para que as seguintes alterações sejam aplicadas. Sempre que você alterar permissões você precisará reinstalar sua aplicação. Clique no link reinstall your app nessa faixa para reinstalar sua aplicação.

      Reinstall your app banner

      Você verá uma tela de confirmação resumindo as permissões que seu bot terá e perguntando se você deseja permitir a instalação. Clique no botão verde Allow para terminar o processo de instalação.

      Reinstall confirmation

      Agora que você fez isso, sua aplicação deve estar pronta. Volte para o canal em que você instalou o CoinBot e envie uma mensagem contendo a frase Hey Sammy, Flip a coin nela. Seu bot irá jogar uma moeda e responder com os resultados. Parabéns! Você criou um Slackbot!

      Hey Sammy, Flip a coin

      Conclusão

      Quando você terminar de desenvolver sua aplicação e estiver pronto para movê-la para a produção, será necessário implantá-la em um servidor. Isso é necessário porque o servidor de desenvolvimento Flask não é um ambiente de produção seguro. Você estará melhor servido se implantar sua aplicação usando um WSGI e talvez até mesmo protegendo um nome de domínio e dando ao seu servidor um registro DNS. Existem muitas opções para implantar aplicações Flask, algumas das quais estão listadas abaixo:

      Há muito mais maneiras de implantar sua aplicação do que apenas essas. Como sempre, quando se trata de implantações e infraestrutura, faça o que funciona melhor para você.

      De qualquer forma, agora você tem um Slackbot que você pode usar para jogar uma moeda para ajudá-lo a tomar decisões, como o que comer para o almoço.

      Também é possível acessar este código base e modificá-lo para atender às suas necessidades, seja para suporte automatizado, gerenciamento de recursos, fotos de gatos ou o que você imaginar. Você pode ver a documentação completa da API do Python Slack aqui.



      Source link