Collections et enregistrements
Les vingt-quatre types d'enregistrement au-delà des livraisons — l'enveloppe d'instantané qu'ils partagent, ce qu'est un instantané, et comment en parcourir une.
Les livraisons sont une ressource façonnée à la main, parce que le contrôle à réception est la première chose qu'on nous ait demandée. Les collections, c'est tout le reste de l'enregistrement : températures, cycles de refroidissement, nettoyage, contrôles des friteuses, étiquettes, vingt-quatre en tout, servies à travers une seule forme générique.
Là où l'endpoint de livraison vous donne un objet conçu pour vous, une collection vous donne l'enregistrement tel que l'application le détient, glissé dans une enveloppe qui vous dit quand il a été saisi et s'il existe encore. Ce compromis est délibéré : c'est lui qui permet à un nouveau module d'atteindre cette API la semaine où il sort, plutôt que le trimestre suivant.
Chaque collection est une autorisation distincte. Une clé lit
temperature-records parce que quelqu'un a accordé temperature-records:read,
et rien d'autre ne vient avec.
Ce que vous pouvez lire
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
Commencez par le premier. Il liste uniquement les collections qui ont été accordées à votre clé, chacune avec la portée qui l'ouvre, vous n'avez donc jamais à deviner :
{
"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
}
]
}
Un data vide n'est pas une erreur et pas une panne : c'est une clé sans aucune
autorisation sur les collections. La plupart des clés émises avant que cette
surface n'existe sont exactement dans ce cas, et l'élargir tient en
un e-mail.
L'enveloppe
Chaque enregistrement de chaque collection porte les mêmes champs extérieurs.
Seul data change de forme.
| Champ | Type | Signification |
|---|---|---|
id | string | L'identifiant de l'enregistrement, stable et unique au sein de la collection. |
restaurantId | string | Le restaurant auquel il appartient. Reprend le chemin. |
collection | string | La collection d'où il vient. Reprend le chemin. |
data | object | absent | L'enregistrement lui-même. Une pierre tombale est le seul cas où il peut manquer, considérez-le donc comme présent partout ailleurs. |
deleted | boolean | true signifie une pierre tombale : l'enregistrement a été supprimé dans l'application. |
capturedAt | string | Quand l'appareil a consigné le changement, en millisecondes depuis l'epoch sous forme de chaîne de caractères. |
receivedAt | string | Quand cette API l'a reçu. Postérieur à capturedAt, parfois de plusieurs heures. |
sequence | string | Une position croissante de façon monotone dans le flux des changements. Comparez-en deux ; ne faites pas d'arithmétique sur une seule. |
mutationId | string | L'identifiant du changement qui a produit cet état. Clé d'idempotence de notre côté ; clé de déduplication du vôtre. |
sourceVersion | string | null | Le marqueur de version de l'enregistrement d'origine, quand il en a un. |
payloadVersion | number | Toujours 1 aujourd'hui. Il augmente si le sens de data vient à changer. |
stateKind | string | Toujours received-shadow-snapshot. Lisez la section suivante. |
capturedAt et receivedAt comptent tous les deux. Une tablette dans une
chambre froide sans réseau enregistre à 09:00 et téléverse à 14:00 ; ordonner
votre propre pipeline sur receivedAt le garde correct, et faire vos rapports
sur capturedAt les garde justes.
Ce qu'est un instantané, et ce qu'il n'est pas
stateKind dit received-shadow-snapshot, et la formulation est choisie avec
soin.
- C'est l'état actuel d'un enregistrement, pas un journal de chaque modification. Lisez un enregistrement deux fois et vous obtenez son état du moment, les deux fois.
- C'est ce que nous avons reçu, pas ce que le restaurant détient. Un appareil qui n'a jamais téléversé un changement, c'est un enregistrement que cette API n'a jamais vu.
- Ce n'est pas une reprise de l'historique. Une collection commence pour un restaurant le jour où la capture est activée pour lui. Les enregistrements créés avant sont dans l'application, pas ici.
Un enregistrement absent est donc réellement ambigu : jamais saisi, ou saisi et pas encore arrivé. N'en tirez pas un chiffre qui se lit comme un audit, et voyez la note sur l'exhaustivité avant de communiquer un décompte à qui que ce soit.
Les pierres tombales sont le seul cas où l'absence est sans ambiguïté.
deleted: true sans data signifie que l'enregistrement a existé et a été
supprimé, et c'est la seule façon d'apprendre que quelque chose a disparu.
Les vingt-quatre collections
Chacune d'elles prend <collection>:read comme portée — cooling exige
cooling:read, et ainsi de suite jusqu'au bas de la liste.
| Collection | Ce qu'est un enregistrement |
|---|---|
restaurants | Le site lui-même : nom, adresse, jours de fermeture, abonnement et paramètres. |
users | Les comptes du personnel qui enregistrent les contrôles, et les modules qu'ils utilisent. |
areas | Les zones dans lesquelles un restaurant est découpé — cuisine, chambre froide, bar. |
equipment | Réfrigérateurs, congélateurs et équipements de la chaîne du froid, avec leurs seuils min/max. |
sensors | Les sondes sans fil, par adresse MAC, et l'équipement que chacune surveille. |
temperature-records | Une température relevée par une personne sur un équipement, avec le service et l'éventuelle action corrective. |
temperature-readings | Une température remontée par une sonde d'elle-même, sans intervention. |
suppliers | Qui livre, avec les moyens de contact et le numéro de compte. |
products | Les produits réceptionnés et utilisés. |
preparations | Les préparations maison, avec leur durée de vie et leurs allergènes. |
cleaning-tasks | Le plan de nettoyage : chaque tâche, sa zone, sa récurrence, et si elle exige une photographie. |
cleaning-task-records | Une tâche de nettoyage réellement effectuée — quand, et par qui. |
cleaning-task-pictures | La photographie qui le prouve. Porte un fichier ; voyez la section sur les fichiers ci-dessous. |
cooling | Un cycle de refroidissement : produit, température de début et de fin, heure de début et de fin. |
freezing | Une opération de congélation, de même forme que le refroidissement. |
reheating | Une opération de remise en température, de même forme que le refroidissement. |
transport | Un produit transporté, avec lieu de départ et d'arrivée, heure et température. |
fryer-equipment | Les friteuses. |
fryer-checks | Un contrôle de la qualité de l'huile et ce qui a été décidé — filtrée, changée, laissée. |
cooking-equipment | Les fours et les équipements de cuisson, avec leurs seuils. |
cooking-temperature-records | Une température de cuisson, relevée par une personne, sur un équipement de cuisson. |
surface-analyses | Un prélèvement de surface : ce qui a été testé, s'il est conforme, le plan d'action sinon. |
traceability-labels | Une étiquette de traçabilité imprimée. Porte un fichier ; voyez la section sur les fichiers ci-dessous. |
drive-files | Un document classé dans l'espace documentaire du restaurant. Porte un fichier ; voyez la section sur les fichiers ci-dessous. |
Les formes champ par champ de chaque objet data sont dans le
document OpenAPI, généré à partir du
service en fonctionnement : c'est la seule description qui ne peut pas s'écarter
de ce qui est déployé.
Parcourir une collection
GET /v1/restaurants/{restaurantId}/collections/{collection}/records?limit=100
| Paramètre | Obligatoire | Règles |
|---|---|---|
limit | non | De 1 à 100. 50 par défaut. |
cursor | non | Le nextCursor de la page précédente, tel quel. |
Il n'y a pas de plage de temps ici, contrairement aux livraisons : vous parcourez une collection, pas une fenêtre de celle-ci.
Un parcours est un instantané cohérent. La première page fixe la position
dans le flux, et chaque page suivante est servie à cette même position. Les
enregistrements écrits pendant que vous paginez ne décalent pas les pages sous
vos pieds et n'apparaissent pas en cours de parcours — vous les verrez au
parcours suivant. Le tri se fait sur sequence, croissant.
Le curseur est lié au restaurant, à la collection et à la limite. Changez la taille de page en cours de parcours et il est rejeté : choisissez une limite, gardez-la pour tout le parcours.
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);
}
Maintenir une copie à jour
Il n'y a pas de paramètre since aujourd'hui. Une actualisation, c'est un
nouveau parcours, et vous réconciliez avec ce que vous détenez déjà :
- Indexez sur
idau sein d'une collection, et écrasez quand lesequenceque vous recevez est supérieur à celui que vous aviez stocké. - Respectez les pierres tombales.
deleted: trueest l'instruction de suppression ; l'appliquer est la seule façon d'empêcher votre copie de diverger. - Parcourez à une heure raisonnable. Une collection entière à
limit=100, c'est une poignée de requêtes pour un seul restaurant, et le budget est de 300 par minute — mais plusieurs centaines de restaurants sur la même minute de cron, c'est un pic qui vous appartient.
Si un curseur incrémental changeait ce que vous pouvez construire, dites-le. C'est un petit changement sur un flux déjà ordonné — s'il n'existe pas, c'est que personne n'en a encore eu besoin.
Fichiers attachés
Trois collections portent un fichier : cleaning-task-pictures,
traceability-labels et drive-files. L'enregistrement le nomme dans
data.asset ; les octets viennent de l'endpoint des fichiers.
GET /v1/restaurants/{restaurantId}/collections/{collection}/records/{recordId}/assets
{
"data": [
{
"id": "a17c93be4f02",
"contentType": "application/pdf",
"byteLength": 184320,
"sha256": "9f2a…",
"uploadedAt": "1789000000000",
"url": "https://…?X-Amz-Signature=…",
"urlExpiresAt": "1789000900000"
}
]
}
Il répond à peu près comme les photographies de livraison : une liste d'URL signées, valables quinze minutes chacune, portant chacune sa propre autorisation, si bien que le téléchargement ne demande aucun en-tête. Deux différences méritent d'être connues.
Ce sont des fichiers, pas seulement des photos. Une preuve de nettoyage est
une photographie, mais une étiquette de traçabilité ou un document de l'espace
documentaire peut être un PDF, un CSV, un tableur ou un document Word — lisez
contentType plutôt que de supposer une image, et ne renommez pas tout en
.jpg à l'entrée.
sha256 est là pour vous éviter du travail. C'est l'empreinte des octets :
si elle correspond à quelque chose que vous avez déjà stocké, vous n'avez pas
besoin de le retélécharger.
Seuls apparaissent les fichiers dont le téléversement s'est achevé et a été vérifié — un fichier encore en transit est absent plutôt que cassé, et c'est également vrai une fois l'enregistrement parent supprimé.
Le reste des conseils est identique, et mérite d'être répété parce que l'échec est silencieux : récupérez les octets maintenant, stockez les octets plutôt que le lien, et rappelez l'endpoint quand vous avez besoin d'une URL fraîche.
Les échecs que vous rencontrerez
403 — votre clé atteint le restaurant, mais pas avec la portée de cette
collection. L'endpoint des collections est le moyen peu coûteux de savoir
lesquelles elle détient bel et bien.
404 — aucune autorisation pour ce restaurant, ou pas d'enregistrement de
ce nom. Les deux sont volontairement indiscernables.
400 — une collection hors des vingt-quatre ci-dessus, ou un curseur qui
n'appartient pas à ce restaurant, cette collection et cette limite.
Les trois sont des documents problème avec un requestId.