npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@rcambiental/external-api

v0.0.8

Published

API de integração com o banco de dados legislativo do Portal RC Ambiental.

Downloads

130

Readme

RC Ambiental External Api

Api de integração com o banco de dados do Portal RC Ambiental.

Instalação

  # Npm
  npm install @rcambiental/external-api
  
  # Yarn
  yarn add @rcambiental/external-api
  
  # PNPM
  pnpm add @rcambiental/external-api

Package namespaces

  • @rcambiental/external-api: Atualmente não exporta nada.
  • @rcambiental/external-api/client: Para uso com integrações no browser (frontend).
  • @rcambiental/external-api/server: Para uso com integrações no servidor (backend).
  • @rcambiental/external-api/internal: Uso interno da biblioteca, não deve ser utilizada diretamente.

Instanciamento do cliente server-side

import { buildRCAmbientalExternalApiServerClient } from '@rcambiental/external-api/server';

const client = buildRCAmbientalExternalApiServerClient({
    apiEndpoint: 'https://www.rcambiental.com.br',
    apiKey: '123-123-123-123'
});

Pingando o servidor

Utilize o resultado da operação de client.common.ping() para saber se o servidor está online e a apiKey informada é válida.

Obtenção dos dados de base

O cliente de servidor fornece 4 endpoints básicos para a integração com o banco de dados do Portal RC Ambiental. Estes dados são utilizados, basicamente, para construir a interface do sistema de pesquisa, embora possam também ter outros usos.

São eles:

const getAllActsAreas = await client.acts.baseData.getAllActAreas();
//    ^ Retornas todas as áreas dos atos jurídicos

const getAllActOrgans = await client.acts.baseData.getAllActOrgans();
//    ^ Retornas todos os órgos dos atos jurídicos

const getAllActTypes = await client.acts.baseData.getAllActTypes();
//    ^ Retornas todos os tipos dos atos jurídicos

const getAllJurisdictionScopes = await client.acts.baseData.getAllJurisdictionScopes();
//    ^ Retornas todos os escopos de jurisdição dos atos jurídicos (Brasil e seus Estados)

Obtenção dados dos usuários cadastrados

É possivel utilizar-se de 02 endpoints que buscam dados dos usuários. São eles:

/**
 * Retorna todos os usuários cadastrados no sistema, em ordem decrescente de data de criação.
 * Por questões de performance, os dados não incluem o plano atual do usuário, somente seus dados cadastrais e
 * seu status de ativação.
 * 
 * A chamada da função aceita 2 parâmetros opcionais no query: 
 * - `page` - Número da página, se não informado ou <= 1, o valor default é 1.
 * - `maxPerPage` - Número de registros por página, se não informado ou <= 0, o valor default é 20.
 *    ^ Neste caso, existe um hard limit no servidor de até 20 registros por página, valores informados
 *    acima de 20 serão ignorados.
 */
const getAllUsers = await client.users.getAllUsers({
    query: {
        page: 1,
        maxPerPage: 20
    }
});

/**
 * Retorna os mesmos dados cadastrais do usuário mas, neste caso, inclui também o 
 * plano atual do usuário no corpo da resposta (ou null, caso não houver).
 *
 * A chamada da função necessita, obrigatoriamente, dos seguintes parâmetros na query:
 * - `userId` - Id do usuario cadastrado no sistema.
 * 
 * A chamada da função pode retornar um erro 404 caso o usuário não seja encontrado.
 */
const getUser = await client.users.getUser({
    query: {
        userId: '123-123-123'
    }
});

Pesquisa dos Atos Jurídicos

Através desse endpoint, é possível realizar pesquisas nos atos jurídicos cadastrados no sistema.

Existem, basicamente, dois tipos de pesquisa, as com texto e as sem texto (que internamente chamos de genéricas).

  • Pesquisa com texto: Seus resultados são ordenados por relevância, dando ênfase sempre para os atos jurídicos mais recentes que foram inseridos no banco de dados.
  • Pesquisa sem texto: Seus resultados são ordenados pela data do ato jurídico, ordenando-os sempre dos mais recentes para o mais antigos.

Todas as pesquisas precisam ser autenticadas pelo usuário que as realizar, para isso, é necessário informar o header do x-ea-user-id no corpo da requisição.

Além disso, os parâmetros de filtragem utilizados são aqueles obtidos pelos dados de base, como especificado anteriormente nesta documentação.

/**
 * Exemplo 1: Pesquisa com texto.
 * 
 * Os resultados são ordenados por relevância, dando ênfase sempre para os atos jurídicos
 * mais recentes que foram inseridos no banco de dados.
 * 
 * O tipo de retorno da pesquisa é `SCORED`, ou seja, cada ato retornado possui um `score` de 
 * relevância, sendo que no próprio corpo da resposta será informado um `maxScore`. Isso é 
 * util para calcular percentagem de relevância do ato e apresentar ao cliente na UI.
 * 
 * Importante: O campo `text` é totalmente case-insensitive e accent-insensitive, 
 * ou seja, "ÁGUA" === "agua".
 */
const searchResultWithText = await client.acts.search({
    headers: {
        /**
         * Este header informa qual usuário está realizando a pesquisa.
         */
        'x-ea-user-id': '123-123-123-123'
    },
    body: {
        /**
         * Especifica que o tipo de pesquisa é "com texto"
         */ 
        type: 'with-text',
        /**
         * Palavras-chave a serem pesquisadas, é basicamente o input do usuário.
         * Aceita, também, expressões com áspas duplas (como no Google), por exemplo: "água potável"
         */ 
        text: 'água', // ou '\"água potável\"'
        /**
         * Filtragem da pesquisa, alguns filtros são obrigatórios.
         */
        filters: {
            /**
             * Filtra pela situação do ato jurídico.
             * - REVOKED: Somente atos jurídicos revogados
             * - IN_EFFECT: Somente atos jurídicos não-revogados
             * - BOTH: Ambos os tipos (é o mesmo que desabilitar esse filtro)
             */ 
            actSituation: 'BOTH',
            /**
             * Filtra pelos números dos atos jurídicos.
             */
            actNumbers: [1, 2, 3],
            /**
             * Áreas de atuação do ato jurídico, pelo menos 01 área tem que ser especificada.
             */
            actAreaIds: [
                // Ids das áreas, obter através de `client.acts.baseData.getAllActAreas()`
                '123-123-123-123'
            ],
            /**
             * Filtra pelo órgão do ato jurídico.
             * Id órgão, obter através de `client.acts.baseData.getAllActOrgans()`
             */
            actOrganId: '123-123-123-123',
            /**
             * Filtra pelo tipo do ato jurídico. 
             * Id do tipo, obter através de `client.acts.baseData.getAllActTypes()`
             * Obs: Esse filtro é `exclusivo`, ou seja, AND.
             */
            actTypeId: '123-123-123-123',
            /**
             * Filtra pela data de publicação dos atos jurídicos.
             * O tipo de `value` depende do `type`, é type-safe, use type-narrowing.
             */
            actPublicationDate: {
                type: 'exact', // ou 'range' ou 'separated'
                value: new Date('2021-01-01')
            },
            /**
             * Filtra pela data dos atos jurídicos.
             * O tipo de `value` depende do `type`, é type-safe, use type-narrowing
             */
            actDate: {
                type: 'range', // ou 'exact' ou 'separated' 
                value: {
                    gte: new Date('2021-01-01'),
                    lte: new Date('2021-12-31')
                } 
            },
            /**
             * Indica em quais módulos se deseja buscar os atos jurídicos, pelo menos 01 field é obrigatório.
             */ 
            actLocalization: {
                /**
                 * Indica que deve ser incluído a Legislação Federal.
                 * Se não informado, o valor default é `false`.
                 */ 
                includeFederal: true,
                /**
                 * Indica que deve ser incluído as legislações estaduais.
                 * Se não informado, o valor default é `[]`, ou seja, nenhum Estado.
                 */ 
                includeStateIds: [
                    // Id do Estado, obter através de `client.acts.baseData.getAllJurisdictionScopes()`
                    '123-123-123-123'
                ]
            }
        },
        /**
         * Filtro de paginação.
         */
        paging: {
            // Página atual, se não informado ou <= 1, o valor default é 1.
            page: 1,
        }
    }
});

/**
 * Exemplo 2: Pesquisa sem texto (genérica).
 * Os resultados serão servidos em ordem decrescente de data de inserção, do mais recente para o mais antigo.
 * O tipo de retorno da pesquisa é `NON_SCORED`.
 */
const searchResultNonText = await client.acts.search({
    headers: {
        'x-ea-user-id': '123-123-123-123'
    },
    body: {
        /**
         * Especifica que o tipo de pesquisa é "sem texto"
         */
        type: 'without-text',
        filters: {
            actSituation: 'REVOKED',
            actAreaIds: [
                '123-123-123-123'
            ],
            /**
             * Exemplo com type `separated`.
             */
            actDate: {
                type: 'separated',
                /**
                 * Pelo menos 1 campo tem que ser informado:
                 * - `day` - dia do mês
                 * - `month` - mês do ano (1-12)
                 * - `year` - ano
                 */
                value: {
                    day: 1,
                    month: 1
                }
            },
            actLocalization: {
                includeFederal: true,
                includeStateIds: [
                    '123-123-123-123'
                ]
            }
        },
        paging: {
            page: 1,
        }
    }
});

Requisitando os metadados de um Ato Jurídico

Os metadados de um ato jurídico específico podem ser requisitados sem gerar, necessariamente, um acesso. Este endpoint é útil para situações onde, por exemplo:

  1. Se deseje atualizar seus dados caso alguma atualização/alteração tenha sido realizada.
  2. Verificar se o ato jurídico existe ou não no banco de dados (em casos de exclusão ou não encontrado, será retornado status 404).
  3. Entre outros.

Utilize o endpoint client.acts.getActMetadata() para requisitar os metadados de um ato jurídico, exemplo:

/**
 * Obs: É necessário informar o usuário que está requisitando
 * os metadados do ato jurídico.
 */
const actMetadata = await client.acts.getActMetadata({
    query: {
        actId: '123-123-123-123'
    },
    headers: {
        'x-ea-user-id': '123-123-123-123'
    }
});

Realizando um acesso a um Ato Jurídico

O acesso a um ato jurídico é feito de forma encadeada, ou seja, é necessário realizar, sequencialmente, algumas operações:

  1. Primeiramente, é necessário obter o id do ato jurídico ao qual se deseja acessar, isso é feito, por exemplo, utilizando-se do resultado do endpoint de pesquisa (client.acts.search()).

  2. Com o id do ato jurídico em mãos, é necessário gerar um id de acesso, isto é possível através do endpoint client.acts.generateActAccess(), exemplo:

/**
 * Obs: É necessário informar o usuário que está requisitando 
 * o acesso do ato jurídico.
 */ 
const actAccess = await client.acts.generateActAccess({
    query: {
        actId: '123-321-123-321'
    },
    headers: {
        'x-ea-user-id': '123-123-123-123'
    }
});

O retorno da chamada do endpoint acima devolve uma resposta com uma estrutura semelhante a esta:

{
    /**
     * Id único, por recorrência de um plano de um usuário, que identifica o acesso
     * a um ato jurídico.
     * Este id é utilizado para:
     * 1. Requisição da decriptação do texto legislativo do ato jurídico (caso
     * ele esteja em estado `ENCRYPTED`).
     */
    "actAccessId": "12346-abcde",
    /**
    * Token de acesso instantâneo de acesso ao ato (atrelado ao `actAccessId`).
    * Este id é utilizado para:
    * 1. Realizar a validação do acesso por parte do
    * usuário (no browser), através da ferramenta do FingerprintJS.
    * 2. Realizar a requisição do texto legislativo (só pode ser utilizado uma
    * única vez e possui data de expiração).
    */
    "actAccessEntryId": "12346-abcde-123456",
    /**
     * Data de expiração do token de acesso para busca do texto do ato jurídico (actAccessEntryId). 
     */
    "actAccessEntryValidUntil": "2021-12-31T23:59:59.999Z",
    "text": {
        /**
         * Indica o status da encryptação do texto do 
         * ato jurídico.
         */
        "encryptionStatus": {
            /**
             * Importante: Todo primeiro acesso a um ato jurídico, numa recorrência, começa
             * como `ENCRYPTED`.
             */
            "value": "ENCRYPTED",
             /**
             * Indica se o usuário pode requisitar a decriptação do
             * ato jurídico. Este valor leva em consideração a quantidade
             * de requisições de decriptação restantes e se o usuário
             * possui o módulo que contempla a acesso a este ato (incluindo
             * o status do plano atual).
             */   
            "canRequestDecryption": true
        },
        /**
         * Número restante de requisições de descriptografia de 
         * texto na recorrência atual do plano do usuário.
         */
        "remainingDecryptionRequests": 30,
        /**
        * Número máximo de requisições de descriptografia de 
        * texto na recorrência atual do plano do usuário.
        */ 
        "maxDecryptionRequests": 30
    },
    // Metadados do Ato Jurídico
    "act": {} 
}
  1. Agora, com o acesso gerado e com o actAccessId em mãos, é possível requisitar o texto do ato jurídico, exemplo:
/**
 * O retorno da função abaixo devolve um objeto com 2 valores distintos de `status`:
 * 
 * 1. `ENCRYPTED` - Indica que o texto do ato jurídico está criptografado.
 * 2. `NOT_ENCRYPTED` - Indica que o código fonte do ato jurídico está em forma decriptada.
 * 
 * O texto (html) do ato jurídico será retornado no field `html`, sendo que algumas 
 * considerações são importantes:
 * 
 * - Recomenda-se o uso da fonte 'Arial' para o tipo de `status`:`NOT_ENCRYPTED` pois a
 * encriptação do html utiliza, exclusiva e unicamente esta fonte, isto mantém a consistência no
 * display pro usuário caso uma request para decriptação seja feita e o ato seja recarregado. 
 * Tentar forçar o uso de outa fontFamily no container onde o texto do ato jurídico que está encriptado
 * foi injetado irá quebrar o display da encriptação e os caracteres serão exibidos de forma incorreta.
 * 
 * - Em quaisquer dos casos do field `status`, recomenda-se a injeção direta do HTML
 * num container, por exemplo, uma `div`, usando `div.innerHTML = actText.html`;
 * 
 * Obs: É necessário informar o usuário que está requisitando
 * o texto do ato jurídico.
 */
const actText = await client.acts.getActText({
    query: {
        /**
         * Este token só poderá ser utilizado uma única vez e
         * é de uso exclusivo para cada usuário que requisitou o acesso
         * ao ato jurídico (e seu texto).
         */
        actAccessEntryId: '123-123-123-123'
    },
    body: {
      /**
       * Configuração dos links dos anexos no texto do ato jurídico.
       */
      attachmentLinks?: {
        /**
         * - `NEW_TAB` - Abre o link em uma nova aba (target="_blank").
         * - `SAME_TAB` - Abre o link na mesma aba (target="_self").
         */
        clickBehavior?: 'NEW_TAB' // ou 'SAME_TAB'
      },
      /**
       * Configuração dos links para outros atos jurídicos 
       * no texto do ato jurídico atual.
       */
      actLinks: {
        /**
         * - `NEW_TAB` - Abre o link em uma nova aba (target="_blank").
         * - `SAME_TAB` - Abre o link na mesma aba (target="_self").
         */
        clickBehavior?: 'NEW_TAB', // ou 'SAME_TAB'
        /**
         * Configura como os links para outros atos jurídicos
         * serão parseados.
         */
        parsing: {
          /**
           * Neste método de parsing os links são 
           * renderizados da seguinte forma:
           * 
           * `<a href="actId://123-123/" data-link-to-act-id="123-123">Link para um ato</a>`
           * 
           * O field `data-link-to-act-id` possui o id do ato jurídico alvo, desta forma
           * você, desenvolvedor, fica responsável por
           * parsear o texto do ato jurídico, buscar esses links como e transformá-los ou 
           * aplicar event handlers (click) da forma que achar mais conveniente, exemplo:           * 
           * ```js
           * const allActLinks = actTextContainer.querySelectorAll('a[data-link-to-act-id]');
           * 
           * allActLinks.forEach((link) => {
           *    link.addEventListener('click', (e) => {
           *        e.preventDefault();
           *        const actId = link.getAttribute('data-link-to-act-id');
           *        // Agora você pode fazer o que quiser com o id do ato jurídico
           *    });
           * });
           * ```
           */
          method: 'ACT_ID_INLINE_WITH_DATA_ATTR'
        }
        /**
         * Outra configuração de parsing também é possível, veja:
         */
        parsing: {
          /**
           * Indica que os links serão renderizados com uma url fixa,
           * especificada no momento da requisição.
           */
          method: 'TO_FIXED_URL',
          /**
           * Indica o placeholder que será substituído pela id do ato jurídico
           * na montagem da url (href).
           */
          placeholder?: '{{ actId }}',
          /**
           * Agora é só informar a URL que deve ser montada, incluindo,
           * obrigatoriamente, o placeholder especificado acima (default é '{{ actId }}'),
           * caso não especificado.           
           */
          url: `https://www.example.com/some-path/?actId={{ actId }}`
          //   ^ <a href="https://www.example.com/some-path/?actId=123-123">Link para um ato</a>
        }
      }
    },
    headers: {
        'x-ea-user-id': '123-123-123-123'
    }
});

A chamada a função acima pode retornar os seguintes erros:

  • Http 404 - Caso o actAccessEntryId informado não seja encontrado.
  • Http 403 - Nos casos:
    • O actAccessEntryId informado não pertença ao usuário informado no header x-ea-user-id.
    • O actAccessEntryId informado já tenha sido utilizado.
    • O actAccessEntryId informado esteja expirado, neste caso, é necessário gerar um novo actAccessEntryId através do endpoint client.acts.generateActAccess().

Em todos os casos acima, uma mensagem descritiva do erro será retornada no corpo da resposta.

Requisitando a decriptação do texto de um Ato Jurídico

A decriptação do texto de um ato jurídico é o que permite o usuário a realizar cópias do conteúdo do mesmo. Toda vez que o usuário quiser realizar uma cópia, seja por seleção de texto (entre outros), é necessário chamar pelo endpoint responsável por requisitar a decriptação do texto.

Uma vez que a decriptação do texto seja aceita pelo sistema, novas requisições pelo texto do ato jurídico serão automaticamente retornadas com o conteúdo (html) do texto de forma decriptada e assim se manterão até o final da recorrência atual do plano do usuário.

Com o actAccessId em mãos, realize a seguinte chamada ao endpoint client.acts.requestActTextDecryption(), exemplo:

/**
 * O resultado desta função retorna um objeto com um field `result` 
 * que indica o resultado a operação.
 * 
 * Caso o valor de `result` seja `ACCEPTED`, significa que a requisição foi aceita, 
 * neste caso, para obter o texto decriptado, é necessário gerar um novo `actAccessEntryId`
 * através do endpoint `client.acts.generateActAccess()` e logo depois realizar, novamente, a requisição
 * do texto do ato jurídico com o novo `actAccessEntryId` com o endpoint `client.acts.getActText()`.
 * 
 * Obs: É necessário informar o usuário que está
 * requisitando esta decriptação de texto.
 */
const requestActTextDecryption = await client.acts.requestActTextDecryption({
    query: {
        actAccessId: '123-123-123-123'
    },
    headers: {
        'x-ea-user-id': '123-123-123-123'
    }
});

Após a chamada acima, considerando o retorno como ACCEPTED, uma nova chamada a client.acts.generateActAccess() com o mesmo actAccessId retornará um objeto com o status de DECRYPTED no field text.encryptionStatus.value, exemplo:

{
    ...omitido
    /**
     * Um novo valor para esse field será gerado, o qual deve ser
     * utilizado para requisitar a versão do texto do ato jurídico
     * em forma decriptada.
     * Lembrando que toda chamada a `client.acts.generateActAccess()`
     * deve ser validada com o FingerprintJS utilizando-se desse token.
     */
    "actAccessEntryId": "123456-new-value",
    "text": {
        "encryptionStatus": {
            "value": "DECRYPTED",
            "decryptedAt": "2024-02-22T14:38:02.309Z"
        },
        "remainingDecryptionRequests": 29,
        "maxDecryptionRequests": 30
    }
    ...omitido
}

Observações

1. Requisições que envolvem o userId (que identifica o usuário)

Todas as requisições que envolvem o usuário podem retornar os seguintes erros na requisição:

  • EXTAPI_USER_NOT_FOUND_OR_INVALID - O userId informado é inválido ou o usuário não existe (não foi encontrado).
  • EXTAPI_USER_DEACTIVATED - O usuário informado está desativado, que pode ser causado pelo contratante ou pela própria administração do Portal RC Ambiental.
  • EXTAPI_USER_HAS_NO_PLAN_CONFIGURED_OR_STARTED - Indica que o usuário não possui um plano configurado ou iniciado, é necessário que o mesmo possua pelo menos um plano configurado e iniciado para começar a usar o sistema.
  • EXTAPI_USER_CURRENT_PLAN_IS_PAUSED - Indica que o plano do usuário está pausado, isso é causado, exclusivamente, por uma medida administrativa por parte do Portal RC Ambiental.

2. Tratamento de erros

Essa bibliotca exporta funções utilitárias para identificação de erros nas chamadas de API, são eles:

/**
 * Contém todos os códigos de erros específicos que podem ocorrer na API,
 * ao inspecionar o código fonte desse tipo, é explicado o motivo de cada erro.
 */
import { RCAmbientalExternalApiErrorCode } from '@rcambiental/external-api/server';

/**
 * Função do tipo "type-guard" que testa se o objeto do erro retornado é
 * do tipo `RCAmbientalExternalApiError`.
 */
import { isRCAmbientalExternalApiServerError } from '@rcambiental/external-api/server';

/**
 * Exemplo: imagine que você fez uma chamada de api usando o client e ele retornou um erro e
 * o erro retornado é similar ao abaixo:
 */
import { isRCAmbientalExternalApiServerError, RCAmbientalExternalApiError } from '@rcambiental/external-api/server';

const errorFromApi: RCAmbientalExternalApiError = {
    type: 'rcambiental-api-error',
    statusCode: 404,
    code: 'EXT_API_RESOURCE_ID_INVALID',
    message: 'Uma mensagem de erro será retornada pelo sistema aqui.'
};

if (isRCAmbientalExternalApiServerError(errorFromApi)) {
    // O erro retornado é um erro específico
} else {
    // O erro retornado não é um erro especifico
}