mirror of
https://github.com/MeshCore-Beacon/beacon-server.git
synced 2026-10-15 20:29:54 +00:00
GET /traces takes an optional cursorTag so tags sharing a millisecond survive paging. Migrations run under an advisory lock and record each file in the same implicit transaction, so a crash can't strand a fresh database. Overview windowHours counts the hours it covers, hourly observer activity fills unrolled hours from raw rows, and integration fixtures are UTC.
248 lines
15 KiB
Go
248 lines
15 KiB
Go
// Copyright 2026 Beacon Contributors
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
// Package api defines the response types and read interface for the Beacon REST API.
|
|
package api
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
// Page is a generic paginated response envelope used by all list endpoints
|
|
// that support cursor-based pagination. NextCursor is the ID of the last item
|
|
// returned and should be passed as the cursor param in the next request.
|
|
// HasMore is true when additional results exist beyond the current page.
|
|
type Page[T any] struct {
|
|
Items []T `json:"items"`
|
|
NextCursor *int64 `json:"nextCursor,omitempty"`
|
|
HasMore bool `json:"hasMore"`
|
|
}
|
|
|
|
type Reader interface {
|
|
// GetRouteEvidence returns the full saved route and exact-prefix retained ordinary reports.
|
|
GetRouteEvidence(ctx context.Context, iata, pathKey string, query RouteEvidenceQuery) (*RouteEvidence, error)
|
|
// ListIATAs returns all known IATA codes with display name and coordinates.
|
|
// IATAs are auto-created on first packet arrival from that location.
|
|
ListIATAs(ctx context.Context) ([]IATA, error)
|
|
|
|
// GetIATA returns a single IATA code by its 3-letter identifier.
|
|
// Returns nil, error if the IATA code is not found.
|
|
GetIATA(ctx context.Context, iata string) (*IATA, error)
|
|
|
|
// GetIATABorder returns the raw GeoJSON Feature JSON for the given IATA's border. Returns
|
|
// nil (no error) if the IATA exists but has no border configured -- the client renders
|
|
// that as "nothing to draw", not an error. Returns an error if the IATA itself is unknown.
|
|
GetIATABorder(ctx context.Context, iata string) (json.RawMessage, error)
|
|
|
|
// ListRegions returns a summary list of all regions ordered by display_order then name.
|
|
// Use GetRegion for full detail including associated IATAs.
|
|
ListRegions(ctx context.Context) ([]RegionSummary, error)
|
|
|
|
// GetRegion returns full detail for a single region including its associated IATA codes.
|
|
// Returns nil, pgx.ErrNoRows if the region is not found.
|
|
GetRegion(ctx context.Context, regionID int32) (*Region, error)
|
|
|
|
// GetRegionBySlug returns full detail for a single region by its URL-safe slug.
|
|
// Returns nil, pgx.ErrNoRows if the region is not found.
|
|
GetRegionBySlug(ctx context.Context, slug string) (*Region, error)
|
|
|
|
// ListChannels returns a paginated list of channels ordered by last seen.
|
|
// Includes both hashtag-derived and explicit key channels.
|
|
// Pass nil hash to skip hash filtering. Pass empty iatas to return all channels;
|
|
// IATAs must be uppercase. Nil keyKnown skips the decryption-key filter.
|
|
// cursor is last_seen epoch ms of the last item; pass 0 to start from the beginning.
|
|
// pageCursor is the precise timestamp/ID boundary; nil preserves the legacy numeric cursor.
|
|
// When non-nil, pageCursor takes precedence over cursor.
|
|
ListChannels(ctx context.Context, limit int32, hash []byte, iatas []string, keyKnown *bool, cursor int64, pageCursor *ChannelCursor) (ChannelPage, error)
|
|
|
|
// GetChannel returns full detail for a single channel by its integer ID.
|
|
// Returns nil, pgx.ErrNoRows if the channel is not found.
|
|
GetChannel(ctx context.Context, channelID int32) (*Channel, error)
|
|
|
|
// ListChannelMessages returns paginated messages for a channel identified by its integer ID.
|
|
// Used by the /channels/{id}/messages endpoint.
|
|
// Pass a zero time.Time for since to return all messages up to limit.
|
|
// Pass empty string iata to return messages from all IATAs.
|
|
// Pass cursor=0 to start from the beginning.
|
|
ListChannelMessages(ctx context.Context, channelID *int32, since time.Time, limit int32, iatas []string, scope string, cursor int64) (Page[ChannelMessage], error)
|
|
|
|
// ListChannelMessagesByHash returns paginated messages for all channels matching the given hash.
|
|
// Used by the /messages?hash= endpoint. May return messages from multiple channels
|
|
// if the hash collides across different keys.
|
|
// Pass a zero time.Time for since to return all messages up to limit.
|
|
// Pass empty string iata to return messages from all IATAs.
|
|
// Pass cursor=0 to start from the beginning.
|
|
ListChannelMessagesByHash(ctx context.Context, hash []byte, since time.Time, limit int32, iatas []string, scope string, cursor int64) (Page[ChannelMessage], error)
|
|
|
|
// ListMessagesAfterID returns channel messages after the given message ID,
|
|
// ordered oldest first. Used for WS reconnect backfill.
|
|
ListMessagesAfterID(ctx context.Context, afterID int64, iatas []string, scope string, limit int32) ([]ChannelMessage, error)
|
|
|
|
// ListObservers returns a paginated list of observers with optional filters.
|
|
// All filter params are optional — pass empty string or nil to skip a filter.
|
|
// status is "online" or "offline" derived from last_status_at recency.
|
|
// cursor is last_seen epoch ms of the last observer; pass 0 to start from the beginning.
|
|
ListObservers(ctx context.Context, iatas []string, observerType, broker, status, name, scope string, cursor int64, limit int32) (Page[ObserverSummary], error)
|
|
|
|
// GetObserver returns full detail for a single observer by UUID.
|
|
// Returns nil, pgx.ErrNoRows if the observer is not found.
|
|
GetObserver(ctx context.Context, observerID uuid.UUID) (*Observer, error)
|
|
|
|
// GetObserverTelemetry returns telemetry points for an observer within the given time range.
|
|
// since and until define the window; pass zero times to use defaults (last 24h).
|
|
GetObserverTelemetry(ctx context.Context, observerID uuid.UUID, since, until time.Time, afterID int64) (*ObserverTelemetry, error)
|
|
|
|
// GetObserverTelemetryBucketed returns telemetry points for an observer bucketed into N-hour intervals.
|
|
GetObserverTelemetryBucketed(ctx context.Context, observerID uuid.UUID, since, until time.Time, bucketHours int32) ([]ObserverTelemetryPoint, error)
|
|
|
|
// GetObserverActivity returns bucketed heard-activity for an observer over the trailing window.
|
|
// interval >= 1h is served from the hourly rollup. Returns pgx.ErrNoRows for an unknown observer.
|
|
// Range and Interval on the result are left empty for the handler to fill.
|
|
GetObserverActivity(ctx context.Context, observerID uuid.UUID, window, interval time.Duration, until time.Time) (*ObserverActivity, error)
|
|
|
|
// GetObserverScopes returns the names of all transport scopes an observer has
|
|
// been seen forwarding packets for, ordered alphabetically.
|
|
GetObserverScopes(ctx context.Context, observerID uuid.UUID) ([]string, error)
|
|
|
|
// ListObserverAdverts returns a paginated list of advert packets heard by an observer.
|
|
// Pass cursor=0 to start from the beginning.
|
|
ListObserverAdverts(ctx context.Context, observerID uuid.UUID, cursor int64, limit int32) (Page[AdvertObservation], error)
|
|
|
|
// ListNodes returns a paginated list of nodes with optional filters.
|
|
// When includeNeighbors is true, each NodeSummary's NeighborIDs field is
|
|
// populated with the distinct set of neighbor node IDs (across all
|
|
// IATAs); otherwise it's left nil to avoid the extra aggregation.
|
|
ListNodes(ctx context.Context, nodeType int16, iatas []string, supportsMultibytePaths, supportsMultibyteTraces *bool, pubkey []byte, pubkeyPrefix, name, scope string, cursor int64, limit int32, includeNeighbors bool) (Page[NodeSummary], error)
|
|
|
|
// GetNode returns full detail for a single node by UUID.
|
|
// Returns nil, pgx.ErrNoRows if the node is not found.
|
|
GetNode(ctx context.Context, nodeID uuid.UUID) (*Node, error)
|
|
|
|
// GetNodesByIDs returns a map of node ID to resolved node details for the given IDs.
|
|
GetNodesByIDs(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]*ResolvedNode, error)
|
|
|
|
// ListNodeObservations returns a paginated list of packet observations originating from a node.
|
|
// Pass cursor=0 to start from the beginning.
|
|
ListNodeObservations(ctx context.Context, nodeID uuid.UUID, cursor int64, limit int32) (Page[PacketObservationSummary], error)
|
|
|
|
// ListPackets returns a paginated list of packets with the latest observation rolled in.
|
|
// Pass 0 for payloadType/routeType to skip those filters.
|
|
// Pass nil for iatas, zero times for since/until to skip those filters.
|
|
// Unfiltered lists order by global last_heard_at; when iatas are set, ordering
|
|
// and the cursor are site-local (heard_at at the requested sites) instead.
|
|
// cursor is epoch ms in either case; pass 0 to start from the beginning.
|
|
// payloadTypes/routeTypes/scopes are OR'd within each filter, ANDed across filters; pass
|
|
// nil/empty to skip a filter.
|
|
ListPackets(ctx context.Context, payloadTypes, routeTypes []int16, iatas []string, scopes []string, since, until time.Time, cursor int64, limit int32) (Page[PacketSummary], error)
|
|
|
|
// ListPacketsAfterID returns packets with observations after the given observation ID,
|
|
// ordered oldest first. Used for WS reconnect backfill.
|
|
ListPacketsAfterID(ctx context.Context, afterObservationID int64, payloadType, routeType int16, iatas []string, scope string, limit int32) ([]PacketSummary, error)
|
|
|
|
// GetPacket returns full packet detail including all observations with radio settings.
|
|
// Returns nil, pgx.ErrNoRows if not found.
|
|
GetPacket(ctx context.Context, packetHash []byte) (*Packet, error)
|
|
// GetRadioPresets returns radio preset usage grouped by preset and IATA(s).
|
|
// Pass empty string for preset or nil iatas to skip those filters.
|
|
GetRadioPresets(ctx context.Context, preset string, iatas []string) ([]RadioPreset, error)
|
|
|
|
// GetObserverComparison compares distinct flood packets reported by two observers
|
|
// during [since, until), optionally restricted to reception IATAs.
|
|
GetObserverComparison(ctx context.Context, observerA, observerB uuid.UUID, since, until time.Time, iatas []string) (*ObserverComparison, error)
|
|
|
|
// GetStatsOverview returns top-line network figures for the last 24 hours.
|
|
// Pass nil iatas to return stats across all IATAs.
|
|
GetStatsOverview(ctx context.Context, iatas []string) (*StatsOverview, error)
|
|
|
|
// GetStatsObservations returns hourly observation counts for charting.
|
|
// Pass nil for iatas to return stats across all IATAs.
|
|
// since defines the start of the window; pass zero time for default (last 7 days).
|
|
GetStatsObservations(ctx context.Context, iatas []string, since time.Time) ([]ObservationPoint, error)
|
|
|
|
// GetStatsSeries returns hourly rollup metrics for [since, until), both on UTC hours.
|
|
GetStatsSeries(ctx context.Context, since, until time.Time, iatas []string) (*StatsSeries, error)
|
|
|
|
// GetSignalStats aggregates retained reception readings in [since, until).
|
|
GetSignalStats(ctx context.Context, since, until time.Time, iatas []string) (*SignalStats, error)
|
|
|
|
// GetPathStats counts received path entries and validated ordinary hash widths.
|
|
GetPathStats(ctx context.Context, since, until time.Time, iatas []string) (*PathStats, error)
|
|
|
|
// GetStatsPayloadBreakdown returns observation counts grouped by payload type.
|
|
// Pass nil for iatas to return stats across all IATAs.
|
|
// since defines the start of the window; pass zero time for default (last 24h).
|
|
GetStatsPayloadBreakdown(ctx context.Context, iatas []string, since time.Time) ([]PayloadBreakdownItem, error)
|
|
|
|
// GetStatsTopNodes returns the top N nodes by observation count.
|
|
// Pass nil for iatas to return stats across all IATAs.
|
|
GetStatsTopNodes(ctx context.Context, iatas []string, since time.Time, limit int32) ([]TopNode, error)
|
|
|
|
// GetStatsTopObservers returns the top N observers by observation count.
|
|
// Pass nil for iatas to return stats across all IATAs.
|
|
// since defines the start of the window; pass zero time for default (last 24h).
|
|
GetStatsTopObservers(ctx context.Context, iatas []string, since time.Time, limit int32) ([]TopObserver, error)
|
|
|
|
// GetStatsTopAdvertisers returns the top N nodes by distinct ADVERT packet count.
|
|
// Pass nil for iatas to return stats across all IATAs.
|
|
// since defines the start of the window; pass zero time for default (last 24h).
|
|
GetStatsTopAdvertisers(ctx context.Context, iatas []string, since time.Time, limit int32) ([]TopAdvertiser, error)
|
|
|
|
// GetStatsClockDrift returns repeaters/room servers whose most recent advert-derived
|
|
// clock drift exceeds the configured threshold (nodes.clock_drift_threshold), worst
|
|
// drift first. Pass nil for iatas to return across all IATAs. Unlike the other stats
|
|
// endpoints this isn't time-windowed -- it reflects each node's current drift state.
|
|
GetStatsClockDrift(ctx context.Context, iatas []string, limit int32) ([]ClockDriftEntry, error)
|
|
|
|
// GetStatsTopTalkers returns the top N companion names by decrypted channel message count.
|
|
// Pass nil for iatas to return stats across all IATAs.
|
|
// since defines the start of the window; pass zero time for default (last 24h).
|
|
GetStatsTopTalkers(ctx context.Context, iatas []string, since time.Time, limit int32) ([]TopTalker, error)
|
|
|
|
// GetScopeStats returns aggregate packet, observer and node counts per transport scope.
|
|
// Pass empty iatas for global totals. IATAs must be uppercase.
|
|
GetScopeStats(ctx context.Context, iatas []string, since time.Time) ([]ScopeStats, error)
|
|
|
|
// GetStatsNodeTypes returns node counts grouped by type, optionally filtered by IATA.
|
|
GetStatsNodeTypes(ctx context.Context, iatas []string) ([]NodeTypeCount, error)
|
|
|
|
// GetScopeNames returns the names of all configured transport scopes, ordered alphabetically.
|
|
// Use when no geographic filter is applied — returns names only for a lightweight response.
|
|
GetScopeNames(ctx context.Context) ([]string, error)
|
|
|
|
// GetScopeByName returns full detail for a single scope by its normalized name (e.g. "#bc"),
|
|
// including packet count, observer count, node count, and the list of IATAs it is active in.
|
|
// Returns nil, pgx.ErrNoRows if the scope is not found.
|
|
GetScopeByName(ctx context.Context, name string) (*ScopeDetail, error)
|
|
|
|
// ListTraceTags returns trace tags newest first, keyset-paged on (cursor ms, cursorTag hex).
|
|
ListTraceTags(ctx context.Context, iatas []string, scope, traceType string, since, until time.Time, cursor time.Time, cursorTag string, limit int32) ([]TraceTagSummary, error)
|
|
|
|
// GetTraceByTag returns all packets for a given trace tag with resolved routes.
|
|
GetTraceByTag(ctx context.Context, tag string) (*TraceDetail, error)
|
|
|
|
// ListKnownRoutes returns known routes filtered by IATA and optional hop count.
|
|
ListKnownRoutes(ctx context.Context, iata string, hopCount int32, cursor time.Time, limit int32) ([]KnownRoute, error)
|
|
|
|
// SearchKnownRoutes returns known routes containing a path from source to destination hash.
|
|
SearchKnownRoutes(ctx context.Context, iata, fromHash, toHash string) ([]KnownRoute, error)
|
|
|
|
// GetNodeNeighbors returns the neighbors of a node ordered by most recently seen.
|
|
GetNodeNeighbors(ctx context.Context, nodeID uuid.UUID) ([]NodeNeighbor, error)
|
|
|
|
// GetKnownRoutesByNode returns all known routes in a given IATA that contain
|
|
// the specified node UUID anywhere in their hop sequence.
|
|
GetKnownRoutesByNode(ctx context.Context, iata string, nodeID uuid.UUID) ([]KnownRoute, error)
|
|
|
|
// GetCrossIATANeighbors returns neighbors of a node that were observed in a
|
|
// different IATA — indicating a potential cross-IATA radio link.
|
|
GetCrossIATANeighbors(ctx context.Context, nodeID uuid.UUID, iata string) ([]NodeNeighbor, error)
|
|
|
|
// SearchCrossIATARoutes finds routes that cross IATA boundaries between
|
|
// a source node/IATA and a destination node/IATA.
|
|
SearchCrossIATARoutes(ctx context.Context, fromHash, fromIATA, toHash, toIATA string) ([]CrossIATARoute, error)
|
|
}
|