npm package: @rcambiental/external-api
Description: API de integração com o banco de dados legislativo do Portal RC Ambiental.
Author: Renato Araujo Carneiro
Latest version: 0.0.8

# RC Ambiental External Api

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

## Instalação

```bash
  # 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

```typescript
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: 

```typescript
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:

```typescript
/**
 * 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.

```typescript
/**
 * 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:

```typescript
/**
 * 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:

```typescript
/**
 * 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:

```javascript
{
    /**
     * 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": {} 
}
```

3. Agora, com o acesso gerado e com o `actAccessId` em mãos, é possível requisitar o texto do ato jurídico, exemplo:

```typescript
/**
 * 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:

```typescript
/**
 * 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:

```javascript
{
    ...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:

```typescript
/**
 * 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
}
```