package schema import ( "context" "encoding/hex" "errors" "fmt" "os" "time" "xorm.io/builder" "git.maze.io/go/ham/protocol" "git.maze.io/go/ham/protocol/meshcore" ) func init() { RegisterModel(new(MeshCorePacket)) RegisterModel(new(MeshCoreNode)) RegisterModel(new(MeshCoreNodePosition)) RegisterModel(new(MeshCoreGroup)) } type MeshCorePacket struct { ID int64 `xorm:"pk autoincr" json:"id"` RadioID int64 `xorm:"index" json:"radio_id"` Radio *Radio `xorm:"-" json:"radio"` SNR float64 `xorm:"not null default 0" json:"snr"` RSSI int `xorm:"not null default 0" json:"rssi"` Version int `xorm:"not null default 1" json:"version"` RouteType uint8 `xorm:"index not null" json:"route_type"` PayloadType uint8 `xorm:"index not null" json:"payload_type"` Hash string `xorm:"varchar(16) index not null" json:"hash"` Path []byte `xorm:"bytea" json:"path"` Payload []byte `xorm:"bytea not null" json:"payload"` Raw []byte `xorm:"bytea not null" json:"raw"` Parsed *string `xorm:"jsonb" json:"parsed"` ChannelHash string `xorm:"varchar(2) index" json:"channel_hash,omitempty"` ReceivedAt time.Time `json:"received_at"` } func (MeshCorePacket) TableName() string { return "meshcore_packet" } func GetMeshCorePackets(ctx context.Context, limit int) ([]*MeshCorePacket, error) { packets := make([]*MeshCorePacket, 0, limit) return packets, Query(ctx). OrderBy("`received_at` DESC"). Limit(limit). Find(&packets) } func GetMeshCorePacketsByHash(ctx context.Context, hash string) ([]*MeshCorePacket, error) { if len(hash) != 16 { return nil, errors.New("invalid hash") } else if _, err := hex.DecodeString(hash); err != nil { return nil, err } packets := make([]*MeshCorePacket, 0, 10) return packets, Query(ctx). Where(builder.Eq{"hash": hash}). OrderBy("`received_at` ASC"). Find(&packets) } func GetMeshCorePacketsByPayloadType(ctx context.Context, payloadType meshcore.PayloadType) ([]*MeshCorePacket, error) { packets := make([]*MeshCorePacket, 0, 10) return packets, Query(ctx). Where(builder.Eq{"payload_type": int(payloadType)}). OrderBy("`received_at` DESC"). Find(&packets) } func GetMeshCorePacketsByChannelHash(ctx context.Context, hash string) ([]*MeshCorePacket, error) { packets := make([]*MeshCorePacket, 0, 10) return packets, Query(ctx). Where(builder.Eq{ "`payload_type`": int(meshcore.TypeGroupText), "`channel_hash`": hash, }). OrderBy("`received_at` DESC"). Find(&packets) } type MeshCoreGroup struct { ID int64 `xorm:"pk autoincr" json:"id"` Name string `xorm:"varchar(32) not null unique" json:"name"` Secret string `xorm:"varchar(32) not null" json:"secret"` IsPublic bool `xorm:"boolean not null default false" json:"-"` } func (MeshCoreGroup) TableName() string { return "meshcore_group" } func GetMeshCoreGroups(ctx context.Context) ([]*MeshCoreGroup, error) { groups := make([]*MeshCoreGroup, 0, 10) return groups, Query(ctx). Where(builder.Eq{"is_public": true}). OrderBy("name asc"). Find(&groups) } type MeshCoreNode struct { ID int64 `xorm:"pk autoincr" json:"id"` PacketHash string `xorm:"varchar(16) index 'meshcore_packet_hash'" json:"-"` Packets []*MeshCorePacket `xorm:"-" json:"packet"` Name string `xorm:"varchar(100) not null" json:"name"` Type uint8 `xorm:"index not null" json:"type"` Prefix string `xorm:"varchar(2) not null" json:"prefix"` PublicKey string `xorm:"varchar(64) not null unique" json:"public_key"` FirstHeardAt time.Time `xorm:"timestamp not null" json:"first_heard_at"` LastHeardAt time.Time `xorm:"timestamp not null" json:"last_heard_at"` LastLatitude *float64 `json:"last_latitude"` LastLongitude *float64 `json:"last_longitude"` Distance float64 `xorm:"-" json:"distance,omitempty"` } func (MeshCoreNode) TableName() string { return "meshcore_node" } func GetMeshCoreNodeByPublicKey(ctx context.Context, publicKey string) (*MeshCoreNode, error) { node := new(MeshCoreNode) if ok, err := Query(ctx). Where(builder.Eq{"public_key": publicKey}). Get(node); err != nil { return nil, err } else if !ok { return nil, os.ErrNotExist } return node, nil } func GetMeshCoreNodes(ctx context.Context) ([]*MeshCoreNode, error) { nodes := make([]*MeshCoreNode, 0, 100) return nodes, Query(ctx). OrderBy("`last_heard_at` DESC"). Find(&nodes) } func GetMeshCoreNodesByType(ctx context.Context, nodeType meshcore.NodeType) ([]*MeshCoreNode, error) { nodes := make([]*MeshCoreNode, 0, 100) return nodes, Query(ctx). Where(builder.Eq{"type": nodeType}). OrderBy("`last_heard_at` DESC"). Find(&nodes) } type MeshCoreNodeWithDistance struct { MeshCoreNode `xorm:"extends"` Distance float64 `xorm:"distance"` } func GetMeshCoreNodesCloseTo(ctx context.Context, publicKey string, radius float64) (*MeshCoreNode, []*MeshCoreNode, error) { node, err := GetMeshCoreNodeByPublicKey(ctx, publicKey) if err != nil { return nil, nil, err } else if node.LastLatitude == nil || node.LastLongitude == nil { return nil, nil, errors.New("node has no location") } nodesWithDistance := make([]*MeshCoreNodeWithDistance, 0, 100) selectClause := fmt.Sprintf("*, "+ "ST_Distance("+ "ST_SetSRID(ST_MakePoint(%f, %f), 4326)::geography, "+ "ST_SetSRID(ST_MakePoint(last_longitude, last_latitude), 4326)::geography"+ ") as distance", *node.LastLongitude, *node.LastLatitude) if err = Query(ctx). Select(selectClause). Where("id != ?", node.ID). Where("ST_DWithin("+ "ST_SetSRID(ST_MakePoint(?, ?), 4326)::geography, "+ "ST_SetSRID(ST_MakePoint(last_longitude, last_latitude), 4326)::geography, ?)", *node.LastLongitude, *node.LastLatitude, radius). OrderBy("`distance` ASC"). Find(&nodesWithDistance); err != nil { return nil, nil, err } nodes := make([]*MeshCoreNode, len(nodesWithDistance)) for i, node := range nodesWithDistance { node.MeshCoreNode.Distance = node.Distance nodes[i] = &node.MeshCoreNode } return node, nodes, nil } type MeshCoreNodePosition struct { ID int64 `xorm:"pk autoincr" json:"id"` MeshCoreNodeId int64 `xorm:"not null 'meshcore_node_id'" json:"-"` MeshCoreNode *MeshCoreNode `xorm:"-" json:"node"` Latitude float64 `json:"latitude"` Longitude float64 `json:"longitude"` ReceivedAt time.Time `xorm:"timestamp not null" json:"received_at"` } func (MeshCoreNodePosition) TableName() string { return "meshcore_node_position" } type MeshCoreStats struct { Messages int64 `json:"messages"` Nodes int64 `json:"nodes"` Receivers int64 `json:"receivers"` Packets struct { Timestamps []int64 `json:"timestamps"` Packets []int64 `json:"packets"` } `json:"packets"` } func GetMeshCoreStats(ctx context.Context) (*MeshCoreStats, error) { var ( engine = Query(ctx) stats = new(MeshCoreStats) err error ) if stats.Messages, err = engine. In("`payload_type`", meshcore.TypeText, meshcore.TypeGroupText). Count(&MeshCorePacket{}); err != nil { return nil, err } if stats.Nodes, err = engine. Count(&MeshCoreNode{}); err != nil { return nil, err } if stats.Receivers, err = engine. Where(builder.Eq{"`protocol`": protocol.MeshCore}). Count(&Radio{}); err != nil { return nil, err } return stats, nil } /* Column | Type | Collation | Nullable | Default --------------+--------------------------+-----------+----------+--------------------------------------------- id | bigint | | not null | nextval('meshcore_packet_id_seq'::regclass) snr | real | | not null | 0 rssi | smallint | | not null | 0 hash | bytea | | not null | route_type | smallint | | not null | payload_type | smallint | | not null | path | bytea | | | payload | bytea | | | raw | bytea | | | parsed | jsonb | | | received_at | timestamp with time zone | | not null | now() created_at | timestamp with time zone | | not null | now() */ /* id | bigint | | not null | nextval('meshcore_node_id_seq'::regclass) last_advert_id | bigint | | | node_type | smallint | | not null | 0 public_key | bytea | | not null | name | text | | | local_time | timestamp with time zone | | not null | first_heard | timestamp with time zone | | not null | now() last_heard | timestamp with time zone | | not null | now() last_latitude | numeric(10,8) | | | last_longitude | numeric(11,8) | | | prefix | bytea | | | generated always as ("substring"(public_key, 0, 2)) stored last_position | geometry(Point,4326) | | | generated always as ( + | | | | CASE + | | | | WHEN last_latitude IS NOT NULL AND last_longitude IS NOT NULL THEN st_setsrid(st_makepoint(last_latitude::double precision, last_longitude::double precision), 4326)+ | | | | ELSE NULL::geometry + | | | | END) stored position | geometry(Point,4326) | | | */