Collections & Datensätze
Die sechsundzwanzig Datensatz-Collections, der Snapshot-Umschlag, den sie teilen, und wie Sie eine durchlaufen.
Eine Collection ist ein Datensatztyp: temperature-records, cooling,
deliveries und dreiundzwanzig weitere. Jede wird auf dieselbe Weise
gelesen, über dieselben vier Endpunkte, und jeder Datensatz kommt im selben
Umschlag zurück: den Feldern um data herum, die sagen, wann er erfasst wurde
und ob es ihn noch gibt.
Jede Collection ist eine eigene Zuweisung. Ein Schlüssel liest
temperature-records, weil jemand temperature-records:read zugewiesen hat,
und nichts sonst kommt damit.
Was Sie lesen können
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
Beginnen Sie mit dem ersten. Er listet nur die Collections, die Ihrem Schlüssel zugewiesen wurden, jede mit dem Scope, der sie geöffnet hat. Sie müssen also nie raten:
{
"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
}
]
}
Ein leeres data ist kein Fehler und keine Störung: Es ist ein Schlüssel ohne
Collection-Zuweisungen. Die meisten Schlüssel, die ausgestellt wurden, bevor es
diese Oberfläche gab, haben genau das, und einen zu erweitern ist
eine E-Mail.
Der Umschlag
Jeder Datensatz in jeder Collection trägt dieselben äußeren Felder. Nur data
ändert seine Struktur.
| Feld | Typ | Bedeutung |
|---|---|---|
id | string | Die Kennung des Datensatzes, stabil und eindeutig innerhalb der Collection. |
restaurantId | string | Das Restaurant, zu dem er gehört. Spiegelt den Pfad wider. |
collection | string | Aus welcher Collection er stammt. Spiegelt den Pfad wider. |
data | object | fehlt | Der Datensatz selbst. Ein Tombstone ist der einzige Fall, in dem es fehlen kann; überall sonst dürfen Sie es als vorhanden behandeln. |
deleted | boolean | true bedeutet einen Tombstone: Der Datensatz wurde in der App gelöscht. |
capturedAt | string | Wann das Gerät die Änderung erfasst hat, als String mit Millisekunden seit Epoch. |
receivedAt | string | Wann diese API sie empfangen hat. Später als capturedAt, manchmal um Stunden. |
sequence | string | Eine monoton steigende Position im Strom der Änderungen. Vergleichen Sie zwei davon; rechnen Sie nicht mit einer. |
mutationId | string | Die Kennung der Änderung, die diesen Zustand erzeugt hat. Auf unserer Seite ein Idempotenzschlüssel, auf Ihrer ein Schlüssel zur Deduplizierung. |
sourceVersion | string | null | Der Versionsstempel des ursprünglichen Datensatzes, sofern er einen hat. |
payloadVersion | number | Derzeit immer 1. Er steigt, falls sich die Bedeutung von data je ändert. |
stateKind | string | Immer received-shadow-snapshot. Lesen Sie den nächsten Abschnitt. |
capturedAt und receivedAt zählen beide. Ein Tablet in einem Kühlraum ohne
Empfang erfasst um 09:00 Uhr und lädt um 14:00 Uhr hoch; Ihre eigene Pipeline
nach receivedAt zu ordnen hält sie korrekt, und nach capturedAt zu berichten
hält sie wahr.
Was ein Snapshot ist und was nicht
stateKind sagt received-shadow-snapshot, und die Formulierung ist mit
Bedacht gewählt.
- Es ist der aktuelle Zustand eines Datensatzes, kein Protokoll jeder Änderung. Lesen Sie einen Datensatz zweimal, und Sie bekommen beide Male, wie er jetzt aussieht.
- Es ist, was wir empfangen haben, nicht, was das Restaurant hält. Ein Gerät, das eine Änderung nie hochgeladen hat, heißt: ein Datensatz, den diese API nie gesehen hat.
- Es ist kein Backfill. Eine Collection beginnt für ein Restaurant an dem Tag, an dem die Erfassung dafür eingeschaltet wird. Datensätze, die davor entstanden sind, liegen in der App und nicht hier. Lieferungen sind die einzige Ausnahme: Diese beiden Collections tragen die Historie, die die App noch hielt, als sie geöffnet wurden.
Ein fehlender Datensatz ist also wirklich mehrdeutig: nie erfasst, oder erfasst und noch nicht angekommen. Bauen Sie darauf keine Zahl, die sich wie ein Audit liest, und lesen Sie die Anmerkung zur Vollständigkeit, bevor Sie irgendjemandem eine Anzahl nennen.
Tombstones sind der eine Fall, in dem Abwesenheit eindeutig ist. deleted: true
ohne data heißt, dass der Datensatz existiert hat und gelöscht wurde, und es
ist der einzige Weg, auf dem Sie erfahren, dass etwas verschwunden ist.
Wie lange Datensätze hier bleiben
Betriebliche Datensätze bleiben hier 90 Tage, gezählt ab dem Zeitpunkt, an
dem sie in der Küche entstanden sind (data.timestamp), nicht ab ihrem Eingang
bei uns. Danach verschiebt ein nächtlicher Job sie in ein gesperrtes Kaltarchiv,
das fünf Jahre aufbewahrt wird, und sie erscheinen in keinem Lesezugriff mehr:
weder in Listen noch als einzelner Datensatz noch mit ihren Assets.
| 90 Tage aufbewahrt | Für die gesamte Lebensdauer des Kontos aufbewahrt |
|---|---|
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 |
Ein archivierter Datensatz ist kein Tombstone, und er ist die einzige Ausnahme von der Tombstone-Regel oben. Er verschwindet ohne
deleted: true, weil ihn niemand gelöscht hat: Er wurde verschoben. Wenn Sie
eine Kopie führen, behalten Sie, was Sie bereits haben, und werten Sie einen in
einem Durchlauf fehlenden Datensatz nie als Löschung. Für alles, was älter als
90 Tage ist, ist Ihre Kopie die Quelle; eine Prüfung, die unser Archiv braucht,
läuft über den Support.
Die sechsundzwanzig Collections
Jede nimmt <collection>:read als Scope (cooling braucht cooling:read, und
so weiter). Sie sind nach Themengebiet dokumentiert, Feld für Feld, auf den
Seiten unten.
| Seite | Collections |
|---|---|
| Temperaturen | temperature-records, temperature-readings, cooking-temperature-records |
| Lieferungen | deliveries, delivery-pictures |
| Rückverfolgbarkeitsetiketten | traceability-labels |
| Reinigung | cleaning-tasks, cleaning-task-records, cleaning-task-pictures |
| Fritteusen | fryer-equipment, fryer-checks |
| Kühlen & Lebensmittelprozesse | cooling, freezing, reheating, transport |
| Oberflächenanalysen | surface-analyses |
| Dokumente | drive-files |
| Katalog | suppliers, products, preparations |
| Standort & Personen | restaurants, users, areas, equipment, sensors, cooking-equipment |
Temperaturen in einer Lieferung sind immer in Celsius: Eine in Fahrenheit
genommene Messung wird beim Eingang umgerechnet, und jedes
temperatureRecords[].unit sagt "C".
Beginnen Sie mit Standort & Personen, wenn Sie eine der anderen Seiten lesen: Die Schwellen der Geräte und die Bereiche sind das, was aus einer nackten Messung etwas macht, das Sie beurteilen können.
Das OpenAPI-Dokument trägt dieselben Strukturen, erzeugt aus dem laufenden Dienst. Wenn eine Seite hier und jenes Dokument sich widersprechen, hat das Dokument recht und wir haben eine Seite zu korrigieren.
Eine Collection durchlaufen
GET /v1/restaurants/{restaurantId}/collections/{collection}/records?limit=100
| Parameter | Pflicht | Regeln |
|---|---|---|
limit | nein | 1 bis 100. Standard ist 50. |
cursor | nein | Der nextCursor der vorherigen Seite, wortgleich. |
Hier gibt es keinen Zeitraum: Sie durchlaufen eine Collection, nicht ein
Fenster daraus. Um eine Woche an Lieferungen zu lesen, durchlaufen Sie
deliveries und behalten die Datensätze, deren data.timestamp in diese Woche
fällt.
Ein Durchlauf ist ein konsistenter Snapshot. Die erste Seite fixiert die
Position im Strom, und jede spätere Seite wird zu genau dieser Position
beantwortet. Datensätze, die geschrieben werden, während Sie blättern,
verschieben die Seiten unter Ihnen nicht und tauchen mitten im Durchlauf nicht
auf: Sie sehen sie beim nächsten Durchlauf. Sortiert wird aufsteigend nach
sequence.
Der Cursor ist an das Restaurant, die Collection und das Limit gebunden. Ändern Sie die Seitengröße mitten im Durchlauf, wird sie abgelehnt: Wählen Sie ein Limit und behalten Sie es für den ganzen Durchlauf.
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);
}
Eine Kopie aktuell halten
Es gibt heute keinen since-Parameter. Eine Auffrischung ist ein weiterer
Durchlauf, und Sie gleichen ihn mit dem ab, was Sie schon halten:
- Nutzen Sie
idals Schlüssel innerhalb einer Collection und überschreiben Sie, wenn diesequence, die Sie bekommen, höher ist als die gespeicherte. - Werten Sie Tombstones aus.
deleted: trueist die Anweisung zu löschen; sie anzuwenden ist der einzige Weg, auf dem Ihre Kopie aufhört auseinanderzulaufen. - Durchlaufen Sie zu einer vernünftigen Stunde. Eine vollständige Collection
mit
limit=100ist für ein einzelnes Restaurant eine Handvoll Anfragen, und das Budget sind 300 pro Minute, aber mehrere hundert Restaurants in derselben Cron-Minute sind eine Spitze, die Ihnen gehört.
Wenn ein inkrementeller Cursor ändern würde, was Sie bauen können, sagen Sie es. Es ist eine kleine Änderung an einem Strom, der ohnehin geordnet ist. Der Grund, warum es ihn nicht gibt, ist, dass ihn bisher niemand gebraucht hat.
Assets
Vier Collections tragen eine Datei: cleaning-task-pictures,
delivery-pictures, traceability-labels und drive-files. Der Datensatz benennt sie in
data.asset; die Bytes kommen vom Assets-Endpunkt.
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"
}
]
}
Er antwortet mit einer Liste signierter URLs, jede fünfzehn Minuten gültig,
und jede URL einer Antwort hat denselben Ablaufzeitpunkt. Jede trägt ihre eigene
Autorisierung, der Download braucht also keinen Authorization-Header, was
zugleich heißt, dass der Link selbst ein Bearer-Credential ist: Fünfzehn Minuten
lang kann jeder, der ihn hat, diese Datei lesen. Zwei Dinge sollte man wissen.
Das sind Dateien, nicht nur Bilder. Ein Reinigungsbeleg oder ein Lieferfoto
ist ein Bild, aber
ein Rückverfolgbarkeitsetikett oder eine Drive-Datei kann ein PDF, eine CSV,
eine Tabelle oder ein Word-Dokument sein. Lesen Sie contentType, statt ein
Bild anzunehmen, und benennen Sie beim Import nicht alles in .jpg um.
sha256 ist da, damit Sie sich Arbeit sparen können. Es ist der Digest der
Bytes: Passt er zu etwas, das Sie schon gespeichert haben, müssen Sie es nicht
erneut herunterladen.
Es erscheinen nur Assets, deren Upload abgeschlossen und geprüft ist: Eines, das noch unterwegs ist, fehlt schlicht, statt defekt zu erscheinen, und dasselbe gilt, sobald der übergeordnete Datensatz gelöscht ist.
Den Rest des Rats zu wiederholen lohnt sich, weil der Fehlschlag leise ist: Holen Sie die Bytes jetzt, speichern Sie die Bytes statt des Links, legen Sie die URL nie irgendwohin, wo sie ihren Zweck überlebt (eine E-Mail, ein Ticket, ein langlebiger Cache), und rufen Sie den Endpunkt erneut auf, wenn Sie eine frische URL brauchen. Das ist billig, und dabei wird die Zuweisung erneut geprüft.
Die Fehler, die Ihnen begegnen werden
403: Ihr Schlüssel erreicht das Restaurant, aber nicht mit dem Scope
dieser Collection. Der Collections-Endpunkt ist der billige Weg herauszufinden,
welche Scopes er hat.
404: überhaupt keine Zuweisung für dieses Restaurant, oder es gibt diesen
Datensatz nicht. Beides ist absichtlich nicht unterscheidbar.
400: eine Collection außerhalb der sechsundzwanzig oben, oder ein Cursor,
der nicht zu diesem Restaurant, dieser Collection und diesem Limit gehört.
Alle drei sind Problemdokumente mit einer requestId.