Checkpoint
This commit is contained in:
87
schema/aprs.go
Normal file
87
schema/aprs.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterModel(new(APRSStation))
|
||||
RegisterModel(new(APRSPacket))
|
||||
}
|
||||
|
||||
type APRSStation struct {
|
||||
ID int64 `xorm:"pk autoincr" json:"id"`
|
||||
Call string `xorm:"varchar(10) unique not null" json:"call"`
|
||||
Symbol string `xorm:"varchar(2)" json:"symbol"`
|
||||
FirstHeardAt time.Time `xorm:"timestamp not null"`
|
||||
LastHeardAt time.Time `xorm:"timestamp not null"`
|
||||
LastLatitude sql.NullFloat64
|
||||
LastLongitude sql.NullFloat64
|
||||
}
|
||||
|
||||
func GetAPRSStation(ctx context.Context, call string) (*APRSStation, error) {
|
||||
station := new(APRSStation)
|
||||
has, err := Query(ctx).
|
||||
Where(builder.Eq{`"call"`: strings.ToUpper(call)}).
|
||||
Get(station)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
return station, nil
|
||||
}
|
||||
|
||||
func (station APRSStation) GetPackets(ctx context.Context) ([]*APRSPacket, error) {
|
||||
packets := make([]*APRSPacket, 0, 10)
|
||||
return packets, Query(ctx).
|
||||
Where(builder.Eq{"`station_id`": station.ID}).
|
||||
Find(&packets)
|
||||
}
|
||||
|
||||
type APRSPacket struct {
|
||||
ID int64 `xorm:"pk autoincr" json:"id"`
|
||||
RadioID int64 `xorm:"index" json:"radio_id"`
|
||||
Radio Radio `json:"radio"`
|
||||
StationID int64 `json:"-"`
|
||||
Station *APRSStation `xorm:"-" json:"station"`
|
||||
Source string `xorm:"varchar(10) not null" json:"src"`
|
||||
Destination string `xorm:"varchar(10) not null" json:"dst"`
|
||||
Path string `xorm:"varchar(88) not null default ''" json:"path"`
|
||||
Comment string `xorm:"varchar(250)" json:"comment"`
|
||||
Latitude sql.NullFloat64 `json:"latitude,omitempty"`
|
||||
Longitude sql.NullFloat64 `json:"longitude,omitempty"`
|
||||
Symbol string `xorm:"varchar(2)" json:"symbol"`
|
||||
Raw string `json:"raw"`
|
||||
ReceivedAt time.Time `json:"received_at"`
|
||||
}
|
||||
|
||||
func GetAPRSPackets(ctx context.Context, limit int) ([]*APRSPacket, error) {
|
||||
packets := make([]*APRSPacket, 0, limit)
|
||||
return packets, Query(ctx).
|
||||
OrderBy("`received_at` DESC").
|
||||
Limit(limit).
|
||||
Find(&packets)
|
||||
}
|
||||
|
||||
func GetAPRSPacketsBySource(ctx context.Context, source string) ([]*APRSPacket, error) {
|
||||
packets := make([]*APRSPacket, 0, 100)
|
||||
return packets, Query(ctx).
|
||||
Where(builder.Eq{"source": strings.ToUpper(source)}).
|
||||
OrderBy("`received_at` DESC").
|
||||
Find(&packets)
|
||||
}
|
||||
|
||||
func GetAPRSPacketsByDestination(ctx context.Context, destination string) ([]*APRSPacket, error) {
|
||||
packets := make([]*APRSPacket, 0, 100)
|
||||
return packets, Query(ctx).
|
||||
Where(builder.Eq{"destination": strings.ToUpper(destination)}).
|
||||
OrderBy("`received_at` DESC").
|
||||
Find(&packets)
|
||||
}
|
||||
192
schema/engine.go
Normal file
192
schema/engine.go
Normal file
@@ -0,0 +1,192 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"xorm.io/xorm"
|
||||
"xorm.io/xorm/log"
|
||||
"xorm.io/xorm/names"
|
||||
|
||||
_ "github.com/lib/pq" // PostgreSQL support
|
||||
_ "github.com/mattn/go-sqlite3" // SQLite support
|
||||
)
|
||||
|
||||
// Logger used by this package
|
||||
var Logger = logrus.New()
|
||||
|
||||
var xormEngine *xorm.Engine
|
||||
|
||||
type engineContextKeyType struct{}
|
||||
|
||||
var engineContextKey = engineContextKeyType{}
|
||||
|
||||
// Engine represents a xorm engine or session.
|
||||
type Engine interface {
|
||||
Table(tableNameOrBean any) *xorm.Session
|
||||
Count(...any) (int64, error)
|
||||
Decr(column string, arg ...any) *xorm.Session
|
||||
Delete(...any) (int64, error)
|
||||
Truncate(...any) (int64, error)
|
||||
Exec(...any) (sql.Result, error)
|
||||
Find(any, ...any) error
|
||||
Get(beans ...any) (bool, error)
|
||||
ID(any) *xorm.Session
|
||||
In(string, ...any) *xorm.Session
|
||||
Incr(column string, arg ...any) *xorm.Session
|
||||
Insert(...any) (int64, error)
|
||||
Iterate(any, xorm.IterFunc) error
|
||||
Join(joinOperator string, tablename, condition any, args ...any) *xorm.Session
|
||||
SQL(any, ...any) *xorm.Session
|
||||
Where(any, ...any) *xorm.Session
|
||||
Asc(colNames ...string) *xorm.Session
|
||||
Desc(colNames ...string) *xorm.Session
|
||||
Limit(limit int, start ...int) *xorm.Session
|
||||
NoAutoTime() *xorm.Session
|
||||
SumInt(bean any, columnName string) (res int64, err error)
|
||||
Sync(...any) error
|
||||
Select(string) *xorm.Session
|
||||
SetExpr(string, any) *xorm.Session
|
||||
NotIn(string, ...any) *xorm.Session
|
||||
OrderBy(any, ...any) *xorm.Session
|
||||
Exist(...any) (bool, error)
|
||||
Distinct(...string) *xorm.Session
|
||||
Query(...any) ([]map[string][]byte, error)
|
||||
Cols(...string) *xorm.Session
|
||||
Context(ctx context.Context) *xorm.Session
|
||||
Ping() error
|
||||
IsTableExist(tableNameOrBean any) (bool, error)
|
||||
Begin() error
|
||||
Rollback() error
|
||||
Commit() error
|
||||
}
|
||||
|
||||
// Query the engine from the context.
|
||||
func Query(ctx context.Context) Engine {
|
||||
if engine, ok := ctx.Value(engineContextKey).(Engine); ok {
|
||||
return engine
|
||||
}
|
||||
return xormEngine.Context(ctx)
|
||||
}
|
||||
|
||||
// Open a database connection.
|
||||
func Open(driver, config string) error {
|
||||
var err error
|
||||
if xormEngine, err = xorm.NewEngine(driver, config); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
gonicNames := []string{
|
||||
"ID",
|
||||
"SSL", "UID",
|
||||
"SNR", "RSSI",
|
||||
"APRS", "MeshCore",
|
||||
"LoRa",
|
||||
}
|
||||
for _, name := range gonicNames {
|
||||
names.LintGonicMapper[name] = true
|
||||
}
|
||||
xormEngine.SetMapper(names.GonicMapper{})
|
||||
xormEngine.SetLogger(xormLogger{})
|
||||
|
||||
for _, model := range registeredModels {
|
||||
Logger.Debugf("schema: sync schema %T", model)
|
||||
if err = xormEngine.Sync(model); err != nil {
|
||||
_ = xormEngine.Close()
|
||||
xormEngine = nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
registeredModels []any
|
||||
registeredInitFuncs []func() error
|
||||
)
|
||||
|
||||
func RegisterModel(model any, initFuncs ...func() error) {
|
||||
registeredModels = append(registeredModels, model)
|
||||
if len(initFuncs) > 0 && initFuncs[0] != nil {
|
||||
registeredInitFuncs = append(registeredInitFuncs, initFuncs...)
|
||||
}
|
||||
}
|
||||
|
||||
func NULLFloat64(v float64) *float64 {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
return &v
|
||||
}
|
||||
|
||||
func NULLString(s string) *string {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
return &s
|
||||
}
|
||||
|
||||
func NULLTime(t time.Time) *time.Time {
|
||||
if t.Equal(time.Time{}) {
|
||||
return nil
|
||||
}
|
||||
return &t
|
||||
}
|
||||
|
||||
type xormLogger struct{}
|
||||
|
||||
func (l xormLogger) BeforeSQL(context log.LogContext) {} // only invoked when IsShowSQL is true
|
||||
func (l xormLogger) AfterSQL(context log.LogContext) {} // only invoked when IsShowSQL is true
|
||||
func (l xormLogger) Debug(args ...any) { Logger.Debug(append([]any{"engine: "}, args...)...) }
|
||||
func (l xormLogger) Debugf(format string, args ...any) { Logger.Debugf("engine: "+format, args...) }
|
||||
func (l xormLogger) Error(args ...any) { Logger.Error(append([]any{"engine: "}, args...)...) }
|
||||
func (l xormLogger) Errorf(format string, args ...any) { Logger.Errorf("engine: "+format, args...) }
|
||||
func (l xormLogger) Info(args ...any) { Logger.Info(append([]any{"engine: "}, args...)...) }
|
||||
func (l xormLogger) Infof(format string, args ...any) { Logger.Infof("engine: "+format, args...) }
|
||||
func (l xormLogger) Warn(args ...any) { Logger.Warn(append([]any{"engine: "}, args...)...) }
|
||||
func (l xormLogger) Warnf(format string, args ...any) { Logger.Warnf("engine: "+format, args...) }
|
||||
|
||||
func (l xormLogger) Level() log.LogLevel {
|
||||
switch Logger.Level {
|
||||
case logrus.TraceLevel:
|
||||
return log.LOG_DEBUG
|
||||
case logrus.DebugLevel:
|
||||
return log.LOG_DEBUG
|
||||
case logrus.InfoLevel:
|
||||
return log.LOG_INFO
|
||||
case logrus.ErrorLevel:
|
||||
return log.LOG_ERR
|
||||
case logrus.WarnLevel:
|
||||
return log.LOG_WARNING
|
||||
case logrus.FatalLevel:
|
||||
return log.LOG_OFF
|
||||
default:
|
||||
return log.LOG_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
func (l xormLogger) SetLevel(level log.LogLevel) {
|
||||
switch level {
|
||||
case log.LOG_DEBUG:
|
||||
Logger.SetLevel(logrus.DebugLevel)
|
||||
case log.LOG_INFO:
|
||||
Logger.SetLevel(logrus.InfoLevel)
|
||||
case log.LOG_ERR:
|
||||
Logger.SetLevel(logrus.ErrorLevel)
|
||||
case log.LOG_OFF:
|
||||
Logger.SetLevel(logrus.FatalLevel)
|
||||
}
|
||||
}
|
||||
|
||||
func (l xormLogger) ShowSQL(show ...bool) {
|
||||
_ = show
|
||||
}
|
||||
|
||||
func (l xormLogger) IsShowSQL() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
var _ log.ContextLogger = (*xormLogger)(nil)
|
||||
273
schema/meshcore.go
Normal file
273
schema/meshcore.go
Normal file
@@ -0,0 +1,273 @@
|
||||
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) | | |
|
||||
*/
|
||||
75
schema/radio.go
Normal file
75
schema/radio.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package schema
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"xorm.io/builder"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterModel(new(Radio))
|
||||
}
|
||||
|
||||
type Radio struct {
|
||||
ID int64 `xorm:"pk autoincr" json:"id"`
|
||||
Name string `xorm:"not null unique" json:"name"`
|
||||
IsOnline bool `xorm:"bool not null default false" json:"is_online"`
|
||||
Manufacturer string `xorm:"varchar(64) not null" json:"manufacturer"`
|
||||
Device *string `xorm:"varchar(64)" json:"device"`
|
||||
FirmwareVersion *string `xorm:"varchar(32)" json:"firmware_version"`
|
||||
FirmwareDate *time.Time `json:"firmware_date"`
|
||||
Antenna *string `xorm:"varchar(100)" json:"antenna"`
|
||||
Modulation string `xorm:"varchar(16) not null" json:"modulation"`
|
||||
Protocol string `xorm:"varchar(16) not null index" json:"protocol"`
|
||||
Latitude *float64 `json:"latitude,omitempty"`
|
||||
Longitude *float64 `json:"longitude,omitempty"`
|
||||
Altitude *float64 `json:"altitude,omitempty"`
|
||||
Frequency float64 `xorm:"not null" json:"frequency"`
|
||||
Bandwidth float64 `xorm:"not null" json:"bandwidth"`
|
||||
Power *float64 `json:"power,omitempty"`
|
||||
Gain *float64 `json:"gain,omitempty"`
|
||||
LoRaSF *uint8 `xorm:"smallint 'lora_sf'" json:"lora_sf,omitempty"`
|
||||
LoRaCR *uint8 `xorm:"smallint 'lora_cr'" json:"lora_cr,omitempty"`
|
||||
Extra []byte `xorm:"jsonb" json:"extra,omitempty"`
|
||||
CreatedAt time.Time `xorm:"timestamp not null default current_timestamp" json:"created_at"`
|
||||
UpdatedAt time.Time `xorm:"timestamp not null default current_timestamp" json:"updated_at"`
|
||||
}
|
||||
|
||||
func GetRadioByEncodedID(ctx context.Context, id string) (*Radio, error) {
|
||||
name, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(id, "="))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
radio := new(Radio)
|
||||
has, err := Query(ctx).Where(builder.Eq{"`name`": name}).Get(radio)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
return nil, os.ErrNotExist
|
||||
}
|
||||
return radio, nil
|
||||
}
|
||||
|
||||
func GetRadios(ctx context.Context) ([]*Radio, error) {
|
||||
radios := make([]*Radio, 0, 5)
|
||||
return radios, Query(ctx).Find(&radios)
|
||||
}
|
||||
|
||||
func GetRadiosByProtocol(ctx context.Context, protocol string) ([]*Radio, error) {
|
||||
radios := make([]*Radio, 0, 5)
|
||||
return radios, Query(ctx).
|
||||
Where(builder.Eq{"`protocol`": protocol}).
|
||||
Find(&radios)
|
||||
}
|
||||
|
||||
func GetRadiosRecentlyOnline(ctx context.Context) ([]*Radio, error) {
|
||||
radios := make([]*Radio, 0, 5)
|
||||
return radios, Query(ctx).
|
||||
Where(builder.Eq{"`is_online`": true}).
|
||||
Find(&radios)
|
||||
}
|
||||
Reference in New Issue
Block a user