Checkpoint
This commit is contained in:
331
server/handlers_meshcore.go
Normal file
331
server/handlers_meshcore.go
Normal file
@@ -0,0 +1,331 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"git.maze.io/go/ham/protocol/meshcore"
|
||||
"git.maze.io/ham/hamview/schema"
|
||||
)
|
||||
|
||||
// handleGetMeshCore returns aggregated statistics for the MeshCore network.
|
||||
//
|
||||
// Endpoint: GET /api/v1/meshcore
|
||||
//
|
||||
// Response: 200 OK
|
||||
//
|
||||
// MeshCoreStats - Network statistics including message counts and packet timeline
|
||||
//
|
||||
// Response: 500 Internal Server Error
|
||||
//
|
||||
// ErrorResponse - Error retrieving statistics
|
||||
//
|
||||
// Example Request:
|
||||
//
|
||||
// GET /api/v1/meshcore
|
||||
//
|
||||
// Example Response:
|
||||
//
|
||||
// {
|
||||
// "messages": 150234,
|
||||
// "nodes": 127,
|
||||
// "receivers": 8,
|
||||
// "packets": {
|
||||
// "timestamps": [1709650800, 1709654400, 1709658000],
|
||||
// "packets": [142, 203, 178]
|
||||
// }
|
||||
// }
|
||||
func (s *Server) handleGetMeshCore(c echo.Context) error {
|
||||
stats, err := schema.GetMeshCoreStats(c.Request().Context())
|
||||
if err != nil {
|
||||
return s.apiError(c, err)
|
||||
}
|
||||
return c.JSON(http.StatusOK, stats)
|
||||
}
|
||||
|
||||
// handleGetMeshCoreGroups returns a list of public MeshCore groups/channels.
|
||||
//
|
||||
// Endpoint: GET /api/v1/meshcore/groups
|
||||
//
|
||||
// Response: 200 OK
|
||||
//
|
||||
// []MeshCoreGroup - Array of public group objects
|
||||
//
|
||||
// Response: 500 Internal Server Error
|
||||
//
|
||||
// ErrorResponse - Error retrieving groups
|
||||
//
|
||||
// Example Request:
|
||||
//
|
||||
// GET /api/v1/meshcore/groups
|
||||
//
|
||||
// Example Response:
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// "id": 5,
|
||||
// "name": "General Chat",
|
||||
// "secret": "0123456789abcdef0123456789abcdef"
|
||||
// },
|
||||
// {
|
||||
// "id": 7,
|
||||
// "name": "Emergency",
|
||||
// "secret": "fedcba9876543210fedcba9876543210"
|
||||
// }
|
||||
// ]
|
||||
func (s *Server) handleGetMeshCoreGroups(c echo.Context) error {
|
||||
groups, err := schema.GetMeshCoreGroups(c.Request().Context())
|
||||
if err != nil {
|
||||
return s.apiError(c, err)
|
||||
}
|
||||
return c.JSON(http.StatusOK, groups)
|
||||
}
|
||||
|
||||
// handleGetMeshCoreNodes returns a list of MeshCore network nodes.
|
||||
//
|
||||
// Endpoint: GET /api/v1/meshcore/nodes
|
||||
//
|
||||
// Query Parameters:
|
||||
// - type (optional): Filter by node type
|
||||
// - "chat" - Chat nodes
|
||||
// - "room" - Room nodes
|
||||
// - "sensor" - Sensor nodes
|
||||
// - "repeater" - Repeater nodes (default)
|
||||
//
|
||||
// Response: 200 OK
|
||||
//
|
||||
// []MeshCoreNode - Array of node objects, sorted by last_heard_at (descending)
|
||||
//
|
||||
// Response: 500 Internal Server Error
|
||||
//
|
||||
// ErrorResponse - Error retrieving nodes
|
||||
//
|
||||
// Example Request:
|
||||
//
|
||||
// GET /api/v1/meshcore/nodes
|
||||
// GET /api/v1/meshcore/nodes?type=chat
|
||||
//
|
||||
// Example Response:
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// "id": 42,
|
||||
// "packet": [],
|
||||
// "name": "NODE-CHARLIE",
|
||||
// "type": 0,
|
||||
// "prefix": "mc",
|
||||
// "public_key": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
||||
// "first_heard_at": "2026-01-15T08:00:00Z",
|
||||
// "last_heard_at": "2026-03-05T14:25:00Z",
|
||||
// "last_latitude": 52.3667,
|
||||
// "last_longitude": 4.8945
|
||||
// }
|
||||
// ]
|
||||
func (s *Server) handleGetMeshCoreNodes(c echo.Context) error {
|
||||
var (
|
||||
nodes []*schema.MeshCoreNode
|
||||
err error
|
||||
)
|
||||
|
||||
if kind := c.QueryParam("type"); kind != "" {
|
||||
switch kind {
|
||||
case "chat":
|
||||
nodes, err = schema.GetMeshCoreNodesByType(c.Request().Context(), meshcore.Chat)
|
||||
case "room":
|
||||
nodes, err = schema.GetMeshCoreNodesByType(c.Request().Context(), meshcore.Room)
|
||||
case "sensor":
|
||||
nodes, err = schema.GetMeshCoreNodesByType(c.Request().Context(), meshcore.Sensor)
|
||||
case "repeater":
|
||||
fallthrough
|
||||
default:
|
||||
nodes, err = schema.GetMeshCoreNodesByType(c.Request().Context(), meshcore.Repeater)
|
||||
}
|
||||
} else {
|
||||
nodes, err = schema.GetMeshCoreNodes(c.Request().Context())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return s.apiError(c, err)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, nodes)
|
||||
}
|
||||
|
||||
// handleGetMeshCoreNodesCloseTo returns nodes within a specified radius of a reference node.
|
||||
//
|
||||
// Endpoint: GET /api/v1/meshcore/nodes/close-to/:publickey
|
||||
//
|
||||
// Path Parameters:
|
||||
// - publickey: Hex-encoded Ed25519 public key (64 characters) of the reference node
|
||||
//
|
||||
// Query Parameters:
|
||||
// - radius (optional): Search radius in meters (default: 25000 = 25 km)
|
||||
//
|
||||
// Response: 200 OK
|
||||
//
|
||||
// NodesCloseToResponse - Object containing the reference node and nearby nodes
|
||||
// {
|
||||
// "node": MeshCoreNode, // The reference node
|
||||
// "nodes": []MeshCoreNode // Nearby nodes, sorted by distance (ascending)
|
||||
// // Each node has the "distance" field populated (in meters)
|
||||
// }
|
||||
//
|
||||
// Response: 404 Not Found
|
||||
//
|
||||
// null - Node not found or has no location data
|
||||
//
|
||||
// Response: 400 Bad Request
|
||||
//
|
||||
// ErrorResponse - Invalid radius parameter
|
||||
//
|
||||
// Response: 500 Internal Server Error
|
||||
//
|
||||
// ErrorResponse - Error performing proximity query
|
||||
//
|
||||
// Example Request:
|
||||
//
|
||||
// GET /api/v1/meshcore/nodes/close-to/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
|
||||
// GET /api/v1/meshcore/nodes/close-to/1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef?radius=50000
|
||||
//
|
||||
// Example Response:
|
||||
//
|
||||
// {
|
||||
// "node": {
|
||||
// "id": 42,
|
||||
// "name": "NODE-CHARLIE",
|
||||
// "type": 0,
|
||||
// "prefix": "mc",
|
||||
// "public_key": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
|
||||
// "first_heard_at": "2026-01-15T08:00:00Z",
|
||||
// "last_heard_at": "2026-03-05T14:25:00Z",
|
||||
// "last_latitude": 52.3667,
|
||||
// "last_longitude": 4.8945,
|
||||
// "distance": 0
|
||||
// },
|
||||
// "nodes": [
|
||||
// {
|
||||
// "id": 43,
|
||||
// "name": "NODE-DELTA",
|
||||
// "type": 0,
|
||||
// "prefix": "mc",
|
||||
// "public_key": "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
|
||||
// "first_heard_at": "2026-02-01T10:00:00Z",
|
||||
// "last_heard_at": "2026-03-05T14:20:00Z",
|
||||
// "last_latitude": 52.3700,
|
||||
// "last_longitude": 4.9000,
|
||||
// "distance": 450.5
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
func (s *Server) handleGetMeshCoreNodesCloseTo(c echo.Context) error {
|
||||
var radius float64
|
||||
if value := c.QueryParam("radius"); value != "" {
|
||||
var err error
|
||||
if radius, err = strconv.ParseFloat(value, 64); err != nil {
|
||||
return s.apiError(c, err)
|
||||
}
|
||||
}
|
||||
if radius <= 0 {
|
||||
radius = 25000.0 // 25 km
|
||||
}
|
||||
|
||||
node, nodes, err := schema.GetMeshCoreNodesCloseTo(c.Request().Context(), c.Param("publickey"), radius)
|
||||
if err != nil {
|
||||
return s.apiError(c, err)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]any{
|
||||
"node": node,
|
||||
"nodes": nodes,
|
||||
})
|
||||
}
|
||||
|
||||
// handleGetMeshCorePackets returns MeshCore packets based on various filter criteria.
|
||||
//
|
||||
// Endpoint: GET /api/v1/meshcore/packets
|
||||
//
|
||||
// Query Parameters (mutually exclusive, evaluated in order):
|
||||
// - hash: 16-character hex hash - Returns all packets with this hash
|
||||
// - type: Integer payload type - Returns packets of this type
|
||||
// - Can be combined with channel_hash for group messages
|
||||
// - channel_hash: 2-character channel hash (requires type parameter)
|
||||
// - (no parameters): Returns the 100 most recent packets
|
||||
//
|
||||
// Response: 200 OK
|
||||
//
|
||||
// []MeshCorePacket - Array of packet objects
|
||||
//
|
||||
// Response: 400 Bad Request
|
||||
//
|
||||
// ErrorResponse - Invalid hash format or payload type
|
||||
//
|
||||
// Response: 500 Internal Server Error
|
||||
//
|
||||
// ErrorResponse - Error retrieving packets
|
||||
//
|
||||
// Example Requests:
|
||||
//
|
||||
// GET /api/v1/meshcore/packets
|
||||
// GET /api/v1/meshcore/packets?hash=a1b2c3d4e5f67890
|
||||
// GET /api/v1/meshcore/packets?type=3
|
||||
// GET /api/v1/meshcore/packets?type=3&channel_hash=ab
|
||||
//
|
||||
// Example Response:
|
||||
//
|
||||
// [
|
||||
// {
|
||||
// "id": 12345,
|
||||
// "radio_id": 1,
|
||||
// "radio": null,
|
||||
// "snr": 8.5,
|
||||
// "rssi": -95,
|
||||
// "version": 1,
|
||||
// "route_type": 0,
|
||||
// "payload_type": 3,
|
||||
// "hash": "a1b2c3d4e5f67890",
|
||||
// "path": "AQIDBA==",
|
||||
// "payload": "SGVsbG8gV29ybGQ=",
|
||||
// "raw": "AQIDBAUGBwg=",
|
||||
// "parsed": {"text": "Hello World"},
|
||||
// "channel_hash": "ab",
|
||||
// "received_at": "2026-03-05T14:30:00Z"
|
||||
// }
|
||||
// ]
|
||||
//
|
||||
// Payload Types:
|
||||
// - 0: Ping
|
||||
// - 1: Node announcement
|
||||
// - 2: Direct text message
|
||||
// - 3: Group text message
|
||||
// - 4: Position update
|
||||
// - (other types may be defined by the protocol)
|
||||
func (s *Server) handleGetMeshCorePackets(c echo.Context) error {
|
||||
var (
|
||||
ctx = c.Request().Context()
|
||||
packets []*schema.MeshCorePacket
|
||||
err error
|
||||
)
|
||||
|
||||
if hash := c.QueryParam("hash"); hash != "" {
|
||||
packets, err = schema.GetMeshCorePacketsByHash(ctx, hash)
|
||||
} else if kind := c.QueryParam("type"); kind != "" {
|
||||
var payloadType int
|
||||
if payloadType, err = strconv.Atoi(kind); err == nil {
|
||||
if hash := c.QueryParam("channel_hash"); hash != "" {
|
||||
packets, err = schema.GetMeshCorePacketsByChannelHash(ctx, hash)
|
||||
} else {
|
||||
packets, err = schema.GetMeshCorePacketsByPayloadType(ctx, meshcore.PayloadType(payloadType))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
packets, err = schema.GetMeshCorePackets(ctx, 100)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return s.apiError(c, err)
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, packets)
|
||||
}
|
||||
Reference in New Issue
Block a user