Merge upstream 2.0.3 into the dev1 preview backend

This commit is contained in:
n30nex committed 2026-10-07 05:48:19 -04:00
commit b0b591c220
47 files changed
+2747 -121

No files matched your search

+276 -1
View File
@@ -897,6 +897,26 @@ const docTemplate = `{
}
}
},
"/info": {
"get": {
"description": "minAppVersion is null when the server sets no requirement.",
"produces": [
"application/json"
],
"tags": [
"Info"
],
"summary": "Server info and minimum mobile app version",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.Info"
}
}
}
}
},
"/messages": {
"get": {
"produces": [
@@ -1430,6 +1450,127 @@ const docTemplate = `{
}
}
},
"/observers/directory": {
"get": {
"description": "Defaults to the trailing seven days through request time. Counts combine completed whole-hour analytics with retained observations for other hours and exact boundary slices. Incomplete history still returns numeric counts and the requested sort; coverage describes available history. Repeat filters and the returned windowStart/windowEnd as since/until with the cursor on later pages. Results may move as data changes; refresh without bounds for a new rolling window.",
"produces": [
"application/json"
],
"tags": [
"Observers"
],
"summary": "List observers with windowed traffic counts",
"parameters": [
{
"type": "string",
"description": "Single IATA",
"name": "iata",
"in": "query"
},
{
"type": "string",
"description": "Comma-separated IATAs",
"name": "iatas",
"in": "query"
},
{
"type": "integer",
"description": "Region ID",
"name": "regionId",
"in": "query"
},
{
"type": "string",
"description": "Region slug",
"name": "region",
"in": "query"
},
{
"type": "string",
"description": "Observer type",
"name": "type",
"in": "query"
},
{
"type": "string",
"description": "Broker membership",
"name": "broker",
"in": "query"
},
{
"type": "string",
"description": "online or offline",
"name": "status",
"in": "query"
},
{
"type": "string",
"description": "Case-insensitive partial name",
"name": "name",
"in": "query"
},
{
"type": "string",
"description": "Transport scope membership",
"name": "scope",
"in": "query"
},
{
"type": "string",
"description": "traffic (default) or name",
"name": "sort",
"in": "query"
},
{
"type": "integer",
"format": "int64",
"description": "Inclusive epoch ms, preserved exactly; default seven days before until; maximum window 31 days",
"name": "since",
"in": "query"
},
{
"type": "integer",
"format": "int64",
"description": "Exclusive epoch ms, preserved exactly; default request time",
"name": "until",
"in": "query"
},
{
"type": "integer",
"format": "int64",
"description": "nextCursor returned by preceding page",
"name": "cursor",
"in": "query"
},
{
"type": "integer",
"description": "Page size, default 50, maximum 200",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectory"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/internal_api_handlers.APIError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/internal_api_handlers.APIError"
}
}
}
}
},
"/observers/{observerId}": {
"get": {
"produces": [
@@ -3601,6 +3742,9 @@ const docTemplate = `{
},
"ingest": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminIngestConfig"
},
"mobile": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminMobileConfig"
}
}
},
@@ -3613,6 +3757,15 @@ const docTemplate = `{
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdminMobileConfig": {
"type": "object",
"properties": {
"min_app_version": {
"description": "empty means no requirement",
"type": "string"
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdvertObservation": {
"type": "object",
"properties": {
@@ -3909,6 +4062,20 @@ const docTemplate = `{
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.Info": {
"type": "object",
"properties": {
"minAppVersion": {
"description": "MinAppVersion is the oldest allowed app version (X.Y.Z); null means no requirement.",
"type": "string",
"example": "0.1.1"
},
"serverVersion": {
"type": "string",
"example": "2.0.2"
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.KnownRoute": {
"type": "object",
"properties": {
@@ -4484,6 +4651,110 @@ const docTemplate = `{
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectory": {
"type": "object",
"properties": {
"coverage": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectoryCoverage"
},
"effectiveSort": {
"type": "string"
},
"generatedAt": {
"type": "integer"
},
"hasMore": {
"type": "boolean"
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectoryItem"
}
},
"maxObservationCount": {
"description": "Maximum over all matching observers, not only this page; zero when none match.",
"type": "integer"
},
"nextCursor": {
"type": "integer"
},
"observerTypes": {
"type": "array",
"items": {
"type": "string"
}
},
"sort": {
"type": "string"
},
"windowEnd": {
"type": "integer"
},
"windowStart": {
"type": "integer"
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectoryCoverage": {
"type": "object",
"properties": {
"completeHours": {
"type": "integer"
},
"expectedHours": {
"type": "integer"
},
"missingHours": {
"type": "integer"
},
"partialHours": {
"type": "integer"
},
"status": {
"type": "string"
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectoryItem": {
"type": "object",
"properties": {
"displayName": {
"description": "friendly name from /status messages",
"type": "string"
},
"iata": {
"description": "most recently heard IATA",
"type": "string"
},
"id": {
"type": "string"
},
"observationCount": {
"description": "Available observations in [windowStart, windowEnd), including zero with missing history.",
"type": "integer"
},
"observerType": {
"description": "e.g. \"meshcoretomqtt\", \"meshcoreha\"",
"type": "string"
},
"radio": {
"description": "friendly radio param string: freqMhz,BwKhz,SF e.g. \"910.525,62.5,7\"",
"type": "string"
},
"scopes": {
"description": "list of observer forwarded scopes matched to config",
"type": "array",
"items": {
"type": "string"
}
},
"status": {
"description": "\"online\" or \"offline\" derived from last_status_at",
"type": "string"
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverSummary": {
"type": "object",
"properties": {
@@ -5997,13 +6268,17 @@ const docTemplate = `{
{
"description": "Network statistics and time series",
"name": "Stats"
},
{
"description": "Server info for the BEACON Mobile app",
"name": "Info"
}
]
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "2.0.1",
Version: "2.0.3",
Host: "localhost:8080",
BasePath: "/api/v1",
Schemes: []string{"http", "https"},
+40 -6
View File
@@ -2,10 +2,10 @@
## The rule
Historical stats never scan `packet_observations` per request or per refresh. They read
hourly rollup tables filled once per closed hour by one background task. Raw rows are read
only for per-entity drill-downs (a packet, a trace, a route), observer comparison and
sub-hour observer activity.
Historical stats read hourly rollup tables filled once per closed hour by one background
task. Raw rows are read for per-entity drill-downs (a packet, a trace, a route), observer
comparison, sub-hour observer activity, and bounded observer-directory slices that cannot
use a completed whole-hour rollup.
## Rollup contract
@@ -32,7 +32,8 @@ sub-hour observer activity.
newest deleted `last_heard_at`. Cleanup and the rollup never write the same row.
- **Partial hours.** A not-yet-complete hour starting at or before `raw_deleted_before + 30 min`
lost raw rows before it was rolled. It is marked `partial` and never rolled; readers
report it without values.
report it without values, except the observer directory, which counts any retained raw
observations in the requested slice.
- **Re-rolls.** Hours are not re-rolled periodically. A message stored by channel backfill (at
startup, and when keys are imported at runtime) queues every hour its packet was heard in
`analytics_dirty_hours`, in the same statement as the message insert. Each pass re-rolls a
@@ -52,10 +53,43 @@ sub-hour observer activity.
| Max path entries | Max |
| SNR / RSSI averages | Σ sum / Σ samples, never an average of averages |
Windows snap to UTC hours on the server, and responses report the effective window.
Historical stats windows snap to UTC hours on the server, and responses report the
effective window. The observer directory preserves exact millisecond bounds instead.
`/stats/overview` spans the 24 newest eligible hours; its `windowHours` counts the complete
ones its totals cover.
## Observer directory
`GET /api/v1/observers/directory` defaults to `[request time - 7 days, request time)`.
Explicit `since` and `until` are epoch milliseconds and remain exact; windows must be
positive and at most 31 days. If only `until` is supplied, `since` defaults to seven days
before it. If only `since` is supplied, `until` defaults to request time.
Counts combine complete rollups for whole UTC hours fully inside the window with retained
raw observations for every other hour and the exact boundary slices. These sources never
overlap. Completed whole-hour history survives raw retention; a sliced boundary whose raw
rows have expired cannot be reconstructed from its whole-hour total.
Every observer receives a numeric `observationCount`, including zero. `maxObservationCount`
is the maximum across all matching observers, independent of pagination, and is zero when
none match. Incomplete history never suppresses these counts or changes `effectiveSort`:
traffic sorting uses available counts, while explicit name sorting remains supported.
Both sorts break name ties by observer ID.
`coverage` is conservative and informational. `expectedHours` counts intersecting UTC
hours, including boundary slices. `completeHours` counts the whole hours read from complete
rollups. Other hours are `partialHours` when there is a partial/boundary rollup or matching
retained raw traffic; otherwise they are `missingHours`. Raw-only traffic does not establish
complete history. Status is `complete` only when all intersecting hours are complete,
`partial` when any complete or partial hour exists, and `unavailable` otherwise. Zero counts
with incomplete coverage mean no available observations, not proof that no traffic occurred.
The response uses `Cache-Control: no-store`. For continuation requests, send `nextCursor`,
the same filters and sort, and the exact returned `windowStart`/`windowEnd` as `since`/`until`.
These bounds fix the time window, not a database snapshot; late arrivals and metadata changes
can still move results. Refresh without bounds to obtain a new rolling window and include
new traffic.
## IATA sets
A packet heard in YVR and YYZ appears in both IATAs' per-IATA rows, so summing per-IATA
+276 -1
View File
@@ -15,7 +15,7 @@
"license": {
"name": "AGPL-3-or-later"
},
"version": "2.0.1"
"version": "2.0.3"
},
"host": "localhost:8080",
"basePath": "/api/v1",
@@ -895,6 +895,26 @@
}
}
},
"/info": {
"get": {
"description": "minAppVersion is null when the server sets no requirement.",
"produces": [
"application/json"
],
"tags": [
"Info"
],
"summary": "Server info and minimum mobile app version",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.Info"
}
}
}
}
},
"/messages": {
"get": {
"produces": [
@@ -1428,6 +1448,127 @@
}
}
},
"/observers/directory": {
"get": {
"description": "Defaults to the trailing seven days through request time. Counts combine completed whole-hour analytics with retained observations for other hours and exact boundary slices. Incomplete history still returns numeric counts and the requested sort; coverage describes available history. Repeat filters and the returned windowStart/windowEnd as since/until with the cursor on later pages. Results may move as data changes; refresh without bounds for a new rolling window.",
"produces": [
"application/json"
],
"tags": [
"Observers"
],
"summary": "List observers with windowed traffic counts",
"parameters": [
{
"type": "string",
"description": "Single IATA",
"name": "iata",
"in": "query"
},
{
"type": "string",
"description": "Comma-separated IATAs",
"name": "iatas",
"in": "query"
},
{
"type": "integer",
"description": "Region ID",
"name": "regionId",
"in": "query"
},
{
"type": "string",
"description": "Region slug",
"name": "region",
"in": "query"
},
{
"type": "string",
"description": "Observer type",
"name": "type",
"in": "query"
},
{
"type": "string",
"description": "Broker membership",
"name": "broker",
"in": "query"
},
{
"type": "string",
"description": "online or offline",
"name": "status",
"in": "query"
},
{
"type": "string",
"description": "Case-insensitive partial name",
"name": "name",
"in": "query"
},
{
"type": "string",
"description": "Transport scope membership",
"name": "scope",
"in": "query"
},
{
"type": "string",
"description": "traffic (default) or name",
"name": "sort",
"in": "query"
},
{
"type": "integer",
"format": "int64",
"description": "Inclusive epoch ms, preserved exactly; default seven days before until; maximum window 31 days",
"name": "since",
"in": "query"
},
{
"type": "integer",
"format": "int64",
"description": "Exclusive epoch ms, preserved exactly; default request time",
"name": "until",
"in": "query"
},
{
"type": "integer",
"format": "int64",
"description": "nextCursor returned by preceding page",
"name": "cursor",
"in": "query"
},
{
"type": "integer",
"description": "Page size, default 50, maximum 200",
"name": "limit",
"in": "query"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectory"
}
},
"400": {
"description": "Bad Request",
"schema": {
"$ref": "#/definitions/internal_api_handlers.APIError"
}
},
"500": {
"description": "Internal Server Error",
"schema": {
"$ref": "#/definitions/internal_api_handlers.APIError"
}
}
}
}
},
"/observers/{observerId}": {
"get": {
"produces": [
@@ -3599,6 +3740,9 @@
},
"ingest": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminIngestConfig"
},
"mobile": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminMobileConfig"
}
}
},
@@ -3611,6 +3755,15 @@
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdminMobileConfig": {
"type": "object",
"properties": {
"min_app_version": {
"description": "empty means no requirement",
"type": "string"
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.AdvertObservation": {
"type": "object",
"properties": {
@@ -3907,6 +4060,20 @@
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.Info": {
"type": "object",
"properties": {
"minAppVersion": {
"description": "MinAppVersion is the oldest allowed app version (X.Y.Z); null means no requirement.",
"type": "string",
"example": "0.1.1"
},
"serverVersion": {
"type": "string",
"example": "2.0.2"
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.KnownRoute": {
"type": "object",
"properties": {
@@ -4482,6 +4649,110 @@
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectory": {
"type": "object",
"properties": {
"coverage": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectoryCoverage"
},
"effectiveSort": {
"type": "string"
},
"generatedAt": {
"type": "integer"
},
"hasMore": {
"type": "boolean"
},
"items": {
"type": "array",
"items": {
"$ref": "#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectoryItem"
}
},
"maxObservationCount": {
"description": "Maximum over all matching observers, not only this page; zero when none match.",
"type": "integer"
},
"nextCursor": {
"type": "integer"
},
"observerTypes": {
"type": "array",
"items": {
"type": "string"
}
},
"sort": {
"type": "string"
},
"windowEnd": {
"type": "integer"
},
"windowStart": {
"type": "integer"
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectoryCoverage": {
"type": "object",
"properties": {
"completeHours": {
"type": "integer"
},
"expectedHours": {
"type": "integer"
},
"missingHours": {
"type": "integer"
},
"partialHours": {
"type": "integer"
},
"status": {
"type": "string"
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectoryItem": {
"type": "object",
"properties": {
"displayName": {
"description": "friendly name from /status messages",
"type": "string"
},
"iata": {
"description": "most recently heard IATA",
"type": "string"
},
"id": {
"type": "string"
},
"observationCount": {
"description": "Available observations in [windowStart, windowEnd), including zero with missing history.",
"type": "integer"
},
"observerType": {
"description": "e.g. \"meshcoretomqtt\", \"meshcoreha\"",
"type": "string"
},
"radio": {
"description": "friendly radio param string: freqMhz,BwKhz,SF e.g. \"910.525,62.5,7\"",
"type": "string"
},
"scopes": {
"description": "list of observer forwarded scopes matched to config",
"type": "array",
"items": {
"type": "string"
}
},
"status": {
"description": "\"online\" or \"offline\" derived from last_status_at",
"type": "string"
}
}
},
"github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverSummary": {
"type": "object",
"properties": {
@@ -5995,6 +6266,10 @@
{
"description": "Network statistics and time series",
"name": "Stats"
},
{
"description": "Server info for the BEACON Mobile app",
"name": "Info"
}
]
}
+196 -1
View File
@@ -52,6 +52,8 @@ definitions:
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminCORSConfig'
ingest:
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminIngestConfig'
mobile:
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.AdminMobileConfig'
type: object
github_com_MeshCore-Beacon_beacon-server_internal_api.AdminIngestConfig:
properties:
@@ -61,6 +63,12 @@ definitions:
or a configurable processing-worker pool.
type: integer
type: object
github_com_MeshCore-Beacon_beacon-server_internal_api.AdminMobileConfig:
properties:
min_app_version:
description: empty means no requirement
type: string
type: object
github_com_MeshCore-Beacon_beacon-server_internal_api.AdvertObservation:
properties:
heardAt:
@@ -264,6 +272,17 @@ definitions:
lon:
type: number
type: object
github_com_MeshCore-Beacon_beacon-server_internal_api.Info:
properties:
minAppVersion:
description: MinAppVersion is the oldest allowed app version (X.Y.Z); null
means no requirement.
example: 0.1.1
type: string
serverVersion:
example: 2.0.2
type: string
type: object
github_com_MeshCore-Beacon_beacon-server_internal_api.KnownRoute:
properties:
firstSeen:
@@ -687,6 +706,79 @@ definitions:
description: exclusive, epoch milliseconds
type: integer
type: object
github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectory:
properties:
coverage:
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectoryCoverage'
effectiveSort:
type: string
generatedAt:
type: integer
hasMore:
type: boolean
items:
items:
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectoryItem'
type: array
maxObservationCount:
description: Maximum over all matching observers, not only this page; zero
when none match.
type: integer
nextCursor:
type: integer
observerTypes:
items:
type: string
type: array
sort:
type: string
windowEnd:
type: integer
windowStart:
type: integer
type: object
github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectoryCoverage:
properties:
completeHours:
type: integer
expectedHours:
type: integer
missingHours:
type: integer
partialHours:
type: integer
status:
type: string
type: object
github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectoryItem:
properties:
displayName:
description: friendly name from /status messages
type: string
iata:
description: most recently heard IATA
type: string
id:
type: string
observationCount:
description: Available observations in [windowStart, windowEnd), including
zero with missing history.
type: integer
observerType:
description: e.g. "meshcoretomqtt", "meshcoreha"
type: string
radio:
description: 'friendly radio param string: freqMhz,BwKhz,SF e.g. "910.525,62.5,7"'
type: string
scopes:
description: list of observer forwarded scopes matched to config
items:
type: string
type: array
status:
description: '"online" or "offline" derived from last_status_at'
type: string
type: object
github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverSummary:
properties:
displayName:
@@ -1707,7 +1799,7 @@ info:
name: AGPL-3-or-later
termsOfService: https://github.com/MeshCore-Beacon/beacon-server
title: MeshCore Beacon API
version: 2.0.1
version: 2.0.3
paths:
/admin/accounts:
get:
@@ -2300,6 +2392,19 @@ paths:
summary: Get an IATA's GeoJSON border, if configured
tags:
- IATAs
/info:
get:
description: minAppVersion is null when the server sets no requirement.
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.Info'
summary: Server info and minimum mobile app version
tags:
- Info
/messages:
get:
parameters:
@@ -2811,6 +2916,94 @@ paths:
summary: Get observer telemetry history
tags:
- Observers
/observers/directory:
get:
description: Defaults to the trailing seven days through request time. Counts
combine completed whole-hour analytics with retained observations for other
hours and exact boundary slices. Incomplete history still returns numeric
counts and the requested sort; coverage describes available history. Repeat
filters and the returned windowStart/windowEnd as since/until with the cursor
on later pages. Results may move as data changes; refresh without bounds for
a new rolling window.
parameters:
- description: Single IATA
in: query
name: iata
type: string
- description: Comma-separated IATAs
in: query
name: iatas
type: string
- description: Region ID
in: query
name: regionId
type: integer
- description: Region slug
in: query
name: region
type: string
- description: Observer type
in: query
name: type
type: string
- description: Broker membership
in: query
name: broker
type: string
- description: online or offline
in: query
name: status
type: string
- description: Case-insensitive partial name
in: query
name: name
type: string
- description: Transport scope membership
in: query
name: scope
type: string
- description: traffic (default) or name
in: query
name: sort
type: string
- description: Inclusive epoch ms, preserved exactly; default seven days before
until; maximum window 31 days
format: int64
in: query
name: since
type: integer
- description: Exclusive epoch ms, preserved exactly; default request time
format: int64
in: query
name: until
type: integer
- description: nextCursor returned by preceding page
format: int64
in: query
name: cursor
type: integer
- description: Page size, default 50, maximum 200
in: query
name: limit
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/github_com_MeshCore-Beacon_beacon-server_internal_api.ObserverDirectory'
"400":
description: Bad Request
schema:
$ref: '#/definitions/internal_api_handlers.APIError'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/internal_api_handlers.APIError'
summary: List observers with windowed traffic counts
tags:
- Observers
/packets:
get:
parameters:
@@ -4164,3 +4357,5 @@ tags:
name: Brokers
- description: Network statistics and time series
name: Stats
- description: Server info for the BEACON Mobile app
name: Info