feat(routes): add resolved hop nodes to known routes API

closes #44
This commit is contained in:
Enot (ded) Skelly
2026-06-08 11:30:26 -07:00
parent 68c254ce04
commit 149452f066
5 changed files with 101 additions and 5 deletions
+18
View File
@@ -186,6 +186,24 @@ func (s *Store) GetNode(ctx context.Context, nodeID uuid.UUID) (*api.Node, error
return node, nil
}
func (s *Store) GetNodesByIDs(ctx context.Context, ids []uuid.UUID) (map[uuid.UUID]*api.ResolvedNode, error) {
rows, err := s.q.GetNodesByIDs(ctx, ids)
if err != nil {
return nil, err
}
result := make(map[uuid.UUID]*api.ResolvedNode, len(rows))
for _, r := range rows {
result[r.ID] = &api.ResolvedNode{
ID: r.ID,
Name: r.Name,
PublicKey: hex.EncodeToString(r.PublicKey),
Latitude: r.Latitude,
Longitude: r.Longitude,
}
}
return result, nil
}
func (s *Store) GetNodeNeighbors(ctx context.Context, nodeID uuid.UUID) ([]api.NodeNeighbor, error) {
rows, err := s.q.GetNodeNeighbors(ctx, nodeID)
if err != nil {
+4
View File
@@ -473,6 +473,10 @@ FROM nodes n
LEFT JOIN transport_scopes ts ON ts.id = n.default_scope_id
WHERE n.id = $1;
-- name: GetNodesByIDs :many
SELECT id, public_key, name, latitude, longitude
FROM nodes
WHERE id = ANY($1::uuid[]);
-- name: ListNodes :many
SELECT n.id, n.public_key, n.node_type, n.name, n.latitude, n.longitude, n.last_seen,
+37 -5
View File
@@ -34,7 +34,12 @@ func (s *Store) ListKnownRoutes(ctx context.Context, iata string, hopCount int32
if err != nil {
return nil, err
}
return toKnownRoutes(rows), nil
ids := collectNodeIDs(rows)
nodes, err := s.GetNodesByIDs(ctx, ids)
if err != nil {
return nil, err
}
return toKnownRoutes(rows, nodes), nil
}
func (s *Store) SearchKnownRoutes(ctx context.Context, iata, fromHash, toHash string) ([]api.KnownRoute, error) {
@@ -54,9 +59,13 @@ func (s *Store) SearchKnownRoutes(ctx context.Context, iata, fromHash, toHash st
if err != nil {
return nil, err
}
ids := collectNodeIDs(rows)
nodes, err := s.GetNodesByIDs(ctx, ids)
if err != nil {
return nil, err
}
items := make([]api.KnownRoute, 0, len(rows))
for _, r := range rows {
// find positions and slice to the subsequence
fromPos, toPos := -1, -1
for i, h := range r.HashPrefix {
if fromPos == -1 && hex.EncodeToString(h) == fromHash {
@@ -74,7 +83,10 @@ func (s *Store) SearchKnownRoutes(ctx context.Context, iata, fromHash, toHash st
hashPrefix := r.HashPrefix[fromPos : toPos+1]
hops := make([]api.RouteHop, 0, len(nodeIDs))
for i, nodeID := range nodeIDs {
hop := api.RouteHop{NodeID: nodeID}
hop := api.RouteHop{
NodeID: nodeID,
Node: nodes[nodeID],
}
if i < len(hashPrefix) {
hop.HashBytes = hex.EncodeToString(hashPrefix[i])
}
@@ -101,7 +113,12 @@ func (s *Store) GetKnownRoutesByNode(ctx context.Context, iata string, nodeID uu
if err != nil {
return nil, err
}
return toKnownRoutes(rows), nil
ids := collectNodeIDs(rows)
nodes, err := s.GetNodesByIDs(ctx, ids)
if err != nil {
return nil, err
}
return toKnownRoutes(rows, nodes), nil
}
func (s *Store) GetCrossIATANeighbors(ctx context.Context, nodeID uuid.UUID, iata string) ([]api.NodeNeighbor, error) {
@@ -249,13 +266,14 @@ func extractFromNode(hops []api.RouteHop, nodeID uuid.UUID) []api.RouteHop {
return hops
}
func toKnownRoutes(rows []sqlc.KnownRoute) []api.KnownRoute {
func toKnownRoutes(rows []sqlc.KnownRoute, nodes map[uuid.UUID]*api.ResolvedNode) []api.KnownRoute {
items := make([]api.KnownRoute, 0, len(rows))
for _, r := range rows {
hops := make([]api.RouteHop, 0, len(r.NodeIds))
for i, nodeID := range r.NodeIds {
hop := api.RouteHop{
NodeID: nodeID,
Node: nodes[nodeID],
}
if i < len(r.HashPrefix) {
hop.HashBytes = hex.EncodeToString(r.HashPrefix[i])
@@ -274,3 +292,17 @@ func toKnownRoutes(rows []sqlc.KnownRoute) []api.KnownRoute {
}
return items
}
func collectNodeIDs(rows []sqlc.KnownRoute) []uuid.UUID {
seen := make(map[uuid.UUID]struct{})
var ids []uuid.UUID
for _, r := range rows {
for _, id := range r.NodeIds {
if _, ok := seen[id]; !ok {
seen[id] = struct{}{}
ids = append(ids, id)
}
}
}
return ids
}
+40
View File
@@ -513,6 +513,46 @@ func (q *Queries) GetNodeNeighbors(ctx context.Context, nodeID uuid.UUID) ([]Get
return items, nil
}
const getNodesByIDs = `-- name: GetNodesByIDs :many
SELECT id, public_key, name, latitude, longitude
FROM nodes
WHERE id = ANY($1::uuid[])
`
type GetNodesByIDsRow struct {
ID uuid.UUID `json:"id"`
PublicKey []byte `json:"public_key"`
Name *string `json:"name"`
Latitude *float64 `json:"latitude"`
Longitude *float64 `json:"longitude"`
}
func (q *Queries) GetNodesByIDs(ctx context.Context, dollar_1 []uuid.UUID) ([]GetNodesByIDsRow, error) {
rows, err := q.db.Query(ctx, getNodesByIDs, dollar_1)
if err != nil {
return nil, err
}
defer rows.Close()
items := []GetNodesByIDsRow{}
for rows.Next() {
var i GetNodesByIDsRow
if err := rows.Scan(
&i.ID,
&i.PublicKey,
&i.Name,
&i.Latitude,
&i.Longitude,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const getObserverBrokers = `-- name: GetObserverBrokers :many
SELECT broker_name, last_seen, last_packet_at
FROM observer_brokers
+2
View File
@@ -86,6 +86,8 @@ type Reader interface {
// 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)