feat(api): add knownNeighborCount to nodesummary and publicKey to node neighbor

This commit is contained in:
Enot (ded) Skelly committed 2026-06-11 13:47:58 -07:00
1 parent ef5257db66
commit ad8e410856
7 files changed
+103 -51

No files matched your search

+35 -19
View File
@@ -106,15 +106,16 @@ func (s *Store) ListNodes(ctx context.Context, nodeType int16, iatas []string, s
items := make([]api.NodeSummary, 0, len(rows))
for _, v := range rows {
node := api.NodeSummary{
ID: v.ID,
PublicKey: hex.EncodeToString(v.PublicKey),
NodeType: v.NodeType,
NodeTypeName: api.NodeTypeName(v.NodeType),
Name: v.Name,
Latitude: v.Latitude,
Longitude: v.Longitude,
IsObserver: v.IsObserver,
ObserverID: nullableUUID(v.ObserverID),
ID: v.ID,
PublicKey: hex.EncodeToString(v.PublicKey),
NodeType: v.NodeType,
NodeTypeName: api.NodeTypeName(v.NodeType),
Name: v.Name,
Latitude: v.Latitude,
Longitude: v.Longitude,
IsObserver: v.IsObserver,
ObserverID: nullableUUID(v.ObserverID),
KnownNeighborCount: v.KnownNeighborCount,
}
if len(v.Iatas) > 0 {
if err := json.Unmarshal(v.Iatas, &node.IATAs); err != nil {
@@ -147,16 +148,17 @@ func (s *Store) GetNode(ctx context.Context, nodeID uuid.UUID) (*api.Node, error
}
node := &api.Node{
NodeSummary: api.NodeSummary{
ID: row.ID,
PublicKey: hex.EncodeToString(row.PublicKey),
NodeType: row.NodeType,
NodeTypeName: api.NodeTypeName(row.NodeType),
Name: row.Name,
Latitude: row.Latitude,
Longitude: row.Longitude,
IsObserver: row.IsObserver,
ObserverID: nullableUUID(row.ObserverID),
DefaultScope: row.DefaultScopeName,
ID: row.ID,
PublicKey: hex.EncodeToString(row.PublicKey),
NodeType: row.NodeType,
NodeTypeName: api.NodeTypeName(row.NodeType),
Name: row.Name,
Latitude: row.Latitude,
Longitude: row.Longitude,
IsObserver: row.IsObserver,
ObserverID: nullableUUID(row.ObserverID),
DefaultScope: row.DefaultScopeName,
KnownNeighborCount: row.KnownNeighborCount,
},
LocationSource: row.LocationSource,
SupportsMultibytePaths: row.SupportsMultibytePaths,
@@ -212,11 +214,25 @@ func (s *Store) GetNodeNeighbors(ctx context.Context, nodeID uuid.UUID) ([]api.N
if err != nil {
return nil, err
}
seen := make(map[uuid.UUID]int)
items := make([]api.NodeNeighbor, 0, len(rows))
for _, r := range rows {
if idx, ok := seen[r.ID]; ok {
items[idx].ObservationCount += r.ObservationCount
if r.LastSeen.Time.After(time.UnixMilli(items[idx].LastSeen)) {
items[idx].LastSeen = r.LastSeen.Time.UnixMilli()
items[idx].IATA = r.Iata
}
if r.FirstSeen.Time.Before(time.UnixMilli(items[idx].FirstSeen)) {
items[idx].FirstSeen = r.FirstSeen.Time.UnixMilli()
}
continue
}
seen[r.ID] = len(items)
items = append(items, api.NodeNeighbor{
ID: r.ID,
Name: r.Name,
PublicKey: hex.EncodeToString(r.PublicKey),
NodeType: r.NodeType,
NodeTypeName: api.NodeTypeName(r.NodeType),
Latitude: r.Latitude,
+5 -3
View File
@@ -472,7 +472,8 @@ SELECT n.*, ts.name AS default_scope_name,
EXISTS (SELECT 1 FROM observers o WHERE o.public_key = n.public_key) AS is_observer,
(SELECT o.id FROM observers o WHERE o.public_key = n.public_key LIMIT 1) AS observer_id,
(SELECT json_agg(json_build_object('iata', ni.iata, 'lastHeard', (extract(epoch from ni.last_heard) * 1000)::bigint) ORDER BY ni.last_heard DESC)
FROM node_iatas ni WHERE ni.node_id = n.id) AS iatas
FROM node_iatas ni WHERE ni.node_id = n.id) AS iatas,
(SELECT COUNT(*) FROM node_neighbors nn WHERE nn.node_id = n.id)::bigint AS known_neighbor_count
FROM nodes n
LEFT JOIN transport_scopes ts ON ts.id = n.default_scope_id
WHERE n.id = $1;
@@ -488,7 +489,8 @@ SELECT n.id, n.public_key, n.node_type, n.name, n.latitude, n.longitude, n.last_
ts.name AS default_scope_name,
json_agg(json_build_object('iata', ni.iata, 'lastHeard', (extract(epoch from ni.last_heard) * 1000)::bigint) ORDER BY ni.last_heard DESC) FILTER (WHERE ni.iata IS NOT NULL) AS iatas,
EXISTS (SELECT 1 FROM observers o WHERE o.public_key = n.public_key) AS is_observer,
(SELECT o.id FROM observers o WHERE o.public_key = n.public_key LIMIT 1) AS observer_id
(SELECT o.id FROM observers o WHERE o.public_key = n.public_key LIMIT 1) AS observer_id,
(SELECT COUNT(*) FROM node_neighbors nn WHERE nn.node_id = n.id)::bigint AS known_neighbor_count
FROM nodes n
LEFT JOIN node_iatas ni ON ni.node_id = n.id
LEFT JOIN transport_scopes ts ON ts.id = n.default_scope_id
@@ -880,7 +882,7 @@ ON CONFLICT (node_id, neighbor_id, iata) DO UPDATE SET
-- name: GetNodeNeighbors :many
-- Returns the neighbors of a node with details, ordered by most recently seen.
SELECT
n.id, n.name, n.node_type, n.latitude, n.longitude,
n.id, n.public_key, n.name, n.node_type, n.latitude, n.longitude,
nn.iata, nn.observation_count, nn.first_seen, nn.last_seen
FROM node_neighbors nn
JOIN nodes n ON n.id = nn.neighbor_id
+25 -17
View File
@@ -216,7 +216,8 @@ SELECT n.id, n.public_key, n.node_type, n.name, n.latitude, n.longitude, n.locat
EXISTS (SELECT 1 FROM observers o WHERE o.public_key = n.public_key) AS is_observer,
(SELECT o.id FROM observers o WHERE o.public_key = n.public_key LIMIT 1) AS observer_id,
(SELECT json_agg(json_build_object('iata', ni.iata, 'lastHeard', (extract(epoch from ni.last_heard) * 1000)::bigint) ORDER BY ni.last_heard DESC)
FROM node_iatas ni WHERE ni.node_id = n.id) AS iatas
FROM node_iatas ni WHERE ni.node_id = n.id) AS iatas,
(SELECT COUNT(*) FROM node_neighbors nn WHERE nn.node_id = n.id)::bigint AS known_neighbor_count
FROM nodes n
LEFT JOIN transport_scopes ts ON ts.id = n.default_scope_id
WHERE n.id = $1
@@ -245,6 +246,7 @@ type GetNodeByIDRow struct {
IsObserver bool `json:"is_observer"`
ObserverID uuid.UUID `json:"observer_id"`
Iatas []byte `json:"iatas"`
KnownNeighborCount int64 `json:"known_neighbor_count"`
}
func (q *Queries) GetNodeByID(ctx context.Context, id uuid.UUID) (GetNodeByIDRow, error) {
@@ -273,13 +275,14 @@ func (q *Queries) GetNodeByID(ctx context.Context, id uuid.UUID) (GetNodeByIDRow
&i.IsObserver,
&i.ObserverID,
&i.Iatas,
&i.KnownNeighborCount,
)
return i, err
}
const getNodeNeighbors = `-- name: GetNodeNeighbors :many
SELECT
n.id, n.name, n.node_type, n.latitude, n.longitude,
n.id, n.public_key, n.name, n.node_type, n.latitude, n.longitude,
nn.iata, nn.observation_count, nn.first_seen, nn.last_seen
FROM node_neighbors nn
JOIN nodes n ON n.id = nn.neighbor_id
@@ -289,6 +292,7 @@ ORDER BY nn.last_seen DESC
type GetNodeNeighborsRow struct {
ID uuid.UUID `json:"id"`
PublicKey []byte `json:"public_key"`
Name *string `json:"name"`
NodeType int16 `json:"node_type"`
Latitude *float64 `json:"latitude"`
@@ -311,6 +315,7 @@ func (q *Queries) GetNodeNeighbors(ctx context.Context, nodeID uuid.UUID) ([]Get
var i GetNodeNeighborsRow
if err := rows.Scan(
&i.ID,
&i.PublicKey,
&i.Name,
&i.NodeType,
&i.Latitude,
@@ -2018,7 +2023,8 @@ SELECT n.id, n.public_key, n.node_type, n.name, n.latitude, n.longitude, n.last_
ts.name AS default_scope_name,
json_agg(json_build_object('iata', ni.iata, 'lastHeard', (extract(epoch from ni.last_heard) * 1000)::bigint) ORDER BY ni.last_heard DESC) FILTER (WHERE ni.iata IS NOT NULL) AS iatas,
EXISTS (SELECT 1 FROM observers o WHERE o.public_key = n.public_key) AS is_observer,
(SELECT o.id FROM observers o WHERE o.public_key = n.public_key LIMIT 1) AS observer_id
(SELECT o.id FROM observers o WHERE o.public_key = n.public_key LIMIT 1) AS observer_id,
(SELECT COUNT(*) FROM node_neighbors nn WHERE nn.node_id = n.id)::bigint AS known_neighbor_count
FROM nodes n
LEFT JOIN node_iatas ni ON ni.node_id = n.id
LEFT JOIN transport_scopes ts ON ts.id = n.default_scope_id
@@ -2057,20 +2063,21 @@ type ListNodesParams struct {
}
type ListNodesRow struct {
ID uuid.UUID `json:"id"`
PublicKey []byte `json:"public_key"`
NodeType int16 `json:"node_type"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
LastSeen pgtype.Timestamptz `json:"last_seen"`
RadioFreqMhz *float32 `json:"radio_freq_mhz"`
RadioSf *int16 `json:"radio_sf"`
RadioBwKhz *float32 `json:"radio_bw_khz"`
DefaultScopeName *string `json:"default_scope_name"`
Iatas []byte `json:"iatas"`
IsObserver bool `json:"is_observer"`
ObserverID uuid.UUID `json:"observer_id"`
ID uuid.UUID `json:"id"`
PublicKey []byte `json:"public_key"`
NodeType int16 `json:"node_type"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
LastSeen pgtype.Timestamptz `json:"last_seen"`
RadioFreqMhz *float32 `json:"radio_freq_mhz"`
RadioSf *int16 `json:"radio_sf"`
RadioBwKhz *float32 `json:"radio_bw_khz"`
DefaultScopeName *string `json:"default_scope_name"`
Iatas []byte `json:"iatas"`
IsObserver bool `json:"is_observer"`
ObserverID uuid.UUID `json:"observer_id"`
KnownNeighborCount int64 `json:"known_neighbor_count"`
}
func (q *Queries) ListNodes(ctx context.Context, arg ListNodesParams) ([]ListNodesRow, error) {
@@ -2107,6 +2114,7 @@ func (q *Queries) ListNodes(ctx context.Context, arg ListNodesParams) ([]ListNod
&i.Iatas,
&i.IsObserver,
&i.ObserverID,
&i.KnownNeighborCount,
); err != nil {
return nil, err
}
+9
View File
@@ -2302,6 +2302,9 @@ const docTemplate = `{
"description": "true if this node is also a known observer",
"type": "boolean"
},
"knownNeighborCount": {
"type": "integer"
},
"lastAdvertAt": {
"description": "epoch ms, nil if no advert received",
"type": "integer"
@@ -2413,6 +2416,9 @@ const docTemplate = `{
},
"observationCount": {
"type": "integer"
},
"publicKey": {
"type": "string"
}
}
},
@@ -2437,6 +2443,9 @@ const docTemplate = `{
"description": "true if this node is also a known observer",
"type": "boolean"
},
"knownNeighborCount": {
"type": "integer"
},
"lat": {
"description": "decimal degrees, from advert AppData",
"type": "number"
+9
View File
@@ -2300,6 +2300,9 @@
"description": "true if this node is also a known observer",
"type": "boolean"
},
"knownNeighborCount": {
"type": "integer"
},
"lastAdvertAt": {
"description": "epoch ms, nil if no advert received",
"type": "integer"
@@ -2411,6 +2414,9 @@
},
"observationCount": {
"type": "integer"
},
"publicKey": {
"type": "string"
}
}
},
@@ -2435,6 +2441,9 @@
"description": "true if this node is also a known observer",
"type": "boolean"
},
"knownNeighborCount": {
"type": "integer"
},
"lat": {
"description": "decimal degrees, from advert AppData",
"type": "number"
+6
View File
@@ -188,6 +188,8 @@ definitions:
isObserver:
description: true if this node is also a known observer
type: boolean
knownNeighborCount:
type: integer
lastAdvertAt:
description: epoch ms, nil if no advert received
type: integer
@@ -267,6 +269,8 @@ definitions:
type: string
observationCount:
type: integer
publicKey:
type: string
type: object
github_com_MeshCore-Beacon_beacon-server_internal_api.NodeSummary:
properties:
@@ -283,6 +287,8 @@ definitions:
isObserver:
description: true if this node is also a known observer
type: boolean
knownNeighborCount:
type: integer
lat:
description: decimal degrees, from advert AppData
type: number
+14 -12
View File
@@ -14,6 +14,7 @@ import (
type NodeNeighbor struct {
ID uuid.UUID `json:"id"`
Name *string `json:"name,omitempty"`
PublicKey string `json:"publicKey"`
NodeType int16 `json:"nodeType"`
NodeTypeName string `json:"nodeTypeName"`
Latitude *float64 `json:"lat,omitempty"`
@@ -32,18 +33,19 @@ type NodeIATA struct {
// NodeSummary is the minimal node representation used in list responses.
type NodeSummary struct {
ID uuid.UUID `json:"id"`
PublicKey string `json:"publicKey"` // hex-encoded Ed25519 public key
NodeType int16 `json:"nodeType"` // 1=companion, 2=repeater, 3=room_server, 4=sensor
NodeTypeName string `json:"nodeTypeName"`
Name *string `json:"name,omitempty"`
IsObserver bool `json:"isObserver"` // true if this node is also a known observer
ObserverID *uuid.UUID `json:"observerId,omitempty"` // UUID of the associated observer row, if any
Latitude *float64 `json:"lat,omitempty"` // decimal degrees, from advert AppData
Longitude *float64 `json:"lng,omitempty"` // decimal degrees, from advert AppData
Radio *string `json:"radio,omitempty"` // shorthand: "freqMhz,bwKhz,sf" e.g. "910.5,62.5,7"
IATAs []NodeIATA `json:"iatas"` // IATAs where this node has been heard, with last heard timestamps
DefaultScope *string `json:"defaultScope,omitempty"` // most recently matched transport scope name e.g. "#bc"
ID uuid.UUID `json:"id"`
PublicKey string `json:"publicKey"` // hex-encoded Ed25519 public key
NodeType int16 `json:"nodeType"` // 1=companion, 2=repeater, 3=room_server, 4=sensor
NodeTypeName string `json:"nodeTypeName"`
Name *string `json:"name,omitempty"`
IsObserver bool `json:"isObserver"` // true if this node is also a known observer
ObserverID *uuid.UUID `json:"observerId,omitempty"` // UUID of the associated observer row, if any
Latitude *float64 `json:"lat,omitempty"` // decimal degrees, from advert AppData
Longitude *float64 `json:"lng,omitempty"` // decimal degrees, from advert AppData
Radio *string `json:"radio,omitempty"` // shorthand: "freqMhz,bwKhz,sf" e.g. "910.5,62.5,7"
IATAs []NodeIATA `json:"iatas"` // IATAs where this node has been heard, with last heard timestamps
DefaultScope *string `json:"defaultScope,omitempty"` // most recently matched transport scope name e.g. "#bc"
KnownNeighborCount int64 `json:"knownNeighborCount"`
}
// Node is the full node representation including firmware capability flags,