Compare commits
14 commits
Author | SHA1 | Date | |
---|---|---|---|
7724ce17a0 | |||
043974b074 | |||
52759db43b | |||
974378ff7b | |||
7abe075113 | |||
144cdd35ec | |||
a1b751fdf0 | |||
692f7d69a3 | |||
5fd035561c | |||
c48d1eaf8d | |||
8207c59815 | |||
5d9a08780f | |||
bba458fe93 | |||
368f231b89 |
39 changed files with 1086 additions and 509 deletions
|
@ -36,7 +36,7 @@ notify:
|
||||||
# subjectTemplate: "Stillbox Alert ({{ highest . }})"
|
# subjectTemplate: "Stillbox Alert ({{ highest . }})"
|
||||||
# bodyTemplate: |
|
# bodyTemplate: |
|
||||||
# {{ range . -}}
|
# {{ range . -}}
|
||||||
# {{ .TGName }} is active with a score of {{ f .Score.Score 4 }}! ({{ f .Score.RecentCount 0 }}/{{ .Score.Count }} recent calls)
|
# {{ .TGName }}{{ if (and .Talkgroup .Talkgroup.AlphaTag) }} ({{ .Talkgroup.StringTag false -}}){{ end }} is active with a score of {{ f .Score.Score 4 }}! ({{ f .Score.RecentCount 0 }}/{{ .Score.Count }} recent calls)
|
||||||
#
|
#
|
||||||
# {{ end -}}
|
# {{ end -}}
|
||||||
config:
|
config:
|
||||||
|
|
|
@ -44,3 +44,12 @@ func PtrOrNull[T comparable](val T) *T {
|
||||||
|
|
||||||
return &val
|
return &val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ZeroOr[T any](v *T) T {
|
||||||
|
var zero T
|
||||||
|
if v == nil {
|
||||||
|
return zero
|
||||||
|
}
|
||||||
|
|
||||||
|
return *v
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/internal/trending"
|
"dynatron.me/x/stillbox/internal/trending"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
@ -16,6 +17,7 @@ type Alert struct {
|
||||||
ID int
|
ID int
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
TGName string
|
TGName string
|
||||||
|
Talkgroup *talkgroups.Talkgroup
|
||||||
Score trending.Score[talkgroups.ID]
|
Score trending.Score[talkgroups.ID]
|
||||||
OrigScore float64
|
OrigScore float64
|
||||||
Weight float32
|
Weight float32
|
||||||
|
@ -43,7 +45,8 @@ func (a *Alert) ToAddAlertParams() database.AddAlertParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make creates an alert for later rendering or storage.
|
// Make creates an alert for later rendering or storage.
|
||||||
func Make(ctx context.Context, store talkgroups.Store, score trending.Score[talkgroups.ID], origScore float64) (Alert, error) {
|
func Make(ctx context.Context, score trending.Score[talkgroups.ID], origScore float64) (Alert, error) {
|
||||||
|
store := tgstore.FromCtx(ctx)
|
||||||
d := Alert{
|
d := Alert{
|
||||||
Score: score,
|
Score: score,
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
|
@ -56,6 +59,7 @@ func Make(ctx context.Context, store talkgroups.Store, score trending.Score[talk
|
||||||
case nil:
|
case nil:
|
||||||
d.Weight = tgRecord.Talkgroup.Weight
|
d.Weight = tgRecord.Talkgroup.Weight
|
||||||
d.TGName = tgRecord.String()
|
d.TGName = tgRecord.String()
|
||||||
|
d.Talkgroup = tgRecord
|
||||||
default:
|
default:
|
||||||
system, has := store.SystemName(ctx, int(score.ID.System))
|
system, has := store.SystemName(ctx, int(score.ID.System))
|
||||||
if has {
|
if has {
|
||||||
|
|
|
@ -3,6 +3,7 @@ package alerting
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -14,7 +15,8 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/notify"
|
"dynatron.me/x/stillbox/pkg/notify"
|
||||||
"dynatron.me/x/stillbox/pkg/sinks"
|
"dynatron.me/x/stillbox/pkg/sinks"
|
||||||
talkgroups "dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/timeseries"
|
"dynatron.me/x/stillbox/internal/timeseries"
|
||||||
"dynatron.me/x/stillbox/internal/trending"
|
"dynatron.me/x/stillbox/internal/trending"
|
||||||
|
@ -50,7 +52,7 @@ type alerter struct {
|
||||||
alertCache map[talkgroups.ID]alert.Alert
|
alertCache map[talkgroups.ID]alert.Alert
|
||||||
renotify time.Duration
|
renotify time.Duration
|
||||||
notifier notify.Notifier
|
notifier notify.Notifier
|
||||||
tgCache talkgroups.Store
|
tgCache tgstore.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
type offsetClock time.Duration
|
type offsetClock time.Duration
|
||||||
|
@ -85,7 +87,7 @@ func WithNotifier(n notify.Notifier) AlertOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Alerter using the provided configuration.
|
// New creates a new Alerter using the provided configuration.
|
||||||
func New(cfg config.Alerting, tgCache talkgroups.Store, opts ...AlertOption) Alerter {
|
func New(cfg config.Alerting, tgCache tgstore.Store, opts ...AlertOption) Alerter {
|
||||||
if !cfg.Enable {
|
if !cfg.Enable {
|
||||||
return &noopAlerter{}
|
return &noopAlerter{}
|
||||||
}
|
}
|
||||||
|
@ -168,7 +170,7 @@ func (as *alerter) eval(ctx context.Context, now time.Time, testMode bool) ([]al
|
||||||
if s.Score > as.cfg.AlertThreshold || testMode {
|
if s.Score > as.cfg.AlertThreshold || testMode {
|
||||||
if old, inCache := as.alertCache[s.ID]; !inCache || now.Sub(old.Timestamp) > as.renotify {
|
if old, inCache := as.alertCache[s.ID]; !inCache || now.Sub(old.Timestamp) > as.renotify {
|
||||||
s.Score *= as.tgCache.Weight(ctx, s.ID, now)
|
s.Score *= as.tgCache.Weight(ctx, s.ID, now)
|
||||||
a, err := alert.Make(ctx, as.tgCache, s, origScore)
|
a, err := alert.Make(ctx, s, origScore)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("makeAlert: %w", err)
|
return nil, fmt.Errorf("makeAlert: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -200,6 +202,14 @@ func (as *alerter) eval(ctx context.Context, now time.Time, testMode bool) ([]al
|
||||||
func (as *alerter) testNotifyHandler(w http.ResponseWriter, r *http.Request) {
|
func (as *alerter) testNotifyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
|
|
||||||
|
ridx := rand.Intn(len(as.scores))
|
||||||
|
a, err := alert.Make(ctx, as.scores[ridx], 1.0)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("test notify make alert fail")
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
alerts, err := as.eval(ctx, time.Now(), true)
|
alerts, err := as.eval(ctx, time.Now(), true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("test notification eval")
|
log.Error().Err(err).Msg("test notification eval")
|
||||||
|
@ -207,6 +217,8 @@ func (as *alerter) testNotifyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
alerts = append(alerts, a)
|
||||||
|
|
||||||
err = as.notifier.Send(ctx, alerts)
|
err = as.notifier.Send(ctx, alerts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("test notification send")
|
log.Error().Err(err).Msg("test notification send")
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/internal/trending"
|
"dynatron.me/x/stillbox/internal/trending"
|
||||||
"dynatron.me/x/stillbox/pkg/config"
|
"dynatron.me/x/stillbox/pkg/config"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
@ -59,7 +60,7 @@ func (s *Simulation) stepClock(t time.Time) {
|
||||||
// Simulate begins the simulation using the DB handle from ctx. It returns final scores.
|
// Simulate begins the simulation using the DB handle from ctx. It returns final scores.
|
||||||
func (s *Simulation) Simulate(ctx context.Context) (trending.Scores[talkgroups.ID], error) {
|
func (s *Simulation) Simulate(ctx context.Context) (trending.Scores[talkgroups.ID], error) {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
tgc := talkgroups.NewCache()
|
tgc := tgstore.NewCache()
|
||||||
|
|
||||||
s.Enable = true
|
s.Enable = true
|
||||||
s.alerter = New(s.Alerting, tgc, WithClock(&s.clock)).(*alerter)
|
s.alerter = New(s.Alerting, tgc, WithClock(&s.clock)).(*alerter)
|
||||||
|
|
|
@ -40,6 +40,23 @@ type jwtAuth interface {
|
||||||
|
|
||||||
type claims map[string]interface{}
|
type claims map[string]interface{}
|
||||||
|
|
||||||
|
func UIDFrom(ctx context.Context) *int32 {
|
||||||
|
tok, _, err := jwtauth.FromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uidStr := tok.Subject()
|
||||||
|
uidInt, err := strconv.Atoi(uidStr)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := int32(uidInt)
|
||||||
|
|
||||||
|
return &uid
|
||||||
|
}
|
||||||
|
|
||||||
func (a *Auth) Authenticated(r *http.Request) (claims, bool) {
|
func (a *Auth) Authenticated(r *http.Request) (claims, bool) {
|
||||||
// TODO: check IP against ACL, or conf.Public, and against map of routes
|
// TODO: check IP against ACL, or conf.Public, and against map of routes
|
||||||
tok, cl, err := jwtauth.FromContext(r.Context())
|
tok, cl, err := jwtauth.FromContext(r.Context())
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
package calls
|
package calls
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/audio"
|
"dynatron.me/x/stillbox/internal/audio"
|
||||||
"dynatron.me/x/stillbox/pkg/auth"
|
"dynatron.me/x/stillbox/pkg/auth"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
|
||||||
"dynatron.me/x/stillbox/pkg/pb"
|
"dynatron.me/x/stillbox/pkg/pb"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
|
||||||
|
@ -113,21 +111,6 @@ func (c *Call) ToPB() *pb.Call {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Call) LearnTG(ctx context.Context, db database.Store) (learnedId int, err error) {
|
|
||||||
err = db.AddTalkgroupWithLearnedFlag(ctx, int32(c.System), int32(c.Talkgroup))
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("addTalkgroupWithLearnedFlag: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return db.AddLearnedTalkgroup(ctx, database.AddLearnedTalkgroupParams{
|
|
||||||
SystemID: c.System,
|
|
||||||
TGID: c.Talkgroup,
|
|
||||||
Name: c.TalkgroupLabel,
|
|
||||||
AlphaTag: c.TGAlphaTag,
|
|
||||||
TGGroup: c.TalkgroupGroup,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Call) computeLength() (err error) {
|
func (c *Call) computeLength() (err error) {
|
||||||
var td time.Duration
|
var td time.Duration
|
||||||
|
|
||||||
|
|
|
@ -1,113 +0,0 @@
|
||||||
package calls
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
|
||||||
"dynatron.me/x/stillbox/pkg/pb"
|
|
||||||
|
|
||||||
tgs "dynatron.me/x/stillbox/pkg/talkgroups"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TalkgroupFilter struct {
|
|
||||||
Talkgroups []tgs.ID `json:"talkgroups,omitempty"`
|
|
||||||
TalkgroupsNot []tgs.ID `json:"talkgroupsNot,omitempty"`
|
|
||||||
TalkgroupTagsAll []string `json:"talkgroupTagsAll,omitempty"`
|
|
||||||
TalkgroupTagsAny []string `json:"talkgroupTagsAny,omitempty"`
|
|
||||||
TalkgroupTagsNot []string `json:"talkgroupTagsNot,omitempty"`
|
|
||||||
|
|
||||||
talkgroups map[tgs.ID]bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func TalkgroupFilterFromPB(ctx context.Context, p *pb.Filter) (*TalkgroupFilter, error) {
|
|
||||||
tgf := &TalkgroupFilter{
|
|
||||||
TalkgroupTagsAll: p.TalkgroupTagsAll,
|
|
||||||
TalkgroupTagsAny: p.TalkgroupTagsAny,
|
|
||||||
TalkgroupTagsNot: p.TalkgroupTagsNot,
|
|
||||||
}
|
|
||||||
|
|
||||||
if l := len(p.Talkgroups); l > 0 {
|
|
||||||
tgf.Talkgroups = make([]tgs.ID, l)
|
|
||||||
for i, t := range p.Talkgroups {
|
|
||||||
tgf.Talkgroups[i] = tgs.ID{
|
|
||||||
System: uint32(t.System),
|
|
||||||
Talkgroup: uint32(t.Talkgroup),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if l := len(p.TalkgroupsNot); l > 0 {
|
|
||||||
tgf.TalkgroupsNot = make([]tgs.ID, l)
|
|
||||||
for i, t := range p.TalkgroupsNot {
|
|
||||||
tgf.TalkgroupsNot[i] = tgs.ID{
|
|
||||||
System: uint32(t.System),
|
|
||||||
Talkgroup: uint32(t.Talkgroup),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tgf, tgf.compile(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TalkgroupFilter) hasTags() bool {
|
|
||||||
return len(f.TalkgroupTagsAny) > 0 || len(f.TalkgroupTagsAll) > 0 || len(f.TalkgroupTagsNot) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TalkgroupFilter) GetFinalTalkgroups() map[tgs.ID]bool {
|
|
||||||
return f.talkgroups
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TalkgroupFilter) compile(ctx context.Context) error {
|
|
||||||
f.talkgroups = make(map[tgs.ID]bool)
|
|
||||||
for _, tg := range f.Talkgroups {
|
|
||||||
f.talkgroups[tg] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.hasTags() { // don't bother with DB if no tags
|
|
||||||
db := database.FromCtx(ctx)
|
|
||||||
tagTGs, err := db.GetTalkgroupIDsByTags(ctx, f.TalkgroupTagsAny, f.TalkgroupTagsAll, f.TalkgroupTagsNot)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tg := range tagTGs {
|
|
||||||
f.talkgroups[tgs.ID{System: uint32(tg.SystemID), Talkgroup: uint32(tg.TGID)}] = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tg := range f.TalkgroupsNot {
|
|
||||||
f.talkgroups[tg] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *TalkgroupFilter) Test(ctx context.Context, call *Call) bool {
|
|
||||||
if f == nil { // no filter means all calls
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if f.talkgroups == nil {
|
|
||||||
err := f.compile(ctx)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tg := call.TalkgroupTuple()
|
|
||||||
|
|
||||||
tgRes, have := f.talkgroups[tg]
|
|
||||||
if have {
|
|
||||||
return tgRes
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, patch := range call.Patches {
|
|
||||||
tg.Talkgroup = uint32(patch)
|
|
||||||
tgRes, have := f.talkgroups[tg]
|
|
||||||
if have {
|
|
||||||
return tgRes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
|
@ -18,6 +18,83 @@ var (
|
||||||
ErrBatchAlreadyClosed = errors.New("batch already closed")
|
ErrBatchAlreadyClosed = errors.New("batch already closed")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const storeTGVersion = `-- name: StoreTGVersion :batchexec
|
||||||
|
INSERT INTO talkgroup_versions(time, created_by,
|
||||||
|
system_id,
|
||||||
|
tgid,
|
||||||
|
name,
|
||||||
|
alpha_tag,
|
||||||
|
tg_group,
|
||||||
|
frequency,
|
||||||
|
metadata,
|
||||||
|
tags,
|
||||||
|
alert,
|
||||||
|
alert_config,
|
||||||
|
weight,
|
||||||
|
learned
|
||||||
|
) SELECT NOW(), $1,
|
||||||
|
tg.system_id,
|
||||||
|
tg.tgid,
|
||||||
|
tg.name,
|
||||||
|
tg.alpha_tag,
|
||||||
|
tg.tg_group,
|
||||||
|
tg.frequency,
|
||||||
|
tg.metadata,
|
||||||
|
tg.tags,
|
||||||
|
tg.alert,
|
||||||
|
tg.alert_config,
|
||||||
|
tg.weight,
|
||||||
|
tg.learned
|
||||||
|
FROM talkgroups tg WHERE tg.system_id = $2 AND tg.tgid = $3
|
||||||
|
`
|
||||||
|
|
||||||
|
type StoreTGVersionBatchResults struct {
|
||||||
|
br pgx.BatchResults
|
||||||
|
tot int
|
||||||
|
closed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type StoreTGVersionParams struct {
|
||||||
|
Submitter *int32 `json:"submitter"`
|
||||||
|
SystemID int32 `json:"system_id"`
|
||||||
|
TGID int32 `json:"tgid"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) StoreTGVersion(ctx context.Context, arg []StoreTGVersionParams) *StoreTGVersionBatchResults {
|
||||||
|
batch := &pgx.Batch{}
|
||||||
|
for _, a := range arg {
|
||||||
|
vals := []interface{}{
|
||||||
|
a.Submitter,
|
||||||
|
a.SystemID,
|
||||||
|
a.TGID,
|
||||||
|
}
|
||||||
|
batch.Queue(storeTGVersion, vals...)
|
||||||
|
}
|
||||||
|
br := q.db.SendBatch(ctx, batch)
|
||||||
|
return &StoreTGVersionBatchResults{br, len(arg), false}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StoreTGVersionBatchResults) Exec(f func(int, error)) {
|
||||||
|
defer b.br.Close()
|
||||||
|
for t := 0; t < b.tot; t++ {
|
||||||
|
if b.closed {
|
||||||
|
if f != nil {
|
||||||
|
f(t, ErrBatchAlreadyClosed)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
_, err := b.br.Exec()
|
||||||
|
if f != nil {
|
||||||
|
f(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StoreTGVersionBatchResults) Close() error {
|
||||||
|
b.closed = true
|
||||||
|
return b.br.Close()
|
||||||
|
}
|
||||||
|
|
||||||
const upsertTalkgroup = `-- name: UpsertTalkgroup :batchone
|
const upsertTalkgroup = `-- name: UpsertTalkgroup :batchone
|
||||||
INSERT INTO talkgroups AS tg (
|
INSERT INTO talkgroups AS tg (
|
||||||
system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned
|
system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned
|
||||||
|
@ -47,7 +124,7 @@ SET
|
||||||
alert_config = COALESCE($10, tg.alert_config),
|
alert_config = COALESCE($10, tg.alert_config),
|
||||||
weight = COALESCE($11, tg.weight),
|
weight = COALESCE($11, tg.weight),
|
||||||
learned = COALESCE($12, tg.learned)
|
learned = COALESCE($12, tg.learned)
|
||||||
RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned
|
RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned, ignored
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpsertTalkgroupBatchResults struct {
|
type UpsertTalkgroupBatchResults struct {
|
||||||
|
@ -119,6 +196,7 @@ func (b *UpsertTalkgroupBatchResults) QueryRow(f func(int, Talkgroup, error)) {
|
||||||
&i.AlertConfig,
|
&i.AlertConfig,
|
||||||
&i.Weight,
|
&i.Weight,
|
||||||
&i.Learned,
|
&i.Learned,
|
||||||
|
&i.Ignored,
|
||||||
)
|
)
|
||||||
if f != nil {
|
if f != nil {
|
||||||
f(t, i, err)
|
f(t, i, err)
|
||||||
|
|
|
@ -21,8 +21,12 @@ func (g Talkgroup) GetSystem() System { return S
|
||||||
func (g Talkgroup) GetLearned() bool { return false }
|
func (g Talkgroup) GetLearned() bool { return false }
|
||||||
|
|
||||||
func (g Talkgroup) String() string {
|
func (g Talkgroup) String() string {
|
||||||
|
return g.StringTag(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g Talkgroup) StringTag(withTag bool) string {
|
||||||
switch {
|
switch {
|
||||||
case g.AlphaTag != nil:
|
case withTag && g.AlphaTag != nil:
|
||||||
return *g.AlphaTag
|
return *g.AlphaTag
|
||||||
case g.Name != nil && g.TGGroup != nil:
|
case g.Name != nil && g.TGGroup != nil:
|
||||||
return *g.TGGroup + " " + *g.Name
|
return *g.TGGroup + " " + *g.Name
|
||||||
|
|
|
@ -123,22 +123,22 @@ func (_c *Store_AddCall_Call) RunAndReturn(run func(context.Context, database.Ad
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddLearnedTalkgroup provides a mock function with given fields: ctx, arg
|
// AddLearnedTalkgroup provides a mock function with given fields: ctx, arg
|
||||||
func (_m *Store) AddLearnedTalkgroup(ctx context.Context, arg database.AddLearnedTalkgroupParams) (int, error) {
|
func (_m *Store) AddLearnedTalkgroup(ctx context.Context, arg database.AddLearnedTalkgroupParams) (database.Talkgroup, error) {
|
||||||
ret := _m.Called(ctx, arg)
|
ret := _m.Called(ctx, arg)
|
||||||
|
|
||||||
if len(ret) == 0 {
|
if len(ret) == 0 {
|
||||||
panic("no return value specified for AddLearnedTalkgroup")
|
panic("no return value specified for AddLearnedTalkgroup")
|
||||||
}
|
}
|
||||||
|
|
||||||
var r0 int
|
var r0 database.Talkgroup
|
||||||
var r1 error
|
var r1 error
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, database.AddLearnedTalkgroupParams) (int, error)); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, database.AddLearnedTalkgroupParams) (database.Talkgroup, error)); ok {
|
||||||
return rf(ctx, arg)
|
return rf(ctx, arg)
|
||||||
}
|
}
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, database.AddLearnedTalkgroupParams) int); ok {
|
if rf, ok := ret.Get(0).(func(context.Context, database.AddLearnedTalkgroupParams) database.Talkgroup); ok {
|
||||||
r0 = rf(ctx, arg)
|
r0 = rf(ctx, arg)
|
||||||
} else {
|
} else {
|
||||||
r0 = ret.Get(0).(int)
|
r0 = ret.Get(0).(database.Talkgroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
if rf, ok := ret.Get(1).(func(context.Context, database.AddLearnedTalkgroupParams) error); ok {
|
if rf, ok := ret.Get(1).(func(context.Context, database.AddLearnedTalkgroupParams) error); ok {
|
||||||
|
@ -169,60 +169,12 @@ func (_c *Store_AddLearnedTalkgroup_Call) Run(run func(ctx context.Context, arg
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *Store_AddLearnedTalkgroup_Call) Return(_a0 int, _a1 error) *Store_AddLearnedTalkgroup_Call {
|
func (_c *Store_AddLearnedTalkgroup_Call) Return(_a0 database.Talkgroup, _a1 error) *Store_AddLearnedTalkgroup_Call {
|
||||||
_c.Call.Return(_a0, _a1)
|
_c.Call.Return(_a0, _a1)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_c *Store_AddLearnedTalkgroup_Call) RunAndReturn(run func(context.Context, database.AddLearnedTalkgroupParams) (int, error)) *Store_AddLearnedTalkgroup_Call {
|
func (_c *Store_AddLearnedTalkgroup_Call) RunAndReturn(run func(context.Context, database.AddLearnedTalkgroupParams) (database.Talkgroup, error)) *Store_AddLearnedTalkgroup_Call {
|
||||||
_c.Call.Return(run)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTalkgroupWithLearnedFlag provides a mock function with given fields: ctx, systemID, tGID
|
|
||||||
func (_m *Store) AddTalkgroupWithLearnedFlag(ctx context.Context, systemID int32, tGID int32) error {
|
|
||||||
ret := _m.Called(ctx, systemID, tGID)
|
|
||||||
|
|
||||||
if len(ret) == 0 {
|
|
||||||
panic("no return value specified for AddTalkgroupWithLearnedFlag")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r0 error
|
|
||||||
if rf, ok := ret.Get(0).(func(context.Context, int32, int32) error); ok {
|
|
||||||
r0 = rf(ctx, systemID, tGID)
|
|
||||||
} else {
|
|
||||||
r0 = ret.Error(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store_AddTalkgroupWithLearnedFlag_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddTalkgroupWithLearnedFlag'
|
|
||||||
type Store_AddTalkgroupWithLearnedFlag_Call struct {
|
|
||||||
*mock.Call
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddTalkgroupWithLearnedFlag is a helper method to define mock.On call
|
|
||||||
// - ctx context.Context
|
|
||||||
// - systemID int32
|
|
||||||
// - tGID int32
|
|
||||||
func (_e *Store_Expecter) AddTalkgroupWithLearnedFlag(ctx interface{}, systemID interface{}, tGID interface{}) *Store_AddTalkgroupWithLearnedFlag_Call {
|
|
||||||
return &Store_AddTalkgroupWithLearnedFlag_Call{Call: _e.mock.On("AddTalkgroupWithLearnedFlag", ctx, systemID, tGID)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *Store_AddTalkgroupWithLearnedFlag_Call) Run(run func(ctx context.Context, systemID int32, tGID int32)) *Store_AddTalkgroupWithLearnedFlag_Call {
|
|
||||||
_c.Call.Run(func(args mock.Arguments) {
|
|
||||||
run(args[0].(context.Context), args[1].(int32), args[2].(int32))
|
|
||||||
})
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *Store_AddTalkgroupWithLearnedFlag_Call) Return(_a0 error) *Store_AddTalkgroupWithLearnedFlag_Call {
|
|
||||||
_c.Call.Return(_a0)
|
|
||||||
return _c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_c *Store_AddTalkgroupWithLearnedFlag_Call) RunAndReturn(run func(context.Context, int32, int32) error) *Store_AddTalkgroupWithLearnedFlag_Call {
|
|
||||||
_c.Call.Return(run)
|
_c.Call.Return(run)
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
@ -1569,6 +1521,63 @@ func (_c *Store_InTx_Call) RunAndReturn(run func(context.Context, func(database.
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RestoreTalkgroupVersion provides a mock function with given fields: ctx, versionIds
|
||||||
|
func (_m *Store) RestoreTalkgroupVersion(ctx context.Context, versionIds int) (database.Talkgroup, error) {
|
||||||
|
ret := _m.Called(ctx, versionIds)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for RestoreTalkgroupVersion")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 database.Talkgroup
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, int) (database.Talkgroup, error)); ok {
|
||||||
|
return rf(ctx, versionIds)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, int) database.Talkgroup); ok {
|
||||||
|
r0 = rf(ctx, versionIds)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(database.Talkgroup)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, int) error); ok {
|
||||||
|
r1 = rf(ctx, versionIds)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store_RestoreTalkgroupVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'RestoreTalkgroupVersion'
|
||||||
|
type Store_RestoreTalkgroupVersion_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreTalkgroupVersion is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - versionIds int
|
||||||
|
func (_e *Store_Expecter) RestoreTalkgroupVersion(ctx interface{}, versionIds interface{}) *Store_RestoreTalkgroupVersion_Call {
|
||||||
|
return &Store_RestoreTalkgroupVersion_Call{Call: _e.mock.On("RestoreTalkgroupVersion", ctx, versionIds)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_RestoreTalkgroupVersion_Call) Run(run func(ctx context.Context, versionIds int)) *Store_RestoreTalkgroupVersion_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(int))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_RestoreTalkgroupVersion_Call) Return(_a0 database.Talkgroup, _a1 error) *Store_RestoreTalkgroupVersion_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_RestoreTalkgroupVersion_Call) RunAndReturn(run func(context.Context, int) (database.Talkgroup, error)) *Store_RestoreTalkgroupVersion_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// SetCallTranscript provides a mock function with given fields: ctx, iD, transcript
|
// SetCallTranscript provides a mock function with given fields: ctx, iD, transcript
|
||||||
func (_m *Store) SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error {
|
func (_m *Store) SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error {
|
||||||
ret := _m.Called(ctx, iD, transcript)
|
ret := _m.Called(ctx, iD, transcript)
|
||||||
|
@ -1666,6 +1675,55 @@ func (_c *Store_SetTalkgroupTags_Call) RunAndReturn(run func(context.Context, []
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StoreTGVersion provides a mock function with given fields: ctx, arg
|
||||||
|
func (_m *Store) StoreTGVersion(ctx context.Context, arg []database.StoreTGVersionParams) *database.StoreTGVersionBatchResults {
|
||||||
|
ret := _m.Called(ctx, arg)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for StoreTGVersion")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 *database.StoreTGVersionBatchResults
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, []database.StoreTGVersionParams) *database.StoreTGVersionBatchResults); ok {
|
||||||
|
r0 = rf(ctx, arg)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).(*database.StoreTGVersionBatchResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store_StoreTGVersion_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'StoreTGVersion'
|
||||||
|
type Store_StoreTGVersion_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// StoreTGVersion is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - arg []database.StoreTGVersionParams
|
||||||
|
func (_e *Store_Expecter) StoreTGVersion(ctx interface{}, arg interface{}) *Store_StoreTGVersion_Call {
|
||||||
|
return &Store_StoreTGVersion_Call{Call: _e.mock.On("StoreTGVersion", ctx, arg)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_StoreTGVersion_Call) Run(run func(ctx context.Context, arg []database.StoreTGVersionParams)) *Store_StoreTGVersion_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].([]database.StoreTGVersionParams))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_StoreTGVersion_Call) Return(_a0 *database.StoreTGVersionBatchResults) *Store_StoreTGVersion_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_StoreTGVersion_Call) RunAndReturn(run func(context.Context, []database.StoreTGVersionParams) *database.StoreTGVersionBatchResults) *Store_StoreTGVersion_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// UpdatePassword provides a mock function with given fields: ctx, username, password
|
// UpdatePassword provides a mock function with given fields: ctx, username, password
|
||||||
func (_m *Store) UpdatePassword(ctx context.Context, username string, password string) error {
|
func (_m *Store) UpdatePassword(ctx context.Context, username string, password string) error {
|
||||||
ret := _m.Called(ctx, username, password)
|
ret := _m.Called(ctx, username, password)
|
||||||
|
|
|
@ -96,15 +96,25 @@ type Talkgroup struct {
|
||||||
AlertConfig rules.AlertRules `json:"alert_config"`
|
AlertConfig rules.AlertRules `json:"alert_config"`
|
||||||
Weight float32 `json:"weight"`
|
Weight float32 `json:"weight"`
|
||||||
Learned bool `json:"learned"`
|
Learned bool `json:"learned"`
|
||||||
|
Ignored bool `json:"ignored"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TalkgroupsLearned struct {
|
type TalkgroupVersion struct {
|
||||||
ID int `json:"id"`
|
ID int `json:"id"`
|
||||||
SystemID int `json:"system_id"`
|
Time pgtype.Timestamptz `json:"time"`
|
||||||
TGID int `json:"tgid"`
|
CreatedBy *int32 `json:"created_by"`
|
||||||
Name string `json:"name"`
|
SystemID *int32 `json:"system_id"`
|
||||||
|
TGID *int32 `json:"tgid"`
|
||||||
|
Name *string `json:"name"`
|
||||||
AlphaTag *string `json:"alpha_tag"`
|
AlphaTag *string `json:"alpha_tag"`
|
||||||
TGGroup *string `json:"tg_group"`
|
TGGroup *string `json:"tg_group"`
|
||||||
|
Frequency *int32 `json:"frequency"`
|
||||||
|
Metadata []byte `json:"metadata"`
|
||||||
|
Tags []string `json:"tags"`
|
||||||
|
Alert *bool `json:"alert"`
|
||||||
|
AlertConfig []byte `json:"alert_config"`
|
||||||
|
Weight *float32 `json:"weight"`
|
||||||
|
Learned *bool `json:"learned"`
|
||||||
Ignored *bool `json:"ignored"`
|
Ignored *bool `json:"ignored"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,7 @@ import (
|
||||||
type Querier interface {
|
type Querier interface {
|
||||||
AddAlert(ctx context.Context, arg AddAlertParams) error
|
AddAlert(ctx context.Context, arg AddAlertParams) error
|
||||||
AddCall(ctx context.Context, arg AddCallParams) error
|
AddCall(ctx context.Context, arg AddCallParams) error
|
||||||
AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (int, error)
|
AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (Talkgroup, error)
|
||||||
AddTalkgroupWithLearnedFlag(ctx context.Context, systemID int32, tGID int32) error
|
|
||||||
CreateAPIKey(ctx context.Context, owner int, expires pgtype.Timestamp, disabled *bool) (ApiKey, error)
|
CreateAPIKey(ctx context.Context, owner int, expires pgtype.Timestamp, disabled *bool) (ApiKey, error)
|
||||||
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
|
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
|
||||||
DeleteAPIKey(ctx context.Context, apiKey string) error
|
DeleteAPIKey(ctx context.Context, apiKey string) error
|
||||||
|
@ -35,8 +34,10 @@ type Querier interface {
|
||||||
GetUserByUID(ctx context.Context, id int) (User, error)
|
GetUserByUID(ctx context.Context, id int) (User, error)
|
||||||
GetUserByUsername(ctx context.Context, username string) (User, error)
|
GetUserByUsername(ctx context.Context, username string) (User, error)
|
||||||
GetUsers(ctx context.Context) ([]User, error)
|
GetUsers(ctx context.Context) ([]User, error)
|
||||||
|
RestoreTalkgroupVersion(ctx context.Context, versionIds int) (Talkgroup, error)
|
||||||
SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error
|
SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error
|
||||||
SetTalkgroupTags(ctx context.Context, tags []string, systemID int32, tGID int32) error
|
SetTalkgroupTags(ctx context.Context, tags []string, systemID int32, tGID int32) error
|
||||||
|
StoreTGVersion(ctx context.Context, arg []StoreTGVersionParams) *StoreTGVersionBatchResults
|
||||||
UpdatePassword(ctx context.Context, username string, password string) error
|
UpdatePassword(ctx context.Context, username string, password string) error
|
||||||
UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams) (Talkgroup, error)
|
UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams) (Talkgroup, error)
|
||||||
UpsertTalkgroup(ctx context.Context, arg []UpsertTalkgroupParams) *UpsertTalkgroupBatchResults
|
UpsertTalkgroup(ctx context.Context, arg []UpsertTalkgroupParams) *UpsertTalkgroupBatchResults
|
||||||
|
|
|
@ -41,20 +41,10 @@ func (t *TGTuples) Append(sys, tg uint32) {
|
||||||
// Below queries are here because sqlc refuses to parse unnest(x, y)
|
// Below queries are here because sqlc refuses to parse unnest(x, y)
|
||||||
|
|
||||||
const getTalkgroupsWithLearnedBySysTGID = `SELECT
|
const getTalkgroupsWithLearnedBySysTGID = `SELECT
|
||||||
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, sys.id, sys.name, tg.learned
|
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, sys.id, sys.name, tg.learned, tg.ignored
|
||||||
FROM talkgroups tg
|
FROM talkgroups tg
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
JOIN UNNEST($1::INT4[], $2::INT4[]) AS tgt(sys, tg) ON (tg.system_id = tgt.sys AND tg.tgid = tgt.tg)
|
JOIN UNNEST($1::INT4[], $2::INT4[]) AS tgt(sys, tg) ON (tg.system_id = tgt.sys AND tg.tgid = tgt.tg);`
|
||||||
WHERE tg.learned IS NOT TRUE
|
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END,
|
|
||||||
NOT tgl.ignored, NULL::JSONB, 1.0, sys.id, sys.name, TRUE learned
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
JOIN UNNEST($1::INT4[], $2::INT4[]) AS tgt(sys, tg) ON (tgl.system_id = tgt.sys AND tgl.tgid = tgt.tg);`
|
|
||||||
|
|
||||||
type GetTalkgroupsRow struct {
|
type GetTalkgroupsRow struct {
|
||||||
Talkgroup Talkgroup `json:"talkgroup"`
|
Talkgroup Talkgroup `json:"talkgroup"`
|
||||||
|
@ -86,6 +76,7 @@ func (q *Queries) GetTalkgroupsWithLearnedBySysTGID(ctx context.Context, ids TGT
|
||||||
&i.System.ID,
|
&i.System.ID,
|
||||||
&i.System.Name,
|
&i.System.Name,
|
||||||
&i.Talkgroup.Learned,
|
&i.Talkgroup.Learned,
|
||||||
|
&i.Talkgroup.Ignored,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -97,7 +88,7 @@ func (q *Queries) GetTalkgroupsWithLearnedBySysTGID(ctx context.Context, ids TGT
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTalkgroupsBySysTGID = `SELECT tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, sys.id, sys.name FROM talkgroups tg
|
const getTalkgroupsBySysTGID = `SELECT tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, tg.ignored, sys.id, sys.name FROM talkgroups tg
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
JOIN UNNEST($1::INT4[], $2::INT4[]) AS tgt(sys, tg) ON (tg.system_id = tgt.sys AND tg.tgid = tgt.tg)
|
JOIN UNNEST($1::INT4[], $2::INT4[]) AS tgt(sys, tg) ON (tg.system_id = tgt.sys AND tg.tgid = tgt.tg)
|
||||||
WHERE tg.learned IS NOT TRUE;`
|
WHERE tg.learned IS NOT TRUE;`
|
||||||
|
@ -124,6 +115,8 @@ func (q *Queries) GetTalkgroupsBySysTGID(ctx context.Context, ids TGTuples) ([]G
|
||||||
&i.Talkgroup.Alert,
|
&i.Talkgroup.Alert,
|
||||||
&i.Talkgroup.AlertConfig,
|
&i.Talkgroup.AlertConfig,
|
||||||
&i.Talkgroup.Weight,
|
&i.Talkgroup.Weight,
|
||||||
|
&i.Talkgroup.Learned,
|
||||||
|
&i.Talkgroup.Ignored,
|
||||||
&i.System.ID,
|
&i.System.ID,
|
||||||
&i.System.Name,
|
&i.System.Name,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
|
|
@ -13,30 +13,32 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const addLearnedTalkgroup = `-- name: AddLearnedTalkgroup :one
|
const addLearnedTalkgroup = `-- name: AddLearnedTalkgroup :one
|
||||||
INSERT INTO talkgroups_learned(
|
INSERT INTO talkgroups(
|
||||||
system_id,
|
system_id,
|
||||||
tgid,
|
tgid,
|
||||||
|
learned,
|
||||||
name,
|
name,
|
||||||
alpha_tag,
|
alpha_tag,
|
||||||
tg_group
|
tg_group
|
||||||
) VALUES (
|
) VALUES (
|
||||||
$1,
|
$1,
|
||||||
$2,
|
$2,
|
||||||
|
TRUE,
|
||||||
$3,
|
$3,
|
||||||
$4,
|
$4,
|
||||||
$5
|
$5
|
||||||
) RETURNING id
|
) RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned, ignored
|
||||||
`
|
`
|
||||||
|
|
||||||
type AddLearnedTalkgroupParams struct {
|
type AddLearnedTalkgroupParams struct {
|
||||||
SystemID int `json:"system_id"`
|
SystemID int32 `json:"system_id"`
|
||||||
TGID int `json:"tgid"`
|
TGID int32 `json:"tgid"`
|
||||||
Name *string `json:"name"`
|
Name *string `json:"name"`
|
||||||
AlphaTag *string `json:"alpha_tag"`
|
AlphaTag *string `json:"alpha_tag"`
|
||||||
TGGroup *string `json:"tg_group"`
|
TGGroup *string `json:"tg_group"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (int, error) {
|
func (q *Queries) AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (Talkgroup, error) {
|
||||||
row := q.db.QueryRow(ctx, addLearnedTalkgroup,
|
row := q.db.QueryRow(ctx, addLearnedTalkgroup,
|
||||||
arg.SystemID,
|
arg.SystemID,
|
||||||
arg.TGID,
|
arg.TGID,
|
||||||
|
@ -44,26 +46,24 @@ func (q *Queries) AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgro
|
||||||
arg.AlphaTag,
|
arg.AlphaTag,
|
||||||
arg.TGGroup,
|
arg.TGGroup,
|
||||||
)
|
)
|
||||||
var id int
|
var i Talkgroup
|
||||||
err := row.Scan(&id)
|
err := row.Scan(
|
||||||
return id, err
|
&i.ID,
|
||||||
}
|
&i.SystemID,
|
||||||
|
&i.TGID,
|
||||||
const addTalkgroupWithLearnedFlag = `-- name: AddTalkgroupWithLearnedFlag :exec
|
&i.Name,
|
||||||
INSERT INTO talkgroups (
|
&i.AlphaTag,
|
||||||
system_id,
|
&i.TGGroup,
|
||||||
tgid,
|
&i.Frequency,
|
||||||
learned
|
&i.Metadata,
|
||||||
) VALUES(
|
&i.Tags,
|
||||||
$1,
|
&i.Alert,
|
||||||
$2,
|
&i.AlertConfig,
|
||||||
TRUE
|
&i.Weight,
|
||||||
|
&i.Learned,
|
||||||
|
&i.Ignored,
|
||||||
)
|
)
|
||||||
`
|
return i, err
|
||||||
|
|
||||||
func (q *Queries) AddTalkgroupWithLearnedFlag(ctx context.Context, systemID int32, tGID int32) error {
|
|
||||||
_, err := q.db.Exec(ctx, addTalkgroupWithLearnedFlag, systemID, tGID)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getSystemName = `-- name: GetSystemName :one
|
const getSystemName = `-- name: GetSystemName :one
|
||||||
|
@ -78,7 +78,7 @@ func (q *Queries) GetSystemName(ctx context.Context, systemID int) (string, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTalkgroup = `-- name: GetTalkgroup :one
|
const getTalkgroup = `-- name: GetTalkgroup :one
|
||||||
SELECT talkgroups.id, talkgroups.system_id, talkgroups.tgid, talkgroups.name, talkgroups.alpha_tag, talkgroups.tg_group, talkgroups.frequency, talkgroups.metadata, talkgroups.tags, talkgroups.alert, talkgroups.alert_config, talkgroups.weight, talkgroups.learned FROM talkgroups
|
SELECT talkgroups.id, talkgroups.system_id, talkgroups.tgid, talkgroups.name, talkgroups.alpha_tag, talkgroups.tg_group, talkgroups.frequency, talkgroups.metadata, talkgroups.tags, talkgroups.alert, talkgroups.alert_config, talkgroups.weight, talkgroups.learned, talkgroups.ignored FROM talkgroups
|
||||||
WHERE (system_id, tgid) = ($1, $2)
|
WHERE (system_id, tgid) = ($1, $2)
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -103,6 +103,7 @@ func (q *Queries) GetTalkgroup(ctx context.Context, systemID int32, tGID int32)
|
||||||
&i.Talkgroup.AlertConfig,
|
&i.Talkgroup.AlertConfig,
|
||||||
&i.Talkgroup.Weight,
|
&i.Talkgroup.Weight,
|
||||||
&i.Talkgroup.Learned,
|
&i.Talkgroup.Learned,
|
||||||
|
&i.Talkgroup.Ignored,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
@ -153,19 +154,10 @@ func (q *Queries) GetTalkgroupTags(ctx context.Context, systemID int32, tGID int
|
||||||
|
|
||||||
const getTalkgroupWithLearned = `-- name: GetTalkgroupWithLearned :one
|
const getTalkgroupWithLearned = `-- name: GetTalkgroupWithLearned :one
|
||||||
SELECT
|
SELECT
|
||||||
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, sys.id, sys.name
|
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, tg.ignored, sys.id, sys.name
|
||||||
FROM talkgroups tg
|
FROM talkgroups tg
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
WHERE (tg.system_id, tg.tgid) = ($1, $2) AND tg.learned IS NOT TRUE
|
WHERE (tg.system_id, tg.tgid) = ($1, $2)
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END,
|
|
||||||
NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
WHERE tgl.system_id = $1 AND tgl.tgid = $2 AND ignored IS NOT TRUE
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetTalkgroupWithLearnedRow struct {
|
type GetTalkgroupWithLearnedRow struct {
|
||||||
|
@ -190,6 +182,7 @@ func (q *Queries) GetTalkgroupWithLearned(ctx context.Context, systemID int32, t
|
||||||
&i.Talkgroup.AlertConfig,
|
&i.Talkgroup.AlertConfig,
|
||||||
&i.Talkgroup.Weight,
|
&i.Talkgroup.Weight,
|
||||||
&i.Talkgroup.Learned,
|
&i.Talkgroup.Learned,
|
||||||
|
&i.Talkgroup.Ignored,
|
||||||
&i.System.ID,
|
&i.System.ID,
|
||||||
&i.System.Name,
|
&i.System.Name,
|
||||||
)
|
)
|
||||||
|
@ -197,7 +190,7 @@ func (q *Queries) GetTalkgroupWithLearned(ctx context.Context, systemID int32, t
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTalkgroupsWithAllTags = `-- name: GetTalkgroupsWithAllTags :many
|
const getTalkgroupsWithAllTags = `-- name: GetTalkgroupsWithAllTags :many
|
||||||
SELECT talkgroups.id, talkgroups.system_id, talkgroups.tgid, talkgroups.name, talkgroups.alpha_tag, talkgroups.tg_group, talkgroups.frequency, talkgroups.metadata, talkgroups.tags, talkgroups.alert, talkgroups.alert_config, talkgroups.weight, talkgroups.learned FROM talkgroups
|
SELECT talkgroups.id, talkgroups.system_id, talkgroups.tgid, talkgroups.name, talkgroups.alpha_tag, talkgroups.tg_group, talkgroups.frequency, talkgroups.metadata, talkgroups.tags, talkgroups.alert, talkgroups.alert_config, talkgroups.weight, talkgroups.learned, talkgroups.ignored FROM talkgroups
|
||||||
WHERE tags && ARRAY[$1]
|
WHERE tags && ARRAY[$1]
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -228,6 +221,7 @@ func (q *Queries) GetTalkgroupsWithAllTags(ctx context.Context, tags []string) (
|
||||||
&i.Talkgroup.AlertConfig,
|
&i.Talkgroup.AlertConfig,
|
||||||
&i.Talkgroup.Weight,
|
&i.Talkgroup.Weight,
|
||||||
&i.Talkgroup.Learned,
|
&i.Talkgroup.Learned,
|
||||||
|
&i.Talkgroup.Ignored,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -240,7 +234,7 @@ func (q *Queries) GetTalkgroupsWithAllTags(ctx context.Context, tags []string) (
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTalkgroupsWithAnyTags = `-- name: GetTalkgroupsWithAnyTags :many
|
const getTalkgroupsWithAnyTags = `-- name: GetTalkgroupsWithAnyTags :many
|
||||||
SELECT talkgroups.id, talkgroups.system_id, talkgroups.tgid, talkgroups.name, talkgroups.alpha_tag, talkgroups.tg_group, talkgroups.frequency, talkgroups.metadata, talkgroups.tags, talkgroups.alert, talkgroups.alert_config, talkgroups.weight, talkgroups.learned FROM talkgroups
|
SELECT talkgroups.id, talkgroups.system_id, talkgroups.tgid, talkgroups.name, talkgroups.alpha_tag, talkgroups.tg_group, talkgroups.frequency, talkgroups.metadata, talkgroups.tags, talkgroups.alert, talkgroups.alert_config, talkgroups.weight, talkgroups.learned, talkgroups.ignored FROM talkgroups
|
||||||
WHERE tags @> ARRAY[$1]
|
WHERE tags @> ARRAY[$1]
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -271,6 +265,7 @@ func (q *Queries) GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) (
|
||||||
&i.Talkgroup.AlertConfig,
|
&i.Talkgroup.AlertConfig,
|
||||||
&i.Talkgroup.Weight,
|
&i.Talkgroup.Weight,
|
||||||
&i.Talkgroup.Learned,
|
&i.Talkgroup.Learned,
|
||||||
|
&i.Talkgroup.Ignored,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -284,18 +279,9 @@ func (q *Queries) GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) (
|
||||||
|
|
||||||
const getTalkgroupsWithLearned = `-- name: GetTalkgroupsWithLearned :many
|
const getTalkgroupsWithLearned = `-- name: GetTalkgroupsWithLearned :many
|
||||||
SELECT
|
SELECT
|
||||||
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, sys.id, sys.name
|
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, tg.ignored, sys.id, sys.name
|
||||||
FROM talkgroups tg
|
FROM talkgroups tg
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
WHERE tg.learned IS NOT TRUE
|
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END,
|
|
||||||
NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
WHERE ignored IS NOT TRUE
|
WHERE ignored IS NOT TRUE
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -327,6 +313,7 @@ func (q *Queries) GetTalkgroupsWithLearned(ctx context.Context) ([]GetTalkgroups
|
||||||
&i.Talkgroup.AlertConfig,
|
&i.Talkgroup.AlertConfig,
|
||||||
&i.Talkgroup.Weight,
|
&i.Talkgroup.Weight,
|
||||||
&i.Talkgroup.Learned,
|
&i.Talkgroup.Learned,
|
||||||
|
&i.Talkgroup.Ignored,
|
||||||
&i.System.ID,
|
&i.System.ID,
|
||||||
&i.System.Name,
|
&i.System.Name,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
@ -342,19 +329,10 @@ func (q *Queries) GetTalkgroupsWithLearned(ctx context.Context) ([]GetTalkgroups
|
||||||
|
|
||||||
const getTalkgroupsWithLearnedBySystem = `-- name: GetTalkgroupsWithLearnedBySystem :many
|
const getTalkgroupsWithLearnedBySystem = `-- name: GetTalkgroupsWithLearnedBySystem :many
|
||||||
SELECT
|
SELECT
|
||||||
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, sys.id, sys.name
|
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, tg.ignored, sys.id, sys.name
|
||||||
FROM talkgroups tg
|
FROM talkgroups tg
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
WHERE tg.system_id = $1 AND tg.learned IS NOT TRUE
|
WHERE tg.system_id = $1
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END,
|
|
||||||
NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
WHERE tgl.system_id = $1 AND ignored IS NOT TRUE
|
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetTalkgroupsWithLearnedBySystemRow struct {
|
type GetTalkgroupsWithLearnedBySystemRow struct {
|
||||||
|
@ -385,6 +363,7 @@ func (q *Queries) GetTalkgroupsWithLearnedBySystem(ctx context.Context, system i
|
||||||
&i.Talkgroup.AlertConfig,
|
&i.Talkgroup.AlertConfig,
|
||||||
&i.Talkgroup.Weight,
|
&i.Talkgroup.Weight,
|
||||||
&i.Talkgroup.Learned,
|
&i.Talkgroup.Learned,
|
||||||
|
&i.Talkgroup.Ignored,
|
||||||
&i.System.ID,
|
&i.System.ID,
|
||||||
&i.System.Name,
|
&i.System.Name,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
|
@ -398,6 +377,73 @@ func (q *Queries) GetTalkgroupsWithLearnedBySystem(ctx context.Context, system i
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const restoreTalkgroupVersion = `-- name: RestoreTalkgroupVersion :one
|
||||||
|
INSERT INTO talkgroups(
|
||||||
|
system_id,
|
||||||
|
tgid,
|
||||||
|
name,
|
||||||
|
alpha_tag,
|
||||||
|
tg_group,
|
||||||
|
frequency,
|
||||||
|
metadata,
|
||||||
|
tags,
|
||||||
|
alert,
|
||||||
|
alert_config,
|
||||||
|
weight,
|
||||||
|
learned,
|
||||||
|
ignored
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
system_id,
|
||||||
|
tgid,
|
||||||
|
name,
|
||||||
|
alpha_tag,
|
||||||
|
tg_group,
|
||||||
|
frequency,
|
||||||
|
metadata,
|
||||||
|
tags,
|
||||||
|
alert,
|
||||||
|
alert_config,
|
||||||
|
weight,
|
||||||
|
learned,
|
||||||
|
ignored
|
||||||
|
FROM talkgroup_versions tgv ON CONFLICT (system_id, tgid) DO UPDATE SET
|
||||||
|
name = excluded.name,
|
||||||
|
alpha_tag = excluded.alpha_tag,
|
||||||
|
tg_group = excluded.tg_group,
|
||||||
|
metadata = excluded.metadata,
|
||||||
|
tags = excluded.tags,
|
||||||
|
alert = excluded.alert,
|
||||||
|
alert_config = excluded.alert_config,
|
||||||
|
weight = excluded.weight,
|
||||||
|
learned = excluded.learner,
|
||||||
|
ignored = excluded.ignored
|
||||||
|
WHERE tgv.id = ANY($1)
|
||||||
|
RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned, ignored
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) RestoreTalkgroupVersion(ctx context.Context, versionIds int) (Talkgroup, error) {
|
||||||
|
row := q.db.QueryRow(ctx, restoreTalkgroupVersion, versionIds)
|
||||||
|
var i Talkgroup
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.SystemID,
|
||||||
|
&i.TGID,
|
||||||
|
&i.Name,
|
||||||
|
&i.AlphaTag,
|
||||||
|
&i.TGGroup,
|
||||||
|
&i.Frequency,
|
||||||
|
&i.Metadata,
|
||||||
|
&i.Tags,
|
||||||
|
&i.Alert,
|
||||||
|
&i.AlertConfig,
|
||||||
|
&i.Weight,
|
||||||
|
&i.Learned,
|
||||||
|
&i.Ignored,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const setTalkgroupTags = `-- name: SetTalkgroupTags :exec
|
const setTalkgroupTags = `-- name: SetTalkgroupTags :exec
|
||||||
UPDATE talkgroups SET tags = $1
|
UPDATE talkgroups SET tags = $1
|
||||||
WHERE system_id = $2 AND tgid = $3
|
WHERE system_id = $2 AND tgid = $3
|
||||||
|
@ -422,7 +468,7 @@ SET
|
||||||
weight = COALESCE($9, weight),
|
weight = COALESCE($9, weight),
|
||||||
learned = COALESCE($10, learned)
|
learned = COALESCE($10, learned)
|
||||||
WHERE id = $11 OR (system_id = $12 AND tgid = $13)
|
WHERE id = $11 OR (system_id = $12 AND tgid = $13)
|
||||||
RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned
|
RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned, ignored
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateTalkgroupParams struct {
|
type UpdateTalkgroupParams struct {
|
||||||
|
@ -472,6 +518,7 @@ func (q *Queries) UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams
|
||||||
&i.AlertConfig,
|
&i.AlertConfig,
|
||||||
&i.Weight,
|
&i.Weight,
|
||||||
&i.Learned,
|
&i.Learned,
|
||||||
|
&i.Ignored,
|
||||||
)
|
)
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
const getTalkgroupWithLearnedTest = `-- name: GetTalkgroupWithLearned :one
|
|
||||||
SELECT
|
|
||||||
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, sys.id, sys.name
|
|
||||||
FROM talkgroups tg
|
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
|
||||||
WHERE (tg.system_id, tg.tgid) = ($1, $2) AND tg.learned IS NOT TRUE
|
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END,
|
|
||||||
NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
WHERE tgl.system_id = $1 AND tgl.tgid = $2 AND ignored IS NOT TRUE
|
|
||||||
`
|
|
||||||
|
|
||||||
const getTalkgroupsWithLearnedBySystemTest = `-- name: GetTalkgroupsWithLearnedBySystem :many
|
|
||||||
SELECT
|
|
||||||
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, sys.id, sys.name
|
|
||||||
FROM talkgroups tg
|
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
|
||||||
WHERE tg.system_id = $1 AND tg.learned IS NOT TRUE
|
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END,
|
|
||||||
NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
WHERE tgl.system_id = $1 AND ignored IS NOT TRUE
|
|
||||||
`
|
|
||||||
|
|
||||||
const getTalkgroupsWithLearnedTest = `-- name: GetTalkgroupsWithLearned :many
|
|
||||||
SELECT
|
|
||||||
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, sys.id, sys.name
|
|
||||||
FROM talkgroups tg
|
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
|
||||||
WHERE tg.learned IS NOT TRUE
|
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END,
|
|
||||||
NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
WHERE ignored IS NOT TRUE
|
|
||||||
`
|
|
||||||
|
|
||||||
func TestQueryColumnsMatch(t *testing.T) {
|
|
||||||
assert.Equal(t, getTalkgroupWithLearnedTest, getTalkgroupWithLearned)
|
|
||||||
assert.Equal(t, getTalkgroupsWithLearnedBySystemTest, getTalkgroupsWithLearnedBySystem)
|
|
||||||
assert.Equal(t, getTalkgroupsWithLearnedTest, getTalkgroupsWithLearned)
|
|
||||||
}
|
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/version"
|
"dynatron.me/x/stillbox/internal/version"
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/pb"
|
"dynatron.me/x/stillbox/pkg/pb"
|
||||||
|
tgfilter "dynatron.me/x/stillbox/pkg/talkgroups/filter"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
@ -33,7 +33,7 @@ type client struct {
|
||||||
Connection
|
Connection
|
||||||
|
|
||||||
liveState pb.LiveState
|
liveState pb.LiveState
|
||||||
filter *calls.TalkgroupFilter
|
filter *tgfilter.TalkgroupFilter
|
||||||
|
|
||||||
nexus *Nexus
|
nexus *Nexus
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@ package nexus
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
|
||||||
"dynatron.me/x/stillbox/pkg/pb"
|
"dynatron.me/x/stillbox/pkg/pb"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
tgfilter "dynatron.me/x/stillbox/pkg/talkgroups/filter"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
|
@ -59,9 +60,9 @@ func (c *client) SendError(cmd *pb.Command, err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) Talkgroup(ctx context.Context, tg *pb.Talkgroup) error {
|
func (c *client) Talkgroup(ctx context.Context, tg *pb.Talkgroup) error {
|
||||||
tgi, err := talkgroups.StoreFrom(ctx).TG(ctx, talkgroups.TG(tg.System, tg.Talkgroup))
|
tgi, err := tgstore.FromCtx(ctx).TG(ctx, talkgroups.TG(tg.System, tg.Talkgroup))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != talkgroups.ErrNotFound {
|
if err != tgstore.ErrNotFound {
|
||||||
log.Error().Err(err).Int32("sys", tg.System).Int32("tg", tg.Talkgroup).Msg("get talkgroup fail")
|
log.Error().Err(err).Int32("sys", tg.System).Int32("tg", tg.Talkgroup).Msg("get talkgroup fail")
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
|
@ -110,7 +111,7 @@ func (c *client) Live(ctx context.Context, cmd *pb.Live) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cmd.Filter != nil {
|
if cmd.Filter != nil {
|
||||||
filter, err := calls.TalkgroupFilterFromPB(ctx, cmd.Filter)
|
filter, err := tgfilter.TalkgroupFilterFromPB(ctx, cmd.Filter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("filter create failed")
|
log.Error().Err(err).Msg("filter create failed")
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -52,7 +52,7 @@ var alertFm = template.FuncMap{
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultBodyTemplStr = `{{ range . -}}
|
defaultBodyTemplStr = `{{ range . -}}
|
||||||
{{ .TGName }} is active with a score of {{ f .Score.Score 4 }}! ({{ f .Score.RecentCount 0 }}/{{ .Score.Count }} recent calls)
|
{{ .TGName }}{{ if (and .Talkgroup .Talkgroup.AlphaTag) }} ({{ .Talkgroup.StringTag false -}}){{ end }} is active with a score of {{ f .Score.Score 4 }}! ({{ f .Score.RecentCount 0 }}/{{ .Score.Count }} recent calls)
|
||||||
|
|
||||||
{{ end -}}`
|
{{ end -}}`
|
||||||
defaultSubjectTemplStr = `Stillbox Alert ({{ highest . }})`
|
defaultSubjectTemplStr = `Stillbox Alert ({{ highest . }})`
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/render"
|
"github.com/go-chi/render"
|
||||||
|
@ -67,6 +67,14 @@ func recordNotFound(err error) render.Renderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func errTextNotFound(err error) render.Renderer {
|
||||||
|
return &errResponse{
|
||||||
|
Err: err,
|
||||||
|
Code: http.StatusNotFound,
|
||||||
|
Error: "Record not found: " + err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func internalError(err error) render.Renderer {
|
func internalError(err error) render.Renderer {
|
||||||
return &errResponse{
|
return &errResponse{
|
||||||
Err: err,
|
Err: err,
|
||||||
|
@ -78,8 +86,8 @@ func internalError(err error) render.Renderer {
|
||||||
type errResponder func(error) render.Renderer
|
type errResponder func(error) render.Renderer
|
||||||
|
|
||||||
var statusMapping = map[error]errResponder{
|
var statusMapping = map[error]errResponder{
|
||||||
talkgroups.ErrNoSuchSystem: recordNotFound,
|
tgstore.ErrNoSuchSystem: errTextNotFound,
|
||||||
talkgroups.ErrNotFound: recordNotFound,
|
tgstore.ErrNotFound: errTextNotFound,
|
||||||
pgx.ErrNoRows: recordNotFound,
|
pgx.ErrNoRows: recordNotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,8 @@ import (
|
||||||
"dynatron.me/x/stillbox/internal/forms"
|
"dynatron.me/x/stillbox/internal/forms"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups/importer"
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/xport"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
@ -53,7 +54,7 @@ func (t tgParams) ToID() talkgroups.ID {
|
||||||
|
|
||||||
func (tga *talkgroupAPI) get(w http.ResponseWriter, r *http.Request) {
|
func (tga *talkgroupAPI) get(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
tgs := talkgroups.StoreFrom(ctx)
|
tgs := tgstore.FromCtx(ctx)
|
||||||
|
|
||||||
var p tgParams
|
var p tgParams
|
||||||
|
|
||||||
|
@ -91,7 +92,7 @@ func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
tgs := talkgroups.StoreFrom(ctx)
|
tgs := tgstore.FromCtx(ctx)
|
||||||
|
|
||||||
input := database.UpdateTalkgroupParams{}
|
input := database.UpdateTalkgroupParams{}
|
||||||
|
|
||||||
|
@ -113,7 +114,7 @@ func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tga *talkgroupAPI) tgImport(w http.ResponseWriter, r *http.Request) {
|
func (tga *talkgroupAPI) tgImport(w http.ResponseWriter, r *http.Request) {
|
||||||
var impJob importer.ImportJob
|
var impJob xport.ImportJob
|
||||||
err := forms.Unmarshal(r, &impJob, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
|
err := forms.Unmarshal(r, &impJob, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
wErr(w, r, badRequest(err))
|
wErr(w, r, badRequest(err))
|
||||||
|
@ -137,12 +138,12 @@ func (tga *talkgroupAPI) putTalkgroups(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if id.System == nil { // don't think this would ever happen
|
if id.System == nil { // don't think this would ever happen
|
||||||
wErr(w, r, badRequest(talkgroups.ErrNoSuchSystem))
|
wErr(w, r, badRequest(tgstore.ErrNoSuchSystem))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
tgs := talkgroups.StoreFrom(ctx)
|
tgs := tgstore.FromCtx(ctx)
|
||||||
|
|
||||||
var input []database.UpsertTalkgroupParams
|
var input []database.UpsertTalkgroupParams
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/internal/version"
|
"dynatron.me/x/stillbox/internal/version"
|
||||||
"dynatron.me/x/stillbox/pkg/config"
|
"dynatron.me/x/stillbox/pkg/config"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/go-chi/httprate"
|
"github.com/go-chi/httprate"
|
||||||
|
@ -28,7 +28,7 @@ func (s *Server) setupRoutes() {
|
||||||
|
|
||||||
r := s.r
|
r := s.r
|
||||||
r.Use(middleware.WithValue(database.DBCtxKey, s.db))
|
r.Use(middleware.WithValue(database.DBCtxKey, s.db))
|
||||||
r.Use(middleware.WithValue(talkgroups.StoreCtxKey, s.tgs))
|
r.Use(middleware.WithValue(tgstore.StoreCtxKey, s.tgs))
|
||||||
|
|
||||||
s.installPprof()
|
s.installPprof()
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,8 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/rest"
|
"dynatron.me/x/stillbox/pkg/rest"
|
||||||
"dynatron.me/x/stillbox/pkg/sinks"
|
"dynatron.me/x/stillbox/pkg/sinks"
|
||||||
"dynatron.me/x/stillbox/pkg/sources"
|
"dynatron.me/x/stillbox/pkg/sources"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
|
@ -37,7 +38,7 @@ type Server struct {
|
||||||
alerter alerting.Alerter
|
alerter alerting.Alerter
|
||||||
notifier notify.Notifier
|
notifier notify.Notifier
|
||||||
hup chan os.Signal
|
hup chan os.Signal
|
||||||
tgs talkgroups.Store
|
tgs tgstore.Store
|
||||||
rest rest.API
|
rest rest.API
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ func New(ctx context.Context, cfg *config.Config) (*Server, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
tgCache := talkgroups.NewCache()
|
tgCache := tgstore.NewCache()
|
||||||
api := rest.New()
|
api := rest.New()
|
||||||
|
|
||||||
srv := &Server{
|
srv := &Server{
|
||||||
|
@ -78,7 +79,7 @@ func New(ctx context.Context, cfg *config.Config) (*Server, error) {
|
||||||
rest: api,
|
rest: api,
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.sinks.Register("database", sinks.NewDatabaseSink(srv.db), true)
|
srv.sinks.Register("database", sinks.NewDatabaseSink(srv.db, tgCache), true)
|
||||||
srv.sinks.Register("nexus", sinks.NewNexusSink(srv.nex), false)
|
srv.sinks.Register("nexus", sinks.NewNexusSink(srv.nex), false)
|
||||||
|
|
||||||
if srv.alerter.Enabled() {
|
if srv.alerter.Enabled() {
|
||||||
|
@ -117,7 +118,7 @@ func (s *Server) Go(ctx context.Context) error {
|
||||||
s.installHupHandler()
|
s.installHupHandler()
|
||||||
|
|
||||||
ctx = database.CtxWithDB(ctx, s.db)
|
ctx = database.CtxWithDB(ctx, s.db)
|
||||||
ctx = talkgroups.CtxWithStore(ctx, s.tgs)
|
ctx = tgstore.CtxWithStore(ctx, s.tgs)
|
||||||
|
|
||||||
httpSrv := &http.Server{
|
httpSrv := &http.Server{
|
||||||
Addr: s.conf.Listen,
|
Addr: s.conf.Listen,
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/internal/common"
|
"dynatron.me/x/stillbox/internal/common"
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
@ -15,10 +16,11 @@ import (
|
||||||
|
|
||||||
type DatabaseSink struct {
|
type DatabaseSink struct {
|
||||||
db database.Store
|
db database.Store
|
||||||
|
tgs tgstore.Store
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDatabaseSink(store database.Store) *DatabaseSink {
|
func NewDatabaseSink(store database.Store, tgs tgstore.Store) *DatabaseSink {
|
||||||
return &DatabaseSink{store}
|
return &DatabaseSink{store, tgs}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *DatabaseSink) Call(ctx context.Context, call *calls.Call) error {
|
func (s *DatabaseSink) Call(ctx context.Context, call *calls.Call) error {
|
||||||
|
@ -43,14 +45,14 @@ func (s *DatabaseSink) Call(ctx context.Context, call *calls.Call) error {
|
||||||
|
|
||||||
if err != nil && database.IsTGConstraintViolation(err) {
|
if err != nil && database.IsTGConstraintViolation(err) {
|
||||||
return s.db.InTx(ctx, func(tx database.Store) error {
|
return s.db.InTx(ctx, func(tx database.Store) error {
|
||||||
_, err := call.LearnTG(ctx, tx)
|
_, err := s.tgs.LearnTG(ctx, call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("add call: learn tg: %w", err)
|
return fmt.Errorf("learn tg: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.AddCall(ctx, params)
|
err = tx.AddCall(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("add call: retry: %w", err)
|
return fmt.Errorf("learn tg retry: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -131,7 +131,7 @@ func (h *RdioHTTP) routeCallUpload(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info().Int("system", cur.System).Int("tgid", cur.Talkgroup).Msg("ingested")
|
log.Info().Int("system", cur.System).Int("tgid", cur.Talkgroup).Str("duration", call.Duration.Duration().String()).Msg("ingested")
|
||||||
|
|
||||||
written, err := w.Write([]byte("Call imported successfully."))
|
written, err := w.Write([]byte("Call imported successfully."))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
168
pkg/talkgroups/filter/filter.go
Normal file
168
pkg/talkgroups/filter/filter.go
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
package filter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
|
"dynatron.me/x/stillbox/pkg/pb"
|
||||||
|
|
||||||
|
tgsp "dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TalkgroupFilter struct {
|
||||||
|
Talkgroups []tgsp.ID `json:"talkgroups,omitempty"`
|
||||||
|
TalkgroupsNot []tgsp.ID `json:"talkgroupsNot,omitempty"`
|
||||||
|
TalkgroupTagsAll []string `json:"talkgroupTagsAll,omitempty"`
|
||||||
|
TalkgroupTagsAny []string `json:"talkgroupTagsAny,omitempty"`
|
||||||
|
TalkgroupTagsNot []string `json:"talkgroupTagsNot,omitempty"`
|
||||||
|
|
||||||
|
talkgroups map[tgsp.ID]bool `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TalkgroupFilter) TGs(ctx context.Context) (tgsp.IDs, error) {
|
||||||
|
err := f.ensureCompiled(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := make(tgsp.IDs, 0, len(f.talkgroups))
|
||||||
|
for tg := range f.talkgroups {
|
||||||
|
r = append(r, tg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TalkgroupFilter) Tuples(ctx context.Context) (database.TGTuples, error) {
|
||||||
|
err := f.ensureCompiled(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return database.TGTuples{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
sys := make([]uint32, len(f.talkgroups))
|
||||||
|
tgs := make([]uint32, len(f.talkgroups))
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
for tg := range f.talkgroups {
|
||||||
|
sys[i] = tg.System
|
||||||
|
tgs[i] = tg.Talkgroup
|
||||||
|
}
|
||||||
|
|
||||||
|
return database.TGTuples{sys, tgs}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TalkgroupFilter) ensureCompiled(ctx context.Context) error {
|
||||||
|
if f.talkgroups == nil {
|
||||||
|
return f.compile(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tgf *TalkgroupFilter) IsEmpty() bool {
|
||||||
|
if tgf == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(tgf.Talkgroups) > 0 ||
|
||||||
|
len(tgf.TalkgroupsNot) > 0 ||
|
||||||
|
len(tgf.TalkgroupTagsAll) > 0 ||
|
||||||
|
len(tgf.TalkgroupTagsAny) > 0 ||
|
||||||
|
len(tgf.TalkgroupsNot) > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TalkgroupFilterFromPB(ctx context.Context, p *pb.Filter) (*TalkgroupFilter, error) {
|
||||||
|
tgf := &TalkgroupFilter{
|
||||||
|
TalkgroupTagsAll: p.TalkgroupTagsAll,
|
||||||
|
TalkgroupTagsAny: p.TalkgroupTagsAny,
|
||||||
|
TalkgroupTagsNot: p.TalkgroupTagsNot,
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := len(p.Talkgroups); l > 0 {
|
||||||
|
tgf.Talkgroups = make([]tgsp.ID, l)
|
||||||
|
for i, t := range p.Talkgroups {
|
||||||
|
tgf.Talkgroups[i] = tgsp.ID{
|
||||||
|
System: uint32(t.System),
|
||||||
|
Talkgroup: uint32(t.Talkgroup),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := len(p.TalkgroupsNot); l > 0 {
|
||||||
|
tgf.TalkgroupsNot = make([]tgsp.ID, l)
|
||||||
|
for i, t := range p.TalkgroupsNot {
|
||||||
|
tgf.TalkgroupsNot[i] = tgsp.ID{
|
||||||
|
System: uint32(t.System),
|
||||||
|
Talkgroup: uint32(t.Talkgroup),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tgf, tgf.compile(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TalkgroupFilter) hasTags() bool {
|
||||||
|
return len(f.TalkgroupTagsAny) > 0 || len(f.TalkgroupTagsAll) > 0 || len(f.TalkgroupTagsNot) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TalkgroupFilter) GetFinalTalkgroups() map[tgsp.ID]bool {
|
||||||
|
return f.talkgroups
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TalkgroupFilter) compile(ctx context.Context) error {
|
||||||
|
f.talkgroups = make(map[tgsp.ID]bool)
|
||||||
|
for _, tg := range f.Talkgroups {
|
||||||
|
f.talkgroups[tg] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if f.hasTags() { // don't bother with DB if no tags
|
||||||
|
db := database.FromCtx(ctx)
|
||||||
|
tagTGs, err := db.GetTalkgroupIDsByTags(ctx, f.TalkgroupTagsAny, f.TalkgroupTagsAll, f.TalkgroupTagsNot)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tg := range tagTGs {
|
||||||
|
f.talkgroups[tgsp.ID{System: uint32(tg.SystemID), Talkgroup: uint32(tg.TGID)}] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tg := range f.TalkgroupsNot {
|
||||||
|
f.talkgroups[tg] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *TalkgroupFilter) Test(ctx context.Context, call *calls.Call) bool {
|
||||||
|
if f == nil { // no filter means all calls
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
err := f.ensureCompiled(ctx)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tg := call.TalkgroupTuple()
|
||||||
|
|
||||||
|
tgRes, have := f.talkgroups[tg]
|
||||||
|
if have {
|
||||||
|
return tgRes
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, patch := range call.Patches {
|
||||||
|
tg.Talkgroup = uint32(patch)
|
||||||
|
tgRes, have := f.talkgroups[tg]
|
||||||
|
if have {
|
||||||
|
return tgRes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
1
pkg/talkgroups/importer/testdata/riscon.json
vendored
1
pkg/talkgroups/importer/testdata/riscon.json
vendored
File diff suppressed because one or more lines are too long
|
@ -1,4 +1,4 @@
|
||||||
package talkgroups
|
package tgstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -8,14 +8,17 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/common"
|
"dynatron.me/x/stillbox/internal/common"
|
||||||
|
"dynatron.me/x/stillbox/pkg/auth"
|
||||||
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
"dynatron.me/x/stillbox/pkg/config"
|
"dynatron.me/x/stillbox/pkg/config"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
|
tgsp "dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tgMap map[ID]*Talkgroup
|
type tgMap map[tgsp.ID]*tgsp.Talkgroup
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNotFound = errors.New("talkgroup not found")
|
ErrNotFound = errors.New("talkgroup not found")
|
||||||
|
@ -24,25 +27,28 @@ var (
|
||||||
|
|
||||||
type Store interface {
|
type Store interface {
|
||||||
// UpdateTG updates a talkgroup record.
|
// UpdateTG updates a talkgroup record.
|
||||||
UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*Talkgroup, error)
|
UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*tgsp.Talkgroup, error)
|
||||||
|
|
||||||
// UpsertTGs upserts a slice of talkgroups.
|
// UpsertTGs upserts a slice of talkgroups.
|
||||||
UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*Talkgroup, error)
|
UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*tgsp.Talkgroup, error)
|
||||||
|
|
||||||
// TG retrieves a Talkgroup from the Store.
|
// TG retrieves a Talkgroup from the Store.
|
||||||
TG(ctx context.Context, tg ID) (*Talkgroup, error)
|
TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error)
|
||||||
|
|
||||||
// TGs retrieves many talkgroups from the Store.
|
// TGs retrieves many talkgroups from the Store.
|
||||||
TGs(ctx context.Context, tgs IDs) ([]*Talkgroup, error)
|
TGs(ctx context.Context, tgs tgsp.IDs) ([]*tgsp.Talkgroup, error)
|
||||||
|
|
||||||
|
// LearnTG learns the talkgroup from a Call.
|
||||||
|
LearnTG(ctx context.Context, call *calls.Call) (*tgsp.Talkgroup, error)
|
||||||
|
|
||||||
// SystemTGs retrieves all Talkgroups associated with a System.
|
// SystemTGs retrieves all Talkgroups associated with a System.
|
||||||
SystemTGs(ctx context.Context, systemID int32) ([]*Talkgroup, error)
|
SystemTGs(ctx context.Context, systemID int32) ([]*tgsp.Talkgroup, error)
|
||||||
|
|
||||||
// SystemName retrieves a system name from the store. It returns the record and whether one was found.
|
// SystemName retrieves a system name from the store. It returns the record and whether one was found.
|
||||||
SystemName(ctx context.Context, id int) (string, bool)
|
SystemName(ctx context.Context, id int) (string, bool)
|
||||||
|
|
||||||
// Hint hints the Store that the provided talkgroups will be asked for.
|
// Hint hints the Store that the provided talkgroups will be asked for.
|
||||||
Hint(ctx context.Context, tgs []ID) error
|
Hint(ctx context.Context, tgs []tgsp.ID) error
|
||||||
|
|
||||||
// Load loads the provided talkgroup ID tuples into the Store.
|
// Load loads the provided talkgroup ID tuples into the Store.
|
||||||
Load(ctx context.Context, tgs database.TGTuples) error
|
Load(ctx context.Context, tgs database.TGTuples) error
|
||||||
|
@ -51,7 +57,7 @@ type Store interface {
|
||||||
Invalidate()
|
Invalidate()
|
||||||
|
|
||||||
// Weight returns the final weight of this talkgroup, including its static and rules-derived weight.
|
// Weight returns the final weight of this talkgroup, including its static and rules-derived weight.
|
||||||
Weight(ctx context.Context, id ID, t time.Time) float64
|
Weight(ctx context.Context, id tgsp.ID, t time.Time) float64
|
||||||
|
|
||||||
// Hupper
|
// Hupper
|
||||||
HUP(*config.Config)
|
HUP(*config.Config)
|
||||||
|
@ -65,7 +71,7 @@ func CtxWithStore(ctx context.Context, s Store) context.Context {
|
||||||
return context.WithValue(ctx, StoreCtxKey, s)
|
return context.WithValue(ctx, StoreCtxKey, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func StoreFrom(ctx context.Context) Store {
|
func FromCtx(ctx context.Context) Store {
|
||||||
s, ok := ctx.Value(StoreCtxKey).(Store)
|
s, ok := ctx.Value(StoreCtxKey).(Store)
|
||||||
if !ok {
|
if !ok {
|
||||||
return NewCache()
|
return NewCache()
|
||||||
|
@ -101,7 +107,7 @@ func NewCache() Store {
|
||||||
return tgc
|
return tgc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) Hint(ctx context.Context, tgs []ID) error {
|
func (t *cache) Hint(ctx context.Context, tgs []tgsp.ID) error {
|
||||||
t.RLock()
|
t.RLock()
|
||||||
var toLoad database.TGTuples
|
var toLoad database.TGTuples
|
||||||
if len(t.tgs) > len(tgs)/2 { // TODO: instrument this
|
if len(t.tgs) > len(tgs)/2 { // TODO: instrument this
|
||||||
|
@ -129,11 +135,20 @@ func (t *cache) Hint(ctx context.Context, tgs []ID) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) add(rec *Talkgroup) {
|
func (t *cache) get(id tgsp.ID) (*tgsp.Talkgroup, bool) {
|
||||||
|
t.RLock()
|
||||||
|
defer t.RUnlock()
|
||||||
|
|
||||||
|
tg, has := t.tgs[id]
|
||||||
|
|
||||||
|
return tg, has
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *cache) add(rec *tgsp.Talkgroup) {
|
||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
|
|
||||||
tg := TG(rec.System.ID, rec.Talkgroup.TGID)
|
tg := tgsp.TG(rec.System.ID, rec.Talkgroup.TGID)
|
||||||
t.tgs[tg] = rec
|
t.tgs[tg] = rec
|
||||||
t.systems[int32(rec.System.ID)] = rec.System.Name
|
t.systems[int32(rec.System.ID)] = rec.System.Name
|
||||||
}
|
}
|
||||||
|
@ -146,15 +161,15 @@ type row interface {
|
||||||
GetLearned() bool
|
GetLearned() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func rowToTalkgroup[T row](r T) *Talkgroup {
|
func rowToTalkgroup[T row](r T) *tgsp.Talkgroup {
|
||||||
return &Talkgroup{
|
return &tgsp.Talkgroup{
|
||||||
Talkgroup: r.GetTalkgroup(),
|
Talkgroup: r.GetTalkgroup(),
|
||||||
System: r.GetSystem(),
|
System: r.GetSystem(),
|
||||||
Learned: r.GetLearned(),
|
Learned: r.GetLearned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addToRowList[T row](t *cache, r []*Talkgroup, tgRecords []T) []*Talkgroup {
|
func addToRowList[T row](t *cache, r []*tgsp.Talkgroup, tgRecords []T) []*tgsp.Talkgroup {
|
||||||
for _, rec := range tgRecords {
|
for _, rec := range tgRecords {
|
||||||
tg := rowToTalkgroup(rec)
|
tg := rowToTalkgroup(rec)
|
||||||
t.add(tg)
|
t.add(tg)
|
||||||
|
@ -165,21 +180,19 @@ func addToRowList[T row](t *cache, r []*Talkgroup, tgRecords []T) []*Talkgroup {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) TGs(ctx context.Context, tgs IDs) ([]*Talkgroup, error) {
|
func (t *cache) TGs(ctx context.Context, tgs tgsp.IDs) ([]*tgsp.Talkgroup, error) {
|
||||||
r := make([]*Talkgroup, 0, len(tgs))
|
r := make([]*tgsp.Talkgroup, 0, len(tgs))
|
||||||
var err error
|
var err error
|
||||||
if tgs != nil {
|
if tgs != nil {
|
||||||
toGet := make(IDs, 0, len(tgs))
|
toGet := make(tgsp.IDs, 0, len(tgs))
|
||||||
t.RLock()
|
|
||||||
for _, id := range tgs {
|
for _, id := range tgs {
|
||||||
rec, has := t.tgs[id]
|
rec, has := t.get(id)
|
||||||
if has {
|
if has {
|
||||||
r = append(r, rec)
|
r = append(r, rec)
|
||||||
} else {
|
} else {
|
||||||
toGet = append(toGet, id)
|
toGet = append(toGet, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.RUnlock()
|
|
||||||
|
|
||||||
tgRecords, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedBySysTGID(ctx, toGet.Tuples())
|
tgRecords, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedBySysTGID(ctx, toGet.Tuples())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -210,7 +223,7 @@ func (t *cache) Load(ctx context.Context, tgs database.TGTuples) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) Weight(ctx context.Context, id ID, tm time.Time) float64 {
|
func (t *cache) Weight(ctx context.Context, id tgsp.ID, tm time.Time) float64 {
|
||||||
tg, err := t.TG(ctx, id)
|
tg, err := t.TG(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 1.0
|
return 1.0
|
||||||
|
@ -223,20 +236,18 @@ func (t *cache) Weight(ctx context.Context, id ID, tm time.Time) float64 {
|
||||||
return float64(m)
|
return float64(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) SystemTGs(ctx context.Context, systemID int32) ([]*Talkgroup, error) {
|
func (t *cache) SystemTGs(ctx context.Context, systemID int32) ([]*tgsp.Talkgroup, error) {
|
||||||
recs, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedBySystem(ctx, systemID)
|
recs, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedBySystem(ctx, systemID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
r := make([]*Talkgroup, 0, len(recs))
|
r := make([]*tgsp.Talkgroup, 0, len(recs))
|
||||||
return addToRowList(t, r, recs), nil
|
return addToRowList(t, r, recs), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) TG(ctx context.Context, tg ID) (*Talkgroup, error) {
|
func (t *cache) TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error) {
|
||||||
t.RLock()
|
rec, has := t.get(tg)
|
||||||
rec, has := t.tgs[tg]
|
|
||||||
t.RUnlock()
|
|
||||||
|
|
||||||
if has {
|
if has {
|
||||||
return rec, nil
|
return rec, nil
|
||||||
|
@ -278,7 +289,7 @@ func (t *cache) SystemName(ctx context.Context, id int) (name string, has bool)
|
||||||
return n, has
|
return n, has
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*Talkgroup, error) {
|
func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*tgsp.Talkgroup, error) {
|
||||||
sysName, has := t.SystemName(ctx, int(*input.SystemID))
|
sysName, has := t.SystemName(ctx, int(*input.SystemID))
|
||||||
if !has {
|
if !has {
|
||||||
return nil, ErrNoSuchSystem
|
return nil, ErrNoSuchSystem
|
||||||
|
@ -289,7 +300,7 @@ func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupPara
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
record := &Talkgroup{
|
record := &tgsp.Talkgroup{
|
||||||
Talkgroup: tg,
|
Talkgroup: tg,
|
||||||
System: database.System{ID: int(tg.SystemID), Name: sysName},
|
System: database.System{ID: int(tg.SystemID), Name: sysName},
|
||||||
}
|
}
|
||||||
|
@ -298,7 +309,40 @@ func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupPara
|
||||||
return record, nil
|
return record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*Talkgroup, error) {
|
func (t *cache) LearnTG(ctx context.Context, c *calls.Call) (*tgsp.Talkgroup, error) {
|
||||||
|
db := database.FromCtx(ctx)
|
||||||
|
|
||||||
|
sys, has := t.SystemName(ctx, c.System)
|
||||||
|
if !has {
|
||||||
|
return nil, ErrNoSuchSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
tgm, err := db.AddLearnedTalkgroup(ctx, database.AddLearnedTalkgroupParams{
|
||||||
|
SystemID: int32(c.System),
|
||||||
|
TGID: int32(c.Talkgroup),
|
||||||
|
Name: c.TalkgroupLabel,
|
||||||
|
AlphaTag: c.TGAlphaTag,
|
||||||
|
TGGroup: c.TalkgroupGroup,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
tg := &tgsp.Talkgroup{
|
||||||
|
Talkgroup: tgm,
|
||||||
|
System: database.System{
|
||||||
|
ID: c.System,
|
||||||
|
Name: sys,
|
||||||
|
},
|
||||||
|
Learned: tgm.Learned,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.add(tg)
|
||||||
|
|
||||||
|
return tg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*tgsp.Talkgroup, error) {
|
||||||
db := database.FromCtx(ctx)
|
db := database.FromCtx(ctx)
|
||||||
sysName, hasSys := t.SystemName(ctx, system)
|
sysName, hasSys := t.SystemName(ctx, system)
|
||||||
if !hasSys {
|
if !hasSys {
|
||||||
|
@ -309,9 +353,10 @@ func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.Upse
|
||||||
Name: sysName,
|
Name: sysName,
|
||||||
}
|
}
|
||||||
|
|
||||||
tgs := make([]*Talkgroup, 0, len(input))
|
tgs := make([]*tgsp.Talkgroup, 0, len(input))
|
||||||
|
|
||||||
err := db.InTx(ctx, func(db database.Store) error {
|
err := db.InTx(ctx, func(db database.Store) error {
|
||||||
|
versionParams := make([]database.StoreTGVersionParams, 0, len(input))
|
||||||
for i := range input {
|
for i := range input {
|
||||||
// normalize tags
|
// normalize tags
|
||||||
for j, tag := range input[i].Tags {
|
for j, tag := range input[i].Tags {
|
||||||
|
@ -325,15 +370,20 @@ func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.Upse
|
||||||
|
|
||||||
var oerr error
|
var oerr error
|
||||||
|
|
||||||
batch := db.UpsertTalkgroup(ctx, input)
|
tgUpsertBatch := db.UpsertTalkgroup(ctx, input)
|
||||||
defer batch.Close()
|
defer tgUpsertBatch.Close()
|
||||||
|
|
||||||
batch.QueryRow(func(_ int, r database.Talkgroup, err error) {
|
tgUpsertBatch.QueryRow(func(_ int, r database.Talkgroup, err error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
oerr = err
|
oerr = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tgs = append(tgs, &Talkgroup{
|
versionParams = append(versionParams, database.StoreTGVersionParams{
|
||||||
|
SystemID: int32(system),
|
||||||
|
TGID: r.TGID,
|
||||||
|
Submitter: auth.UIDFrom(ctx),
|
||||||
|
})
|
||||||
|
tgs = append(tgs, &tgsp.Talkgroup{
|
||||||
Talkgroup: r,
|
Talkgroup: r,
|
||||||
System: sys,
|
System: sys,
|
||||||
Learned: r.Learned,
|
Learned: r.Learned,
|
||||||
|
@ -344,7 +394,17 @@ func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.Upse
|
||||||
return oerr
|
return oerr
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
versionBatch := db.StoreTGVersion(ctx, versionParams)
|
||||||
|
defer versionBatch.Close()
|
||||||
|
|
||||||
|
versionBatch.Exec(func(_ int, err error) {
|
||||||
|
if err != nil {
|
||||||
|
oerr = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return oerr
|
||||||
}, pgx.TxOptions{})
|
}, pgx.TxOptions{})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
54
pkg/talkgroups/xport/export.go
Normal file
54
pkg/talkgroups/xport/export.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package xport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/filter"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/xport/sdrtrunk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Exporter interface {
|
||||||
|
ExportTalkgroups(ctx context.Context, w io.Writer, tgs []*talkgroups.Talkgroup, tmpl []byte) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExportJob struct {
|
||||||
|
Type Format `json:"type"`
|
||||||
|
SystemID int `json:"systemID"`
|
||||||
|
Template []byte `json:"template"`
|
||||||
|
|
||||||
|
filter.TalkgroupFilter
|
||||||
|
Exporter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ej *ExportJob) Export(ctx context.Context, w io.Writer) error {
|
||||||
|
var tgs []*talkgroups.Talkgroup
|
||||||
|
var err error
|
||||||
|
tgst := tgstore.FromCtx(ctx)
|
||||||
|
if ej.TalkgroupFilter.IsEmpty() {
|
||||||
|
tgs, err = tgst.SystemTGs(ctx, int32(ej.SystemID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ids, err := ej.TalkgroupFilter.TGs(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tgs, err = tgst.TGs(ctx, ids)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ej.Type {
|
||||||
|
case FormatSDRTrunk:
|
||||||
|
ej.Exporter = sdrtrunk.New()
|
||||||
|
default:
|
||||||
|
return ErrBadType
|
||||||
|
}
|
||||||
|
|
||||||
|
return ej.ExportTalkgroups(ctx, w, tgs, ej.Template)
|
||||||
|
}
|
16
pkg/talkgroups/xport/format.go
Normal file
16
pkg/talkgroups/xport/format.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package xport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Format string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FormatRadioReference Format = "radioreference"
|
||||||
|
FormatSDRTrunk Format = "sdrtrunk"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadType = errors.New("unknown format type")
|
||||||
|
)
|
35
pkg/talkgroups/xport/import.go
Normal file
35
pkg/talkgroups/xport/import.go
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
package xport
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/xport/radioref"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Importer interface {
|
||||||
|
ImportTalkgroups(ctx context.Context, sys int, r io.Reader) ([]talkgroups.Talkgroup, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImportJob struct {
|
||||||
|
Type Format `json:"type"`
|
||||||
|
SystemID int `json:"systemID"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
|
||||||
|
Importer `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ij *ImportJob) Import(ctx context.Context) ([]talkgroups.Talkgroup, error) {
|
||||||
|
r := bytes.NewReader([]byte(ij.Body))
|
||||||
|
|
||||||
|
switch ij.Type {
|
||||||
|
case FormatRadioReference:
|
||||||
|
ij.Importer = radioref.New()
|
||||||
|
default:
|
||||||
|
return nil, ErrBadType
|
||||||
|
}
|
||||||
|
|
||||||
|
return ij.ImportTalkgroups(ctx, ij.SystemID, r)
|
||||||
|
}
|
51
pkg/talkgroups/xport/import_test.go
Normal file
51
pkg/talkgroups/xport/import_test.go
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
package xport_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/xport"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestImport(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
impType string
|
||||||
|
input []byte
|
||||||
|
sysID int
|
||||||
|
sysName string
|
||||||
|
jsExpect []byte
|
||||||
|
expectErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "unknown importer",
|
||||||
|
impType: "nonexistent",
|
||||||
|
expectErr: xport.ErrBadType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
ij := &xport.ImportJob{
|
||||||
|
Type: xport.Format(tc.impType),
|
||||||
|
SystemID: tc.sysID,
|
||||||
|
Body: string(tc.input),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ij.Import(ctx)
|
||||||
|
|
||||||
|
if tc.expectErr != nil {
|
||||||
|
require.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), tc.expectErr.Error())
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,8 @@
|
||||||
package importer
|
package radioref
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"io"
|
"io"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -13,44 +11,13 @@ import (
|
||||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ImportSource string
|
type Driver struct{}
|
||||||
|
|
||||||
const (
|
func New() *Driver {
|
||||||
ImportSrcRadioReference ImportSource = "radioreference"
|
return new(Driver)
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrBadImportType = errors.New("unknown import type")
|
|
||||||
)
|
|
||||||
|
|
||||||
type importer interface {
|
|
||||||
importTalkgroups(ctx context.Context, sys int, r io.Reader) ([]talkgroups.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) ([]talkgroups.Talkgroup, error) {
|
|
||||||
r := bytes.NewReader([]byte(ij.Body))
|
|
||||||
|
|
||||||
switch ij.Type {
|
|
||||||
case ImportSrcRadioReference:
|
|
||||||
ij.importer = new(radioReferenceImporter)
|
|
||||||
default:
|
|
||||||
return nil, ErrBadImportType
|
|
||||||
}
|
|
||||||
|
|
||||||
return ij.importTalkgroups(ctx, ij.SystemID, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
type radioReferenceImporter struct {
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type rrState int
|
type rrState int
|
||||||
|
@ -63,12 +30,12 @@ const (
|
||||||
|
|
||||||
var rrRE = regexp.MustCompile(`DEC\s+HEX\s+Mode\s+Alpha Tag\s+Description\s+Tag`)
|
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) ([]talkgroups.Talkgroup, error) {
|
func (rr *Driver) ImportTalkgroups(ctx context.Context, sys int, r io.Reader) ([]talkgroups.Talkgroup, error) {
|
||||||
sc := bufio.NewScanner(r)
|
sc := bufio.NewScanner(r)
|
||||||
tgs := make([]talkgroups.Talkgroup, 0, 8)
|
tgs := make([]talkgroups.Talkgroup, 0, 8)
|
||||||
sysn, has := talkgroups.StoreFrom(ctx).SystemName(ctx, sys)
|
sysn, has := tgstore.FromCtx(ctx).SystemName(ctx, sys)
|
||||||
if !has {
|
if !has {
|
||||||
return nil, talkgroups.ErrNoSuchSystem
|
return nil, tgstore.ErrNoSuchSystem
|
||||||
}
|
}
|
||||||
|
|
||||||
var groupName string
|
var groupName string
|
||||||
|
@ -119,6 +86,7 @@ func (rr *radioReferenceImporter) importTalkgroups(ctx context.Context, sys int,
|
||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
Tags: tags,
|
Tags: tags,
|
||||||
Weight: 1.0,
|
Weight: 1.0,
|
||||||
|
Alert: true,
|
||||||
},
|
},
|
||||||
System: database.System{
|
System: database.System{
|
||||||
ID: sys,
|
ID: sys,
|
|
@ -1,4 +1,4 @@
|
||||||
package importer_test
|
package radioref_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
@ -15,7 +15,8 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/database/mocks"
|
"dynatron.me/x/stillbox/pkg/database/mocks"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups/importer"
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/xport"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getFixture(fixture string) []byte {
|
func getFixture(fixture string) []byte {
|
||||||
|
@ -27,7 +28,7 @@ func getFixture(fixture string) []byte {
|
||||||
return fixt
|
return fixt
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestImport(t *testing.T) {
|
func TestRadioRef(t *testing.T) {
|
||||||
// this is for deterministic UUIDs
|
// this is for deterministic UUIDs
|
||||||
uuid.SetRand(rand.New(rand.NewSource(1)))
|
uuid.SetRand(rand.New(rand.NewSource(1)))
|
||||||
|
|
||||||
|
@ -41,18 +42,13 @@ func TestImport(t *testing.T) {
|
||||||
expectErr error
|
expectErr error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "radioreference",
|
name: "radioreference import",
|
||||||
impType: "radioreference",
|
impType: "radioreference",
|
||||||
input: getFixture("riscon.txt"),
|
input: getFixture("riscon.txt"),
|
||||||
jsExpect: getFixture("riscon.json"),
|
jsExpect: getFixture("riscon.json"),
|
||||||
sysID: 197,
|
sysID: 197,
|
||||||
sysName: "RISCON",
|
sysName: "RISCON",
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "unknown importer",
|
|
||||||
impType: "nonexistent",
|
|
||||||
expectErr: importer.ErrBadImportType,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range tests {
|
for _, tc := range tests {
|
||||||
|
@ -62,9 +58,9 @@ func TestImport(t *testing.T) {
|
||||||
dbMock.EXPECT().GetSystemName(mock.AnythingOfType("*context.valueCtx"), tc.sysID).Return(tc.sysName, nil)
|
dbMock.EXPECT().GetSystemName(mock.AnythingOfType("*context.valueCtx"), tc.sysID).Return(tc.sysName, nil)
|
||||||
}
|
}
|
||||||
ctx := database.CtxWithDB(context.Background(), dbMock)
|
ctx := database.CtxWithDB(context.Background(), dbMock)
|
||||||
ctx = talkgroups.CtxWithStore(ctx, talkgroups.NewCache())
|
ctx = tgstore.CtxWithStore(ctx, tgstore.NewCache())
|
||||||
ij := &importer.ImportJob{
|
ij := &xport.ImportJob{
|
||||||
Type: importer.ImportSource(tc.impType),
|
Type: xport.Format(tc.impType),
|
||||||
SystemID: tc.sysID,
|
SystemID: tc.sysID,
|
||||||
Body: string(tc.input),
|
Body: string(tc.input),
|
||||||
}
|
}
|
1
pkg/talkgroups/xport/radioref/testdata/riscon.json
vendored
Normal file
1
pkg/talkgroups/xport/radioref/testdata/riscon.json
vendored
Normal file
File diff suppressed because one or more lines are too long
128
pkg/talkgroups/xport/sdrtrunk/sdrtrunk.go
Normal file
128
pkg/talkgroups/xport/sdrtrunk/sdrtrunk.go
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
package sdrtrunk
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/xml"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/internal/common"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Playlist struct {
|
||||||
|
XMLName xml.Name `xml:"playlist"`
|
||||||
|
Version int `xml:"version,attr"`
|
||||||
|
Aliases []Alias `xml:"alias"`
|
||||||
|
Channels []Channel `xml:"channel,omitempty"`
|
||||||
|
Streams []Stream `xml:"stream,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Alias struct {
|
||||||
|
XMLName xml.Name `xml:"alias"`
|
||||||
|
Name string `xml:"name,attr,omitempty"`
|
||||||
|
Color int `xml:"color,attr,omitempty"`
|
||||||
|
Group string `xml:"group,attr,omitempty"`
|
||||||
|
IconName string `xml:"iconName,attr,omitempty"`
|
||||||
|
List string `xml:"list,attr,omitempty"`
|
||||||
|
IDs []ID `xml:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func tgToAlias(tg *talkgroups.Talkgroup) Alias {
|
||||||
|
return Alias{
|
||||||
|
XMLName: xml.Name{Local: "alias"},
|
||||||
|
Name: common.ZeroOr(tg.Name),
|
||||||
|
Group: common.ZeroOr(tg.TGGroup),
|
||||||
|
List: "Stillbox",
|
||||||
|
IDs: []ID{
|
||||||
|
ID{
|
||||||
|
XMLName: xml.Name{Local: "id"},
|
||||||
|
Type: "talkgroup",
|
||||||
|
Value: common.PtrTo(int(tg.TGID)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ID struct {
|
||||||
|
XMLName xml.Name `xml:"id"`
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
Priority *int `xml:"priority,attr,omitempty"`
|
||||||
|
Channel *string `xml:"channel,attr,omitempty"`
|
||||||
|
Protocol *string `xml:"protocol,attr,omitempty"`
|
||||||
|
Value *int `xml:"value,attr,omitempty"`
|
||||||
|
Min *int `xml:"min,attr,omitempty"`
|
||||||
|
Max *int `xml:"max,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Channel struct {
|
||||||
|
XMLName xml.Name `xml:"channel"`
|
||||||
|
Name string `xml:"name,attr"`
|
||||||
|
System string `xml:"system,attr"`
|
||||||
|
Enabled bool `xml:"enabled,attr"`
|
||||||
|
Site string `xml:"site,attr"`
|
||||||
|
Order int `xml:"order,attr"`
|
||||||
|
|
||||||
|
AliasListName string `xml:"alias_list_name"`
|
||||||
|
EventLogConfig EventLogConfig `xml:"event_log_configuration"`
|
||||||
|
SourceConfig SourceConfig `xml:"source_configuration"`
|
||||||
|
AuxDecodeConfig AuxDecodeConfig `xml:"aux_decode_configuration"`
|
||||||
|
DecodeConfig DecodeConfig `xml:"decode_configuration"`
|
||||||
|
RecordConfig RecordConfig `xml:"record_configuration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventLogConfig struct {
|
||||||
|
EventLogConfig []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SourceConfig struct {
|
||||||
|
SourceConfig []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuxDecodeConfig struct {
|
||||||
|
AuxDecodeConfig []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DecodeConfig struct {
|
||||||
|
DecodeConfig []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RecordConfig struct {
|
||||||
|
RecordConfig []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stream struct {
|
||||||
|
Attributes []xml.Attr `xml:",any,attr"`
|
||||||
|
Stream []byte `xml:",innerxml"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() *Driver {
|
||||||
|
return new(Driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Driver struct{}
|
||||||
|
|
||||||
|
func (st *Driver) ExportTalkgroups(ctx context.Context, w io.Writer, tgs []*talkgroups.Talkgroup, tmpl []byte) error {
|
||||||
|
var pl Playlist
|
||||||
|
|
||||||
|
if tmpl != nil {
|
||||||
|
err := xml.Unmarshal(tmpl, &pl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pl.Aliases = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tg := range tgs {
|
||||||
|
pl.Aliases = append(pl.Aliases, tgToAlias(tg))
|
||||||
|
}
|
||||||
|
|
||||||
|
enc := xml.NewEncoder(w)
|
||||||
|
enc.Indent("", " ")
|
||||||
|
err := enc.Encode(&pl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return enc.Close()
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ CREATE TABLE IF NOT EXISTS talkgroups(
|
||||||
alert_config JSONB,
|
alert_config JSONB,
|
||||||
weight REAL NOT NULL DEFAULT 1.0,
|
weight REAL NOT NULL DEFAULT 1.0,
|
||||||
learned BOOLEAN NOT NULL DEFAULT FALSE,
|
learned BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
|
ignored BOOLEAN NOT NULL DEFAULT FALSE,
|
||||||
UNIQUE (system_id, tgid)
|
UNIQUE (system_id, tgid)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -44,15 +45,25 @@ CREATE INDEX talkgroups_system_tgid_idx ON talkgroups (system_id, tgid);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS talkgroup_id_tags ON talkgroups USING GIN (tags);
|
CREATE INDEX IF NOT EXISTS talkgroup_id_tags ON talkgroups USING GIN (tags);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS talkgroups_learned(
|
CREATE TABLE IF NOT EXISTS talkgroup_versions(
|
||||||
|
-- version metadata
|
||||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||||
system_id INTEGER REFERENCES systems(id) NOT NULL,
|
time TIMESTAMPTZ NOT NULL,
|
||||||
tgid INTEGER NOT NULL,
|
created_by INTEGER REFERENCES users(id),
|
||||||
name TEXT NOT NULL,
|
-- talkgroup snapshot
|
||||||
|
system_id INT4 REFERENCES systems(id),
|
||||||
|
tgid INT4,
|
||||||
|
name TEXT,
|
||||||
alpha_tag TEXT,
|
alpha_tag TEXT,
|
||||||
tg_group TEXT,
|
tg_group TEXT,
|
||||||
ignored BOOLEAN,
|
frequency INTEGER,
|
||||||
UNIQUE (system_id, tgid, name)
|
metadata JSONB,
|
||||||
|
tags TEXT[],
|
||||||
|
alert BOOLEAN,
|
||||||
|
alert_config JSONB,
|
||||||
|
weight REAL,
|
||||||
|
learned BOOLEAN,
|
||||||
|
ignored BOOLEAN
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS alerts(
|
CREATE TABLE IF NOT EXISTS alerts(
|
||||||
|
|
|
@ -29,47 +29,20 @@ SELECT
|
||||||
sqlc.embed(tg), sqlc.embed(sys)
|
sqlc.embed(tg), sqlc.embed(sys)
|
||||||
FROM talkgroups tg
|
FROM talkgroups tg
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
WHERE (tg.system_id, tg.tgid) = (@system_id, @tgid) AND tg.learned IS NOT TRUE
|
WHERE (tg.system_id, tg.tgid) = (@system_id, @tgid);
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END,
|
|
||||||
NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
WHERE tgl.system_id = @system_id AND tgl.tgid = @tgid AND ignored IS NOT TRUE;
|
|
||||||
|
|
||||||
-- name: GetTalkgroupsWithLearnedBySystem :many
|
-- name: GetTalkgroupsWithLearnedBySystem :many
|
||||||
SELECT
|
SELECT
|
||||||
sqlc.embed(tg), sqlc.embed(sys)
|
sqlc.embed(tg), sqlc.embed(sys)
|
||||||
FROM talkgroups tg
|
FROM talkgroups tg
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
WHERE tg.system_id = @system AND tg.learned IS NOT TRUE
|
WHERE tg.system_id = @system;
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END,
|
|
||||||
NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
WHERE tgl.system_id = @system AND ignored IS NOT TRUE;
|
|
||||||
|
|
||||||
-- name: GetTalkgroupsWithLearned :many
|
-- name: GetTalkgroupsWithLearned :many
|
||||||
SELECT
|
SELECT
|
||||||
sqlc.embed(tg), sqlc.embed(sys)
|
sqlc.embed(tg), sqlc.embed(sys)
|
||||||
FROM talkgroups tg
|
FROM talkgroups tg
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
WHERE tg.learned IS NOT TRUE
|
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.tg_group, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.tg_group IS NULL THEN NULL ELSE ARRAY[tgl.tg_group] END,
|
|
||||||
NOT tgl.ignored, NULL::JSONB, 1.0, TRUE learned, sys.id, sys.name
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
WHERE ignored IS NOT TRUE;
|
WHERE ignored IS NOT TRUE;
|
||||||
|
|
||||||
-- name: GetSystemName :one
|
-- name: GetSystemName :one
|
||||||
|
@ -122,28 +95,92 @@ SET
|
||||||
learned = COALESCE(sqlc.narg('learned'), tg.learned)
|
learned = COALESCE(sqlc.narg('learned'), tg.learned)
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
-- name: AddTalkgroupWithLearnedFlag :exec
|
-- name: StoreTGVersion :batchexec
|
||||||
|
INSERT INTO talkgroup_versions(time, created_by,
|
||||||
|
system_id,
|
||||||
|
tgid,
|
||||||
|
name,
|
||||||
|
alpha_tag,
|
||||||
|
tg_group,
|
||||||
|
frequency,
|
||||||
|
metadata,
|
||||||
|
tags,
|
||||||
|
alert,
|
||||||
|
alert_config,
|
||||||
|
weight,
|
||||||
|
learned
|
||||||
|
) SELECT NOW(), @submitter,
|
||||||
|
tg.system_id,
|
||||||
|
tg.tgid,
|
||||||
|
tg.name,
|
||||||
|
tg.alpha_tag,
|
||||||
|
tg.tg_group,
|
||||||
|
tg.frequency,
|
||||||
|
tg.metadata,
|
||||||
|
tg.tags,
|
||||||
|
tg.alert,
|
||||||
|
tg.alert_config,
|
||||||
|
tg.weight,
|
||||||
|
tg.learned
|
||||||
|
FROM talkgroups tg WHERE tg.system_id = @system_id AND tg.tgid = @tgid;
|
||||||
|
|
||||||
|
-- name: AddLearnedTalkgroup :one
|
||||||
INSERT INTO talkgroups(
|
INSERT INTO talkgroups(
|
||||||
system_id,
|
system_id,
|
||||||
tgid,
|
tgid,
|
||||||
learned
|
learned,
|
||||||
) VALUES(
|
|
||||||
@system_id,
|
|
||||||
@tgid,
|
|
||||||
TRUE
|
|
||||||
);
|
|
||||||
|
|
||||||
-- name: AddLearnedTalkgroup :one
|
|
||||||
INSERT INTO talkgroups_learned(
|
|
||||||
system_id,
|
|
||||||
tgid,
|
|
||||||
name,
|
name,
|
||||||
alpha_tag,
|
alpha_tag,
|
||||||
tg_group
|
tg_group
|
||||||
) VALUES (
|
) VALUES (
|
||||||
@system_id,
|
@system_id,
|
||||||
@tgid,
|
@tgid,
|
||||||
|
TRUE,
|
||||||
sqlc.narg('name'),
|
sqlc.narg('name'),
|
||||||
sqlc.narg('alpha_tag'),
|
sqlc.narg('alpha_tag'),
|
||||||
sqlc.narg('tg_group')
|
sqlc.narg('tg_group')
|
||||||
) RETURNING id;
|
) RETURNING *;
|
||||||
|
|
||||||
|
-- name: RestoreTalkgroupVersion :one
|
||||||
|
INSERT INTO talkgroups(
|
||||||
|
system_id,
|
||||||
|
tgid,
|
||||||
|
name,
|
||||||
|
alpha_tag,
|
||||||
|
tg_group,
|
||||||
|
frequency,
|
||||||
|
metadata,
|
||||||
|
tags,
|
||||||
|
alert,
|
||||||
|
alert_config,
|
||||||
|
weight,
|
||||||
|
learned,
|
||||||
|
ignored
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
system_id,
|
||||||
|
tgid,
|
||||||
|
name,
|
||||||
|
alpha_tag,
|
||||||
|
tg_group,
|
||||||
|
frequency,
|
||||||
|
metadata,
|
||||||
|
tags,
|
||||||
|
alert,
|
||||||
|
alert_config,
|
||||||
|
weight,
|
||||||
|
learned,
|
||||||
|
ignored
|
||||||
|
FROM talkgroup_versions tgv ON CONFLICT (system_id, tgid) DO UPDATE SET
|
||||||
|
name = excluded.name,
|
||||||
|
alpha_tag = excluded.alpha_tag,
|
||||||
|
tg_group = excluded.tg_group,
|
||||||
|
metadata = excluded.metadata,
|
||||||
|
tags = excluded.tags,
|
||||||
|
alert = excluded.alert,
|
||||||
|
alert_config = excluded.alert_config,
|
||||||
|
weight = excluded.weight,
|
||||||
|
learned = excluded.learner,
|
||||||
|
ignored = excluded.ignored
|
||||||
|
WHERE tgv.id = ANY(@version_ids)
|
||||||
|
RETURNING *;
|
||||||
|
|
Loading…
Reference in a new issue