Files
beacon-server/internal/api/nodes.go
T
Enot (ded) Skelly c86bad3875 chore: typo in refresh view comment and add note about node type func
NodeTypeName is fine as is with possible concatenation
of the value as there is unlikely to be more than 256
node types
2026-06-12 08:49:41 -07:00

99 lines
4.3 KiB
Go

// Copyright 2026 Beacon Contributors
// SPDX-License-Identifier: AGPL-3.0-or-later
package api
import (
"strings"
"github.com/google/uuid"
"github.com/meshcore-go/meshcore-go"
)
// NodeNeighbor represents a neighboring node relationship observed in a given IATA.
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"`
Longitude *float64 `json:"lng,omitempty"`
IATA string `json:"iata"`
ObservationCount int64 `json:"observationCount"`
FirstSeen int64 `json:"firstSeen"` // epoch ms
LastSeen int64 `json:"lastSeen"` // epoch ms
}
// NodeIATA represents a single IATA code and the last time the node was heard there.
type NodeIATA struct {
IATA string `json:"iata"`
LastHeard int64 `json:"lastHeard"` // epoch ms
}
// 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"
KnownNeighborCount int64 `json:"knownNeighborCount"`
}
// Node is the full node representation including firmware capability flags,
// location source, and timing metadata.
type Node struct {
NodeSummary
LocationSource *string `json:"locationSource,omitempty"` // "advert" or "manual"
LastAdvertAt *int64 `json:"lastAdvertAt,omitempty"` // epoch ms, nil if no advert received
SupportsMultibytePaths bool `json:"supportsMultibytePaths"` // firmware >= 1.14.0; detected via path hash size
SupportsMultibyteTraces bool `json:"supportsMultibyteTraces"` // firmware >= 1.11.0; detected via trace hash size
MinFirmwareVersion *string `json:"minFirmwareVersion,omitempty"` // derived from capability flags
FirstSeen int64 `json:"firstSeen"` // epoch ms
LastSeen int64 `json:"lastSeen"` // epoch ms
Metadata any `json:"metadata,omitempty"` // raw JSONB metadata
Neighbors []NodeNeighbor `json:"neighbors"`
}
// NodeTypeName returns a human-readable name for a node type integer.
// NOTE: truncation is fine here until there are at least over 200 types of node
func NodeTypeName(t int16) string {
switch byte(t) {
case meshcore.AdvertTypeChat:
return "companion"
case meshcore.AdvertTypeRepeater:
return "repeater"
case meshcore.AdvertTypeRoom:
return "room_server"
case meshcore.AdvertTypeSensor:
return "sensor"
default:
return "unknown"
}
}
// NodeTypeFromString returns the integer node type for a given name.
// Returns 0 (no filter) if the string is empty or unrecognized.
func NodeTypeFromString(s string) int16 {
switch strings.ToLower(s) {
case "companion", "chat":
return int16(meshcore.AdvertTypeChat)
case "repeater":
return int16(meshcore.AdvertTypeRepeater)
case "room_server", "roomserver", "room-server", "room":
return int16(meshcore.AdvertTypeRoom)
case "sensor":
return int16(meshcore.AdvertTypeSensor)
default:
return 0
}
}