Compare commits
No commits in common. "fb1b6a475c7442113a754fb7f087abe680430582" and "4b2b9399e91ccddab69512a3fa437fa2a8f46792" have entirely different histories.
fb1b6a475c
...
4b2b9399e9
15 changed files with 74 additions and 233 deletions
|
@ -7,7 +7,7 @@ import (
|
|||
"text/template"
|
||||
"time"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/internal/jsontime"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -27,7 +27,7 @@ var (
|
|||
}
|
||||
return dict, nil
|
||||
},
|
||||
"formTime": func(t jsontypes.Time) string {
|
||||
"formTime": func(t jsontime.Time) string {
|
||||
return time.Time(t).Format("2006-01-02T15:04")
|
||||
},
|
||||
"ago": func(s string) (string, error) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/internal/jsontime"
|
||||
|
||||
"github.com/araddon/dateparse"
|
||||
)
|
||||
|
@ -262,13 +262,13 @@ func (o *options) iterFields(r *http.Request, destStruct reflect.Value) error {
|
|||
return err
|
||||
}
|
||||
setVal(destFieldVal, set, val)
|
||||
case time.Time, *time.Time, jsontypes.Time, *jsontypes.Time:
|
||||
case time.Time, *time.Time, jsontime.Time, *jsontime.Time:
|
||||
t, set, err := o.parseTime(ff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
setVal(destFieldVal, set, t)
|
||||
case time.Duration, *time.Duration, jsontypes.Duration, *jsontypes.Duration:
|
||||
case time.Duration, *time.Duration, jsontime.Duration, *jsontime.Duration:
|
||||
d, set, err := o.parseDuration(ff)
|
||||
if err != nil {
|
||||
return err
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
"dynatron.me/x/stillbox/internal/common"
|
||||
"dynatron.me/x/stillbox/internal/forms"
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/internal/jsontime"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/alerting"
|
||||
"dynatron.me/x/stillbox/pkg/config"
|
||||
|
@ -49,18 +49,18 @@ type urlEncTest struct {
|
|||
|
||||
type urlEncTestJT struct {
|
||||
LookbackDays uint `json:"lookbackDays"`
|
||||
HalfLife jsontypes.Duration `json:"halfLife"`
|
||||
HalfLife jsontime.Duration `json:"halfLife"`
|
||||
Recent string `json:"recent"`
|
||||
ScoreStart jsontypes.Time `json:"scoreStart"`
|
||||
ScoreEnd jsontypes.Time `json:"scoreEnd"`
|
||||
ScoreStart jsontime.Time `json:"scoreStart"`
|
||||
ScoreEnd jsontime.Time `json:"scoreEnd"`
|
||||
}
|
||||
|
||||
type ptrTestJT struct {
|
||||
LookbackDays uint `form:"lookbackDays"`
|
||||
HalfLife *jsontypes.Duration `form:"halfLife"`
|
||||
HalfLife *jsontime.Duration `form:"halfLife"`
|
||||
Recent *string `form:"recent"`
|
||||
ScoreStart *jsontypes.Time `form:"scoreStart"`
|
||||
ScoreEnd jsontypes.Time `form:"scoreEnd"`
|
||||
ScoreStart *jsontime.Time `form:"scoreStart"`
|
||||
ScoreEnd jsontime.Time `form:"scoreEnd"`
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -73,33 +73,33 @@ var (
|
|||
|
||||
UrlEncTestJT = urlEncTestJT{
|
||||
LookbackDays: 7,
|
||||
HalfLife: jsontypes.Duration(30 * time.Minute),
|
||||
HalfLife: jsontime.Duration(30 * time.Minute),
|
||||
Recent: "2h0m0s",
|
||||
ScoreStart: jsontypes.Time(time.Date(2024, time.October, 28, 9, 25, 0, 0, time.UTC)),
|
||||
ScoreStart: jsontime.Time(time.Date(2024, time.October, 28, 9, 25, 0, 0, time.UTC)),
|
||||
}
|
||||
|
||||
PtrTestJT = ptrTestJT{
|
||||
LookbackDays: 7,
|
||||
HalfLife: common.PtrTo(jsontypes.Duration(30 * time.Minute)),
|
||||
HalfLife: common.PtrTo(jsontime.Duration(30 * time.Minute)),
|
||||
Recent: common.PtrTo("2h0m0s"),
|
||||
ScoreStart: common.PtrTo(jsontypes.Time(time.Date(2024, time.October, 28, 9, 25, 0, 0, time.UTC))),
|
||||
ScoreStart: common.PtrTo(jsontime.Time(time.Date(2024, time.October, 28, 9, 25, 0, 0, time.UTC))),
|
||||
}
|
||||
|
||||
UrlEncTestJTLocal = urlEncTestJT{
|
||||
LookbackDays: 7,
|
||||
HalfLife: jsontypes.Duration(30 * time.Minute),
|
||||
HalfLife: jsontime.Duration(30 * time.Minute),
|
||||
Recent: "2h0m0s",
|
||||
ScoreStart: jsontypes.Time(time.Date(2024, time.October, 28, 9, 25, 0, 0, time.Local)),
|
||||
ScoreStart: jsontime.Time(time.Date(2024, time.October, 28, 9, 25, 0, 0, time.Local)),
|
||||
}
|
||||
|
||||
realSim = &alerting.Simulation{
|
||||
Alerting: config.Alerting{
|
||||
LookbackDays: 7,
|
||||
HalfLife: jsontypes.Duration(30 * time.Minute),
|
||||
Recent: jsontypes.Duration(2 * time.Hour),
|
||||
HalfLife: jsontime.Duration(30 * time.Minute),
|
||||
Recent: jsontime.Duration(2 * time.Hour),
|
||||
},
|
||||
SimInterval: jsontypes.Duration(5 * time.Minute),
|
||||
ScoreStart: jsontypes.Time(time.Date(2024, time.October, 22, 17, 49, 0, 0, time.Local)),
|
||||
SimInterval: jsontime.Duration(5 * time.Minute),
|
||||
ScoreStart: jsontime.Time(time.Date(2024, time.October, 22, 17, 49, 0, 0, time.Local)),
|
||||
}
|
||||
|
||||
Call1 = callUploadRequest{
|
||||
|
@ -194,7 +194,7 @@ func TestUnmarshal(t *testing.T) {
|
|||
opts: []forms.Option{forms.WithAcceptBlank()},
|
||||
},
|
||||
{
|
||||
name: "url encoded jsontypes",
|
||||
name: "url encoded jsontime",
|
||||
r: makeRequest("urlenc.http"),
|
||||
dest: &urlEncTestJT{},
|
||||
expect: &UrlEncTestJT,
|
||||
|
@ -202,14 +202,14 @@ func TestUnmarshal(t *testing.T) {
|
|||
opts: []forms.Option{forms.WithTag("json")},
|
||||
},
|
||||
{
|
||||
name: "url encoded jsontypes with tz",
|
||||
name: "url encoded jsontime with tz",
|
||||
r: makeRequest("urlenc.http"),
|
||||
dest: &urlEncTestJT{},
|
||||
expect: &UrlEncTestJT,
|
||||
opts: []forms.Option{forms.WithAcceptBlank(), forms.WithParseTimeInTZ(time.UTC), forms.WithTag("json")},
|
||||
},
|
||||
{
|
||||
name: "url encoded jsontypes with local",
|
||||
name: "url encoded jsontime with local",
|
||||
r: makeRequest("urlenc.http"),
|
||||
dest: &urlEncTestJT{},
|
||||
expect: &UrlEncTestJTLocal,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package jsontypes
|
||||
package jsontime
|
||||
|
||||
import (
|
||||
"encoding/json"
|
|
@ -1,3 +0,0 @@
|
|||
package jsontypes
|
||||
|
||||
type Metadata map[string]interface{}
|
|
@ -9,7 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/forms"
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/internal/jsontime"
|
||||
"dynatron.me/x/stillbox/internal/trending"
|
||||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
|
@ -23,12 +23,12 @@ type Simulation struct {
|
|||
config.Alerting
|
||||
|
||||
// ScoreStart is the time when scoring begins
|
||||
ScoreStart jsontypes.Time `json:"scoreStart" yaml:"scoreStart" form:"scoreStart"`
|
||||
ScoreStart jsontime.Time `json:"scoreStart" yaml:"scoreStart" form:"scoreStart"`
|
||||
// ScoreEnd is the time when the score simulator ends. Left blank, it defaults to time.Now()
|
||||
ScoreEnd jsontypes.Time `json:"scoreEnd" yaml:"scoreEnd" form:"scoreEnd"`
|
||||
ScoreEnd jsontime.Time `json:"scoreEnd" yaml:"scoreEnd" form:"scoreEnd"`
|
||||
|
||||
// SimInterval is the interval at which the scorer will be called
|
||||
SimInterval jsontypes.Duration `json:"simInterval" yaml:"simInterval" form:"simInterval"`
|
||||
SimInterval jsontime.Duration `json:"simInterval" yaml:"simInterval" form:"simInterval"`
|
||||
|
||||
clock offsetClock `json:"-"`
|
||||
*alerter `json:"-"`
|
||||
|
@ -64,7 +64,7 @@ func (s *Simulation) Simulate(ctx context.Context) (trending.Scores[talkgroups.I
|
|||
s.Enable = true
|
||||
s.alerter = New(s.Alerting, tgc, WithClock(&s.clock)).(*alerter)
|
||||
if time.Time(s.ScoreEnd).IsZero() {
|
||||
s.ScoreEnd = jsontypes.Time(now)
|
||||
s.ScoreEnd = jsontime.Time(now)
|
||||
}
|
||||
log.Debug().Time("scoreStart", s.ScoreStart.Time()).
|
||||
Time("scoreEnd", s.ScoreEnd.Time()).
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/internal/jsontime"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -56,10 +56,10 @@ type RateLimit struct {
|
|||
type Alerting struct {
|
||||
Enable bool `yaml:"enable" form:"enable"`
|
||||
LookbackDays uint `yaml:"lookbackDays" form:"lookbackDays"`
|
||||
HalfLife jsontypes.Duration `yaml:"halfLife" form:"halfLife"`
|
||||
Recent jsontypes.Duration `yaml:"recent" form:"recent"`
|
||||
HalfLife jsontime.Duration `yaml:"halfLife" form:"halfLife"`
|
||||
Recent jsontime.Duration `yaml:"recent" form:"recent"`
|
||||
AlertThreshold float64 `yaml:"alertThreshold" form:"alertThreshold"`
|
||||
Renotify *jsontypes.Duration `yaml:"renotify,omitempty" form:"renotify,omitempty"`
|
||||
Renotify *jsontime.Duration `yaml:"renotify,omitempty" form:"renotify,omitempty"`
|
||||
}
|
||||
|
||||
type Notify []NotifyService
|
||||
|
|
|
@ -7,7 +7,6 @@ package database
|
|||
import (
|
||||
"time"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/pkg/alerting/rules"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
@ -90,7 +89,7 @@ type Talkgroup struct {
|
|||
AlphaTag *string `json:"alpha_tag"`
|
||||
TgGroup *string `json:"tg_group"`
|
||||
Frequency *int32 `json:"frequency"`
|
||||
Metadata jsontypes.Metadata `json:"metadata"`
|
||||
Metadata []byte `json:"metadata"`
|
||||
Tags []string `json:"tags"`
|
||||
Alert bool `json:"alert"`
|
||||
AlertConfig rules.AlertRules `json:"alert_config"`
|
||||
|
|
|
@ -8,7 +8,6 @@ package database
|
|||
import (
|
||||
"context"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/pkg/alerting/rules"
|
||||
)
|
||||
|
||||
|
@ -497,7 +496,7 @@ type UpdateTalkgroupParams struct {
|
|||
AlphaTag *string `json:"alpha_tag"`
|
||||
TgGroup *string `json:"tg_group"`
|
||||
Frequency *int32 `json:"frequency"`
|
||||
Metadata jsontypes.Metadata `json:"metadata"`
|
||||
Metadata []byte `json:"metadata"`
|
||||
Tags []string `json:"tags"`
|
||||
Alert *bool `json:"alert"`
|
||||
AlertConfig rules.AlertRules `json:"alert_config"`
|
||||
|
|
|
@ -2,6 +2,7 @@ package nexus
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
"dynatron.me/x/stillbox/pkg/pb"
|
||||
|
@ -69,7 +70,12 @@ func (c *client) Talkgroup(ctx context.Context, tg *pb.Talkgroup) error {
|
|||
|
||||
var md *structpb.Struct
|
||||
if len(tgi.Talkgroup.Metadata) > 0 {
|
||||
md, err = structpb.NewStruct(tgi.Talkgroup.Metadata)
|
||||
m := make(map[string]interface{})
|
||||
err := json.Unmarshal(tgi.Talkgroup.Metadata, &m)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Int32("sys", tg.System).Int32("tg", tg.Talkgroup).Msg("unmarshal tg metadata")
|
||||
}
|
||||
md, err = structpb.NewStruct(m)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Int32("sys", tg.System).Int32("tg", tg.Talkgroup).Msg("new pb struct for tg metadata")
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ type errResponse struct {
|
|||
|
||||
func (e *errResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
switch e.Code {
|
||||
case http.StatusNotFound:
|
||||
default:
|
||||
log.Error().Str("path", r.URL.Path).Err(e.Err).Int("code", e.Code).Str("msg", e.Error).Msg("request failed")
|
||||
}
|
||||
|
@ -78,7 +79,6 @@ func internalError(err error) render.Renderer {
|
|||
type errResponder func(error) render.Renderer
|
||||
|
||||
var statusMapping = map[error]errResponder{
|
||||
talkgroups.ErrNoSuchSystem: recordNotFound,
|
||||
talkgroups.ErrNotFound: recordNotFound,
|
||||
pgx.ErrNoRows: recordNotFound,
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ func (tga *talkgroupAPI) Subrouter() http.Handler {
|
|||
r.Put("/{system:\\d+}/{id:\\d+}", tga.put)
|
||||
r.Get("/{system:\\d+}/", tga.get)
|
||||
r.Get("/", tga.get)
|
||||
r.Post("/import", tga.tgImport)
|
||||
|
||||
return r
|
||||
}
|
||||
|
@ -107,19 +106,3 @@ func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
respond(w, r, record)
|
||||
}
|
||||
|
||||
func (tga *talkgroupAPI) tgImport(w http.ResponseWriter, r *http.Request) {
|
||||
var impJob talkgroups.ImportJob
|
||||
err := forms.Unmarshal(r, &impJob, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
|
||||
if err != nil {
|
||||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
recs, err := impJob.Import(r.Context())
|
||||
if err != nil {
|
||||
wErr(w, r, autoError(err))
|
||||
return
|
||||
}
|
||||
|
||||
respond(w, r, recs)
|
||||
}
|
||||
|
|
|
@ -1,136 +0,0 @@
|
|||
package talkgroups
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
)
|
||||
|
||||
type ImportSource string
|
||||
|
||||
const (
|
||||
ImportSrcRadioReference ImportSource = "radioreference"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadImportType = errors.New("unknown import type")
|
||||
)
|
||||
|
||||
type importer interface {
|
||||
importTalkgroups(ctx context.Context, sys int, r io.Reader) ([]Talkgroup, error)
|
||||
}
|
||||
|
||||
type ImportJob struct {
|
||||
Type ImportSource `json:"type"`
|
||||
SystemID int `json:"systemID"`
|
||||
Body string `json:"body"`
|
||||
|
||||
importer `json:"-"`
|
||||
}
|
||||
|
||||
func (ij *ImportJob) Import(ctx context.Context) ([]Talkgroup, error) {
|
||||
r := bytes.NewReader([]byte(ij.Body))
|
||||
|
||||
switch ij.Type {
|
||||
case ImportSrcRadioReference:
|
||||
ij.importer = &radioReferenceImporter{}
|
||||
default:
|
||||
return nil, ErrBadImportType
|
||||
}
|
||||
return ij.importTalkgroups(ctx, ij.SystemID, r)
|
||||
}
|
||||
|
||||
type radioReferenceImporter struct {
|
||||
}
|
||||
|
||||
type rrState int
|
||||
|
||||
const (
|
||||
rrsInitial rrState = iota
|
||||
rrsGroupDesc
|
||||
rrsTG
|
||||
)
|
||||
|
||||
var rrRE = regexp.MustCompile(`DEC\s+HEX\s+Mode\s+Alpha Tag\s+Description\s+Tag`)
|
||||
|
||||
func (rr *radioReferenceImporter) importTalkgroups(ctx context.Context, sys int, r io.Reader) ([]Talkgroup, error) {
|
||||
sc := bufio.NewScanner(r)
|
||||
tgs := make([]Talkgroup, 0, 8)
|
||||
sysn, has := StoreFrom(ctx).SystemName(ctx, sys)
|
||||
if !has {
|
||||
return nil, ErrNoSuchSystem
|
||||
}
|
||||
|
||||
var groupName string
|
||||
state := rrsInitial
|
||||
for sc.Scan() {
|
||||
if err := ctx.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ln := strings.Trim(sc.Text(), " \t\r\n")
|
||||
|
||||
switch state {
|
||||
case rrsInitial:
|
||||
groupName = ln
|
||||
state++
|
||||
case rrsGroupDesc:
|
||||
if rrRE.MatchString(ln) {
|
||||
state++
|
||||
}
|
||||
case rrsTG:
|
||||
fields := strings.Split(ln, "\t")
|
||||
if len(fields) < 6 {
|
||||
state = rrsGroupDesc
|
||||
groupName = ln
|
||||
continue
|
||||
}
|
||||
tgid, err := strconv.Atoi(fields[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var metadata jsontypes.Metadata
|
||||
tgt := TG(sys, tgid)
|
||||
mode := fields[2]
|
||||
if strings.Contains(mode, "E") {
|
||||
metadata = jsontypes.Metadata{
|
||||
"encrypted": true,
|
||||
}
|
||||
}
|
||||
tags := []string{fields[5]}
|
||||
gn := groupName // must take a copy
|
||||
tgs = append(tgs, Talkgroup{
|
||||
Talkgroup: database.Talkgroup{
|
||||
ID: tgt.Pack(),
|
||||
Tgid: int32(tgt.Talkgroup),
|
||||
SystemID: int32(tgt.System),
|
||||
Name: &fields[4],
|
||||
AlphaTag: &fields[3],
|
||||
TgGroup: &gn,
|
||||
Metadata: metadata,
|
||||
Tags: tags,
|
||||
Weight: 1.0,
|
||||
},
|
||||
System: database.System{
|
||||
ID: sys,
|
||||
Name: sysn,
|
||||
},
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if err := sc.Err(); err != nil {
|
||||
return tgs, err
|
||||
}
|
||||
|
||||
return tgs, nil
|
||||
}
|
|
@ -12,8 +12,6 @@ type Talkgroup struct {
|
|||
Learned bool `json:"learned"`
|
||||
}
|
||||
|
||||
type Metadata map[string]interface{}
|
||||
|
||||
type Names struct {
|
||||
System string
|
||||
Talkgroup string
|
||||
|
|
|
@ -32,8 +32,3 @@ sql:
|
|||
import: "dynatron.me/x/stillbox/pkg/alerting/rules"
|
||||
type: "AlertRules"
|
||||
nullable: true
|
||||
- column: "talkgroups.metadata"
|
||||
go_type:
|
||||
import: "dynatron.me/x/stillbox/internal/jsontypes"
|
||||
type: "Metadata"
|
||||
nullable: true
|
||||
|
|
Loading…
Reference in a new issue