Pojęcia
Stronicowanie i synchronizacja
Listy zwracają dane strona po stronie z kursorem, a updatedSince pobiera tylko to, co zmieniło się od ostatniego uruchomienia.
Strony i kursory
Każda lista odpowiada tak samo: elementy, kursor do następnej strony i informacja, czy taka strona istnieje.
{
"data": [ { "sessionId": "b3ac0004-0000-4000-8000-000000000000", "title": "Registration and coffee" } ],
"nextCursor": "YzE6MDYzOTI3NjEwODQ4MjgyMDAwMHxiM2Fj...",
"hasMore": true
}Przekazuj nextCursor z powrotem jako cursor, z tymi samymi filtrami, dopóki nie przyjmie wartości null. limit ustala rozmiar strony: domyślnie 100, maksymalnie 500. Kursor jest nieprzezroczysty: przechowuj go w otrzymanej postaci i nie twórz go samodzielnie. Kursor, którego nie wygenerował serwer, zostanie odrzucony z kodem invalidCursor, zamiast po cichu zacząć od początku.
Listy mają stałą kolejność: od najdawniej zmienionych, a przy remisie decyduje identyfikator. Dzięki temu strona nigdy nie pominie ani nie powtórzy elementu, który nie zmienił się w trakcie odczytu. Element zmieniony w trakcie stronicowania przesuwa się na koniec i zobaczysz go ponownie – a tego właśnie oczekuje synchronizacja.
Listy uczestników, zamówień i check-inów przyjmują też page i pageSize, tak jak przed wprowadzeniem kursorów. Nadal działają, ale lepiej używać kursora, który niczego nie musi liczyć.
Synchronizacja: tylko to, co się zmieniło
Listy elementów, które się zmieniają, podają w updatedUtc, kiedy każdy element zmienił się ostatnio, i przyjmują updatedSince. Synchronizacja zapamiętuje najnowszą wartość updatedUtc, jaką zobaczyła, i następnym razem pyta od tego momentu:
- Odczytaj wszystkie strony z
updatedSinceustawionym na wartość zapamiętaną ostatnio (za pierwszym razem bez niego). - Zapisz lub zaktualizuj każdy element według jego identyfikatora.
- Zapamiętaj najnowszą wartość
updatedUtcz tego uruchomienia na następny raz.
updatedSince jest włączne: element zmieniony w tej samej milisekundzie co twój znacznik pojawi się ponownie, zamiast zostać pominięty. Ponowna aktualizacja elementu, który już masz, nie szkodzi. Pominięcie elementu – już tak.
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 $newestCzasy są podawane w formacie ISO 8601 w UTC, z trzema miejscami po przecinku i literą Z, więc porównują się poprawnie także jako tekst.
Co nie ma czasu zmiany
Sale, ścieżki, prelegenci, sponsorzy i poziomy sponsorskie to krótkie listy bez własnego czasu zmiany: odczytuj je w całości, jeśli chcesz – z kursorem. Pojedyncze zasoby z tych list oraz samo wydarzenie odpowiadają z nagłówkiem ETag, więc ponowne zapytanie z If-None-Match zwraca 304 Not Modified bez treści, gdy nic się nie zmieniło.
Check-iny po zapisaniu nigdy się nie zmieniają: przeglądaj je według cursor albo według after, czyli ostatniego checkInId, jaki masz – ta wartość tylko rośnie.
Usunięcia i usuwanie danych
Anulowany bilet pozostaje na liście z "status": "cancelled", więc synchronizacja widzi anulowanie. Gdy ktoś usunie swoje dane w aplikacji wydarzenia, jego rekord uczestnika pozostaje z "erased": true i bez żadnych danych osobowych. Webhook attendee.erased od razu cię o tym powiadamia, więc możesz usunąć własną kopię. Zobacz Prywatność i usuwanie danych.
Webhooki czy odpytywanie
Odpytywanie z updatedSince co kilka minut jest proste i niczego nie przegapi. Webhooki powiadamiają w ciągu kilku sekund. Wiele integracji korzysta z obu: z webhooków dla szybkości, a z nocnej synchronizacji dla pewności.