# Coleções e registos

> As vinte e seis coleções de registos, o envelope de snapshot que partilham, e como percorrer uma.

Source: https://developers.backresto.com/pt/docs/collections

Uma **coleção** é um tipo de registo: `temperature-records`, `cooling`,
`deliveries` e outros vinte e três. Todas se leem da mesma maneira,
através dos mesmos quatro endpoints, e todos os registos voltam no mesmo
envelope: os campos à volta de `data` que dizem quando foi capturado e se ainda
existe.

Cada coleção é uma concessão à parte. Uma chave lê `temperature-records` porque
alguém concedeu `temperature-records:read`, e mais nada vem com isso.

## O que pode ler

```http
GET /v1/restaurants/{restaurantId}/collections
GET /v1/restaurants/{restaurantId}/collections/{collection}/records?limit=…&cursor=…
GET /v1/restaurants/{restaurantId}/collections/{collection}/records/{recordId}
GET /v1/restaurants/{restaurantId}/collections/{collection}/records/{recordId}/assets
```

Comece pelo primeiro. Ele lista **apenas as coleções que foram concedidas à sua
chave**, cada uma com o scope que a abriu, para que nunca tenha de adivinhar:

```json
{
  "data": [
    {
      "collection": "temperature-records",
      "readScope": "temperature-records:read",
      "description": "Received shadow snapshots for temperature-records; not complete primary-store history.",
      "stateKind": "received-shadow-snapshot",
      "payloadVersion": 1
    }
  ]
}
```

Um `data` vazio não é um erro nem uma falha de serviço: é uma chave sem
concessões de coleções. A maioria das chaves emitidas antes de esta superfície
existir está exatamente nesse estado, e alargar uma é [um email](https://developers.backresto.com/pt/docs/access).

## O envelope

Todos os registos de todas as coleções trazem os mesmos campos exteriores. Só o
`data` muda de formato.

| Campo | Tipo | Significado |
| --- | --- | --- |
| `id` | string | O identificador do registo, estável e único dentro da coleção. |
| `restaurantId` | string | O restaurante a que pertence. Repete o caminho. |
| `collection` | string | De que coleção veio. Repete o caminho. |
| `data` | object \| ausente | O registo em si. Um tombstone é o único caso em que pode faltar, por isso trate-o como presente em todo o resto. |
| `deleted` | boolean | `true` significa um tombstone: o registo foi apagado na aplicação. |
| `capturedAt` | string | Quando o dispositivo registou a alteração, em milissegundos desde a época Unix, em forma de cadeia de caracteres. |
| `receivedAt` | string | Quando esta API o recebeu. Posterior a `capturedAt`, às vezes por horas. |
| `sequence` | string | Uma posição monotonicamente crescente no fluxo de alterações. Compare duas; não faça aritmética com uma. |
| `mutationId` | string | O identificador da alteração que produziu este estado. Chave de idempotência do nosso lado; chave de desduplicação do seu. |
| `sourceVersion` | string \| null | O carimbo de versão do registo de origem, quando ele tem um. |
| `payloadVersion` | number | De momento é sempre `1`. Sobe se o significado de `data` alguma vez mudar. |
| `stateKind` | string | Sempre `received-shadow-snapshot`. Leia a secção seguinte. |

`capturedAt` e `receivedAt` contam os dois. Um tablet numa câmara frigorífica
sem sinal regista às 09:00 e carrega às 14:00; ordenar o seu próprio
processamento por `receivedAt` mantém-no correto, e fazer os relatórios por
`capturedAt` mantém-no verdadeiro.

## O que um snapshot é, e o que não é

O `stateKind` diz `received-shadow-snapshot`, e a formulação é cuidadosa.

- **É o estado atual de um registo**, não um histórico de todas as edições. Leia
  um registo duas vezes e recebe, nas duas, o aspeto que ele tem agora.
- **É o que nós recebemos**, não o que o restaurante tem. Um dispositivo que
  nunca carregou uma alteração significa um registo que esta API nunca viu.
- **Não é uma recuperação de histórico.** Uma coleção começa, para um
  restaurante, no dia em que a captura é ativada para ele. Os registos criados
  antes disso estão na aplicação e não aqui. As [entregas](https://developers.backresto.com/pt/docs/deliveries)
  são a única exceção: essas duas coleções trazem o histórico que a aplicação
  ainda tinha quando foram abertas.

Por isso, um registo ausente é genuinamente ambíguo: nunca foi registado, ou foi
registado e ainda não chegou. Não construa em cima disso um número que se leia
como uma auditoria, e veja [a nota sobre a completude](https://developers.backresto.com/pt/docs/support) antes de
comunicar uma contagem a quem quer que seja.

Os tombstones são o único caso em que a ausência não é ambígua.
`deleted: true` sem `data` significa que o registo existiu e foi apagado, e é a
única forma de ficar a saber que alguma coisa desapareceu.

## Quanto tempo os registos ficam aqui

Os registos operacionais ficam aqui **90 dias**, contados a partir do momento em
que aconteceram na cozinha (`data.timestamp`), não de quando os recebemos.
Depois disso, uma tarefa noturna move-os para um arquivo frio bloqueado,
conservado durante cinco anos, e deixam de aparecer em qualquer leitura: listas,
registo individual e respetivos ficheiros.

| Conservados 90 dias | Conservados durante toda a vida da conta |
| --- | --- |
| `temperature-readings`, `temperature-records`, `cooking-temperature-records`, `cleaning-task-records`, `cleaning-task-pictures`, `cooling`, `freezing`, `reheating`, `transport`, `fryer-checks`, `surface-analyses`, `traceability-labels`, `deliveries`, `delivery-pictures` | `restaurants`, `users`, `areas`, `equipment`, `sensors`, `cooking-equipment`, `suppliers`, `products`, `preparations`, `cleaning-tasks`, `fryer-equipment`, `drive-files` |

**Um registo arquivado não é um tombstone**, e é a única exceção à regra dos tombstones acima. Sai sem `deleted: true`, porque
ninguém o apagou: foi movido. Se mantém uma cópia, guarde o que já tem e nunca
trate como eliminação um registo que falta num percurso. Para tudo o que tenha
mais de 90 dias, a sua cópia é a fonte; uma inspeção que precise do nosso
arquivo passa pelo [suporte](https://developers.backresto.com/pt/docs/support).

## As vinte e seis coleções

Cada uma delas tem `<collection>:read` como scope: `cooling` precisa de
`cooling:read`, e assim por diante. Estão documentadas por domínio, campo a
campo, nas páginas abaixo.

| Página | Coleções |
| --- | --- |
| [Temperaturas](https://developers.backresto.com/pt/docs/temperatures) | `temperature-records`, `temperature-readings`, `cooking-temperature-records` |
| [Entregas](https://developers.backresto.com/pt/docs/deliveries) | `deliveries`, `delivery-pictures` |
| [Etiquetas de rastreabilidade](https://developers.backresto.com/pt/docs/traceability-labels) | `traceability-labels` |
| [Limpeza](https://developers.backresto.com/pt/docs/cleaning) | `cleaning-tasks`, `cleaning-task-records`, `cleaning-task-pictures` |
| [Fritadeiras](https://developers.backresto.com/pt/docs/fryers) | `fryer-equipment`, `fryer-checks` |
| [Arrefecimento e processos alimentares](https://developers.backresto.com/pt/docs/food-processes) | `cooling`, `freezing`, `reheating`, `transport` |
| [Análises de superfície](https://developers.backresto.com/pt/docs/surface-analyses) | `surface-analyses` |
| [Documentos](https://developers.backresto.com/pt/docs/drive-files) | `drive-files` |
| [Catálogo](https://developers.backresto.com/pt/docs/catalogue) | `suppliers`, `products`, `preparations` |
| [Instalações e pessoas](https://developers.backresto.com/pt/docs/facilities) | `restaurants`, `users`, `areas`, `equipment`, `sensors`, `cooking-equipment` |

As temperaturas dentro de uma entrega estão sempre em graus Celsius: uma
leitura feita em Fahrenheit é convertida quando chega, e todos os
`temperatureRecords[].unit` dizem `"C"`.

Comece por **Instalações e pessoas** se for ler qualquer uma das outras: são os
limiares dos equipamentos e as zonas que transformam uma medição nua em algo que
consegue julgar.

O [documento OpenAPI](https://api.backresto.com/openapi.json) traz os mesmos
formatos, gerados a partir do serviço em execução. Quando uma página daqui e
esse documento discordarem, o documento é que está certo e nós temos uma página
para corrigir.

## Percorrer uma coleção

```http
GET /v1/restaurants/{restaurantId}/collections/{collection}/records?limit=100
```

| Parâmetro | Obrigatório | Regras |
| --- | --- | --- |
| `limit` | não | De 1 a 100. Por omissão, 50. |
| `cursor` | não | O `nextCursor` da página anterior, tal e qual. |

Não há intervalo de tempo: percorre-se uma coleção, não uma janela dela. Para
ler uma semana de entregas, percorra `deliveries` e guarde os registos cujo
`data.timestamp` cai nessa semana.

**Um percurso é um snapshot coerente.** A primeira página fixa a posição no
fluxo, e todas as páginas seguintes são respondidas a partir dessa mesma
posição. Os registos escritos enquanto está a paginar não deslocam as páginas
debaixo de si e não aparecem a meio do percurso. Vê-os no percurso seguinte. A
ordenação é por `sequence`, ascendente.

O cursor está ligado ao restaurante, à coleção **e ao limite**. Mudar o tamanho
da página a meio do percurso é rejeitado: escolha um limite e mantenha-o durante
todo o percurso.

```js
async function* records({ apiKey, restaurantId, collection }) {
  const base = `https://api.backresto.com/v1/restaurants/${restaurantId}/collections/${collection}/records`;
  let cursor = null;

  do {
    const url = new URL(base);
    url.searchParams.set('limit', '100');
    if (cursor !== null) {
      url.searchParams.set('cursor', cursor);
    }

    const response = await fetch(url, {
      headers: { Authorization: `Bearer ${apiKey}` }
    });
    if (!response.ok) {
      throw new Error(`BackResto ${response.status}`);
    }

    const page = await response.json();
    yield* page.data;
    cursor = page.nextCursor;
  } while (cursor !== null);
}
```

### Manter uma cópia atualizada

De momento não há parâmetro `since`. Uma atualização é outro percurso, e
reconcilia-se com o que já tem:

- **Indexe pelo `id`** dentro de uma coleção, e substitua quando o `sequence`
  que recebe for maior do que o que guardou.
- **Respeite os tombstones.** `deleted: true` é a instrução de apagar;
  aplicá-la é a única forma de a sua cópia deixar de divergir.
- **Percorra a uma hora sensata.** Uma coleção inteira com `limit=100` são
  poucos pedidos para um único restaurante, e o
  [orçamento](https://developers.backresto.com/pt/docs/rate-limits) é de 300 por minuto, mas várias centenas de
  restaurantes no mesmo minuto do cron são um pico que é seu.

Se um cursor incremental mudasse aquilo que consegue construir, diga-o. É uma
alteração pequena num fluxo que já está ordenado: a razão para não existir é
que ainda ninguém precisou dele.

## Ficheiros

Quatro coleções trazem um ficheiro: `cleaning-task-pictures`,
`delivery-pictures`, `traceability-labels` e `drive-files`. O registo indica-o em `data.asset`; os
bytes vêm do endpoint `assets`.

```http
GET /v1/restaurants/{restaurantId}/collections/{collection}/records/{recordId}/assets
```

```json
{
  "data": [
    {
      "id": "a17c93be4f02",
      "contentType": "application/pdf",
      "byteLength": 184320,
      "sha256": "9f2a…",
      "uploadedAt": "1789000000000",
      "url": "https://…?X-Amz-Signature=…",
      "urlExpiresAt": "1789000900000"
    }
  ]
}
```

Responde com uma lista de URL assinados, cada um válido durante **quinze
minutos**, e todos os URL de uma mesma resposta partilham a mesma expiração.
Cada um leva a sua própria autorização, por isso a transferência não precisa de
qualquer cabeçalho `Authorization`, o que também significa que a ligação é, ela
própria, uma credencial ao portador: durante quinze minutos, qualquer pessoa
que a tenha consegue ler esse ficheiro. Vale a pena saber duas coisas.

**Isto são ficheiros, não só imagens.** Uma prova de limpeza ou uma fotografia
de entrega é uma imagem, mas uma etiqueta de rastreabilidade ou um ficheiro do drive pode ser um PDF, um
CSV, uma folha de cálculo ou um documento Word. Leia o `contentType` em vez de
assumir que é uma imagem, e não renomeie tudo para `.jpg` à entrada.

**O `sha256` está ali para lhe poupar trabalho.** É o resumo criptográfico dos
bytes: se corresponder a alguma coisa que já guardou, não precisa de a voltar a
transferir.

Só aparecem os ficheiros cujo carregamento terminou e foi verificado: um que
ainda vem a caminho está ausente, não avariado, e o mesmo acontece assim que o
registo a que pertence é apagado.

O resto dos conselhos vale a pena repetir, porque a falha é silenciosa: vá
buscar os bytes agora, guarde os bytes e não a ligação, nunca ponha o URL num
sítio onde sobreviva ao seu propósito (um email, um pedido de suporte, uma
cache de longa duração), e volte a chamar o endpoint quando precisar de um URL
novo. É barato, e volta a verificar a concessão.

## As falhas que vai encontrar

**`403`**: a sua chave alcança o restaurante, mas não com o scope dessa
coleção. O endpoint das coleções é a forma barata de descobrir quais é que ela
tem.

**`404`**: nenhuma concessão para esse restaurante, ou esse registo não existe.
Os dois casos são indistinguíveis de propósito.

**`400`**: uma coleção fora das vinte e seis acima, ou um cursor que não
pertence a este restaurante, a esta coleção e a este limite.

Os três são [documentos de problema](https://developers.backresto.com/pt/docs/errors) com um `requestId`.
