Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
3ba24f8c0e |
33 changed files with 368 additions and 961 deletions
|
@ -35,6 +35,5 @@ func main() {
|
|||
cmds := append([]*cobra.Command{serve.Command(cfg)}, admin.Command(cfg)...)
|
||||
rootCmd.AddCommand(cmds...)
|
||||
|
||||
// cobra is already checking for errors and will print them
|
||||
_ = rootCmd.Execute()
|
||||
rootCmd.Execute()
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ notify:
|
|||
# subjectTemplate: "Stillbox Alert ({{ highest . }})"
|
||||
# bodyTemplate: |
|
||||
# {{ range . -}}
|
||||
# {{ .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)
|
||||
# {{ .TGName }} is active with a score of {{ f .Score.Score 4 }}! ({{ f .Score.RecentCount 0 }}/{{ .Score.Count }} recent calls)
|
||||
#
|
||||
# {{ end -}}
|
||||
config:
|
||||
|
|
|
@ -3,12 +3,12 @@ package alert
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/trending"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
@ -17,7 +17,6 @@ type Alert struct {
|
|||
ID int
|
||||
Timestamp time.Time
|
||||
TGName string
|
||||
Talkgroup *talkgroups.Talkgroup
|
||||
Score trending.Score[talkgroups.ID]
|
||||
OrigScore float64
|
||||
Weight float32
|
||||
|
@ -45,8 +44,7 @@ func (a *Alert) ToAddAlertParams() database.AddAlertParams {
|
|||
}
|
||||
|
||||
// Make creates an alert for later rendering or storage.
|
||||
func Make(ctx context.Context, score trending.Score[talkgroups.ID], origScore float64) (Alert, error) {
|
||||
store := tgstore.FromCtx(ctx)
|
||||
func Make(ctx context.Context, store talkgroups.Store, score trending.Score[talkgroups.ID], origScore float64) (Alert, error) {
|
||||
d := Alert{
|
||||
Score: score,
|
||||
Timestamp: time.Now(),
|
||||
|
@ -58,8 +56,15 @@ func Make(ctx context.Context, score trending.Score[talkgroups.ID], origScore fl
|
|||
switch err {
|
||||
case nil:
|
||||
d.Weight = tgRecord.Talkgroup.Weight
|
||||
d.TGName = tgRecord.String()
|
||||
d.Talkgroup = tgRecord
|
||||
if tgRecord.System.Name == "" {
|
||||
tgRecord.System.Name = strconv.Itoa(int(score.ID.System))
|
||||
}
|
||||
|
||||
if tgRecord.Talkgroup.Name != nil {
|
||||
d.TGName = fmt.Sprintf("%s %s [%d]", tgRecord.System.Name, *tgRecord.Talkgroup.Name, score.ID.Talkgroup)
|
||||
} else {
|
||||
d.TGName = fmt.Sprintf("%s:%d", tgRecord.System.Name, int(score.ID.Talkgroup))
|
||||
}
|
||||
default:
|
||||
system, has := store.SystemName(ctx, int(score.ID.System))
|
||||
if has {
|
||||
|
|
|
@ -3,7 +3,6 @@ package alerting
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"sort"
|
||||
"sync"
|
||||
|
@ -15,8 +14,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/notify"
|
||||
"dynatron.me/x/stillbox/pkg/sinks"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
talkgroups "dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/timeseries"
|
||||
"dynatron.me/x/stillbox/internal/trending"
|
||||
|
@ -52,7 +50,7 @@ type alerter struct {
|
|||
alertCache map[talkgroups.ID]alert.Alert
|
||||
renotify time.Duration
|
||||
notifier notify.Notifier
|
||||
tgCache tgstore.Store
|
||||
tgCache talkgroups.Store
|
||||
}
|
||||
|
||||
type offsetClock time.Duration
|
||||
|
@ -87,7 +85,7 @@ func WithNotifier(n notify.Notifier) AlertOption {
|
|||
}
|
||||
|
||||
// New creates a new Alerter using the provided configuration.
|
||||
func New(cfg config.Alerting, tgCache tgstore.Store, opts ...AlertOption) Alerter {
|
||||
func New(cfg config.Alerting, tgCache talkgroups.Store, opts ...AlertOption) Alerter {
|
||||
if !cfg.Enable {
|
||||
return &noopAlerter{}
|
||||
}
|
||||
|
@ -170,7 +168,7 @@ func (as *alerter) eval(ctx context.Context, now time.Time, testMode bool) ([]al
|
|||
if s.Score > as.cfg.AlertThreshold || testMode {
|
||||
if old, inCache := as.alertCache[s.ID]; !inCache || now.Sub(old.Timestamp) > as.renotify {
|
||||
s.Score *= as.tgCache.Weight(ctx, s.ID, now)
|
||||
a, err := alert.Make(ctx, s, origScore)
|
||||
a, err := alert.Make(ctx, as.tgCache, s, origScore)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("makeAlert: %w", err)
|
||||
}
|
||||
|
@ -200,16 +198,9 @@ func (as *alerter) eval(ctx context.Context, now time.Time, testMode bool) ([]al
|
|||
}
|
||||
|
||||
func (as *alerter) testNotifyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
alerts := make([]alert.Alert, 0, len(as.scores))
|
||||
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)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("test notification eval")
|
||||
|
@ -217,8 +208,6 @@ func (as *alerter) testNotifyHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
alerts = append(alerts, a)
|
||||
|
||||
err = as.notifier.Send(ctx, alerts)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("test notification send")
|
||||
|
|
|
@ -13,7 +13,6 @@ import (
|
|||
"dynatron.me/x/stillbox/internal/trending"
|
||||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
@ -60,7 +59,7 @@ func (s *Simulation) stepClock(t time.Time) {
|
|||
// 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) {
|
||||
now := time.Now()
|
||||
tgc := tgstore.NewCache()
|
||||
tgc := talkgroups.NewCache()
|
||||
|
||||
s.Enable = true
|
||||
s.alerter = New(s.Alerting, tgc, WithClock(&s.clock)).(*alerter)
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
{{ $tg := (index $.TGs .ID) }}
|
||||
<tr>
|
||||
<td>{{ $tg.System.Name}}</td>
|
||||
<td>{{ $tg.Talkgroup }}</td>
|
||||
<td>{{ $tg.Talkgroup.Name}}</td>
|
||||
<td>{{ .ID.Talkgroup }}</td>
|
||||
<td>{{ f .Count 0 }}</td>
|
||||
<td>{{ f .RecentCount 0 }}</td>
|
||||
|
|
|
@ -40,23 +40,6 @@ type jwtAuth 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) {
|
||||
// TODO: check IP against ACL, or conf.Public, and against map of routes
|
||||
tok, cl, err := jwtauth.FromContext(r.Context())
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package calls
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/audio"
|
||||
"dynatron.me/x/stillbox/pkg/auth"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/pb"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
|
||||
|
@ -111,6 +113,21 @@ 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) {
|
||||
var td time.Duration
|
||||
|
||||
|
|
|
@ -1,210 +0,0 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.27.0
|
||||
// source: batch.go
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/pkg/alerting/rules"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
var (
|
||||
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
|
||||
INSERT INTO talkgroups AS tg (
|
||||
system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7,
|
||||
$8,
|
||||
$9,
|
||||
$10,
|
||||
$11,
|
||||
$12
|
||||
)
|
||||
ON CONFLICT (system_id, tgid) DO UPDATE
|
||||
SET
|
||||
name = COALESCE($3, tg.name),
|
||||
alpha_tag = COALESCE($4, tg.alpha_tag),
|
||||
tg_group = COALESCE($5, tg.tg_group),
|
||||
frequency = COALESCE($6, tg.frequency),
|
||||
metadata = COALESCE($7, tg.metadata),
|
||||
tags = COALESCE($8, tg.tags),
|
||||
alert = COALESCE($9, tg.alert),
|
||||
alert_config = COALESCE($10, tg.alert_config),
|
||||
weight = COALESCE($11, tg.weight),
|
||||
learned = COALESCE($12, tg.learned)
|
||||
RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned, ignored
|
||||
`
|
||||
|
||||
type UpsertTalkgroupBatchResults struct {
|
||||
br pgx.BatchResults
|
||||
tot int
|
||||
closed bool
|
||||
}
|
||||
|
||||
type UpsertTalkgroupParams struct {
|
||||
SystemID int32 `json:"system_id"`
|
||||
TGID int32 `json:"tgid"`
|
||||
Name *string `json:"name"`
|
||||
AlphaTag *string `json:"alpha_tag"`
|
||||
TGGroup *string `json:"tg_group"`
|
||||
Frequency *int32 `json:"frequency"`
|
||||
Metadata jsontypes.Metadata `json:"metadata"`
|
||||
Tags []string `json:"tags"`
|
||||
Alert *bool `json:"alert"`
|
||||
AlertConfig rules.AlertRules `json:"alert_config"`
|
||||
Weight *float32 `json:"weight"`
|
||||
Learned *bool `json:"learned"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpsertTalkgroup(ctx context.Context, arg []UpsertTalkgroupParams) *UpsertTalkgroupBatchResults {
|
||||
batch := &pgx.Batch{}
|
||||
for _, a := range arg {
|
||||
vals := []interface{}{
|
||||
a.SystemID,
|
||||
a.TGID,
|
||||
a.Name,
|
||||
a.AlphaTag,
|
||||
a.TGGroup,
|
||||
a.Frequency,
|
||||
a.Metadata,
|
||||
a.Tags,
|
||||
a.Alert,
|
||||
a.AlertConfig,
|
||||
a.Weight,
|
||||
a.Learned,
|
||||
}
|
||||
batch.Queue(upsertTalkgroup, vals...)
|
||||
}
|
||||
br := q.db.SendBatch(ctx, batch)
|
||||
return &UpsertTalkgroupBatchResults{br, len(arg), false}
|
||||
}
|
||||
|
||||
func (b *UpsertTalkgroupBatchResults) QueryRow(f func(int, Talkgroup, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
var i Talkgroup
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, i, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
row := b.br.QueryRow()
|
||||
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,
|
||||
)
|
||||
if f != nil {
|
||||
f(t, i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *UpsertTalkgroupBatchResults) Close() error {
|
||||
b.closed = true
|
||||
return b.br.Close()
|
||||
}
|
|
@ -43,7 +43,6 @@ func (db *Database) InTx(ctx context.Context, f func(Store) error, opts pgx.TxOp
|
|||
return fmt.Errorf("Tx begin: %w", err)
|
||||
}
|
||||
|
||||
//nolint:errcheck
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
dbtx := &Database{Pool: db.Pool, Queries: db.Queries.WithTx(tx)}
|
||||
|
|
|
@ -15,7 +15,6 @@ type DBTX interface {
|
|||
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||
SendBatch(context.Context, *pgx.Batch) pgx.BatchResults
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func (d GetTalkgroupsRow) GetTalkgroup() Talkgroup { return d.Talkgroup }
|
||||
func (d GetTalkgroupsRow) GetSystem() System { return d.System }
|
||||
func (d GetTalkgroupsRow) GetLearned() bool { return d.Talkgroup.Learned }
|
||||
|
@ -19,22 +15,3 @@ func (g GetTalkgroupsWithLearnedBySystemRow) GetLearned() bool { return g
|
|||
func (g Talkgroup) GetTalkgroup() Talkgroup { return g }
|
||||
func (g Talkgroup) GetSystem() System { return System{ID: int(g.SystemID)} }
|
||||
func (g Talkgroup) GetLearned() bool { return false }
|
||||
|
||||
func (g Talkgroup) String() string {
|
||||
return g.StringTag(true)
|
||||
}
|
||||
|
||||
func (g Talkgroup) StringTag(withTag bool) string {
|
||||
switch {
|
||||
case withTag && g.AlphaTag != nil:
|
||||
return *g.AlphaTag
|
||||
case g.Name != nil && g.TGGroup != nil:
|
||||
return *g.TGGroup + " " + *g.Name
|
||||
case g.Name != nil:
|
||||
return *g.Name + " [" + strconv.Itoa(int(g.TGID)) + "]"
|
||||
case g.TGGroup != nil:
|
||||
return *g.TGGroup + " [" + strconv.Itoa(int(g.TGID)) + "]"
|
||||
}
|
||||
|
||||
return strconv.Itoa(int(g.TGID))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
func (_m *Store) AddLearnedTalkgroup(ctx context.Context, arg database.AddLearnedTalkgroupParams) (database.Talkgroup, error) {
|
||||
func (_m *Store) AddLearnedTalkgroup(ctx context.Context, arg database.AddLearnedTalkgroupParams) (int, error) {
|
||||
ret := _m.Called(ctx, arg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AddLearnedTalkgroup")
|
||||
}
|
||||
|
||||
var r0 database.Talkgroup
|
||||
var r0 int
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, database.AddLearnedTalkgroupParams) (database.Talkgroup, error)); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, database.AddLearnedTalkgroupParams) (int, error)); ok {
|
||||
return rf(ctx, arg)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, database.AddLearnedTalkgroupParams) database.Talkgroup); ok {
|
||||
if rf, ok := ret.Get(0).(func(context.Context, database.AddLearnedTalkgroupParams) int); ok {
|
||||
r0 = rf(ctx, arg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(database.Talkgroup)
|
||||
r0 = ret.Get(0).(int)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, database.AddLearnedTalkgroupParams) error); ok {
|
||||
|
@ -169,12 +169,60 @@ func (_c *Store_AddLearnedTalkgroup_Call) Run(run func(ctx context.Context, arg
|
|||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_AddLearnedTalkgroup_Call) Return(_a0 database.Talkgroup, _a1 error) *Store_AddLearnedTalkgroup_Call {
|
||||
func (_c *Store_AddLearnedTalkgroup_Call) Return(_a0 int, _a1 error) *Store_AddLearnedTalkgroup_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_AddLearnedTalkgroup_Call) RunAndReturn(run func(context.Context, database.AddLearnedTalkgroupParams) (database.Talkgroup, error)) *Store_AddLearnedTalkgroup_Call {
|
||||
func (_c *Store_AddLearnedTalkgroup_Call) RunAndReturn(run func(context.Context, database.AddLearnedTalkgroupParams) (int, 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)
|
||||
return _c
|
||||
}
|
||||
|
@ -1521,63 +1569,6 @@ func (_c *Store_InTx_Call) RunAndReturn(run func(context.Context, func(database.
|
|||
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
|
||||
func (_m *Store) SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error {
|
||||
ret := _m.Called(ctx, iD, transcript)
|
||||
|
@ -1675,55 +1666,6 @@ func (_c *Store_SetTalkgroupTags_Call) RunAndReturn(run func(context.Context, []
|
|||
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
|
||||
func (_m *Store) UpdatePassword(ctx context.Context, username string, password string) error {
|
||||
ret := _m.Called(ctx, username, password)
|
||||
|
@ -1829,55 +1771,6 @@ func (_c *Store_UpdateTalkgroup_Call) RunAndReturn(run func(context.Context, dat
|
|||
return _c
|
||||
}
|
||||
|
||||
// UpsertTalkgroup provides a mock function with given fields: ctx, arg
|
||||
func (_m *Store) UpsertTalkgroup(ctx context.Context, arg []database.UpsertTalkgroupParams) *database.UpsertTalkgroupBatchResults {
|
||||
ret := _m.Called(ctx, arg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpsertTalkgroup")
|
||||
}
|
||||
|
||||
var r0 *database.UpsertTalkgroupBatchResults
|
||||
if rf, ok := ret.Get(0).(func(context.Context, []database.UpsertTalkgroupParams) *database.UpsertTalkgroupBatchResults); ok {
|
||||
r0 = rf(ctx, arg)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*database.UpsertTalkgroupBatchResults)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Store_UpsertTalkgroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpsertTalkgroup'
|
||||
type Store_UpsertTalkgroup_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpsertTalkgroup is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - arg []database.UpsertTalkgroupParams
|
||||
func (_e *Store_Expecter) UpsertTalkgroup(ctx interface{}, arg interface{}) *Store_UpsertTalkgroup_Call {
|
||||
return &Store_UpsertTalkgroup_Call{Call: _e.mock.On("UpsertTalkgroup", ctx, arg)}
|
||||
}
|
||||
|
||||
func (_c *Store_UpsertTalkgroup_Call) Run(run func(ctx context.Context, arg []database.UpsertTalkgroupParams)) *Store_UpsertTalkgroup_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].([]database.UpsertTalkgroupParams))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_UpsertTalkgroup_Call) Return(_a0 *database.UpsertTalkgroupBatchResults) *Store_UpsertTalkgroup_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_UpsertTalkgroup_Call) RunAndReturn(run func(context.Context, []database.UpsertTalkgroupParams) *database.UpsertTalkgroupBatchResults) *Store_UpsertTalkgroup_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// NewStore creates a new instance of Store. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
// The first argument is typically a *testing.T value.
|
||||
func NewStore(t interface {
|
||||
|
|
|
@ -96,26 +96,16 @@ type Talkgroup struct {
|
|||
AlertConfig rules.AlertRules `json:"alert_config"`
|
||||
Weight float32 `json:"weight"`
|
||||
Learned bool `json:"learned"`
|
||||
Ignored bool `json:"ignored"`
|
||||
}
|
||||
|
||||
type TalkgroupVersion struct {
|
||||
ID int `json:"id"`
|
||||
Time pgtype.Timestamptz `json:"time"`
|
||||
CreatedBy *int32 `json:"created_by"`
|
||||
SystemID *int32 `json:"system_id"`
|
||||
TGID *int32 `json:"tgid"`
|
||||
Name *string `json:"name"`
|
||||
AlphaTag *string `json:"alpha_tag"`
|
||||
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"`
|
||||
type TalkgroupsLearned struct {
|
||||
ID int `json:"id"`
|
||||
SystemID int `json:"system_id"`
|
||||
TGID int `json:"tgid"`
|
||||
Name string `json:"name"`
|
||||
AlphaTag *string `json:"alpha_tag"`
|
||||
TGGroup *string `json:"tg_group"`
|
||||
Ignored bool `json:"ignored"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
|
|
|
@ -14,7 +14,8 @@ import (
|
|||
type Querier interface {
|
||||
AddAlert(ctx context.Context, arg AddAlertParams) error
|
||||
AddCall(ctx context.Context, arg AddCallParams) error
|
||||
AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (Talkgroup, error)
|
||||
AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (int, error)
|
||||
AddTalkgroupWithLearnedFlag(ctx context.Context, systemID int32, tGID int32) error
|
||||
CreateAPIKey(ctx context.Context, owner int, expires pgtype.Timestamp, disabled *bool) (ApiKey, error)
|
||||
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
|
||||
DeleteAPIKey(ctx context.Context, apiKey string) error
|
||||
|
@ -34,13 +35,10 @@ type Querier interface {
|
|||
GetUserByUID(ctx context.Context, id int) (User, error)
|
||||
GetUserByUsername(ctx context.Context, username string) (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
|
||||
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
|
||||
UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams) (Talkgroup, error)
|
||||
UpsertTalkgroup(ctx context.Context, arg []UpsertTalkgroupParams) *UpsertTalkgroupBatchResults
|
||||
}
|
||||
|
||||
var _ Querier = (*Queries)(nil)
|
||||
|
|
|
@ -41,11 +41,20 @@ func (t *TGTuples) Append(sys, tg uint32) {
|
|||
// Below queries are here because sqlc refuses to parse unnest(x, y)
|
||||
|
||||
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.ignored
|
||||
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
|
||||
FROM talkgroups tg
|
||||
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)
|
||||
WHERE tg.learned IS NOT TRUE;`
|
||||
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 {
|
||||
Talkgroup Talkgroup `json:"talkgroup"`
|
||||
|
@ -77,7 +86,6 @@ func (q *Queries) GetTalkgroupsWithLearnedBySysTGID(ctx context.Context, ids TGT
|
|||
&i.System.ID,
|
||||
&i.System.Name,
|
||||
&i.Talkgroup.Learned,
|
||||
&i.Talkgroup.Ignored,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -89,7 +97,7 @@ func (q *Queries) GetTalkgroupsWithLearnedBySysTGID(ctx context.Context, ids TGT
|
|||
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, tg.learned, tg.ignored, 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, sys.id, sys.name FROM talkgroups tg
|
||||
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)
|
||||
WHERE tg.learned IS NOT TRUE;`
|
||||
|
@ -116,8 +124,6 @@ func (q *Queries) GetTalkgroupsBySysTGID(ctx context.Context, ids TGTuples) ([]G
|
|||
&i.Talkgroup.Alert,
|
||||
&i.Talkgroup.AlertConfig,
|
||||
&i.Talkgroup.Weight,
|
||||
&i.Talkgroup.Learned,
|
||||
&i.Talkgroup.Ignored,
|
||||
&i.System.ID,
|
||||
&i.System.Name,
|
||||
); err != nil {
|
||||
|
|
|
@ -13,32 +13,30 @@ import (
|
|||
)
|
||||
|
||||
const addLearnedTalkgroup = `-- name: AddLearnedTalkgroup :one
|
||||
INSERT INTO talkgroups(
|
||||
INSERT INTO talkgroups_learned(
|
||||
system_id,
|
||||
tgid,
|
||||
learned,
|
||||
name,
|
||||
alpha_tag,
|
||||
tg_group
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
TRUE,
|
||||
$3,
|
||||
$4,
|
||||
$5
|
||||
) RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned, ignored
|
||||
) RETURNING id
|
||||
`
|
||||
|
||||
type AddLearnedTalkgroupParams struct {
|
||||
SystemID int32 `json:"system_id"`
|
||||
TGID int32 `json:"tgid"`
|
||||
SystemID int `json:"system_id"`
|
||||
TGID int `json:"tgid"`
|
||||
Name *string `json:"name"`
|
||||
AlphaTag *string `json:"alpha_tag"`
|
||||
TGGroup *string `json:"tg_group"`
|
||||
}
|
||||
|
||||
func (q *Queries) AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (Talkgroup, error) {
|
||||
func (q *Queries) AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (int, error) {
|
||||
row := q.db.QueryRow(ctx, addLearnedTalkgroup,
|
||||
arg.SystemID,
|
||||
arg.TGID,
|
||||
|
@ -46,24 +44,26 @@ func (q *Queries) AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgro
|
|||
arg.AlphaTag,
|
||||
arg.TGGroup,
|
||||
)
|
||||
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
|
||||
var id int
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
const addTalkgroupWithLearnedFlag = `-- name: AddTalkgroupWithLearnedFlag :exec
|
||||
INSERT INTO talkgroups (
|
||||
system_id,
|
||||
tgid,
|
||||
learned
|
||||
) VALUES(
|
||||
$1,
|
||||
$2,
|
||||
't'
|
||||
)
|
||||
`
|
||||
|
||||
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
|
||||
|
@ -78,7 +78,7 @@ func (q *Queries) GetSystemName(ctx context.Context, systemID int) (string, erro
|
|||
}
|
||||
|
||||
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, talkgroups.ignored 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 FROM talkgroups
|
||||
WHERE (system_id, tgid) = ($1, $2)
|
||||
`
|
||||
|
||||
|
@ -103,7 +103,6 @@ func (q *Queries) GetTalkgroup(ctx context.Context, systemID int32, tGID int32)
|
|||
&i.Talkgroup.AlertConfig,
|
||||
&i.Talkgroup.Weight,
|
||||
&i.Talkgroup.Learned,
|
||||
&i.Talkgroup.Ignored,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
@ -154,10 +153,19 @@ func (q *Queries) GetTalkgroupTags(ctx context.Context, systemID int32, tGID int
|
|||
|
||||
const getTalkgroupWithLearned = `-- 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, tg.ignored, 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, sys.id, sys.name
|
||||
FROM talkgroups tg
|
||||
JOIN systems sys ON tg.system_id = sys.id
|
||||
WHERE (tg.system_id, tg.tgid) = ($1, $2)
|
||||
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
|
||||
`
|
||||
|
||||
type GetTalkgroupWithLearnedRow struct {
|
||||
|
@ -182,7 +190,6 @@ func (q *Queries) GetTalkgroupWithLearned(ctx context.Context, systemID int32, t
|
|||
&i.Talkgroup.AlertConfig,
|
||||
&i.Talkgroup.Weight,
|
||||
&i.Talkgroup.Learned,
|
||||
&i.Talkgroup.Ignored,
|
||||
&i.System.ID,
|
||||
&i.System.Name,
|
||||
)
|
||||
|
@ -190,7 +197,7 @@ func (q *Queries) GetTalkgroupWithLearned(ctx context.Context, systemID int32, t
|
|||
}
|
||||
|
||||
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, talkgroups.ignored 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 FROM talkgroups
|
||||
WHERE tags && ARRAY[$1]
|
||||
`
|
||||
|
||||
|
@ -221,7 +228,6 @@ func (q *Queries) GetTalkgroupsWithAllTags(ctx context.Context, tags []string) (
|
|||
&i.Talkgroup.AlertConfig,
|
||||
&i.Talkgroup.Weight,
|
||||
&i.Talkgroup.Learned,
|
||||
&i.Talkgroup.Ignored,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -234,7 +240,7 @@ func (q *Queries) GetTalkgroupsWithAllTags(ctx context.Context, tags []string) (
|
|||
}
|
||||
|
||||
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, talkgroups.ignored 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 FROM talkgroups
|
||||
WHERE tags @> ARRAY[$1]
|
||||
`
|
||||
|
||||
|
@ -265,7 +271,6 @@ func (q *Queries) GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) (
|
|||
&i.Talkgroup.AlertConfig,
|
||||
&i.Talkgroup.Weight,
|
||||
&i.Talkgroup.Learned,
|
||||
&i.Talkgroup.Ignored,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -279,9 +284,18 @@ func (q *Queries) GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) (
|
|||
|
||||
const getTalkgroupsWithLearned = `-- 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, tg.ignored, 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, 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
|
||||
`
|
||||
|
||||
|
@ -313,7 +327,6 @@ func (q *Queries) GetTalkgroupsWithLearned(ctx context.Context) ([]GetTalkgroups
|
|||
&i.Talkgroup.AlertConfig,
|
||||
&i.Talkgroup.Weight,
|
||||
&i.Talkgroup.Learned,
|
||||
&i.Talkgroup.Ignored,
|
||||
&i.System.ID,
|
||||
&i.System.Name,
|
||||
); err != nil {
|
||||
|
@ -329,10 +342,19 @@ func (q *Queries) GetTalkgroupsWithLearned(ctx context.Context) ([]GetTalkgroups
|
|||
|
||||
const getTalkgroupsWithLearnedBySystem = `-- 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, tg.ignored, 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, sys.id, sys.name
|
||||
FROM talkgroups tg
|
||||
JOIN systems sys ON tg.system_id = sys.id
|
||||
WHERE tg.system_id = $1
|
||||
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
|
||||
`
|
||||
|
||||
type GetTalkgroupsWithLearnedBySystemRow struct {
|
||||
|
@ -363,7 +385,6 @@ func (q *Queries) GetTalkgroupsWithLearnedBySystem(ctx context.Context, system i
|
|||
&i.Talkgroup.AlertConfig,
|
||||
&i.Talkgroup.Weight,
|
||||
&i.Talkgroup.Learned,
|
||||
&i.Talkgroup.Ignored,
|
||||
&i.System.ID,
|
||||
&i.System.Name,
|
||||
); err != nil {
|
||||
|
@ -377,73 +398,6 @@ func (q *Queries) GetTalkgroupsWithLearnedBySystem(ctx context.Context, system i
|
|||
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
|
||||
UPDATE talkgroups SET tags = $1
|
||||
WHERE system_id = $2 AND tgid = $3
|
||||
|
@ -465,10 +419,9 @@ SET
|
|||
tags = COALESCE($6, tags),
|
||||
alert = COALESCE($7, alert),
|
||||
alert_config = COALESCE($8, alert_config),
|
||||
weight = COALESCE($9, weight),
|
||||
learned = COALESCE($10, learned)
|
||||
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, ignored
|
||||
weight = COALESCE($9, weight)
|
||||
WHERE id = $10 OR (system_id = $11 AND tgid = $12)
|
||||
RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned
|
||||
`
|
||||
|
||||
type UpdateTalkgroupParams struct {
|
||||
|
@ -481,7 +434,6 @@ type UpdateTalkgroupParams struct {
|
|||
Alert *bool `json:"alert"`
|
||||
AlertConfig rules.AlertRules `json:"alert_config"`
|
||||
Weight *float32 `json:"weight"`
|
||||
Learned *bool `json:"learned"`
|
||||
ID *int32 `json:"id"`
|
||||
SystemID *int32 `json:"system_id"`
|
||||
TGID *int32 `json:"tgid"`
|
||||
|
@ -498,7 +450,6 @@ func (q *Queries) UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams
|
|||
arg.Alert,
|
||||
arg.AlertConfig,
|
||||
arg.Weight,
|
||||
arg.Learned,
|
||||
arg.ID,
|
||||
arg.SystemID,
|
||||
arg.TGID,
|
||||
|
@ -518,7 +469,6 @@ func (q *Queries) UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams
|
|||
&i.AlertConfig,
|
||||
&i.Weight,
|
||||
&i.Learned,
|
||||
&i.Ignored,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
64
pkg/database/talkgroups.sql_test.go
Normal file
64
pkg/database/talkgroups.sql_test.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
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)
|
||||
}
|
|
@ -6,7 +6,6 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
"dynatron.me/x/stillbox/pkg/pb"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
@ -60,9 +59,9 @@ func (c *client) SendError(cmd *pb.Command, err error) {
|
|||
}
|
||||
|
||||
func (c *client) Talkgroup(ctx context.Context, tg *pb.Talkgroup) error {
|
||||
tgi, err := tgstore.FromCtx(ctx).TG(ctx, talkgroups.TG(tg.System, tg.Talkgroup))
|
||||
tgi, err := talkgroups.StoreFrom(ctx).TG(ctx, talkgroups.TG(tg.System, tg.Talkgroup))
|
||||
if err != nil {
|
||||
if err != tgstore.ErrNotFound {
|
||||
if err != talkgroups.ErrNotFound {
|
||||
log.Error().Err(err).Int32("sys", tg.System).Int32("tg", tg.Talkgroup).Msg("get talkgroup fail")
|
||||
}
|
||||
return err
|
||||
|
|
|
@ -52,7 +52,7 @@ var alertFm = template.FuncMap{
|
|||
|
||||
const (
|
||||
defaultBodyTemplStr = `{{ range . -}}
|
||||
{{ .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)
|
||||
{{ .TGName }} is active with a score of {{ f .Score.Score 4 }}! ({{ f .Score.RecentCount 0 }}/{{ .Score.Count }} recent calls)
|
||||
|
||||
{{ end -}}`
|
||||
defaultSubjectTemplStr = `Stillbox Alert ({{ highest . }})`
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"errors"
|
||||
"net/http"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
|
@ -42,6 +42,7 @@ type errResponse struct {
|
|||
|
||||
func (e *errResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
switch e.Code {
|
||||
case http.StatusNotFound:
|
||||
default:
|
||||
log.Error().Str("path", r.URL.Path).Err(e.Err).Int("code", e.Code).Str("msg", e.Error).Msg("request failed")
|
||||
}
|
||||
|
@ -67,19 +68,10 @@ 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 {
|
||||
return &errResponse{
|
||||
Err: err,
|
||||
Code: http.StatusInternalServerError,
|
||||
Code: http.StatusNotFound,
|
||||
Error: "Internal server error",
|
||||
}
|
||||
}
|
||||
|
@ -87,8 +79,8 @@ func internalError(err error) render.Renderer {
|
|||
type errResponder func(error) render.Renderer
|
||||
|
||||
var statusMapping = map[error]errResponder{
|
||||
tgstore.ErrNoSuchSystem: errTextNotFound,
|
||||
tgstore.ErrNotFound: errTextNotFound,
|
||||
talkgroups.ErrNoSuchSystem: recordNotFound,
|
||||
talkgroups.ErrNotFound: recordNotFound,
|
||||
pgx.ErrNoRows: recordNotFound,
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"dynatron.me/x/stillbox/internal/forms"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/importer"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
@ -18,10 +17,9 @@ type talkgroupAPI struct {
|
|||
func (tga *talkgroupAPI) Subrouter() http.Handler {
|
||||
r := chi.NewMux()
|
||||
|
||||
r.Get(`/{system:\d+}/{id:\d+}`, tga.get)
|
||||
r.Put(`/{system:\d+}/{id:\d+}`, tga.put)
|
||||
r.Put(`/{system:\d+}`, tga.putTalkgroups)
|
||||
r.Get(`/{system:\d+}/`, tga.get)
|
||||
r.Get("/{system:\\d+}/{id:\\d+}", tga.get)
|
||||
r.Put("/{system:\\d+}/{id:\\d+}", tga.put)
|
||||
r.Get("/{system:\\d+}/", tga.get)
|
||||
r.Get("/", tga.get)
|
||||
r.Post("/import", tga.tgImport)
|
||||
|
||||
|
@ -33,7 +31,7 @@ type tgParams struct {
|
|||
ID *int `param:"id"`
|
||||
}
|
||||
|
||||
func (t tgParams) hasBoth() bool {
|
||||
func (t tgParams) haveBoth() bool {
|
||||
return t.System != nil && t.ID != nil
|
||||
}
|
||||
|
||||
|
@ -54,7 +52,7 @@ func (t tgParams) ToID() talkgroups.ID {
|
|||
|
||||
func (tga *talkgroupAPI) get(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
tgs := tgstore.FromCtx(ctx)
|
||||
tgs := talkgroups.StoreFrom(ctx)
|
||||
|
||||
var p tgParams
|
||||
|
||||
|
@ -66,12 +64,11 @@ func (tga *talkgroupAPI) get(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
var res interface{}
|
||||
switch {
|
||||
case p.hasBoth():
|
||||
case p.System != nil && p.ID != nil:
|
||||
res, err = tgs.TG(ctx, talkgroups.TG(*p.System, *p.ID))
|
||||
case p.System != nil:
|
||||
res, err = tgs.SystemTGs(ctx, int32(*p.System))
|
||||
default:
|
||||
// get all talkgroups
|
||||
res, err = tgs.TGs(ctx, nil)
|
||||
}
|
||||
|
||||
|
@ -92,7 +89,7 @@ func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
ctx := r.Context()
|
||||
tgs := tgstore.FromCtx(ctx)
|
||||
tgs := talkgroups.StoreFrom(ctx)
|
||||
|
||||
input := database.UpdateTalkgroupParams{}
|
||||
|
||||
|
@ -102,8 +99,6 @@ func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
input.Learned = nil // ignore for this call
|
||||
|
||||
record, err := tgs.UpdateTG(ctx, input)
|
||||
if err != nil {
|
||||
wErr(w, r, autoError(err))
|
||||
|
@ -128,36 +123,3 @@ func (tga *talkgroupAPI) tgImport(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
respond(w, r, recs)
|
||||
}
|
||||
|
||||
func (tga *talkgroupAPI) putTalkgroups(w http.ResponseWriter, r *http.Request) {
|
||||
var id tgParams
|
||||
err := decodeParams(&id, r)
|
||||
if err != nil {
|
||||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
if id.System == nil { // don't think this would ever happen
|
||||
wErr(w, r, badRequest(tgstore.ErrNoSuchSystem))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
tgs := tgstore.FromCtx(ctx)
|
||||
|
||||
var input []database.UpsertTalkgroupParams
|
||||
|
||||
err = forms.Unmarshal(r, &input, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
|
||||
if err != nil {
|
||||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
record, err := tgs.UpsertTGs(ctx, *id.System, input)
|
||||
if err != nil {
|
||||
wErr(w, r, autoError(err))
|
||||
return
|
||||
}
|
||||
|
||||
respond(w, r, record)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"dynatron.me/x/stillbox/internal/version"
|
||||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/httprate"
|
||||
|
@ -28,7 +28,7 @@ func (s *Server) setupRoutes() {
|
|||
|
||||
r := s.r
|
||||
r.Use(middleware.WithValue(database.DBCtxKey, s.db))
|
||||
r.Use(middleware.WithValue(tgstore.StoreCtxKey, s.tgs))
|
||||
r.Use(middleware.WithValue(talkgroups.StoreCtxKey, s.tgs))
|
||||
|
||||
s.installPprof()
|
||||
|
||||
|
|
|
@ -15,8 +15,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/rest"
|
||||
"dynatron.me/x/stillbox/pkg/sinks"
|
||||
"dynatron.me/x/stillbox/pkg/sources"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/go-chi/cors"
|
||||
|
@ -38,7 +37,7 @@ type Server struct {
|
|||
alerter alerting.Alerter
|
||||
notifier notify.Notifier
|
||||
hup chan os.Signal
|
||||
tgs tgstore.Store
|
||||
tgs talkgroups.Store
|
||||
rest rest.API
|
||||
}
|
||||
|
||||
|
@ -62,7 +61,7 @@ func New(ctx context.Context, cfg *config.Config) (*Server, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
tgCache := tgstore.NewCache()
|
||||
tgCache := talkgroups.NewCache()
|
||||
api := rest.New()
|
||||
|
||||
srv := &Server{
|
||||
|
@ -79,7 +78,7 @@ func New(ctx context.Context, cfg *config.Config) (*Server, error) {
|
|||
rest: api,
|
||||
}
|
||||
|
||||
srv.sinks.Register("database", sinks.NewDatabaseSink(srv.db, tgCache), true)
|
||||
srv.sinks.Register("database", sinks.NewDatabaseSink(srv.db), true)
|
||||
srv.sinks.Register("nexus", sinks.NewNexusSink(srv.nex), false)
|
||||
|
||||
if srv.alerter.Enabled() {
|
||||
|
@ -118,7 +117,7 @@ func (s *Server) Go(ctx context.Context) error {
|
|||
s.installHupHandler()
|
||||
|
||||
ctx = database.CtxWithDB(ctx, s.db)
|
||||
ctx = tgstore.CtxWithStore(ctx, s.tgs)
|
||||
ctx = talkgroups.CtxWithStore(ctx, s.tgs)
|
||||
|
||||
httpSrv := &http.Server{
|
||||
Addr: s.conf.Listen,
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"dynatron.me/x/stillbox/internal/common"
|
||||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
|
@ -16,11 +15,10 @@ import (
|
|||
|
||||
type DatabaseSink struct {
|
||||
db database.Store
|
||||
tgs tgstore.Store
|
||||
}
|
||||
|
||||
func NewDatabaseSink(store database.Store, tgs tgstore.Store) *DatabaseSink {
|
||||
return &DatabaseSink{store, tgs}
|
||||
func NewDatabaseSink(store database.Store) *DatabaseSink {
|
||||
return &DatabaseSink{store}
|
||||
}
|
||||
|
||||
func (s *DatabaseSink) Call(ctx context.Context, call *calls.Call) error {
|
||||
|
@ -45,14 +43,14 @@ func (s *DatabaseSink) Call(ctx context.Context, call *calls.Call) error {
|
|||
|
||||
if err != nil && database.IsTGConstraintViolation(err) {
|
||||
return s.db.InTx(ctx, func(tx database.Store) error {
|
||||
_, err := s.tgs.LearnTG(ctx, call)
|
||||
_, err := call.LearnTG(ctx, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("learn tg: %w", err)
|
||||
return fmt.Errorf("add call: learn tg: %w", err)
|
||||
}
|
||||
|
||||
err = tx.AddCall(ctx, params)
|
||||
if err != nil {
|
||||
return fmt.Errorf("learn tg retry: %w", err)
|
||||
return fmt.Errorf("add call: retry: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -131,7 +131,7 @@ func (h *RdioHTTP) routeCallUpload(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
log.Info().Int("system", cur.System).Int("tgid", cur.Talkgroup).Str("duration", call.Duration.Duration().String()).Msg("ingested")
|
||||
log.Info().Int("system", cur.System).Int("tgid", cur.Talkgroup).Msg("ingested")
|
||||
|
||||
written, err := w.Write([]byte("Call imported successfully."))
|
||||
if err != nil {
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
package tgstore
|
||||
package talkgroups
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/common"
|
||||
"dynatron.me/x/stillbox/pkg/auth"
|
||||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/calls"
|
||||
tgsp "dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type tgMap map[tgsp.ID]*tgsp.Talkgroup
|
||||
type tgMap map[ID]*Talkgroup
|
||||
|
||||
var (
|
||||
ErrNotFound = errors.New("talkgroup not found")
|
||||
|
@ -27,28 +22,22 @@ var (
|
|||
|
||||
type Store interface {
|
||||
// UpdateTG updates a talkgroup record.
|
||||
UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*tgsp.Talkgroup, error)
|
||||
|
||||
// UpsertTGs upserts a slice of talkgroups.
|
||||
UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*tgsp.Talkgroup, error)
|
||||
UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*Talkgroup, error)
|
||||
|
||||
// TG retrieves a Talkgroup from the Store.
|
||||
TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error)
|
||||
TG(ctx context.Context, tg ID) (*Talkgroup, error)
|
||||
|
||||
// TGs retrieves many talkgroups from the Store.
|
||||
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)
|
||||
TGs(ctx context.Context, tgs IDs) ([]*Talkgroup, error)
|
||||
|
||||
// SystemTGs retrieves all Talkgroups associated with a System.
|
||||
SystemTGs(ctx context.Context, systemID int32) ([]*tgsp.Talkgroup, error)
|
||||
SystemTGs(ctx context.Context, systemID int32) ([]*Talkgroup, error)
|
||||
|
||||
// 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)
|
||||
|
||||
// Hint hints the Store that the provided talkgroups will be asked for.
|
||||
Hint(ctx context.Context, tgs []tgsp.ID) error
|
||||
Hint(ctx context.Context, tgs []ID) error
|
||||
|
||||
// Load loads the provided talkgroup ID tuples into the Store.
|
||||
Load(ctx context.Context, tgs database.TGTuples) error
|
||||
|
@ -57,7 +46,7 @@ type Store interface {
|
|||
Invalidate()
|
||||
|
||||
// Weight returns the final weight of this talkgroup, including its static and rules-derived weight.
|
||||
Weight(ctx context.Context, id tgsp.ID, t time.Time) float64
|
||||
Weight(ctx context.Context, id ID, t time.Time) float64
|
||||
|
||||
// Hupper
|
||||
HUP(*config.Config)
|
||||
|
@ -71,7 +60,7 @@ func CtxWithStore(ctx context.Context, s Store) context.Context {
|
|||
return context.WithValue(ctx, StoreCtxKey, s)
|
||||
}
|
||||
|
||||
func FromCtx(ctx context.Context) Store {
|
||||
func StoreFrom(ctx context.Context) Store {
|
||||
s, ok := ctx.Value(StoreCtxKey).(Store)
|
||||
if !ok {
|
||||
return NewCache()
|
||||
|
@ -107,7 +96,7 @@ func NewCache() Store {
|
|||
return tgc
|
||||
}
|
||||
|
||||
func (t *cache) Hint(ctx context.Context, tgs []tgsp.ID) error {
|
||||
func (t *cache) Hint(ctx context.Context, tgs []ID) error {
|
||||
t.RLock()
|
||||
var toLoad database.TGTuples
|
||||
if len(t.tgs) > len(tgs)/2 { // TODO: instrument this
|
||||
|
@ -135,13 +124,15 @@ func (t *cache) Hint(ctx context.Context, tgs []tgsp.ID) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (t *cache) add(rec *tgsp.Talkgroup) {
|
||||
func (t *cache) add(rec *Talkgroup) error {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
tg := tgsp.TG(rec.System.ID, rec.Talkgroup.TGID)
|
||||
tg := TG(rec.System.ID, rec.Talkgroup.TGID)
|
||||
t.tgs[tg] = rec
|
||||
t.systems[int32(rec.System.ID)] = rec.System.Name
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type row interface {
|
||||
|
@ -152,30 +143,33 @@ type row interface {
|
|||
GetLearned() bool
|
||||
}
|
||||
|
||||
func rowToTalkgroup[T row](r T) *tgsp.Talkgroup {
|
||||
return &tgsp.Talkgroup{
|
||||
func rowToTalkgroup[T row](r T) *Talkgroup {
|
||||
return &Talkgroup{
|
||||
Talkgroup: r.GetTalkgroup(),
|
||||
System: r.GetSystem(),
|
||||
Learned: r.GetLearned(),
|
||||
}
|
||||
}
|
||||
|
||||
func addToRowList[T row](t *cache, r []*tgsp.Talkgroup, tgRecords []T) []*tgsp.Talkgroup {
|
||||
func addToRowList[T row](t *cache, r []*Talkgroup, tgRecords []T) ([]*Talkgroup, error) {
|
||||
for _, rec := range tgRecords {
|
||||
tg := rowToTalkgroup(rec)
|
||||
t.add(tg)
|
||||
err := t.add(tg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r = append(r, tg)
|
||||
}
|
||||
|
||||
return r
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (t *cache) TGs(ctx context.Context, tgs tgsp.IDs) ([]*tgsp.Talkgroup, error) {
|
||||
r := make([]*tgsp.Talkgroup, 0, len(tgs))
|
||||
func (t *cache) TGs(ctx context.Context, tgs IDs) ([]*Talkgroup, error) {
|
||||
r := make([]*Talkgroup, 0, len(tgs))
|
||||
var err error
|
||||
if tgs != nil {
|
||||
toGet := make(tgsp.IDs, 0, len(tgs))
|
||||
toGet := make(IDs, 0, len(tgs))
|
||||
t.RLock()
|
||||
for _, id := range tgs {
|
||||
rec, has := t.tgs[id]
|
||||
|
@ -191,7 +185,7 @@ func (t *cache) TGs(ctx context.Context, tgs tgsp.IDs) ([]*tgsp.Talkgroup, error
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addToRowList(t, r, tgRecords), nil
|
||||
return addToRowList(t, r, tgRecords)
|
||||
}
|
||||
|
||||
// get all talkgroups
|
||||
|
@ -200,7 +194,7 @@ func (t *cache) TGs(ctx context.Context, tgs tgsp.IDs) ([]*tgsp.Talkgroup, error
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addToRowList(t, r, tgRecords), nil
|
||||
return addToRowList(t, r, tgRecords)
|
||||
}
|
||||
|
||||
func (t *cache) Load(ctx context.Context, tgs database.TGTuples) error {
|
||||
|
@ -210,13 +204,17 @@ func (t *cache) Load(ctx context.Context, tgs database.TGTuples) error {
|
|||
}
|
||||
|
||||
for _, rec := range tgRecords {
|
||||
t.add(rowToTalkgroup(rec))
|
||||
err := t.add(rowToTalkgroup(rec))
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("add alert config fail")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *cache) Weight(ctx context.Context, id tgsp.ID, tm time.Time) float64 {
|
||||
func (t *cache) Weight(ctx context.Context, id ID, tm time.Time) float64 {
|
||||
tg, err := t.TG(ctx, id)
|
||||
if err != nil {
|
||||
return 1.0
|
||||
|
@ -229,17 +227,17 @@ func (t *cache) Weight(ctx context.Context, id tgsp.ID, tm time.Time) float64 {
|
|||
return float64(m)
|
||||
}
|
||||
|
||||
func (t *cache) SystemTGs(ctx context.Context, systemID int32) ([]*tgsp.Talkgroup, error) {
|
||||
func (t *cache) SystemTGs(ctx context.Context, systemID int32) ([]*Talkgroup, error) {
|
||||
recs, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedBySystem(ctx, systemID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := make([]*tgsp.Talkgroup, 0, len(recs))
|
||||
return addToRowList(t, r, recs), nil
|
||||
r := make([]*Talkgroup, 0, len(recs))
|
||||
return addToRowList(t, r, recs)
|
||||
}
|
||||
|
||||
func (t *cache) TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error) {
|
||||
func (t *cache) TG(ctx context.Context, tg ID) (*Talkgroup, error) {
|
||||
t.RLock()
|
||||
rec, has := t.tgs[tg]
|
||||
t.RUnlock()
|
||||
|
@ -258,7 +256,11 @@ func (t *cache) TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error) {
|
|||
return nil, errors.Join(ErrNotFound, err)
|
||||
}
|
||||
|
||||
t.add(rowToTalkgroup(record))
|
||||
err = t.add(rowToTalkgroup(record))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("TG() cache add")
|
||||
return rowToTalkgroup(record), errors.Join(ErrNotFound, err)
|
||||
}
|
||||
|
||||
return rowToTalkgroup(record), nil
|
||||
}
|
||||
|
@ -284,7 +286,7 @@ func (t *cache) SystemName(ctx context.Context, id int) (name string, has bool)
|
|||
return n, has
|
||||
}
|
||||
|
||||
func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*tgsp.Talkgroup, error) {
|
||||
func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*Talkgroup, error) {
|
||||
sysName, has := t.SystemName(ctx, int(*input.SystemID))
|
||||
if !has {
|
||||
return nil, ErrNoSuchSystem
|
||||
|
@ -295,7 +297,7 @@ func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupPara
|
|||
return nil, err
|
||||
}
|
||||
|
||||
record := &tgsp.Talkgroup{
|
||||
record := &Talkgroup{
|
||||
Talkgroup: tg,
|
||||
System: database.System{ID: int(tg.SystemID), Name: sysName},
|
||||
}
|
||||
|
@ -303,114 +305,3 @@ func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupPara
|
|||
|
||||
return record, nil
|
||||
}
|
||||
|
||||
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)
|
||||
sysName, hasSys := t.SystemName(ctx, system)
|
||||
if !hasSys {
|
||||
return nil, ErrNoSuchSystem
|
||||
}
|
||||
sys := database.System{
|
||||
ID: system,
|
||||
Name: sysName,
|
||||
}
|
||||
|
||||
tgs := make([]*tgsp.Talkgroup, 0, len(input))
|
||||
|
||||
err := db.InTx(ctx, func(db database.Store) error {
|
||||
versionParams := make([]database.StoreTGVersionParams, 0, len(input))
|
||||
for i := range input {
|
||||
// normalize tags
|
||||
for j, tag := range input[i].Tags {
|
||||
input[i].Tags[j] = strings.ToLower(tag)
|
||||
}
|
||||
|
||||
input[i].SystemID = int32(system)
|
||||
input[i].Learned = common.PtrTo(false)
|
||||
|
||||
|
||||
}
|
||||
|
||||
var oerr error
|
||||
|
||||
tgUpsertBatch := db.UpsertTalkgroup(ctx, input)
|
||||
defer tgUpsertBatch.Close()
|
||||
|
||||
tgUpsertBatch.QueryRow(func(_ int, r database.Talkgroup, err error) {
|
||||
if err != nil {
|
||||
oerr = err
|
||||
return
|
||||
}
|
||||
versionParams = append(versionParams, database.StoreTGVersionParams{
|
||||
SystemID: int32(system),
|
||||
TGID: r.TGID,
|
||||
Submitter: auth.UIDFrom(ctx),
|
||||
})
|
||||
tgs = append(tgs, &tgsp.Talkgroup{
|
||||
Talkgroup: r,
|
||||
System: sys,
|
||||
Learned: r.Learned,
|
||||
})
|
||||
})
|
||||
|
||||
if oerr != nil {
|
||||
return oerr
|
||||
}
|
||||
|
||||
versionBatch := db.StoreTGVersion(ctx, versionParams)
|
||||
defer versionBatch.Close()
|
||||
|
||||
versionBatch.Exec(func(_ int, err error) {
|
||||
if err != nil {
|
||||
oerr = err
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
return oerr
|
||||
}, pgx.TxOptions{})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// update the cache
|
||||
for _, tg := range tgs {
|
||||
t.add(tg)
|
||||
}
|
||||
|
||||
return tgs, nil
|
||||
}
|
|
@ -13,7 +13,6 @@ import (
|
|||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
)
|
||||
|
||||
type ImportSource string
|
||||
|
@ -67,9 +66,9 @@ 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) {
|
||||
sc := bufio.NewScanner(r)
|
||||
tgs := make([]talkgroups.Talkgroup, 0, 8)
|
||||
sysn, has := tgstore.FromCtx(ctx).SystemName(ctx, sys)
|
||||
sysn, has := talkgroups.StoreFrom(ctx).SystemName(ctx, sys)
|
||||
if !has {
|
||||
return nil, tgstore.ErrNoSuchSystem
|
||||
return nil, talkgroups.ErrNoSuchSystem
|
||||
}
|
||||
|
||||
var groupName string
|
||||
|
@ -111,7 +110,6 @@ func (rr *radioReferenceImporter) importTalkgroups(ctx context.Context, sys int,
|
|||
gn := groupName // must take a copy
|
||||
tgs = append(tgs, talkgroups.Talkgroup{
|
||||
Talkgroup: database.Talkgroup{
|
||||
ID: len(tgs), // need unique ID for the UI to track
|
||||
TGID: int32(tgt.Talkgroup),
|
||||
SystemID: int32(tgt.System),
|
||||
Name: &fields[4],
|
||||
|
@ -120,7 +118,6 @@ func (rr *radioReferenceImporter) importTalkgroups(ctx context.Context, sys int,
|
|||
Metadata: metadata,
|
||||
Tags: tags,
|
||||
Weight: 1.0,
|
||||
Alert: true,
|
||||
},
|
||||
System: database.System{
|
||||
ID: sys,
|
||||
|
|
|
@ -16,7 +16,6 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/database/mocks"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/importer"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
)
|
||||
|
||||
func getFixture(fixture string) []byte {
|
||||
|
@ -63,7 +62,7 @@ func TestImport(t *testing.T) {
|
|||
dbMock.EXPECT().GetSystemName(mock.AnythingOfType("*context.valueCtx"), tc.sysID).Return(tc.sysName, nil)
|
||||
}
|
||||
ctx := database.CtxWithDB(context.Background(), dbMock)
|
||||
ctx = tgstore.CtxWithStore(ctx, tgstore.NewCache())
|
||||
ctx = talkgroups.CtxWithStore(ctx, talkgroups.NewCache())
|
||||
ij := &importer.ImportJob{
|
||||
Type: importer.ImportSource(tc.impType),
|
||||
SystemID: tc.sysID,
|
||||
|
|
2
pkg/talkgroups/importer/testdata/riscon.json
vendored
2
pkg/talkgroups/importer/testdata/riscon.json
vendored
File diff suppressed because one or more lines are too long
|
@ -2,7 +2,6 @@ package talkgroups
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
)
|
||||
|
@ -13,20 +12,13 @@ type Talkgroup struct {
|
|||
Learned bool `json:"learned"`
|
||||
}
|
||||
|
||||
func (t Talkgroup) String() string {
|
||||
if t.System.Name == "" {
|
||||
t.System.Name = strconv.Itoa(int(t.Talkgroup.TGID))
|
||||
}
|
||||
|
||||
if t.Talkgroup.Name != nil || t.Talkgroup.TGGroup != nil || t.Talkgroup.AlphaTag != nil {
|
||||
return t.System.Name + " " + t.Talkgroup.String()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d", t.System.Name, int(t.Talkgroup.TGID))
|
||||
}
|
||||
|
||||
type Metadata map[string]interface{}
|
||||
|
||||
type Names struct {
|
||||
System string
|
||||
Talkgroup string
|
||||
}
|
||||
|
||||
type ID struct {
|
||||
System uint32 `json:"sys"`
|
||||
Talkgroup uint32 `json:"tg"`
|
||||
|
|
|
@ -37,7 +37,6 @@ CREATE TABLE IF NOT EXISTS talkgroups(
|
|||
alert_config JSONB,
|
||||
weight REAL NOT NULL DEFAULT 1.0,
|
||||
learned BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
ignored BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
UNIQUE (system_id, tgid)
|
||||
);
|
||||
|
||||
|
@ -45,25 +44,15 @@ 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 TABLE IF NOT EXISTS talkgroup_versions(
|
||||
-- version metadata
|
||||
CREATE TABLE IF NOT EXISTS talkgroups_learned(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
time TIMESTAMPTZ NOT NULL,
|
||||
created_by INTEGER REFERENCES users(id),
|
||||
-- talkgroup snapshot
|
||||
system_id INT4 REFERENCES systems(id),
|
||||
tgid INT4,
|
||||
name TEXT,
|
||||
system_id INTEGER REFERENCES systems(id) NOT NULL,
|
||||
tgid INTEGER NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
alpha_tag TEXT,
|
||||
tg_group TEXT,
|
||||
frequency INTEGER,
|
||||
metadata JSONB,
|
||||
tags TEXT[],
|
||||
alert BOOLEAN,
|
||||
alert_config JSONB,
|
||||
weight REAL,
|
||||
learned BOOLEAN,
|
||||
ignored BOOLEAN
|
||||
ignored BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
UNIQUE (system_id, tgid, name)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS alerts(
|
||||
|
|
|
@ -29,20 +29,47 @@ SELECT
|
|||
sqlc.embed(tg), sqlc.embed(sys)
|
||||
FROM talkgroups tg
|
||||
JOIN systems sys ON tg.system_id = sys.id
|
||||
WHERE (tg.system_id, tg.tgid) = (@system_id, @tgid);
|
||||
WHERE (tg.system_id, tg.tgid) = (@system_id, @tgid) 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 = @system_id AND tgl.tgid = @tgid AND ignored IS NOT TRUE;
|
||||
|
||||
-- name: GetTalkgroupsWithLearnedBySystem :many
|
||||
SELECT
|
||||
sqlc.embed(tg), sqlc.embed(sys)
|
||||
FROM talkgroups tg
|
||||
JOIN systems sys ON tg.system_id = sys.id
|
||||
WHERE tg.system_id = @system;
|
||||
WHERE tg.system_id = @system 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 = @system AND ignored IS NOT TRUE;
|
||||
|
||||
-- name: GetTalkgroupsWithLearned :many
|
||||
SELECT
|
||||
sqlc.embed(tg), sqlc.embed(sys)
|
||||
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;
|
||||
|
||||
-- name: GetSystemName :one
|
||||
|
@ -59,128 +86,32 @@ SET
|
|||
tags = COALESCE(sqlc.narg('tags'), tags),
|
||||
alert = COALESCE(sqlc.narg('alert'), alert),
|
||||
alert_config = COALESCE(sqlc.narg('alert_config'), alert_config),
|
||||
weight = COALESCE(sqlc.narg('weight'), weight),
|
||||
learned = COALESCE(sqlc.narg('learned'), learned)
|
||||
weight = COALESCE(sqlc.narg('weight'), weight)
|
||||
WHERE id = sqlc.narg('id') OR (system_id = sqlc.narg('system_id') AND tgid = sqlc.narg('tgid'))
|
||||
RETURNING *;
|
||||
|
||||
-- name: UpsertTalkgroup :batchone
|
||||
INSERT INTO talkgroups AS tg (
|
||||
system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned
|
||||
) VALUES (
|
||||
-- name: AddTalkgroupWithLearnedFlag :exec
|
||||
INSERT INTO talkgroups (
|
||||
system_id,
|
||||
tgid,
|
||||
learned
|
||||
) VALUES(
|
||||
@system_id,
|
||||
@tgid,
|
||||
sqlc.narg('name'),
|
||||
sqlc.narg('alpha_tag'),
|
||||
sqlc.narg('tg_group'),
|
||||
sqlc.narg('frequency'),
|
||||
sqlc.narg('metadata'),
|
||||
sqlc.narg('tags'),
|
||||
sqlc.narg('alert'),
|
||||
sqlc.narg('alert_config'),
|
||||
sqlc.narg('weight'),
|
||||
sqlc.narg('learned')
|
||||
)
|
||||
ON CONFLICT (system_id, tgid) DO UPDATE
|
||||
SET
|
||||
name = COALESCE(sqlc.narg('name'), tg.name),
|
||||
alpha_tag = COALESCE(sqlc.narg('alpha_tag'), tg.alpha_tag),
|
||||
tg_group = COALESCE(sqlc.narg('tg_group'), tg.tg_group),
|
||||
frequency = COALESCE(sqlc.narg('frequency'), tg.frequency),
|
||||
metadata = COALESCE(sqlc.narg('metadata'), tg.metadata),
|
||||
tags = COALESCE(sqlc.narg('tags'), tg.tags),
|
||||
alert = COALESCE(sqlc.narg('alert'), tg.alert),
|
||||
alert_config = COALESCE(sqlc.narg('alert_config'), tg.alert_config),
|
||||
weight = COALESCE(sqlc.narg('weight'), tg.weight),
|
||||
learned = COALESCE(sqlc.narg('learned'), tg.learned)
|
||||
RETURNING *;
|
||||
|
||||
-- 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;
|
||||
't'
|
||||
);
|
||||
|
||||
-- name: AddLearnedTalkgroup :one
|
||||
INSERT INTO talkgroups(
|
||||
INSERT INTO talkgroups_learned(
|
||||
system_id,
|
||||
tgid,
|
||||
learned,
|
||||
name,
|
||||
alpha_tag,
|
||||
tg_group
|
||||
) VALUES (
|
||||
@system_id,
|
||||
@tgid,
|
||||
TRUE,
|
||||
sqlc.narg('name'),
|
||||
sqlc.narg('alpha_tag'),
|
||||
sqlc.narg('tg_group')
|
||||
) 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 *;
|
||||
) RETURNING id;
|
||||
|
|
Loading…
Reference in a new issue