Skip to content

Latest commit

 

History

History
118 lines (96 loc) · 6.16 KB

File metadata and controls

118 lines (96 loc) · 6.16 KB

Observer directory

Status: pending server PR #212, not yet deployed. The existing /observers endpoint remains unchanged.

Requests

GET /api/v1/observers/directory?iata=YVR&sort=traffic&limit=50

Parameter Contract
sort traffic (default) or name.
limit Positive integer, default 50, capped at 200 per page. No directory-wide row cap.
cursor Nonnegative integer offset, default 0. Pass the returned nextCursor.
since, until Positive epoch milliseconds, inclusive start and exclusive end, rounded down to UTC hours. Window must be 1 hour through 31 days; until cannot be in the future. Required when cursor is nonzero.
iata, iatas Case-insensitive IATA or comma-separated IATAs. Nonempty iatas takes precedence.
regionId, region Expand region ID or slug and union with explicit IATAs. ID takes precedence. An empty region without explicit IATAs matches nothing.
name Case-insensitive display-name substring using SQL ILIKE semantics (% and _ are wildcards).
type, broker, scope Exact observer type, broker membership or transport scope name. URL-encode # in scopes.
status online or offline.

All filters apply before ordering and pagination. Type, broker, name and scope are limited to 200 UTF-8 bytes each. At most 200 IATAs are accepted after region expansion and before deduplication. Unknown/repeated parameters return 400.

The first page defaults to until = floorToUtcHour(now - 95 minutes) + 1 hour, and since = until - 24 hours. This excludes the latest 35 to 95 minutes; these are hourly analytics counts, not live packet counters.

For every continuation, repeat all filters and sort, pass the first response's windowStart as since and windowEnd as until, and use nextCursor:

/api/v1/observers/directory?iata=YVR&sort=traffic&since=1767225600000&until=1767312000000&cursor=50&limit=50

Response

Items contain the existing ObserverSummary fields plus required nullable observationCount. Optional names, type, radio and empty scopes may be omitted.

{
  "items": [
    {"id": "00000000-0000-0000-0000-000000000001", "displayName": "North receiver", "iata": "YVR", "status": "online", "observationCount": 1200}
  ],
  "nextCursor": 1,
  "hasMore": true,
  "generatedAt": 1767314100000,
  "windowStart": 1767225600000,
  "windowEnd": 1767312000000,
  "sort": "traffic",
  "effectiveSort": "traffic",
  "coverage": {"status": "complete", "expectedHours": 24, "completeHours": 24, "partialHours": 0, "missingHours": 0},
  "maxObservationCount": 1200,
  "observerTypes": ["meshcore-ha", "meshcoretomqtt"]
}

Timestamps are epoch milliseconds. items and observerTypes are always arrays. A terminal page has hasMore: false and no nextCursor. A cursor past the end returns an empty terminal page.

Counts sum analytics_hourly_observer_identity for the requested window. They represent stored hearings per observer, deduplicated across brokers, not MQTT messages or the observer detail's cumulative presence counter. No raw observation scan is needed. Directory membership uses current observer IATA; counts include only observations in selected IATAs, or all IATAs without a location filter. Broker and scope filter membership, not the source of counted observations.

When every requested hour is complete, missing observer analytics mean 0. If any hour is partial or missing, every count and maxObservationCount is null, and effectiveSort is name. Coverage is complete when all hours are complete, partial when some are complete or partial but not all complete, and unavailable when all are missing. Absent rollup records count as missing. Never render null as zero.

Traffic order is count descending, then lowercase display name ascending using PostgreSQL C collation, then UUID ascending. Name order uses the last two keys. Missing names sort as empty strings. Online status means last status or last seen within five minutes at request time.

maxObservationCount covers the whole filtered result, including in name order. It is 0 for an empty complete result. observerTypes includes all distinct nonempty types matching every filter except the type filter itself, independent of pagination.

Consistency and client behavior

Each request reads current data. There is no stored snapshot, expiry, new table or snapshot capacity limit. Keeping the window fixed avoids hourly window drift, but analytics corrections and metadata changes can still move rows between pages, causing repeats or skips. Counts, coverage, type choices and maximum can also change. This endpoint is a browsing list, not a consistent export.

Use one infinite query keyed by server, filters, window and sort. A query change starts from page one and discards stale responses. Preserve server order, allow one continuation in flight, deduplicate by observer UUID, and stop on hasMore=false or a missing/repeated cursor. Fetch again near the scroll end, including when the viewport is not filled. No per-row count requests or separate top-observers request is needed.

For stable bars while scrolling, retain the first page's maximum and clamp count/max to [0,1]; reset on refresh. Render unavailable when count or maximum is null, and zero width when the maximum is zero. If effectiveSort changes between pages as coverage changes, restart from page one rather than mixing orderings. Live status badges can update separately. Refresh the list to update membership or order; selection and deep links continue to use UUIDs.

Invalid parameters and unknown regions return 400. Database failures return 500; retain loaded rows and offer retry. Successful pages and store errors send Cache-Control: no-store; database reads have a five-second operation budget.

Probe /api/v1/observers/directory?limit=1 for compatibility. A supported server returns the response above. Older servers may return 404, or the legacy detail route's 400 with message failed to parse observer UUID. Other errors do not prove lack of support. Use an explicitly limited legacy fallback or request a server upgrade; do not assume the old /observers route honors new parameters.