Begreper
Sideinndeling og synkronisering
Lister svarer med én side om gangen og en peker; updatedSince henter bare det som er endret siden forrige kjøring.
Sider og pekere
Hver liste svarer på samme måte: elementene, pekeren til neste side, og om det finnes en.
{
"data": [ { "sessionId": "b3ac0004-0000-4000-8000-000000000000", "title": "Registration and coffee" } ],
"nextCursor": "YzE6MDYzOTI3NjEwODQ4MjgyMDAwMHxiM2Fj...",
"hasMore": true
}Send nextCursor tilbake som cursor, med de samme filtrene, til den er null. limit angir sidestørrelsen, 100 som standard og maksimalt 500. En peker er ugjennomsiktig: behold den slik den kom, og lag aldri en selv. En peker som serveren ikke har laget, avvises med invalidCursor i stedet for at listen stille begynner på nytt.
Listene har en fast rekkefølge, eldste endring først, med ID-en som skiller ved likhet, så en side hopper aldri over eller gjentar et element som ikke ble endret mens du leste. Et element som endres mens du blar, flyttes til slutten, og du ser det igjen, som er nettopp det en synkronisering vil ha.
Listene over deltakere, bestillinger og innsjekkinger tar også page og pageSize, som de tok før pekerne fantes. De virker fortsatt; bruk heller pekeren, som aldri teller.
Synkronisering: bare det som er endret
Lister over ting som endres, oppgir når hvert element sist ble endret, i updatedUtc, og tar updatedSince. En synkronisering tar vare på den nyeste updatedUtc den har sett, og spør derfra neste gang:
- Les alle sidene med
updatedSincesatt til det du tok vare på sist (ingenting første gang). - Lagre eller oppdater hvert element etter ID-en.
- Ta vare på den nyeste
updatedUtcfra kjøringen til neste gang.
updatedSince er inkluderende: et element som ble endret i samme millisekund som merket ditt, kommer igjen i stedet for å bli oversett. Å oppdatere et element du allerede har, er ufarlig; å gå glipp av et er det ikke.
const base = "https://attendbeacon.com/api/v1/events/b3ac0000-0000-4000-8000-000000000001/attendees";
const headers = { Authorization: `Bearer ${process.env.ATTENDBEACON_KEY}` };
let since = await loadMark(); // null on the first run
let newest = since;
let cursor = null;
do {
const url = new URL(base);
url.searchParams.set("limit", "500");
if (since) url.searchParams.set("updatedSince", since);
if (cursor) url.searchParams.set("cursor", cursor);
const page = await fetch(url, { headers }).then((r) => r.json());
for (const attendee of page.data) {
await upsert(attendee); // by attendee.attendeeId
if (!newest || attendee.updatedUtc > newest) newest = attendee.updatedUtc;
}
cursor = page.nextCursor;
} while (cursor);
await saveMark(newest);var since = await LoadMarkAsync(); // null on the first run
var newest = since;
string? cursor = null;
do
{
var query = $"events/b3ac0000-0000-4000-8000-000000000001/attendees?limit=500"
+ (since is null ? "" : $"&updatedSince={Uri.EscapeDataString(since)}")
+ (cursor is null ? "" : $"&cursor={cursor}");
var page = await client.GetFromJsonAsync<JsonElement>(query);
foreach (var attendee in page.GetProperty("data").EnumerateArray())
{
await UpsertAsync(attendee); // by attendeeId
var updated = attendee.GetProperty("updatedUtc").GetString()!;
if (newest is null || string.CompareOrdinal(updated, newest) > 0) newest = updated;
}
cursor = page.GetProperty("nextCursor").GetString();
}
while (cursor is not null);
await SaveMarkAsync(newest);base = "https://attendbeacon.com/api/v1/events/b3ac0000-0000-4000-8000-000000000001/attendees"
headers = {"Authorization": f"Bearer {os.environ['ATTENDBEACON_KEY']}"}
since = load_mark() # None on the first run
newest = since
cursor = None
while True:
params = {"limit": 500}
if since: params["updatedSince"] = since
if cursor: params["cursor"] = cursor
page = requests.get(base, params=params, headers=headers).json()
for attendee in page["data"]:
upsert(attendee) # by attendee["attendeeId"]
if newest is None or attendee["updatedUtc"] > newest:
newest = attendee["updatedUtc"]
cursor = page["nextCursor"]
if cursor is None:
break
save_mark(newest)$base = "https://attendbeacon.com/api/v1/events/b3ac0000-0000-4000-8000-000000000001/attendees"
$since = Get-Content mark.txt -ErrorAction SilentlyContinue # nothing on the first run
$newest = $since
$cursor = $null
do {
$query = "?limit=500"
if ($since) { $query += "&updatedSince=" + [uri]::EscapeDataString($since) }
if ($cursor) { $query += "&cursor=$cursor" }
$page = Invoke-RestMethod -Uri ($base + $query) -Headers $headers
foreach ($attendee in $page.data) {
Save-Attendee $attendee # by attendeeId
if (-not $newest -or $attendee.updatedUtc -gt $newest) { $newest = $attendee.updatedUtc }
}
$cursor = $page.nextCursor
} while ($cursor)
Set-Content mark.txt $newestTider er ISO 8601 i UTC med tre desimaler og en Z, så de kan også sammenlignes riktig som tekst.
Det som ikke har noe endringstidspunkt
Rom, spor, foredragsholdere, sponsorer og sponsornivåer er små lister uten eget endringstidspunkt: les dem i sin helhet, gjerne med pekeren. Enkeltressursene deres og selve arrangementet svarer med en ETag, så et nytt spørsmål med If-None-Match svarer 304 Not Modified, uten innhold, når ingenting er endret.
Innsjekkinger endres aldri etter at de er gjort: bla gjennom dem med cursor, eller med after, den siste checkInId du har, som bare øker.
Avbestillinger og sletting
En avbestilt billett blir stående i listen med "status": "cancelled", så en synkronisering ser avbestillingen. Når noen sletter dataene sine fra arrangementsappen, blir deltakeren stående med "erased": true og ingenting personlig; webhooken attendee.erased gir deg beskjed med en gang, så du kan slette din egen kopi. Se Personvern og sletting.
Webhooks eller polling
Polling med updatedSince med noen minutters mellomrom er enkelt og fanger opp alt. Webhooks gir deg beskjed i løpet av sekunder. Mange integrasjoner gjør begge deler: webhooks for hastigheten, en nattlig synkronisering for sikkerhets skyld.