Compare commits
18 commits
4b2b9399e9
...
36b7773cb0
Author | SHA1 | Date | |
---|---|---|---|
36b7773cb0 | |||
1b051c6ad9 | |||
c9a32cd4bf | |||
e82f07e094 | |||
0a88e7f42e | |||
1cb301acdf | |||
641b0c151a | |||
8569ae6d4a | |||
9b93243a4b | |||
af80e46068 | |||
05eccf588b | |||
e38ebe6802 | |||
9ad1ed17c2 | |||
45eb4c9f3e | |||
18866d893c | |||
4f18747255 | |||
fb1b6a475c | |||
f195b6e9b6 |
44 changed files with 2717 additions and 398 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,6 +1,6 @@
|
||||||
config.yaml
|
config.yaml
|
||||||
config.test.yaml
|
config.test.yaml
|
||||||
mydb.sql
|
/*.sql
|
||||||
client/calls/
|
client/calls/
|
||||||
!client/calls/.gitkeep
|
!client/calls/.gitkeep
|
||||||
/gordio
|
/gordio
|
||||||
|
@ -10,3 +10,4 @@ Session.vim
|
||||||
*.log
|
*.log
|
||||||
*.dlv
|
*.dlv
|
||||||
cover.out
|
cover.out
|
||||||
|
backups/
|
||||||
|
|
10
.mockery.yaml
Normal file
10
.mockery.yaml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
dir: '{{ replaceAll .InterfaceDirRelative "internal" "internal_" }}/mocks'
|
||||||
|
mockname: "{{.InterfaceName}}"
|
||||||
|
outpkg: "mocks"
|
||||||
|
filename: "{{.InterfaceName}}.go"
|
||||||
|
with-expecter: true
|
||||||
|
packages:
|
||||||
|
dynatron.me/x/stillbox/pkg/database:
|
||||||
|
config:
|
||||||
|
interfaces:
|
||||||
|
DB:
|
12
Makefile
12
Makefile
|
@ -2,6 +2,7 @@ VPKG=dynatron.me/x/stillbox/internal/version
|
||||||
VER!=git describe --tags --always --dirty
|
VER!=git describe --tags --always --dirty
|
||||||
BUILDDATE!=date '+%Y%m%d'
|
BUILDDATE!=date '+%Y%m%d'
|
||||||
LDFLAGS=-ldflags="-X '${VPKG}.Version=${VER}' -X '${VPKG}.Built=${BUILDDATE}'"
|
LDFLAGS=-ldflags="-X '${VPKG}.Version=${VER}' -X '${VPKG}.Built=${BUILDDATE}'"
|
||||||
|
GOFLAGS=-v
|
||||||
|
|
||||||
all: checkcalls
|
all: checkcalls
|
||||||
go build -o stillbox ${GOFLAGS} ${LDFLAGS} ./cmd/stillbox/
|
go build -o stillbox ${GOFLAGS} ${LDFLAGS} ./cmd/stillbox/
|
||||||
|
@ -24,6 +25,7 @@ getcalls:
|
||||||
generate:
|
generate:
|
||||||
sqlc generate -f sql/sqlc.yaml
|
sqlc generate -f sql/sqlc.yaml
|
||||||
protoc -I=pkg/pb/ --go_out=pkg/ pkg/pb/stillbox.proto
|
protoc -I=pkg/pb/ --go_out=pkg/ pkg/pb/stillbox.proto
|
||||||
|
go generate ./...
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
golangci-lint run
|
golangci-lint run
|
||||||
|
@ -34,5 +36,15 @@ coverage-html:
|
||||||
coverage:
|
coverage:
|
||||||
go test -coverprofile cover.out
|
go test -coverprofile cover.out
|
||||||
|
|
||||||
|
# backup backs up the database without calls
|
||||||
|
backup:
|
||||||
|
sh util/dumpdb.sh
|
||||||
|
|
||||||
|
backupplain:
|
||||||
|
sh util/dumpdb.sh -p
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -v ./...
|
go test -v ./...
|
||||||
|
|
||||||
|
run:
|
||||||
|
go run -v ./cmd/stillbox/ serve
|
||||||
|
|
|
@ -24,7 +24,7 @@ func main() {
|
||||||
}
|
}
|
||||||
rootCmd.PersistentFlags().BoolP("version", "V", false, "show version")
|
rootCmd.PersistentFlags().BoolP("version", "V", false, "show version")
|
||||||
cfg := config.New(rootCmd)
|
cfg := config.New(rootCmd)
|
||||||
rootCmd.Run = func(cmd *cobra.Command, args []string) {
|
rootCmd.PreRun = func(cmd *cobra.Command, args []string) {
|
||||||
v, _ := rootCmd.PersistentFlags().GetBool("version")
|
v, _ := rootCmd.PersistentFlags().GetBool("version")
|
||||||
if v {
|
if v {
|
||||||
fmt.Print(version.String())
|
fmt.Print(version.String())
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -57,6 +57,7 @@ require (
|
||||||
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
github.com/segmentio/asm v1.2.0 // indirect
|
github.com/segmentio/asm v1.2.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.5 // indirect
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
go.uber.org/atomic v1.7.0 // indirect
|
go.uber.org/atomic v1.7.0 // indirect
|
||||||
golang.org/x/exp/shiny v0.0.0-20240719175910-8a7402abbf56 // indirect
|
golang.org/x/exp/shiny v0.0.0-20240719175910-8a7402abbf56 // indirect
|
||||||
golang.org/x/image v0.14.0 // indirect
|
golang.org/x/image v0.14.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -134,6 +134,8 @@ github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3k
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/jsontime"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -27,7 +27,7 @@ var (
|
||||||
}
|
}
|
||||||
return dict, nil
|
return dict, nil
|
||||||
},
|
},
|
||||||
"formTime": func(t jsontime.Time) string {
|
"formTime": func(t jsontypes.Time) string {
|
||||||
return time.Time(t).Format("2006-01-02T15:04")
|
return time.Time(t).Format("2006-01-02T15:04")
|
||||||
},
|
},
|
||||||
"ago": func(s string) (string, error) {
|
"ago": func(s string) (string, error) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/jsontime"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
|
|
||||||
"github.com/araddon/dateparse"
|
"github.com/araddon/dateparse"
|
||||||
)
|
)
|
||||||
|
@ -262,13 +262,13 @@ func (o *options) iterFields(r *http.Request, destStruct reflect.Value) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
setVal(destFieldVal, set, val)
|
setVal(destFieldVal, set, val)
|
||||||
case time.Time, *time.Time, jsontime.Time, *jsontime.Time:
|
case time.Time, *time.Time, jsontypes.Time, *jsontypes.Time:
|
||||||
t, set, err := o.parseTime(ff)
|
t, set, err := o.parseTime(ff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
setVal(destFieldVal, set, t)
|
setVal(destFieldVal, set, t)
|
||||||
case time.Duration, *time.Duration, jsontime.Duration, *jsontime.Duration:
|
case time.Duration, *time.Duration, jsontypes.Duration, *jsontypes.Duration:
|
||||||
d, set, err := o.parseDuration(ff)
|
d, set, err := o.parseDuration(ff)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/common"
|
"dynatron.me/x/stillbox/internal/common"
|
||||||
"dynatron.me/x/stillbox/internal/forms"
|
"dynatron.me/x/stillbox/internal/forms"
|
||||||
"dynatron.me/x/stillbox/internal/jsontime"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/alerting"
|
"dynatron.me/x/stillbox/pkg/alerting"
|
||||||
"dynatron.me/x/stillbox/pkg/config"
|
"dynatron.me/x/stillbox/pkg/config"
|
||||||
|
@ -48,19 +48,19 @@ type urlEncTest struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type urlEncTestJT struct {
|
type urlEncTestJT struct {
|
||||||
LookbackDays uint `json:"lookbackDays"`
|
LookbackDays uint `json:"lookbackDays"`
|
||||||
HalfLife jsontime.Duration `json:"halfLife"`
|
HalfLife jsontypes.Duration `json:"halfLife"`
|
||||||
Recent string `json:"recent"`
|
Recent string `json:"recent"`
|
||||||
ScoreStart jsontime.Time `json:"scoreStart"`
|
ScoreStart jsontypes.Time `json:"scoreStart"`
|
||||||
ScoreEnd jsontime.Time `json:"scoreEnd"`
|
ScoreEnd jsontypes.Time `json:"scoreEnd"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ptrTestJT struct {
|
type ptrTestJT struct {
|
||||||
LookbackDays uint `form:"lookbackDays"`
|
LookbackDays uint `form:"lookbackDays"`
|
||||||
HalfLife *jsontime.Duration `form:"halfLife"`
|
HalfLife *jsontypes.Duration `form:"halfLife"`
|
||||||
Recent *string `form:"recent"`
|
Recent *string `form:"recent"`
|
||||||
ScoreStart *jsontime.Time `form:"scoreStart"`
|
ScoreStart *jsontypes.Time `form:"scoreStart"`
|
||||||
ScoreEnd jsontime.Time `form:"scoreEnd"`
|
ScoreEnd jsontypes.Time `form:"scoreEnd"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -73,33 +73,33 @@ var (
|
||||||
|
|
||||||
UrlEncTestJT = urlEncTestJT{
|
UrlEncTestJT = urlEncTestJT{
|
||||||
LookbackDays: 7,
|
LookbackDays: 7,
|
||||||
HalfLife: jsontime.Duration(30 * time.Minute),
|
HalfLife: jsontypes.Duration(30 * time.Minute),
|
||||||
Recent: "2h0m0s",
|
Recent: "2h0m0s",
|
||||||
ScoreStart: jsontime.Time(time.Date(2024, time.October, 28, 9, 25, 0, 0, time.UTC)),
|
ScoreStart: jsontypes.Time(time.Date(2024, time.October, 28, 9, 25, 0, 0, time.UTC)),
|
||||||
}
|
}
|
||||||
|
|
||||||
PtrTestJT = ptrTestJT{
|
PtrTestJT = ptrTestJT{
|
||||||
LookbackDays: 7,
|
LookbackDays: 7,
|
||||||
HalfLife: common.PtrTo(jsontime.Duration(30 * time.Minute)),
|
HalfLife: common.PtrTo(jsontypes.Duration(30 * time.Minute)),
|
||||||
Recent: common.PtrTo("2h0m0s"),
|
Recent: common.PtrTo("2h0m0s"),
|
||||||
ScoreStart: common.PtrTo(jsontime.Time(time.Date(2024, time.October, 28, 9, 25, 0, 0, time.UTC))),
|
ScoreStart: common.PtrTo(jsontypes.Time(time.Date(2024, time.October, 28, 9, 25, 0, 0, time.UTC))),
|
||||||
}
|
}
|
||||||
|
|
||||||
UrlEncTestJTLocal = urlEncTestJT{
|
UrlEncTestJTLocal = urlEncTestJT{
|
||||||
LookbackDays: 7,
|
LookbackDays: 7,
|
||||||
HalfLife: jsontime.Duration(30 * time.Minute),
|
HalfLife: jsontypes.Duration(30 * time.Minute),
|
||||||
Recent: "2h0m0s",
|
Recent: "2h0m0s",
|
||||||
ScoreStart: jsontime.Time(time.Date(2024, time.October, 28, 9, 25, 0, 0, time.Local)),
|
ScoreStart: jsontypes.Time(time.Date(2024, time.October, 28, 9, 25, 0, 0, time.Local)),
|
||||||
}
|
}
|
||||||
|
|
||||||
realSim = &alerting.Simulation{
|
realSim = &alerting.Simulation{
|
||||||
Alerting: config.Alerting{
|
Alerting: config.Alerting{
|
||||||
LookbackDays: 7,
|
LookbackDays: 7,
|
||||||
HalfLife: jsontime.Duration(30 * time.Minute),
|
HalfLife: jsontypes.Duration(30 * time.Minute),
|
||||||
Recent: jsontime.Duration(2 * time.Hour),
|
Recent: jsontypes.Duration(2 * time.Hour),
|
||||||
},
|
},
|
||||||
SimInterval: jsontime.Duration(5 * time.Minute),
|
SimInterval: jsontypes.Duration(5 * time.Minute),
|
||||||
ScoreStart: jsontime.Time(time.Date(2024, time.October, 22, 17, 49, 0, 0, time.Local)),
|
ScoreStart: jsontypes.Time(time.Date(2024, time.October, 22, 17, 49, 0, 0, time.Local)),
|
||||||
}
|
}
|
||||||
|
|
||||||
Call1 = callUploadRequest{
|
Call1 = callUploadRequest{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package jsontime
|
package jsontypes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
3
internal/jsontypes/metadata.go
Normal file
3
internal/jsontypes/metadata.go
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package jsontypes
|
||||||
|
|
||||||
|
type Metadata map[string]interface{}
|
|
@ -37,7 +37,7 @@ func (a *Alert) ToAddAlertParams() database.AddAlertParams {
|
||||||
ID: a.ID,
|
ID: a.ID,
|
||||||
Time: pgtype.Timestamptz{Time: a.Timestamp, Valid: true},
|
Time: pgtype.Timestamptz{Time: a.Timestamp, Valid: true},
|
||||||
SystemID: int(a.Score.ID.System),
|
SystemID: int(a.Score.ID.System),
|
||||||
Tgid: int(a.Score.ID.Talkgroup),
|
TGID: int(a.Score.ID.Talkgroup),
|
||||||
Weight: &a.Weight,
|
Weight: &a.Weight,
|
||||||
Score: &f32score,
|
Score: &f32score,
|
||||||
OrigScore: origScore,
|
OrigScore: origScore,
|
||||||
|
|
|
@ -228,11 +228,11 @@ func (as *alerter) scoredTGs() []talkgroups.ID {
|
||||||
return tgs
|
return tgs
|
||||||
}
|
}
|
||||||
|
|
||||||
// packedScoredTGs gets a list of packed TGIDs.
|
// packedScoredTGs gets a list of TGID tuples.
|
||||||
func (as *alerter) packedScoredTGs() []int64 {
|
func (as *alerter) scoredTGsTuple() (tgs database.TGTuples) {
|
||||||
tgs := make([]int64, 0, len(as.scores))
|
tgs = database.MakeTGTuples(len(as.scores))
|
||||||
for _, s := range as.scores {
|
for _, s := range as.scores {
|
||||||
tgs = append(tgs, s.ID.Pack())
|
tgs.Append(s.ID.System, s.ID.Talkgroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
return tgs
|
return tgs
|
||||||
|
@ -312,7 +312,7 @@ func (as *alerter) backfill(ctx context.Context, since time.Time, until time.Tim
|
||||||
db := database.FromCtx(ctx)
|
db := database.FromCtx(ctx)
|
||||||
const backfillStatsQuery = `SELECT system, talkgroup, call_date FROM calls WHERE call_date > $1 AND call_date < $2 ORDER BY call_date ASC`
|
const backfillStatsQuery = `SELECT system, talkgroup, call_date FROM calls WHERE call_date > $1 AND call_date < $2 ORDER BY call_date ASC`
|
||||||
|
|
||||||
rows, err := db.Query(ctx, backfillStatsQuery, since, until)
|
rows, err := db.DB().Query(ctx, backfillStatsQuery, since, until)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return count, err
|
return count, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/forms"
|
"dynatron.me/x/stillbox/internal/forms"
|
||||||
"dynatron.me/x/stillbox/internal/jsontime"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
"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"
|
||||||
|
@ -23,12 +23,12 @@ type Simulation struct {
|
||||||
config.Alerting
|
config.Alerting
|
||||||
|
|
||||||
// ScoreStart is the time when scoring begins
|
// ScoreStart is the time when scoring begins
|
||||||
ScoreStart jsontime.Time `json:"scoreStart" yaml:"scoreStart" form:"scoreStart"`
|
ScoreStart jsontypes.Time `json:"scoreStart" yaml:"scoreStart" form:"scoreStart"`
|
||||||
// ScoreEnd is the time when the score simulator ends. Left blank, it defaults to time.Now()
|
// ScoreEnd is the time when the score simulator ends. Left blank, it defaults to time.Now()
|
||||||
ScoreEnd jsontime.Time `json:"scoreEnd" yaml:"scoreEnd" form:"scoreEnd"`
|
ScoreEnd jsontypes.Time `json:"scoreEnd" yaml:"scoreEnd" form:"scoreEnd"`
|
||||||
|
|
||||||
// SimInterval is the interval at which the scorer will be called
|
// SimInterval is the interval at which the scorer will be called
|
||||||
SimInterval jsontime.Duration `json:"simInterval" yaml:"simInterval" form:"simInterval"`
|
SimInterval jsontypes.Duration `json:"simInterval" yaml:"simInterval" form:"simInterval"`
|
||||||
|
|
||||||
clock offsetClock `json:"-"`
|
clock offsetClock `json:"-"`
|
||||||
*alerter `json:"-"`
|
*alerter `json:"-"`
|
||||||
|
@ -64,7 +64,7 @@ func (s *Simulation) Simulate(ctx context.Context) (trending.Scores[talkgroups.I
|
||||||
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)
|
||||||
if time.Time(s.ScoreEnd).IsZero() {
|
if time.Time(s.ScoreEnd).IsZero() {
|
||||||
s.ScoreEnd = jsontime.Time(now)
|
s.ScoreEnd = jsontypes.Time(now)
|
||||||
}
|
}
|
||||||
log.Debug().Time("scoreStart", s.ScoreStart.Time()).
|
log.Debug().Time("scoreStart", s.ScoreStart.Time()).
|
||||||
Time("scoreEnd", s.ScoreEnd.Time()).
|
Time("scoreEnd", s.ScoreEnd.Time()).
|
||||||
|
|
|
@ -40,20 +40,20 @@ func (as *alerter) tgStatsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
db := database.FromCtx(ctx)
|
db := database.FromCtx(ctx)
|
||||||
|
|
||||||
tgs, err := db.GetTalkgroupsWithLearnedByPackedIDs(ctx, as.packedScoredTGs())
|
tgs, err := db.GetTalkgroupsWithLearnedBySysTGID(ctx, as.scoredTGsTuple())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("stats TG get failed")
|
log.Error().Err(err).Msg("stats TG get failed")
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tgMap := make(map[talkgroups.ID]database.GetTalkgroupsWithLearnedByPackedIDsRow, len(tgs))
|
tgMap := make(map[talkgroups.ID]database.GetTalkgroupsRow, len(tgs))
|
||||||
for _, t := range tgs {
|
for _, t := range tgs {
|
||||||
tgMap[talkgroups.ID{System: uint32(t.System.ID), Talkgroup: uint32(t.Talkgroup.Tgid)}] = t
|
tgMap[talkgroups.ID{System: uint32(t.System.ID), Talkgroup: uint32(t.Talkgroup.TGID)}] = t
|
||||||
}
|
}
|
||||||
|
|
||||||
renderData := struct {
|
renderData := struct {
|
||||||
TGs map[talkgroups.ID]database.GetTalkgroupsWithLearnedByPackedIDsRow
|
TGs map[talkgroups.ID]database.GetTalkgroupsRow
|
||||||
Scores trending.Scores[talkgroups.ID]
|
Scores trending.Scores[talkgroups.ID]
|
||||||
LastScore time.Time
|
LastScore time.Time
|
||||||
Simulation *Simulation
|
Simulation *Simulation
|
||||||
|
|
|
@ -70,7 +70,7 @@ func (a *Auth) initJWT() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) Login(ctx context.Context, username, password string) (token string, err error) {
|
func (a *Auth) Login(ctx context.Context, username, password string) (token string, err error) {
|
||||||
q := database.New(database.FromCtx(ctx))
|
q := database.FromCtx(ctx)
|
||||||
users, err := q.GetUsers(ctx)
|
users, err := q.GetUsers(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("getUsers failed")
|
log.Error().Err(err).Msg("getUsers failed")
|
||||||
|
|
|
@ -71,7 +71,7 @@ func (f *TalkgroupFilter) compile(ctx context.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tg := range tagTGs {
|
for _, tg := range tagTGs {
|
||||||
f.talkgroups[tgs.ID{System: uint32(tg.SystemID), Talkgroup: uint32(tg.Tgid)}] = true
|
f.talkgroups[tgs.ID{System: uint32(tg.SystemID), Talkgroup: uint32(tg.TGID)}] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/internal/jsontime"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
|
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -37,7 +37,8 @@ type CORS struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
Connect string `yaml:"connect"`
|
Connect string `yaml:"connect"`
|
||||||
|
LogQueries bool `yaml:"logQueries"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Logger struct {
|
type Logger struct {
|
||||||
|
@ -54,12 +55,12 @@ type RateLimit struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Alerting struct {
|
type Alerting struct {
|
||||||
Enable bool `yaml:"enable" form:"enable"`
|
Enable bool `yaml:"enable" form:"enable"`
|
||||||
LookbackDays uint `yaml:"lookbackDays" form:"lookbackDays"`
|
LookbackDays uint `yaml:"lookbackDays" form:"lookbackDays"`
|
||||||
HalfLife jsontime.Duration `yaml:"halfLife" form:"halfLife"`
|
HalfLife jsontypes.Duration `yaml:"halfLife" form:"halfLife"`
|
||||||
Recent jsontime.Duration `yaml:"recent" form:"recent"`
|
Recent jsontypes.Duration `yaml:"recent" form:"recent"`
|
||||||
AlertThreshold float64 `yaml:"alertThreshold" form:"alertThreshold"`
|
AlertThreshold float64 `yaml:"alertThreshold" form:"alertThreshold"`
|
||||||
Renotify *jsontime.Duration `yaml:"renotify,omitempty" form:"renotify,omitempty"`
|
Renotify *jsontypes.Duration `yaml:"renotify,omitempty" form:"renotify,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Notify []NotifyService
|
type Notify []NotifyService
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.26.0
|
// sqlc v1.27.0
|
||||||
// source: calls.sql
|
// source: calls.sql
|
||||||
|
|
||||||
package database
|
package database
|
||||||
|
@ -31,7 +31,7 @@ VALUES
|
||||||
type AddAlertParams struct {
|
type AddAlertParams struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
Time pgtype.Timestamptz `json:"time"`
|
Time pgtype.Timestamptz `json:"time"`
|
||||||
Tgid int `json:"tgid"`
|
TGID int `json:"tgid"`
|
||||||
SystemID int `json:"system_id"`
|
SystemID int `json:"system_id"`
|
||||||
Weight *float32 `json:"weight"`
|
Weight *float32 `json:"weight"`
|
||||||
Score *float32 `json:"score"`
|
Score *float32 `json:"score"`
|
||||||
|
@ -44,7 +44,7 @@ func (q *Queries) AddAlert(ctx context.Context, arg AddAlertParams) error {
|
||||||
_, err := q.db.Exec(ctx, addAlert,
|
_, err := q.db.Exec(ctx, addAlert,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
arg.Time,
|
arg.Time,
|
||||||
arg.Tgid,
|
arg.TGID,
|
||||||
arg.SystemID,
|
arg.SystemID,
|
||||||
arg.Weight,
|
arg.Weight,
|
||||||
arg.Score,
|
arg.Score,
|
||||||
|
|
|
@ -11,16 +11,37 @@ import (
|
||||||
_ "github.com/golang-migrate/migrate/v4/database/pgx/v5"
|
_ "github.com/golang-migrate/migrate/v4/database/pgx/v5"
|
||||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
|
"github.com/jackc/pgx/v5/tracelog"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DB is a database handle.
|
// DB is a database handle.
|
||||||
type DB struct {
|
|
||||||
|
//go:generate mockery
|
||||||
|
type DB interface {
|
||||||
|
Querier
|
||||||
|
talkgroupQuerier
|
||||||
|
|
||||||
|
DB() *Database
|
||||||
|
}
|
||||||
|
|
||||||
|
type Database struct {
|
||||||
*pgxpool.Pool
|
*pgxpool.Pool
|
||||||
*Queries
|
*Queries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *Database) DB() *Database {
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
type dbLogger struct{}
|
||||||
|
|
||||||
|
func (m dbLogger) Log(ctx context.Context, level tracelog.LogLevel, msg string, data map[string]any) {
|
||||||
|
log.Debug().Fields(data).Msg(msg)
|
||||||
|
}
|
||||||
|
|
||||||
// NewClient creates a new DB using the provided config.
|
// NewClient creates a new DB using the provided config.
|
||||||
func NewClient(ctx context.Context, conf config.DB) (*DB, error) {
|
func NewClient(ctx context.Context, conf config.DB) (DB, error) {
|
||||||
dir, err := iofs.New(sqlembed.Migrations, "postgres/migrations")
|
dir, err := iofs.New(sqlembed.Migrations, "postgres/migrations")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -43,12 +64,19 @@ func NewClient(ctx context.Context, conf config.DB) (*DB, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.LogQueries {
|
||||||
|
pgConf.ConnConfig.Tracer = &tracelog.TraceLog{
|
||||||
|
Logger: dbLogger{},
|
||||||
|
LogLevel: tracelog.LogLevelTrace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pool, err := pgxpool.NewWithConfig(ctx, pgConf)
|
pool, err := pgxpool.NewWithConfig(ctx, pgConf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
db := &DB{
|
db := &Database{
|
||||||
Pool: pool,
|
Pool: pool,
|
||||||
Queries: New(pool),
|
Queries: New(pool),
|
||||||
}
|
}
|
||||||
|
@ -61,8 +89,8 @@ type dBCtxKey string
|
||||||
const DBCtxKey dBCtxKey = "dbctx"
|
const DBCtxKey dBCtxKey = "dbctx"
|
||||||
|
|
||||||
// FromCtx returns the database handle from the provided Context.
|
// FromCtx returns the database handle from the provided Context.
|
||||||
func FromCtx(ctx context.Context) *DB {
|
func FromCtx(ctx context.Context) DB {
|
||||||
c, ok := ctx.Value(DBCtxKey).(*DB)
|
c, ok := ctx.Value(DBCtxKey).(DB)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("no DB in context")
|
panic("no DB in context")
|
||||||
}
|
}
|
||||||
|
@ -71,7 +99,7 @@ func FromCtx(ctx context.Context) *DB {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CtxWithDB returns a Context with the provided database handle.
|
// CtxWithDB returns a Context with the provided database handle.
|
||||||
func CtxWithDB(ctx context.Context, conn *DB) context.Context {
|
func CtxWithDB(ctx context.Context, conn DB) context.Context {
|
||||||
return context.WithValue(ctx, DBCtxKey, conn)
|
return context.WithValue(ctx, DBCtxKey, conn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.26.0
|
// sqlc v1.27.0
|
||||||
|
|
||||||
package database
|
package database
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,17 @@
|
||||||
package database
|
package database
|
||||||
|
|
||||||
func (d GetTalkgroupsWithLearnedByPackedIDsRow) GetTalkgroup() Talkgroup { return d.Talkgroup }
|
func (d GetTalkgroupsRow) GetTalkgroup() Talkgroup { return d.Talkgroup }
|
||||||
func (d GetTalkgroupsWithLearnedByPackedIDsRow) GetSystem() System { return d.System }
|
func (d GetTalkgroupsRow) GetSystem() System { return d.System }
|
||||||
func (d GetTalkgroupsWithLearnedByPackedIDsRow) GetLearned() bool { return d.Learned }
|
func (d GetTalkgroupsRow) GetLearned() bool { return d.Learned }
|
||||||
func (g GetTalkgroupsWithLearnedRow) GetTalkgroup() Talkgroup { return g.Talkgroup }
|
func (g GetTalkgroupWithLearnedRow) GetTalkgroup() Talkgroup { return g.Talkgroup }
|
||||||
func (g GetTalkgroupsWithLearnedRow) GetSystem() System { return g.System }
|
func (g GetTalkgroupWithLearnedRow) GetSystem() System { return g.System }
|
||||||
func (g GetTalkgroupsWithLearnedRow) GetLearned() bool { return g.Learned }
|
func (g GetTalkgroupWithLearnedRow) GetLearned() bool { return g.Learned }
|
||||||
func (g GetTalkgroupsWithLearnedBySystemRow) GetTalkgroup() Talkgroup { return g.Talkgroup }
|
func (g GetTalkgroupsWithLearnedRow) GetTalkgroup() Talkgroup { return g.Talkgroup }
|
||||||
func (g GetTalkgroupsWithLearnedBySystemRow) GetSystem() System { return g.System }
|
func (g GetTalkgroupsWithLearnedRow) GetSystem() System { return g.System }
|
||||||
func (g GetTalkgroupsWithLearnedBySystemRow) GetLearned() bool { return g.Learned }
|
func (g GetTalkgroupsWithLearnedRow) GetLearned() bool { return g.Learned }
|
||||||
func (g Talkgroup) GetTalkgroup() Talkgroup { return g }
|
func (g GetTalkgroupsWithLearnedBySystemRow) GetTalkgroup() Talkgroup { return g.Talkgroup }
|
||||||
func (g Talkgroup) GetSystem() System { return System{ID: int(g.SystemID)} }
|
func (g GetTalkgroupsWithLearnedBySystemRow) GetSystem() System { return g.System }
|
||||||
func (g Talkgroup) GetLearned() bool { return false }
|
func (g GetTalkgroupsWithLearnedBySystemRow) GetLearned() bool { return g.Learned }
|
||||||
|
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 }
|
||||||
|
|
1631
pkg/database/mocks/DB.go
Normal file
1631
pkg/database/mocks/DB.go
Normal file
File diff suppressed because it is too large
Load diff
|
@ -1,12 +1,13 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.26.0
|
// sqlc v1.27.0
|
||||||
|
|
||||||
package database
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
"dynatron.me/x/stillbox/pkg/alerting/rules"
|
"dynatron.me/x/stillbox/pkg/alerting/rules"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
@ -15,7 +16,7 @@ import (
|
||||||
type Alert struct {
|
type Alert struct {
|
||||||
ID uuid.UUID `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
Time pgtype.Timestamptz `json:"time"`
|
Time pgtype.Timestamptz `json:"time"`
|
||||||
Tgid int `json:"tgid"`
|
TGID int `json:"tgid"`
|
||||||
SystemID int `json:"system_id"`
|
SystemID int `json:"system_id"`
|
||||||
Weight *float32 `json:"weight"`
|
Weight *float32 `json:"weight"`
|
||||||
Score *float32 `json:"score"`
|
Score *float32 `json:"score"`
|
||||||
|
@ -82,24 +83,24 @@ type System struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Talkgroup struct {
|
type Talkgroup struct {
|
||||||
ID int64 `json:"id"`
|
ID uuid.UUID `json:"id"`
|
||||||
SystemID int32 `json:"system_id"`
|
SystemID int32 `json:"system_id"`
|
||||||
Tgid int32 `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"`
|
||||||
Frequency *int32 `json:"frequency"`
|
Frequency *int32 `json:"frequency"`
|
||||||
Metadata []byte `json:"metadata"`
|
Metadata jsontypes.Metadata `json:"metadata"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
Alert bool `json:"alert"`
|
Alert bool `json:"alert"`
|
||||||
AlertConfig rules.AlertRules `json:"alert_config"`
|
AlertConfig rules.AlertRules `json:"alert_config"`
|
||||||
Weight float32 `json:"weight"`
|
Weight float32 `json:"weight"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TalkgroupsLearned struct {
|
type TalkgroupsLearned struct {
|
||||||
ID int32 `json:"id"`
|
ID int32 `json:"id"`
|
||||||
SystemID int `json:"system_id"`
|
SystemID int `json:"system_id"`
|
||||||
Tgid int `json:"tgid"`
|
TGID int `json:"tgid"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
AlphaTag *string `json:"alpha_tag"`
|
AlphaTag *string `json:"alpha_tag"`
|
||||||
Ignored *bool `json:"ignored"`
|
Ignored *bool `json:"ignored"`
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.26.0
|
// sqlc v1.27.0
|
||||||
|
|
||||||
package database
|
package database
|
||||||
|
|
||||||
|
@ -14,7 +14,6 @@ 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
|
||||||
BulkSetTalkgroupTags(ctx context.Context, iD int64, tags []string) 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
|
||||||
|
@ -22,22 +21,20 @@ type Querier interface {
|
||||||
GetAPIKey(ctx context.Context, apiKey string) (ApiKey, error)
|
GetAPIKey(ctx context.Context, apiKey string) (ApiKey, error)
|
||||||
GetDatabaseSize(ctx context.Context) (string, error)
|
GetDatabaseSize(ctx context.Context) (string, error)
|
||||||
GetSystemName(ctx context.Context, systemID int) (string, error)
|
GetSystemName(ctx context.Context, systemID int) (string, error)
|
||||||
GetTalkgroup(ctx context.Context, systemID int, tgid int) (GetTalkgroupRow, error)
|
GetTalkgroup(ctx context.Context, systemID int32, tgID int32) (GetTalkgroupRow, error)
|
||||||
GetTalkgroupIDsByTags(ctx context.Context, anytags []string, alltags []string, nottags []string) ([]GetTalkgroupIDsByTagsRow, error)
|
GetTalkgroupIDsByTags(ctx context.Context, anyTags []string, allTags []string, notTags []string) ([]GetTalkgroupIDsByTagsRow, error)
|
||||||
GetTalkgroupTags(ctx context.Context, sys int, tg int) ([]string, error)
|
GetTalkgroupTags(ctx context.Context, systemID int32, tgID int32) ([]string, error)
|
||||||
GetTalkgroupWithLearned(ctx context.Context, systemID int, tgid int) (GetTalkgroupWithLearnedRow, error)
|
GetTalkgroupWithLearned(ctx context.Context, systemID int32, tGID int32) (GetTalkgroupWithLearnedRow, error)
|
||||||
GetTalkgroupsByPackedIDs(ctx context.Context, dollar_1 []int64) ([]GetTalkgroupsByPackedIDsRow, error)
|
|
||||||
GetTalkgroupsWithAllTags(ctx context.Context, tags []string) ([]GetTalkgroupsWithAllTagsRow, error)
|
GetTalkgroupsWithAllTags(ctx context.Context, tags []string) ([]GetTalkgroupsWithAllTagsRow, error)
|
||||||
GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) ([]GetTalkgroupsWithAnyTagsRow, error)
|
GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) ([]GetTalkgroupsWithAnyTagsRow, error)
|
||||||
GetTalkgroupsWithLearned(ctx context.Context) ([]GetTalkgroupsWithLearnedRow, error)
|
GetTalkgroupsWithLearned(ctx context.Context) ([]GetTalkgroupsWithLearnedRow, error)
|
||||||
GetTalkgroupsWithLearnedByPackedIDs(ctx context.Context, dollar_1 []int64) ([]GetTalkgroupsWithLearnedByPackedIDsRow, error)
|
|
||||||
GetTalkgroupsWithLearnedBySystem(ctx context.Context, system int32) ([]GetTalkgroupsWithLearnedBySystemRow, error)
|
GetTalkgroupsWithLearnedBySystem(ctx context.Context, system int32) ([]GetTalkgroupsWithLearnedBySystemRow, error)
|
||||||
GetUserByID(ctx context.Context, id int32) (User, error)
|
GetUserByID(ctx context.Context, id int32) (User, error)
|
||||||
GetUserByUID(ctx context.Context, id int32) (User, error)
|
GetUserByUID(ctx context.Context, id int32) (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)
|
||||||
SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error
|
SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error
|
||||||
SetTalkgroupTags(ctx context.Context, sys int, tg int, tags []string) error
|
SetTalkgroupTags(ctx context.Context, tags []string, systemID int32, tgID int32) error
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
132
pkg/database/talkgroups.go
Normal file
132
pkg/database/talkgroups.go
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type talkgroupQuerier interface {
|
||||||
|
GetTalkgroupsWithLearnedBySysTGID(ctx context.Context, ids TGTuples) ([]GetTalkgroupsRow, error)
|
||||||
|
GetTalkgroupsBySysTGID(ctx context.Context, ids TGTuples) ([]GetTalkgroupsRow, error)
|
||||||
|
BulkSetTalkgroupTags(ctx context.Context, tgs TGTuples, tags []string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type TGTuples [2][]uint32
|
||||||
|
|
||||||
|
func MakeTGTuples(cap int) TGTuples {
|
||||||
|
return [2][]uint32{
|
||||||
|
make([]uint32, 0, cap),
|
||||||
|
make([]uint32, 0, cap),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TGTuples) Append(sys, tg uint32) {
|
||||||
|
t[0] = append(t[0], sys)
|
||||||
|
t[1] = append(t[1], tg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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,
|
||||||
|
FALSE 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)
|
||||||
|
UNION
|
||||||
|
SELECT
|
||||||
|
NULL::UUID, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
||||||
|
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
||||||
|
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
||||||
|
TRUE, 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"`
|
||||||
|
System System `json:"system"`
|
||||||
|
Learned bool `json:"learned"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetTalkgroupsWithLearnedBySysTGID(ctx context.Context, ids TGTuples) ([]GetTalkgroupsRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getTalkgroupsWithLearnedBySysTGID, ids[0], ids[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetTalkgroupsRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetTalkgroupsRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.Talkgroup.ID,
|
||||||
|
&i.Talkgroup.SystemID,
|
||||||
|
&i.Talkgroup.TGID,
|
||||||
|
&i.Talkgroup.Name,
|
||||||
|
&i.Talkgroup.AlphaTag,
|
||||||
|
&i.Talkgroup.TgGroup,
|
||||||
|
&i.Talkgroup.Frequency,
|
||||||
|
&i.Talkgroup.Metadata,
|
||||||
|
&i.Talkgroup.Tags,
|
||||||
|
&i.Talkgroup.Alert,
|
||||||
|
&i.Talkgroup.AlertConfig,
|
||||||
|
&i.Talkgroup.Weight,
|
||||||
|
&i.System.ID,
|
||||||
|
&i.System.Name,
|
||||||
|
&i.Learned,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
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
|
||||||
|
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);`
|
||||||
|
|
||||||
|
func (q *Queries) GetTalkgroupsBySysTGID(ctx context.Context, ids TGTuples) ([]GetTalkgroupsRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getTalkgroupsBySysTGID, ids[0], ids[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetTalkgroupsRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetTalkgroupsRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.Talkgroup.ID,
|
||||||
|
&i.Talkgroup.SystemID,
|
||||||
|
&i.Talkgroup.TGID,
|
||||||
|
&i.Talkgroup.Name,
|
||||||
|
&i.Talkgroup.AlphaTag,
|
||||||
|
&i.Talkgroup.TgGroup,
|
||||||
|
&i.Talkgroup.Frequency,
|
||||||
|
&i.Talkgroup.Metadata,
|
||||||
|
&i.Talkgroup.Tags,
|
||||||
|
&i.Talkgroup.Alert,
|
||||||
|
&i.Talkgroup.AlertConfig,
|
||||||
|
&i.Talkgroup.Weight,
|
||||||
|
&i.System.ID,
|
||||||
|
&i.System.Name,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const bulkSetTalkgroupTags = `UPDATE talkgroups tg SET tags = $3 FROM UNNEST($1::INT4[], $2::INT4[]) AS tgt(sys, tg) WHERE (tg.system_id = tgt.sys AND tg.tgid = tgt.tg);`
|
||||||
|
|
||||||
|
func (q *Queries) BulkSetTalkgroupTags(ctx context.Context, tgs TGTuples, tags []string) error {
|
||||||
|
_, err := q.db.Exec(ctx, bulkSetTalkgroupTags, tgs[0], tgs[1], tags)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.26.0
|
// sqlc v1.27.0
|
||||||
// source: talkgroups.sql
|
// source: talkgroups.sql
|
||||||
|
|
||||||
package database
|
package database
|
||||||
|
@ -8,19 +8,11 @@ package database
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
"dynatron.me/x/stillbox/pkg/alerting/rules"
|
"dynatron.me/x/stillbox/pkg/alerting/rules"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
const bulkSetTalkgroupTags = `-- name: BulkSetTalkgroupTags :exec
|
|
||||||
UPDATE talkgroups SET tags = $2
|
|
||||||
WHERE id = ANY($1)
|
|
||||||
`
|
|
||||||
|
|
||||||
func (q *Queries) BulkSetTalkgroupTags(ctx context.Context, iD int64, tags []string) error {
|
|
||||||
_, err := q.db.Exec(ctx, bulkSetTalkgroupTags, iD, tags)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSystemName = `-- name: GetSystemName :one
|
const getSystemName = `-- name: GetSystemName :one
|
||||||
SELECT name FROM systems WHERE id = $1
|
SELECT name FROM systems WHERE id = $1
|
||||||
`
|
`
|
||||||
|
@ -34,20 +26,20 @@ 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 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 FROM talkgroups
|
||||||
WHERE id = systg2id($1, $2)
|
WHERE (system_id, tgid) = ($1, $2)
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetTalkgroupRow struct {
|
type GetTalkgroupRow struct {
|
||||||
Talkgroup Talkgroup `json:"talkgroup"`
|
Talkgroup Talkgroup `json:"talkgroup"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetTalkgroup(ctx context.Context, systemID int, tgid int) (GetTalkgroupRow, error) {
|
func (q *Queries) GetTalkgroup(ctx context.Context, systemID int32, tgID int32) (GetTalkgroupRow, error) {
|
||||||
row := q.db.QueryRow(ctx, getTalkgroup, systemID, tgid)
|
row := q.db.QueryRow(ctx, getTalkgroup, systemID, tgID)
|
||||||
var i GetTalkgroupRow
|
var i GetTalkgroupRow
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.Talkgroup.ID,
|
&i.Talkgroup.ID,
|
||||||
&i.Talkgroup.SystemID,
|
&i.Talkgroup.SystemID,
|
||||||
&i.Talkgroup.Tgid,
|
&i.Talkgroup.TGID,
|
||||||
&i.Talkgroup.Name,
|
&i.Talkgroup.Name,
|
||||||
&i.Talkgroup.AlphaTag,
|
&i.Talkgroup.AlphaTag,
|
||||||
&i.Talkgroup.TgGroup,
|
&i.Talkgroup.TgGroup,
|
||||||
|
@ -70,11 +62,11 @@ AND NOT (tags @> ARRAY[$3])
|
||||||
|
|
||||||
type GetTalkgroupIDsByTagsRow struct {
|
type GetTalkgroupIDsByTagsRow struct {
|
||||||
SystemID int32 `json:"system_id"`
|
SystemID int32 `json:"system_id"`
|
||||||
Tgid int32 `json:"tgid"`
|
TGID int32 `json:"tgid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetTalkgroupIDsByTags(ctx context.Context, anytags []string, alltags []string, nottags []string) ([]GetTalkgroupIDsByTagsRow, error) {
|
func (q *Queries) GetTalkgroupIDsByTags(ctx context.Context, anyTags []string, allTags []string, notTags []string) ([]GetTalkgroupIDsByTagsRow, error) {
|
||||||
rows, err := q.db.Query(ctx, getTalkgroupIDsByTags, anytags, alltags, nottags)
|
rows, err := q.db.Query(ctx, getTalkgroupIDsByTags, anyTags, allTags, notTags)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -82,7 +74,7 @@ func (q *Queries) GetTalkgroupIDsByTags(ctx context.Context, anytags []string, a
|
||||||
var items []GetTalkgroupIDsByTagsRow
|
var items []GetTalkgroupIDsByTagsRow
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var i GetTalkgroupIDsByTagsRow
|
var i GetTalkgroupIDsByTagsRow
|
||||||
if err := rows.Scan(&i.SystemID, &i.Tgid); err != nil {
|
if err := rows.Scan(&i.SystemID, &i.TGID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
items = append(items, i)
|
items = append(items, i)
|
||||||
|
@ -95,11 +87,11 @@ func (q *Queries) GetTalkgroupIDsByTags(ctx context.Context, anytags []string, a
|
||||||
|
|
||||||
const getTalkgroupTags = `-- name: GetTalkgroupTags :one
|
const getTalkgroupTags = `-- name: GetTalkgroupTags :one
|
||||||
SELECT tags FROM talkgroups
|
SELECT tags FROM talkgroups
|
||||||
WHERE id = systg2id($1, $2)
|
WHERE system_id = $1 AND tgid = $2
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) GetTalkgroupTags(ctx context.Context, sys int, tg int) ([]string, error) {
|
func (q *Queries) GetTalkgroupTags(ctx context.Context, systemID int32, tgID int32) ([]string, error) {
|
||||||
row := q.db.QueryRow(ctx, getTalkgroupTags, sys, tg)
|
row := q.db.QueryRow(ctx, getTalkgroupTags, systemID, tgID)
|
||||||
var tags []string
|
var tags []string
|
||||||
err := row.Scan(&tags)
|
err := row.Scan(&tags)
|
||||||
return tags, err
|
return tags, err
|
||||||
|
@ -111,10 +103,10 @@ tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency,
|
||||||
FALSE learned
|
FALSE learned
|
||||||
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.id = systg2id($1, $2)
|
WHERE (tg.system_id, tg.tgid) = ($1, $2)
|
||||||
UNION
|
UNION
|
||||||
SELECT
|
SELECT
|
||||||
tgl.id::INT8, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
NULL::UUID, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
||||||
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
||||||
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
||||||
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
||||||
|
@ -130,13 +122,13 @@ type GetTalkgroupWithLearnedRow struct {
|
||||||
Learned bool `json:"learned"`
|
Learned bool `json:"learned"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) GetTalkgroupWithLearned(ctx context.Context, systemID int, tgid int) (GetTalkgroupWithLearnedRow, error) {
|
func (q *Queries) GetTalkgroupWithLearned(ctx context.Context, systemID int32, tGID int32) (GetTalkgroupWithLearnedRow, error) {
|
||||||
row := q.db.QueryRow(ctx, getTalkgroupWithLearned, systemID, tgid)
|
row := q.db.QueryRow(ctx, getTalkgroupWithLearned, systemID, tGID)
|
||||||
var i GetTalkgroupWithLearnedRow
|
var i GetTalkgroupWithLearnedRow
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.Talkgroup.ID,
|
&i.Talkgroup.ID,
|
||||||
&i.Talkgroup.SystemID,
|
&i.Talkgroup.SystemID,
|
||||||
&i.Talkgroup.Tgid,
|
&i.Talkgroup.TGID,
|
||||||
&i.Talkgroup.Name,
|
&i.Talkgroup.Name,
|
||||||
&i.Talkgroup.AlphaTag,
|
&i.Talkgroup.AlphaTag,
|
||||||
&i.Talkgroup.TgGroup,
|
&i.Talkgroup.TgGroup,
|
||||||
|
@ -153,52 +145,6 @@ func (q *Queries) GetTalkgroupWithLearned(ctx context.Context, systemID int, tgi
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTalkgroupsByPackedIDs = `-- name: GetTalkgroupsByPackedIDs :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, sys.id, sys.name FROM talkgroups tg
|
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
|
||||||
WHERE tg.id = ANY($1::INT8[])
|
|
||||||
`
|
|
||||||
|
|
||||||
type GetTalkgroupsByPackedIDsRow struct {
|
|
||||||
Talkgroup Talkgroup `json:"talkgroup"`
|
|
||||||
System System `json:"system"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetTalkgroupsByPackedIDs(ctx context.Context, dollar_1 []int64) ([]GetTalkgroupsByPackedIDsRow, error) {
|
|
||||||
rows, err := q.db.Query(ctx, getTalkgroupsByPackedIDs, dollar_1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var items []GetTalkgroupsByPackedIDsRow
|
|
||||||
for rows.Next() {
|
|
||||||
var i GetTalkgroupsByPackedIDsRow
|
|
||||||
if err := rows.Scan(
|
|
||||||
&i.Talkgroup.ID,
|
|
||||||
&i.Talkgroup.SystemID,
|
|
||||||
&i.Talkgroup.Tgid,
|
|
||||||
&i.Talkgroup.Name,
|
|
||||||
&i.Talkgroup.AlphaTag,
|
|
||||||
&i.Talkgroup.TgGroup,
|
|
||||||
&i.Talkgroup.Frequency,
|
|
||||||
&i.Talkgroup.Metadata,
|
|
||||||
&i.Talkgroup.Tags,
|
|
||||||
&i.Talkgroup.Alert,
|
|
||||||
&i.Talkgroup.AlertConfig,
|
|
||||||
&i.Talkgroup.Weight,
|
|
||||||
&i.System.ID,
|
|
||||||
&i.System.Name,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, i)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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 FROM talkgroups
|
||||||
WHERE tags && ARRAY[$1]
|
WHERE tags && ARRAY[$1]
|
||||||
|
@ -220,7 +166,7 @@ func (q *Queries) GetTalkgroupsWithAllTags(ctx context.Context, tags []string) (
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Talkgroup.ID,
|
&i.Talkgroup.ID,
|
||||||
&i.Talkgroup.SystemID,
|
&i.Talkgroup.SystemID,
|
||||||
&i.Talkgroup.Tgid,
|
&i.Talkgroup.TGID,
|
||||||
&i.Talkgroup.Name,
|
&i.Talkgroup.Name,
|
||||||
&i.Talkgroup.AlphaTag,
|
&i.Talkgroup.AlphaTag,
|
||||||
&i.Talkgroup.TgGroup,
|
&i.Talkgroup.TgGroup,
|
||||||
|
@ -262,7 +208,7 @@ func (q *Queries) GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) (
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Talkgroup.ID,
|
&i.Talkgroup.ID,
|
||||||
&i.Talkgroup.SystemID,
|
&i.Talkgroup.SystemID,
|
||||||
&i.Talkgroup.Tgid,
|
&i.Talkgroup.TGID,
|
||||||
&i.Talkgroup.Name,
|
&i.Talkgroup.Name,
|
||||||
&i.Talkgroup.AlphaTag,
|
&i.Talkgroup.AlphaTag,
|
||||||
&i.Talkgroup.TgGroup,
|
&i.Talkgroup.TgGroup,
|
||||||
|
@ -291,7 +237,7 @@ FROM talkgroups tg
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
UNION
|
UNION
|
||||||
SELECT
|
SELECT
|
||||||
tgl.id::INT8, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
NULL::UUID, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
||||||
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
||||||
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
||||||
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
||||||
|
@ -319,68 +265,7 @@ func (q *Queries) GetTalkgroupsWithLearned(ctx context.Context) ([]GetTalkgroups
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Talkgroup.ID,
|
&i.Talkgroup.ID,
|
||||||
&i.Talkgroup.SystemID,
|
&i.Talkgroup.SystemID,
|
||||||
&i.Talkgroup.Tgid,
|
&i.Talkgroup.TGID,
|
||||||
&i.Talkgroup.Name,
|
|
||||||
&i.Talkgroup.AlphaTag,
|
|
||||||
&i.Talkgroup.TgGroup,
|
|
||||||
&i.Talkgroup.Frequency,
|
|
||||||
&i.Talkgroup.Metadata,
|
|
||||||
&i.Talkgroup.Tags,
|
|
||||||
&i.Talkgroup.Alert,
|
|
||||||
&i.Talkgroup.AlertConfig,
|
|
||||||
&i.Talkgroup.Weight,
|
|
||||||
&i.System.ID,
|
|
||||||
&i.System.Name,
|
|
||||||
&i.Learned,
|
|
||||||
); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
items = append(items, i)
|
|
||||||
}
|
|
||||||
if err := rows.Err(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const getTalkgroupsWithLearnedByPackedIDs = `-- name: GetTalkgroupsWithLearnedByPackedIDs :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, sys.id, sys.name,
|
|
||||||
FALSE learned
|
|
||||||
FROM talkgroups tg
|
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
|
||||||
WHERE tg.id = ANY($1::INT8[])
|
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id::INT8, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
|
||||||
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
|
||||||
TRUE learned
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
WHERE systg2id(tgl.system_id, tgl.tgid) = ANY($1::INT8[]) AND ignored IS NOT TRUE
|
|
||||||
`
|
|
||||||
|
|
||||||
type GetTalkgroupsWithLearnedByPackedIDsRow struct {
|
|
||||||
Talkgroup Talkgroup `json:"talkgroup"`
|
|
||||||
System System `json:"system"`
|
|
||||||
Learned bool `json:"learned"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetTalkgroupsWithLearnedByPackedIDs(ctx context.Context, dollar_1 []int64) ([]GetTalkgroupsWithLearnedByPackedIDsRow, error) {
|
|
||||||
rows, err := q.db.Query(ctx, getTalkgroupsWithLearnedByPackedIDs, dollar_1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var items []GetTalkgroupsWithLearnedByPackedIDsRow
|
|
||||||
for rows.Next() {
|
|
||||||
var i GetTalkgroupsWithLearnedByPackedIDsRow
|
|
||||||
if err := rows.Scan(
|
|
||||||
&i.Talkgroup.ID,
|
|
||||||
&i.Talkgroup.SystemID,
|
|
||||||
&i.Talkgroup.Tgid,
|
|
||||||
&i.Talkgroup.Name,
|
&i.Talkgroup.Name,
|
||||||
&i.Talkgroup.AlphaTag,
|
&i.Talkgroup.AlphaTag,
|
||||||
&i.Talkgroup.TgGroup,
|
&i.Talkgroup.TgGroup,
|
||||||
|
@ -413,7 +298,7 @@ JOIN systems sys ON tg.system_id = sys.id
|
||||||
WHERE tg.system_id = $1
|
WHERE tg.system_id = $1
|
||||||
UNION
|
UNION
|
||||||
SELECT
|
SELECT
|
||||||
tgl.id::INT8, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
NULL::UUID, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
||||||
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
||||||
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
||||||
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
||||||
|
@ -441,7 +326,7 @@ func (q *Queries) GetTalkgroupsWithLearnedBySystem(ctx context.Context, system i
|
||||||
if err := rows.Scan(
|
if err := rows.Scan(
|
||||||
&i.Talkgroup.ID,
|
&i.Talkgroup.ID,
|
||||||
&i.Talkgroup.SystemID,
|
&i.Talkgroup.SystemID,
|
||||||
&i.Talkgroup.Tgid,
|
&i.Talkgroup.TGID,
|
||||||
&i.Talkgroup.Name,
|
&i.Talkgroup.Name,
|
||||||
&i.Talkgroup.AlphaTag,
|
&i.Talkgroup.AlphaTag,
|
||||||
&i.Talkgroup.TgGroup,
|
&i.Talkgroup.TgGroup,
|
||||||
|
@ -466,12 +351,12 @@ func (q *Queries) GetTalkgroupsWithLearnedBySystem(ctx context.Context, system i
|
||||||
}
|
}
|
||||||
|
|
||||||
const setTalkgroupTags = `-- name: SetTalkgroupTags :exec
|
const setTalkgroupTags = `-- name: SetTalkgroupTags :exec
|
||||||
UPDATE talkgroups SET tags = $3
|
UPDATE talkgroups SET tags = $1
|
||||||
WHERE id = systg2id($1, $2)
|
WHERE system_id = $2 AND tgid = $3
|
||||||
`
|
`
|
||||||
|
|
||||||
func (q *Queries) SetTalkgroupTags(ctx context.Context, sys int, tg int, tags []string) error {
|
func (q *Queries) SetTalkgroupTags(ctx context.Context, tags []string, systemID int32, tgID int32) error {
|
||||||
_, err := q.db.Exec(ctx, setTalkgroupTags, sys, tg, tags)
|
_, err := q.db.Exec(ctx, setTalkgroupTags, tags, systemID, tgID)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -487,21 +372,23 @@ SET
|
||||||
alert = COALESCE($7, alert),
|
alert = COALESCE($7, alert),
|
||||||
alert_config = COALESCE($8, alert_config),
|
alert_config = COALESCE($8, alert_config),
|
||||||
weight = COALESCE($9, weight)
|
weight = COALESCE($9, weight)
|
||||||
WHERE id = $10
|
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
|
RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight
|
||||||
`
|
`
|
||||||
|
|
||||||
type UpdateTalkgroupParams struct {
|
type UpdateTalkgroupParams struct {
|
||||||
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"`
|
||||||
Frequency *int32 `json:"frequency"`
|
Frequency *int32 `json:"frequency"`
|
||||||
Metadata []byte `json:"metadata"`
|
Metadata jsontypes.Metadata `json:"metadata"`
|
||||||
Tags []string `json:"tags"`
|
Tags []string `json:"tags"`
|
||||||
Alert *bool `json:"alert"`
|
Alert *bool `json:"alert"`
|
||||||
AlertConfig rules.AlertRules `json:"alert_config"`
|
AlertConfig rules.AlertRules `json:"alert_config"`
|
||||||
Weight *float32 `json:"weight"`
|
Weight *float32 `json:"weight"`
|
||||||
ID int64 `json:"id"`
|
ID pgtype.UUID `json:"id"`
|
||||||
|
SystemID *int32 `json:"system_id"`
|
||||||
|
TGID *int32 `json:"tgid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams) (Talkgroup, error) {
|
func (q *Queries) UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams) (Talkgroup, error) {
|
||||||
|
@ -516,12 +403,14 @@ func (q *Queries) UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams
|
||||||
arg.AlertConfig,
|
arg.AlertConfig,
|
||||||
arg.Weight,
|
arg.Weight,
|
||||||
arg.ID,
|
arg.ID,
|
||||||
|
arg.SystemID,
|
||||||
|
arg.TGID,
|
||||||
)
|
)
|
||||||
var i Talkgroup
|
var i Talkgroup
|
||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&i.ID,
|
&i.ID,
|
||||||
&i.SystemID,
|
&i.SystemID,
|
||||||
&i.Tgid,
|
&i.TGID,
|
||||||
&i.Name,
|
&i.Name,
|
||||||
&i.AlphaTag,
|
&i.AlphaTag,
|
||||||
&i.TgGroup,
|
&i.TgGroup,
|
||||||
|
|
|
@ -6,34 +6,16 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
const getTalkgroupsWithLearnedByPackedIDsTest = `-- name: GetTalkgroupsWithLearnedByPackedIDs :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, sys.id, sys.name,
|
|
||||||
FALSE learned
|
|
||||||
FROM talkgroups tg
|
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
|
||||||
WHERE tg.id = ANY($1::INT8[])
|
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id::INT8, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
|
||||||
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
|
||||||
TRUE learned
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
WHERE systg2id(tgl.system_id, tgl.tgid) = ANY($1::INT8[]) AND ignored IS NOT TRUE
|
|
||||||
`
|
|
||||||
const getTalkgroupWithLearnedTest = `-- name: GetTalkgroupWithLearned :one
|
const getTalkgroupWithLearnedTest = `-- 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, 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, sys.id, sys.name,
|
||||||
FALSE learned
|
FALSE learned
|
||||||
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.id = systg2id($1, $2)
|
WHERE (tg.system_id, tg.tgid) = ($1, $2)
|
||||||
UNION
|
UNION
|
||||||
SELECT
|
SELECT
|
||||||
tgl.id::INT8, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
NULL::UUID, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
||||||
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
||||||
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
||||||
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
||||||
|
@ -52,7 +34,7 @@ JOIN systems sys ON tg.system_id = sys.id
|
||||||
WHERE tg.system_id = $1
|
WHERE tg.system_id = $1
|
||||||
UNION
|
UNION
|
||||||
SELECT
|
SELECT
|
||||||
tgl.id::INT8, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
NULL::UUID, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
||||||
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
||||||
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
||||||
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
||||||
|
@ -70,7 +52,7 @@ FROM talkgroups tg
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
UNION
|
UNION
|
||||||
SELECT
|
SELECT
|
||||||
tgl.id::INT8, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
NULL::UUID, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
||||||
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
||||||
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
||||||
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
||||||
|
@ -81,7 +63,6 @@ WHERE ignored IS NOT TRUE
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestQueryColumnsMatch(t *testing.T) {
|
func TestQueryColumnsMatch(t *testing.T) {
|
||||||
require.Equal(t, getTalkgroupsWithLearnedByPackedIDsTest, getTalkgroupsWithLearnedByPackedIDs)
|
|
||||||
require.Equal(t, getTalkgroupWithLearnedTest, getTalkgroupWithLearned)
|
require.Equal(t, getTalkgroupWithLearnedTest, getTalkgroupWithLearned)
|
||||||
require.Equal(t, getTalkgroupsWithLearnedBySystemTest, getTalkgroupsWithLearnedBySystem)
|
require.Equal(t, getTalkgroupsWithLearnedBySystemTest, getTalkgroupsWithLearnedBySystem)
|
||||||
require.Equal(t, getTalkgroupsWithLearnedTest, getTalkgroupsWithLearned)
|
require.Equal(t, getTalkgroupsWithLearnedTest, getTalkgroupsWithLearned)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// Code generated by sqlc. DO NOT EDIT.
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// sqlc v1.26.0
|
// sqlc v1.27.0
|
||||||
// source: users.sql
|
// source: users.sql
|
||||||
|
|
||||||
package database
|
package database
|
||||||
|
|
|
@ -2,7 +2,6 @@ package nexus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/calls"
|
"dynatron.me/x/stillbox/pkg/calls"
|
||||||
"dynatron.me/x/stillbox/pkg/pb"
|
"dynatron.me/x/stillbox/pkg/pb"
|
||||||
|
@ -70,12 +69,7 @@ func (c *client) Talkgroup(ctx context.Context, tg *pb.Talkgroup) error {
|
||||||
|
|
||||||
var md *structpb.Struct
|
var md *structpb.Struct
|
||||||
if len(tgi.Talkgroup.Metadata) > 0 {
|
if len(tgi.Talkgroup.Metadata) > 0 {
|
||||||
m := make(map[string]interface{})
|
md, err = structpb.NewStruct(tgi.Talkgroup.Metadata)
|
||||||
err := json.Unmarshal(tgi.Talkgroup.Metadata, &m)
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Int32("sys", tg.System).Int32("tg", tg.Talkgroup).Msg("unmarshal tg metadata")
|
|
||||||
}
|
|
||||||
md, err = structpb.NewStruct(m)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Int32("sys", tg.System).Int32("tg", tg.Talkgroup).Msg("new pb struct for tg metadata")
|
log.Error().Err(err).Int32("sys", tg.System).Int32("tg", tg.Talkgroup).Msg("new pb struct for tg metadata")
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,8 +79,9 @@ 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.ErrNotFound: recordNotFound,
|
talkgroups.ErrNoSuchSystem: recordNotFound,
|
||||||
pgx.ErrNoRows: recordNotFound,
|
talkgroups.ErrNotFound: recordNotFound,
|
||||||
|
pgx.ErrNoRows: recordNotFound,
|
||||||
}
|
}
|
||||||
|
|
||||||
func autoError(err error) render.Renderer {
|
func autoError(err error) render.Renderer {
|
||||||
|
|
|
@ -6,6 +6,7 @@ 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"
|
||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
@ -20,6 +21,7 @@ func (tga *talkgroupAPI) Subrouter() http.Handler {
|
||||||
r.Put("/{system:\\d+}/{id:\\d+}", tga.put)
|
r.Put("/{system:\\d+}/{id:\\d+}", tga.put)
|
||||||
r.Get("/{system:\\d+}/", tga.get)
|
r.Get("/{system:\\d+}/", tga.get)
|
||||||
r.Get("/", tga.get)
|
r.Get("/", tga.get)
|
||||||
|
r.Post("/import", tga.tgImport)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -96,7 +98,6 @@ func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) {
|
||||||
wErr(w, r, badRequest(err))
|
wErr(w, r, badRequest(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
input.ID = id.ToID().Pack()
|
|
||||||
|
|
||||||
record, err := tgs.UpdateTG(ctx, input)
|
record, err := tgs.UpdateTG(ctx, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -106,3 +107,19 @@ func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
respond(w, r, record)
|
respond(w, r, record)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tga *talkgroupAPI) tgImport(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var impJob importer.ImportJob
|
||||||
|
err := forms.Unmarshal(r, &impJob, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
|
||||||
|
if err != nil {
|
||||||
|
wErr(w, r, badRequest(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
recs, err := impJob.Import(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
wErr(w, r, autoError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
respond(w, r, recs)
|
||||||
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ const shutdownTimeout = 5 * time.Second
|
||||||
type Server struct {
|
type Server struct {
|
||||||
auth *auth.Auth
|
auth *auth.Auth
|
||||||
conf *config.Config
|
conf *config.Config
|
||||||
db *database.DB
|
db database.DB
|
||||||
r *chi.Mux
|
r *chi.Mux
|
||||||
sources sources.Sources
|
sources sources.Sources
|
||||||
sinks sinks.Sinks
|
sinks sinks.Sinks
|
||||||
|
@ -103,7 +103,7 @@ func New(ctx context.Context, cfg *config.Config) (*Server, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Go(ctx context.Context) error {
|
func (s *Server) Go(ctx context.Context) error {
|
||||||
defer s.db.Close()
|
defer s.db.DB().Close()
|
||||||
|
|
||||||
s.installHupHandler()
|
s.installHupHandler()
|
||||||
|
|
||||||
|
|
|
@ -13,10 +13,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type DatabaseSink struct {
|
type DatabaseSink struct {
|
||||||
db *database.DB
|
db database.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDatabaseSink(db *database.DB) *DatabaseSink {
|
func NewDatabaseSink(db database.DB) *DatabaseSink {
|
||||||
return &DatabaseSink{db: db}
|
return &DatabaseSink{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,8 +39,8 @@ type Store interface {
|
||||||
// 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 []ID) error
|
||||||
|
|
||||||
// Load loads the provided packed talkgroup IDs into the Store.
|
// Load loads the provided talkgroup ID tuples into the Store.
|
||||||
Load(ctx context.Context, tgs []int64) error
|
Load(ctx context.Context, tgs database.TGTuples) error
|
||||||
|
|
||||||
// Invalidate invalidates any caching in the Store.
|
// Invalidate invalidates any caching in the Store.
|
||||||
Invalidate()
|
Invalidate()
|
||||||
|
@ -98,19 +98,20 @@ func NewCache() Store {
|
||||||
|
|
||||||
func (t *cache) Hint(ctx context.Context, tgs []ID) error {
|
func (t *cache) Hint(ctx context.Context, tgs []ID) error {
|
||||||
t.RLock()
|
t.RLock()
|
||||||
var toLoad []int64
|
var toLoad database.TGTuples
|
||||||
if len(t.tgs) > len(tgs)/2 { // TODO: instrument this
|
if len(t.tgs) > len(tgs)/2 { // TODO: instrument this
|
||||||
for _, tg := range tgs {
|
for _, tg := range tgs {
|
||||||
_, ok := t.tgs[tg]
|
_, ok := t.tgs[tg]
|
||||||
if !ok {
|
if !ok {
|
||||||
toLoad = append(toLoad, tg.Pack())
|
toLoad.Append(tg.System, tg.Talkgroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
toLoad = make([]int64, 0, len(tgs))
|
toLoad[0] = make([]uint32, 0, len(tgs))
|
||||||
|
toLoad[1] = make([]uint32, 0, len(tgs))
|
||||||
for _, g := range tgs {
|
for _, g := range tgs {
|
||||||
toLoad = append(toLoad, g.Pack())
|
toLoad.Append(g.System, g.Talkgroup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -127,7 +128,7 @@ func (t *cache) add(rec *Talkgroup) error {
|
||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
|
|
||||||
tg := TG(rec.System.ID, rec.Talkgroup.Tgid)
|
tg := 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
|
||||||
|
|
||||||
|
@ -135,8 +136,8 @@ func (t *cache) add(rec *Talkgroup) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type row interface {
|
type row interface {
|
||||||
database.GetTalkgroupsWithLearnedByPackedIDsRow | database.GetTalkgroupsWithLearnedRow |
|
database.GetTalkgroupsRow | database.GetTalkgroupsWithLearnedRow |
|
||||||
database.GetTalkgroupsWithLearnedBySystemRow
|
database.GetTalkgroupsWithLearnedBySystemRow | database.GetTalkgroupWithLearnedRow
|
||||||
GetTalkgroup() database.Talkgroup
|
GetTalkgroup() database.Talkgroup
|
||||||
GetSystem() database.System
|
GetSystem() database.System
|
||||||
GetLearned() bool
|
GetLearned() bool
|
||||||
|
@ -180,7 +181,7 @@ func (t *cache) TGs(ctx context.Context, tgs IDs) ([]*Talkgroup, error) {
|
||||||
}
|
}
|
||||||
t.RUnlock()
|
t.RUnlock()
|
||||||
|
|
||||||
tgRecords, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedByPackedIDs(ctx, toGet.Packed())
|
tgRecords, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedBySysTGID(ctx, toGet.Tuples())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -196,8 +197,8 @@ func (t *cache) TGs(ctx context.Context, tgs IDs) ([]*Talkgroup, error) {
|
||||||
return addToRowList(t, r, tgRecords)
|
return addToRowList(t, r, tgRecords)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) Load(ctx context.Context, tgs []int64) error {
|
func (t *cache) Load(ctx context.Context, tgs database.TGTuples) error {
|
||||||
tgRecords, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedByPackedIDs(ctx, tgs)
|
tgRecords, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedBySysTGID(ctx, tgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -245,7 +246,7 @@ func (t *cache) TG(ctx context.Context, tg ID) (*Talkgroup, error) {
|
||||||
return rec, nil
|
return rec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
recs, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedByPackedIDs(ctx, []int64{tg.Pack()})
|
record, err := database.FromCtx(ctx).GetTalkgroupWithLearned(ctx, int32(tg.System), int32(tg.Talkgroup))
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
case pgx.ErrNoRows:
|
case pgx.ErrNoRows:
|
||||||
|
@ -255,17 +256,13 @@ func (t *cache) TG(ctx context.Context, tg ID) (*Talkgroup, error) {
|
||||||
return nil, errors.Join(ErrNotFound, err)
|
return nil, errors.Join(ErrNotFound, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(recs) < 1 {
|
err = t.add(rowToTalkgroup(record))
|
||||||
return nil, ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
err = t.add(rowToTalkgroup(recs[0]))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("TG() cache add")
|
log.Error().Err(err).Msg("TG() cache add")
|
||||||
return rowToTalkgroup(recs[0]), errors.Join(ErrNotFound, err)
|
return rowToTalkgroup(record), errors.Join(ErrNotFound, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return rowToTalkgroup(recs[0]), nil
|
return rowToTalkgroup(record), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) SystemName(ctx context.Context, id int) (name string, has bool) {
|
func (t *cache) SystemName(ctx context.Context, id int) (name string, has bool) {
|
||||||
|
@ -290,7 +287,7 @@ func (t *cache) SystemName(ctx context.Context, id int) (name string, has bool)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*Talkgroup, error) {
|
func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*Talkgroup, error) {
|
||||||
sysName, has := t.SystemName(ctx, int(Unpack(input.ID).System))
|
sysName, has := t.SystemName(ctx, int(*input.SystemID))
|
||||||
if !has {
|
if !has {
|
||||||
return nil, ErrNoSuchSystem
|
return nil, ErrNoSuchSystem
|
||||||
}
|
}
|
||||||
|
|
139
pkg/talkgroups/importer/import.go
Normal file
139
pkg/talkgroups/importer/import.go
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
package importer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ImportSource string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ImportSrcRadioReference ImportSource = "radioreference"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrBadImportType = errors.New("unknown import type")
|
||||||
|
)
|
||||||
|
|
||||||
|
type importer interface {
|
||||||
|
importTalkgroups(ctx context.Context, sys int, r io.Reader) ([]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
|
||||||
|
|
||||||
|
const (
|
||||||
|
rrsInitial rrState = iota
|
||||||
|
rrsGroupDesc
|
||||||
|
rrsTG
|
||||||
|
)
|
||||||
|
|
||||||
|
var rrRE = regexp.MustCompile(`DEC\s+HEX\s+Mode\s+Alpha Tag\s+Description\s+Tag`)
|
||||||
|
|
||||||
|
func (rr *radioReferenceImporter) importTalkgroups(ctx context.Context, sys int, r io.Reader) ([]talkgroups.Talkgroup, error) {
|
||||||
|
sc := bufio.NewScanner(r)
|
||||||
|
tgs := make([]talkgroups.Talkgroup, 0, 8)
|
||||||
|
sysn, has := talkgroups.StoreFrom(ctx).SystemName(ctx, sys)
|
||||||
|
if !has {
|
||||||
|
return nil, talkgroups.ErrNoSuchSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
var groupName string
|
||||||
|
state := rrsInitial
|
||||||
|
for sc.Scan() {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ln := strings.Trim(sc.Text(), " \t\r\n")
|
||||||
|
|
||||||
|
switch state {
|
||||||
|
case rrsInitial:
|
||||||
|
groupName = ln
|
||||||
|
state++
|
||||||
|
case rrsGroupDesc:
|
||||||
|
if rrRE.MatchString(ln) {
|
||||||
|
state++
|
||||||
|
}
|
||||||
|
case rrsTG:
|
||||||
|
fields := strings.Split(ln, "\t")
|
||||||
|
if len(fields) != 6 {
|
||||||
|
state = rrsGroupDesc
|
||||||
|
groupName = ln
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tgid, err := strconv.Atoi(fields[0])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
var metadata jsontypes.Metadata
|
||||||
|
tgt := talkgroups.TG(sys, tgid)
|
||||||
|
mode := fields[2]
|
||||||
|
if strings.Contains(mode, "E") {
|
||||||
|
metadata = make(jsontypes.Metadata)
|
||||||
|
metadata["encrypted"] = true
|
||||||
|
}
|
||||||
|
tags := []string{fields[5]}
|
||||||
|
gn := groupName // must take a copy
|
||||||
|
tgs = append(tgs, talkgroups.Talkgroup{
|
||||||
|
Talkgroup: database.Talkgroup{
|
||||||
|
ID: uuid.New(),
|
||||||
|
TGID: int32(tgt.Talkgroup),
|
||||||
|
SystemID: int32(tgt.System),
|
||||||
|
Name: &fields[4],
|
||||||
|
AlphaTag: &fields[3],
|
||||||
|
TgGroup: &gn,
|
||||||
|
Metadata: metadata,
|
||||||
|
Tags: tags,
|
||||||
|
Weight: 1.0,
|
||||||
|
},
|
||||||
|
System: database.System{
|
||||||
|
ID: sys,
|
||||||
|
Name: sysn,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := sc.Err(); err != nil {
|
||||||
|
return tgs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tgs, nil
|
||||||
|
}
|
90
pkg/talkgroups/importer/import_test.go
Normal file
90
pkg/talkgroups/importer/import_test.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package importer_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
|
"dynatron.me/x/stillbox/pkg/database/mocks"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/importer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getFixture(fixture string) []byte {
|
||||||
|
fixt, err := os.ReadFile("testdata/" + fixture)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fixt
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImport(t *testing.T) {
|
||||||
|
// this is for deterministic UUIDs
|
||||||
|
uuid.SetRand(rand.New(rand.NewSource(1)))
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input []byte
|
||||||
|
impType string
|
||||||
|
sysID int
|
||||||
|
sysName string
|
||||||
|
jsExpect []byte
|
||||||
|
expectErr error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "radioreference",
|
||||||
|
impType: "radioreference",
|
||||||
|
input: getFixture("riscon.txt"),
|
||||||
|
jsExpect: getFixture("riscon.json"),
|
||||||
|
sysID: 197,
|
||||||
|
sysName: "RISCON",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unknown importer",
|
||||||
|
impType: "nonexistent",
|
||||||
|
expectErr: importer.ErrBadImportType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
dbMock := mocks.NewDB(t)
|
||||||
|
if tc.expectErr == nil {
|
||||||
|
dbMock.EXPECT().GetSystemName(mock.AnythingOfType("*context.valueCtx"), tc.sysID).Return(tc.sysName, nil)
|
||||||
|
}
|
||||||
|
ctx := database.CtxWithDB(context.Background(), dbMock)
|
||||||
|
ctx = talkgroups.CtxWithStore(ctx, talkgroups.NewCache())
|
||||||
|
ij := &importer.ImportJob{
|
||||||
|
Type: importer.ImportSource(tc.impType),
|
||||||
|
SystemID: tc.sysID,
|
||||||
|
Body: string(tc.input),
|
||||||
|
}
|
||||||
|
|
||||||
|
tgs, 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)
|
||||||
|
|
||||||
|
var fixt []talkgroups.Talkgroup
|
||||||
|
err = json.Unmarshal(tc.jsExpect, &fixt)
|
||||||
|
// jse, _ := json.Marshal(tgs); os.WriteFile("testdata/riscon.json", jse, 0600)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, fixt, tgs)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
1
pkg/talkgroups/importer/testdata/riscon.json
vendored
Normal file
1
pkg/talkgroups/importer/testdata/riscon.json
vendored
Normal file
File diff suppressed because one or more lines are too long
408
pkg/talkgroups/importer/testdata/riscon.txt
vendored
Normal file
408
pkg/talkgroups/importer/testdata/riscon.txt
vendored
Normal file
|
@ -0,0 +1,408 @@
|
||||||
|
Statewide Mutual Aid/Intersystem
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
2 002 D Intercity FD Intercity Fire Interop
|
||||||
|
3 003 D Intercity PD Intercity Police Interop
|
||||||
|
State Police - District A (North)
|
||||||
|
|
||||||
|
District A comprises barracks in Lincoln Woods and Scituate in the northern region of the state
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
21 015 D RISP N Disp North Dispatch Law Dispatch
|
||||||
|
22 016 DE RISP N Car North Car-to-Car/Information Law Talk
|
||||||
|
24 018 DE RISP N Tac 1 North Tactical Ops 1 Law Tac
|
||||||
|
23 017 DE RISP N Tac 2 North Tactical Ops 2 Law Tac
|
||||||
|
State Police - District B (South)
|
||||||
|
|
||||||
|
District B comprises barracks in Hope Valley, Wickford as well as detail assignments at TF Green Airport and Block Island in the southern region of the state
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
25 019 D RISP S Disp South Dispatch Law Dispatch
|
||||||
|
27 01b DE RISP S Car South Car-to-Car/Information Law Talk
|
||||||
|
Statewide Fire
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
16 010 D State FMO State Fire Marshall Fire-Talk
|
||||||
|
1038 40e D NRI Fire Chi Northern Rhode Island Fire Chiefs Fire-Talk
|
||||||
|
1041 411 D SRI Fire Chi Southern Rhode Island Fire Chiefs Fire-Talk
|
||||||
|
1314 522 D Tanker TF 1 Tanker Taskforce 1 Fire-Talk
|
||||||
|
Statewide EMS and Hospitals
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
194 0c2 D Lifepact Amb Lifepact Ambulance (Statewide) EMS Dispatch
|
||||||
|
212 0d4 D Fatima-St Joes Fatima St Josephs Business
|
||||||
|
220 0dc DE Health 1 Health 1 EMS-Talk
|
||||||
|
221 0dd DE Health 2 Health 2 EMS-Talk
|
||||||
|
222 0de D Dept of HealthSW Department of Health - Statewide EMS-Talk
|
||||||
|
228 0e4 DE DMAT South DMAT South Emergency Ops
|
||||||
|
232 0e8 D Life Span 1 Life Span Net 1 EMS-Tac
|
||||||
|
234 0ea D RI Hosp Ops RI Hospital Operations Business
|
||||||
|
Department of Environmental Management
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
120 078 D DEM PD Ops Law Enforcement Operations Law Dispatch
|
||||||
|
122 07a D DEM Police Law Enforcement Police Law Talk
|
||||||
|
Emergency Management Agency
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
10 00a D EMA-1 Emergency Management Agency 1 Emergency Ops
|
||||||
|
20 014 D EMA Emergency Management Agency Emergency Ops
|
||||||
|
Statewide Area/Events
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
4 004 D Wide Area 3 Wide Area 3 Interop
|
||||||
|
5 005 D Wide Area 4 Wide Area 4 Interop
|
||||||
|
6 006 D Wide Area 5 Wide Area 5 Interop
|
||||||
|
7 007 D Wide Area 6 Wide Area 6 Interop
|
||||||
|
1018 3fa D SOUTHWIDE 1 Southwide CH-1 Interop
|
||||||
|
1019 3fb D SOUTHWIDE 2 Southwide CH-2 Interop
|
||||||
|
1022 3fe D WIDE AREA 7 Wide Area 7 Interop
|
||||||
|
1023 3ff DE WIDE AREA 8 Wide Area 8 Interop
|
||||||
|
1025 401 D Inland Marine IO Inland Marine Interop Interop
|
||||||
|
1037 40d DE SOUTHSIDE 5 Southside CH 5 Interop
|
||||||
|
1173 495 D NORTHWIDE1 North Wide 1 Interop
|
||||||
|
1174 496 D NORTHWIDE2 North Wide 2 Interop
|
||||||
|
1177 499 DE NORTHWIDE5 North Wide 5 Interop
|
||||||
|
1185 4a1 D METROWIDE1 Metro Wide 1 Interop
|
||||||
|
1186 4a2 D METROWIDE2 Metro Wide 2 Interop
|
||||||
|
1187 4a3 DE METROWIDE3 Metro Wide 3 Interop
|
||||||
|
1335 537 D EASTWIDE 1 East Wide 1 Interop
|
||||||
|
1336 538 D EASTWIDE 2 East Wide 2 Interop
|
||||||
|
1337 539 DE EASTWIDE 3 East Wide 3 Interop
|
||||||
|
11186 2bb2 D METROWIDE2 Metro Wide 2 Interop
|
||||||
|
Statewide Emergency Response
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1033 409 D TANK TF Tanker Taskforce Fire-Tac
|
||||||
|
1034 40a D HZT DC1 Hazmat 1 Fire-Tac
|
||||||
|
1035 40b D HZT DC2 Hazmat 2 Fire-Tac
|
||||||
|
Department of Transportation
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
176 0b0 D RIDOT Primary Department of Transportation - Primary Public Works
|
||||||
|
Tunnel and Bridge Authority
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
4421 1145 D RITBA - Pell Bdg Newport Pell Bridge Operations Public Works
|
||||||
|
Federal
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
274 112 D VA Police Providence VA Police Law Dispatch
|
||||||
|
RIPTA
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
186 0ba DE RIPTA Rhode Island Public Transit Auth. Transportation
|
||||||
|
187 0bb D RIPTA Rhode Island Public Transit Auth. Transportation
|
||||||
|
188 0bc D RIPTA Rhode Island Public Transit Auth. Transportation
|
||||||
|
189 0bd D RIPTA Rhode Island Public Transit Auth. Transportation
|
||||||
|
190 0be D RIPTA Rhode Island Public Transit. Auth. Transportation
|
||||||
|
Quonset ANGB
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
304 130 D Quonset ANGB FD Fire Operations Fire Dispatch
|
||||||
|
Rhode Island Airport Commission
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
17 011 DE TF Green PD Airport Police Operations Law Dispatch
|
||||||
|
19 013 D TF Green FD Airport Fire Operations Fire Dispatch
|
||||||
|
College/Education Security
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1126 466 DE URI PD University of Rhode Island Police - Dispatch Law Dispatch
|
||||||
|
1131 46b DE URI EMS University of Rhode Island - EMS EMS Dispatch
|
||||||
|
1348 544 D St George Sec St. George's School (Middletown) - Security Security
|
||||||
|
10228 27f4 DE RISD Secuty Rhode Island School of Design - Security Security
|
||||||
|
10229 27f5 DE PROV COLL Providence College Security - Dispatch Security
|
||||||
|
10230 27f6 D RI COL SEC Rhode Island College Security Security
|
||||||
|
11001 2af9 DE BROWN UNIV Brown University Police - Dispatch Law Dispatch
|
||||||
|
11002 2afa DE BROWN CAR Brown University Police - Car-to-Car Law Talk
|
||||||
|
11003 2afb DE BROWN TAC Brown University Police - Tactical Law Tac
|
||||||
|
Statewide Misc.
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
12 00c DE METROWIDE2 Metro Wide 2 Interop
|
||||||
|
14 00e DE METROWIDE4 Metro Wide 4 Interop
|
||||||
|
70 046 DE TFC TRIBUNAL RI Traffic Tribunal Security Security
|
||||||
|
168 0a8 D Red Cross 1 Rhode Island Red Cross - Primary Other
|
||||||
|
169 0a9 D Red Cross 2 Rhode Island Red Cross - Secondary Other
|
||||||
|
223 0df D NURSING HM Statewide Nursing Homes Net Other
|
||||||
|
243 0f3 D Slater Hosp Ops Hospital Operations Business
|
||||||
|
244 0f4 D Slater Hosp Sec Slater Hospital Security Security
|
||||||
|
Washington County
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1042 412 D WashCo FireG County Fireground Fire-Tac
|
||||||
|
1479 5c7 D WashCo FireS County Fire Station/Station Fire-Talk
|
||||||
|
Barrington
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1712 6b0 D BarringtnFD1 Fire 1 Dispatch Fire Dispatch
|
||||||
|
1713 6b1 D BarringtnFD2 Fire 2 Fire-Tac
|
||||||
|
1715 6b3 DE BarringtonPD 1 Police Operations Law Dispatch
|
||||||
|
1716 6b4 D BarringtonPD 2 Police Secondary Law Tac
|
||||||
|
Bristol
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1744 6d0 D Bristol FD Fire Operations (Patch from VHF) Fire Dispatch
|
||||||
|
1755 6db D Bristol Harbor Harbormaster Public Works
|
||||||
|
Burrillville
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
2003 7d3 D Burrville PD Police Law Dispatch
|
||||||
|
2004 7d4 D Burrvl PD2 Police 2 Law Talk
|
||||||
|
2005 7d5 DE Burrvl PD3 Police 3 Detectives Law Tac
|
||||||
|
2006 7d6 D Burrvl PD4 Police 4 Law Tac
|
||||||
|
2000 7d0 D Burrvl FD Fire Misc (Ops are VHF) Fire-Tac
|
||||||
|
2001 7d1 D Burvl FDTAC1 Fire TAC-1 Fire-Tac
|
||||||
|
2009 7d9 D Burvl FDTAC2 Fire TAC-2 Fire-Tac
|
||||||
|
2002 7d2 D Burrvl EMS EMS Misc (Ops are VHF) EMS-Tac
|
||||||
|
2007 7d7 D Burrvl Town Town-Wide Multi-Tac
|
||||||
|
2008 7d8 D Burrvl EMA Emergency Management Emergency Ops
|
||||||
|
Central Falls
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1838 72e D CentFallsPD1 Police 1 Dispatch Law Dispatch
|
||||||
|
1839 72f D CentFallsPD2 Police 2 Law Dispatch
|
||||||
|
1835 72b D CentFalls FD 1 Fire Dispatch (Simulcast of UHF) Fire Dispatch
|
||||||
|
1836 72c D CentFalls FD 2 Fireground Fire-Tac
|
||||||
|
Charlestown
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1425 591 D CharlestownPD Police Operations - Simulcast of UHF Law Dispatch
|
||||||
|
1429 595 D Chastown EMS EMS - Linked to 151.3325 EMS Dispatch
|
||||||
|
Coventry
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1483 5cb D Coventry PD Police 1 - Dispatch Law Dispatch
|
||||||
|
1484 5cc D Coventry PD2 Police 2 Law Tac
|
||||||
|
1480 5c8 D Coventry FD Fire Fire Dispatch
|
||||||
|
Cranston
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1500 5dc D Cranston FD Disp Fire - Dispatch/Operations Fire Dispatch
|
||||||
|
1501 5dd D Cranston FD FG2 Fire - Fireground 2 Fire-Tac
|
||||||
|
1502 5de D Cranston FD FG3 Fire - Fireground 3 Fire-Tac
|
||||||
|
1503 5df D Cranston FD FG4 Fire - Fireground 4 Fire-Talk
|
||||||
|
1504 5e0 D Cranston FD Admi Fire - Admin/Alt Fireground 5 Fire-Talk
|
||||||
|
Cumberland
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1520 5f0 D Cumberland FD Fire Fire Dispatch
|
||||||
|
1523 5f3 D Cumberland PD Police Secondary Law Dispatch
|
||||||
|
East Greenwich
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1776 6f0 D E Greenwich F-TA Fire Talk Around Fire-Talk
|
||||||
|
1779 6f3 D E Greenwich PD Police Operations Law Dispatch
|
||||||
|
East Providence
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1869 74d D E Prov PD 1 Police 1 - Dispatch Law Dispatch
|
||||||
|
1872 750 DE E Prov PD 2 Police 2 Law Talk
|
||||||
|
1870 74e DE E Prov PD 3 Police 3 Law Talk
|
||||||
|
1883 75b DE E Prov PD12 Detectives Law Talk
|
||||||
|
1866 74a D E Prov FD 1 Fire - Dispatch/Operations Fire Dispatch
|
||||||
|
1867 74b D E Prov FD 2 Fire "Channel 2" Fire-Tac
|
||||||
|
1878 756 D E Prov FD 3 Fire "Channel 3" Fire-Tac
|
||||||
|
Exeter
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
2064 810 D Exeter FD-G Fire - Fireground Fire-Tac
|
||||||
|
Foster
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1904 770 D Foster Fire Fire Fire Dispatch
|
||||||
|
Glocester
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1939 793 D Glocester PD Police Law Dispatch
|
||||||
|
1940 794 D Glocester PD 2 Police Secondary Law Tac
|
||||||
|
Hopkinton
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1410 582 DE Hopkinton PD Police Law Dispatch
|
||||||
|
Jamestown
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1100 44c DE Jamestown PD 1 Police 1 - Dispatch Law Dispatch
|
||||||
|
1101 44d DE Jamestown PD 2 Police 2 Law Dispatch
|
||||||
|
1108 454 D Jamestown FD Fire Fire Dispatch
|
||||||
|
1120 460 D Jamestown FG 1 Fireground 1 Fire-Tac
|
||||||
|
1121 461 D Jamestown FG 2 Fireground 2 Fire-Tac
|
||||||
|
1114 45a D Jamestown DPW Public Works Public Works
|
||||||
|
1107 453 D Jamestown School Town Schools Schools
|
||||||
|
Johnston
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1619 653 DE Johnston PD Police Operations Law Dispatch
|
||||||
|
1616 650 D Johnston FD Fire Operations Fire Dispatch
|
||||||
|
1617 651 D Johnston FG Fireground Fire-Tac
|
||||||
|
Lincoln
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1683 693 D Lincoln Police Police F1 Law Dispatch
|
||||||
|
1684 694 D Lincoln Police 2 Police F2 Law Tac
|
||||||
|
1680 690 D Lincoln Fire 1 Fire Dispatch Fire Dispatch
|
||||||
|
1681 691 D Lincoln Fire 2 Fireground 2 Fire-Tac
|
||||||
|
1691 69b D Lincoln Fire 3 Fireground 3 Fire-Tac
|
||||||
|
1682 692 D Lincoln EMS EMS EMS Dispatch
|
||||||
|
1688 698 D Lincoln EMA Emergency Management Emergency Ops
|
||||||
|
1687 697 D Lincoln Townwide Townwide Interop
|
||||||
|
1692 69c D Lincoln DPW Public Works Public Works
|
||||||
|
Little Compton
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1264 4f0 D LittleCompPD Police Law Dispatch
|
||||||
|
1266 4f2 D LittleCompFD Fire Fire Dispatch
|
||||||
|
Middletown
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1338 53a D MiddletownPD Police Operations Law Dispatch
|
||||||
|
1343 53f D Middletown FD Fire Operations Fire Dispatch
|
||||||
|
1345 541 D MiddletownTW Townwide Multi-Dispatch
|
||||||
|
Narragansett
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1001 3e9 DE Narrag PD 1 Police - Dispatch Law Dispatch
|
||||||
|
1002 3ea DE Narrag PD 2 Police - Car/Car Law Talk
|
||||||
|
1003 3eb DE Narrag PD 3 Police - Special Details 1/Town Beaches Law Tac
|
||||||
|
1004 3ec DE Narrag PD 4 Police - Special Details 2 Law Tac
|
||||||
|
1005 3ed DE Narrag PD 5 Police - Harbormaster Law Talk
|
||||||
|
1007 3ef DE Narrag PD 7 Police - Detectives Law Talk
|
||||||
|
1008 3f0 DE Narrag PD 8 Police - Detectives Law Talk
|
||||||
|
1006 3ee D Narrag FD Fire - Dispatch Fire Dispatch
|
||||||
|
1012 3f4 D Narrag FDFG1 Fire - Fireground 1 Fire-Tac
|
||||||
|
1013 3f5 D Narrag FDFG2 Fire - Fireground 2 Fire-Tac
|
||||||
|
1016 3f8 D Narrag FD AD Fire - Administration Fire-Talk
|
||||||
|
1014 3f6 D Narrag EMS Fire - EMS Ops EMS Dispatch
|
||||||
|
1017 3f9 D Narrag DPW Public Works Public Works
|
||||||
|
1010 3f2 D Narrag TownA Town Administration Other
|
||||||
|
1011 3f3 D Narrag IOP Townwide Interop Interop
|
||||||
|
New Shoreham
|
||||||
|
|
||||||
|
New Shoreham is on Block Island. New Shoreham operates primarily on their own Capacity Plus trunk.
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1376 560 D New Shore PD Police Law Dispatch
|
||||||
|
Newport
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1300 514 DE Newport PD 1 Police 1 - Dispatch Law Dispatch
|
||||||
|
1302 516 DE Newport PD 2 Police 2 - Records Law Talk
|
||||||
|
1304 518 DE Newport PD 4 Police 4 - Tactical 1 Law Talk
|
||||||
|
1307 51b DE Newport PD 7 Police 7 - Tactical 4 Law Talk
|
||||||
|
1308 51c DE Newport PD 8 Police 8 - Tactical 5 Law Talk
|
||||||
|
1303 517 D Newport FD1 Fire Dispatch/Operations Fire Dispatch
|
||||||
|
1305 519 D Newport FG1 Fireground Ops 1 Fire-Tac
|
||||||
|
1306 51a D Newport FG2 Fireground Ops 2 Fire-Tac
|
||||||
|
1301 515 D Newport FDT Fire - Training Fire-Talk
|
||||||
|
1291 50b D Newport Water Water Department Public Works
|
||||||
|
1293 50d D Newport DPW Public Works Public Works
|
||||||
|
1297 511 D Newport Evnt Citywide Events Public Works
|
||||||
|
1312 520 D Newport CW Newport Citywide Interop
|
||||||
|
North Kingstown
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1285 505 D NKing PD 1 Police 1 - Dispatch Law Dispatch
|
||||||
|
1286 506 DE NKing PD 2 Police 2 - Admin Law Talk
|
||||||
|
1287 507 De NKing PD 3 Police 3 - Car/Car Law Tac
|
||||||
|
1280 500 D NKing Fire D Fire - Dispatch Fire Dispatch
|
||||||
|
1281 501 D NKing Fire G Fire - Fireground Fire-Tac
|
||||||
|
North Providence
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1536 600 DE NorthPrv PD1 Police 1 - Dispatch Law Dispatch
|
||||||
|
1537 601 DE NorthPrv PD2 Police 2 - Car/Car Law Talk
|
||||||
|
1538 602 DE NorthPrv PD3 Police 3 - Tactical Law Tac
|
||||||
|
1547 60b D NorthPrv FDD Fire Dispatch Fire Dispatch
|
||||||
|
1548 60c D NorthPrv Fire 2 Fire 2 Fire-Tac
|
||||||
|
1549 60d D NorthPrv Fire 3 Fire 3 Fire-Tac
|
||||||
|
1550 60e D NorthPrv Fire 4 Fire 4 Fire-Tac
|
||||||
|
1551 60f D NorthPrv Fire 5 Fire 5 Fire Dispatch
|
||||||
|
1552 610 DE NorthPrv Fire 6 Fire 6 Fire-Tac
|
||||||
|
1544 608 D NorthPrv TownW 1 Townwide 1 Interop
|
||||||
|
1545 609 D NorthPrv TownW 2 Townwide 2 Interop
|
||||||
|
1554 612 D NorthPrv DPW Public Works Public Works
|
||||||
|
North Smithfield
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1971 7b3 DE N Smithfd PD Police Law Dispatch
|
||||||
|
1968 7b0 D N Smithfield FD Fire Dispatch/Operations Fire Dispatch
|
||||||
|
1969 7b1 D N Smithfield FD2 Fire Secondary Fire-Tac
|
||||||
|
1981 7bd D N Smithfield FD3 Fireground Fire-Tac
|
||||||
|
Pawtucket
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1440 5a0 D Pawtucket FD 1 Fire - Operations Fire Dispatch
|
||||||
|
1441 5a1 D Pawtucket FG Fireground Fire-Tac
|
||||||
|
1442 5a2 D Pawtucket EMSTac EMS Tac EMS-Tac
|
||||||
|
Portsmouth
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1248 4e0 DE PortsmouthPD Police Law Dispatch
|
||||||
|
1253 4e5 D Portsmouth FD Fire Dispatch (Patch to VHF Primary) Fire Dispatch
|
||||||
|
1255 4e7 D Portsmouth FG Fireground Fire-Tac
|
||||||
|
1262 4ee D Prudence Isl FD Island Fire Dispatch Fire Dispatch
|
||||||
|
Providence (City)
|
||||||
|
|
||||||
|
Providence fireground channels may be patched.
|
||||||
|
As of this writing,
|
||||||
|
FG 05 (10102) and 02 (10107) are patched
|
||||||
|
FG 06 (10103) and 03 (10108) are patched
|
||||||
|
FG 07 (10104) and 04 (10109) are patched.
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
10000 2710 D PPD ATG Police - All Call - Emergency Broadcasts Emergency Ops
|
||||||
|
10001 2711 D PPD CH 1 Police 1 - Dispatch Law Dispatch
|
||||||
|
10002 2712 DE PPD CH 2 Police 2 Law Talk
|
||||||
|
10003 2713 DE PPD CH 3 Police 3 Law Talk
|
||||||
|
10004 2714 DE PPD CH-4 Police 4 Law Talk
|
||||||
|
10005 2715 DE PPD DETEC 1 Police 5 -Detectives 1 Law Talk
|
||||||
|
10006 2716 DE PPD T/A Police 6 - Car-to-Car Law Talk
|
||||||
|
10007 2717 DE PPD NARC 1 Police 7 - Narcotics 1 Law Talk
|
||||||
|
10008 2718 DE PPD NARC 2 Police 8 - Narcotics 2 Law Tac
|
||||||
|
10009 2719 DE PPD DETEC 2 Police 9 - Detectives 2 Law Talk
|
||||||
|
10010 271a DE PPD DETAIL 1 Police 10 - Special Details 1 Law Tac
|
||||||
|
10011 271b DE PPD DETAIL 2 Police 11 - Special Details 2 Law Tac
|
||||||
|
10012 271c DE PPD CORR SEC Police 12 - Corrections Security Law Talk
|
||||||
|
10013 271d DE PPD SRU Police 13 - Special Response Unit Law Tac
|
||||||
|
10014 271e DE PPD ADMIN Police 14 - Administration Law Talk
|
||||||
|
10100 2774 D PROV FD ATG Fire All Call - Emergency Broadcasts Emergency Ops
|
||||||
|
10101 2775 D PFD DISPATCH Fire Dispatch Fire Dispatch
|
||||||
|
10107 277b D PFD CH-2 FG Fireground 2 Fire-Tac
|
||||||
|
10108 277c D PFD CH-3 FG Fireground 3 Fire-Tac
|
||||||
|
10109 277d D PFD CH-4 FG Fireground 4 Fire-Tac
|
||||||
|
10102 2776 D PFD CH-5 Fire 5 Fire-Tac
|
||||||
|
10103 2777 D PFD CH-6 Fire 6 Fire-Tac
|
||||||
|
10104 2778 D PFD CH-7 Fire 7 Fire-Tac
|
||||||
|
10110 277e D PFD M/A 1 Fire - Mutual Aid 1 Fire-Tac
|
||||||
|
10111 277f D PFD M/A 2 Fire - Mutual Aid 2 Fire-Tac
|
||||||
|
10112 2780 D PFD M/A 3 Fire - Mutual Aid 3 Fire-Tac
|
||||||
|
10113 2781 D PFD Fireground 8 Fireground 8 Fire-Talk
|
||||||
|
10105 2779 D PFD ADMIN Fire - Administration Fire-Talk
|
||||||
|
10106 277a D PFD COMM Fire - Communications Fire-Talk
|
||||||
|
10207 27df D PROV DPW Public Works Public Works
|
||||||
|
Richmond
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
2035 7f3 D Richmond PD Police Law Dispatch
|
||||||
|
2042 7fa D Chariho Reg HS Chariho Regional High School Schools
|
||||||
|
Scituate
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1460 5b4 D Scituate PD Police Law Dispatch
|
||||||
|
1463 5b7 D Scituate FD Fire Operations Fire Dispatch
|
||||||
|
Smithfield
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1651 673 D SmithfieldPD Police Operations Law Dispatch
|
||||||
|
1652 674 D Smfld PD 2 Police Secondary Law Tac
|
||||||
|
1653 675 DE Smfld PD Det Police Detectives Law Tac
|
||||||
|
1654 676 DE Smfld PD Adm Police Admin Law Talk
|
||||||
|
1661 67d D Smfld PD Dtl Police Details Law Talk
|
||||||
|
1648 670 D SmithfieldFD Fire - Fireground Fire-Tac
|
||||||
|
1655 677 D Smfld Town Town-Wide Multi-Tac
|
||||||
|
1657 679 D Smfld EMA Emergency Management Emergency Ops
|
||||||
|
1660 67c D Smfld DPW Public Works Public Works
|
||||||
|
South Kingstown
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1225 4c9 DE SKing PD 1 Police 1 - Dispatch Law Dispatch
|
||||||
|
1226 4ca DE SKing PD 2 Police 2 - Car/Car Law Talk
|
||||||
|
1235 4d3 DE SKing PD 3 Police 3 - Tactical Law Tac
|
||||||
|
1236 4d4 DE SKing PD 5 Police 5 - Tactical Law Tac
|
||||||
|
1232 4d0 D SKing FD Lnk Fire - UHF Simulcast Fire Dispatch
|
||||||
|
1240 4d8 D SKing Fire D Fire - Detail Fire-Talk
|
||||||
|
1227 4cb D UnionFD FG 1 Union Fire District - Fireground 1 Fire-Tac
|
||||||
|
1237 4d5 D UnionFD FG 2 Union Fire District - Fireground 2 Fire-Tac
|
||||||
|
1026 402 D UnionFD Evnt Union Fire District - Special Events Fire-Talk
|
||||||
|
1015 3f7 DE SKing EMS EMS EMS Dispatch
|
||||||
|
Tiverton
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1316 524 D Tiverton PD Police (Simulcast 482.9625) Law Dispatch
|
||||||
|
1315 523 D Tiverton FD Fire (Simulcast 471.7875) Fire Dispatch
|
||||||
|
Warwick
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1162 48a D Warwick FD Fire Fire Dispatch
|
||||||
|
1170 492 D Warwick FG Fireground Fire-Tac
|
||||||
|
West Greenwich
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1805 70d D W Greenwh PD Police Law Dispatch
|
||||||
|
1806 70e D W GreenwichPD2 Police Secondary Law Tac
|
||||||
|
West Warwick
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1208 4b8 D W Warwick FD Fire Operations Fire Dispatch
|
||||||
|
Westerly
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1050 41a DE Westerly PD1 Police 1 - Dispatch Law Dispatch
|
||||||
|
1051 41b DE Westerly PD2 Police 2 Law Talk
|
||||||
|
1052 41c DE Westerly PD3 Police 3 Law Talk
|
||||||
|
1053 41d DE Westerly PD4 Police 4 Law Talk
|
||||||
|
1054 41e D Westerly PD5 Police 5 - Reserve Officers Law Talk
|
||||||
|
1064 428 D Westerly PD6 Police 6 - Traffic Division Law Talk
|
||||||
|
1063 427 D Westerly FD Fire Operations Fire Dispatch
|
||||||
|
1072 430 D Westerly PFE Police/Fire/EMS Ops Multi-Talk
|
||||||
|
1082 43a D Westerly EMS EMS Operations EMS Dispatch
|
||||||
|
Woonsocket
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1363 553 D Woonskt PD 1 Police 1 - Dispatch Law Dispatch
|
||||||
|
1364 554 DE Woonskt PD 2 Police 2 Law Talk
|
||||||
|
1360 550 D Woonsocket FD D Fire Dispatch - Operations Fire-Talk
|
||||||
|
1361 551 D Woonsocket FD 2 Fire Secondary Fire Dispatch
|
||||||
|
1354 54a D Woonskt FD 3 Fire - Fireground 3 Fire-Tac
|
||||||
|
1367 557 D Woonskt City Citywide Multi-Talk
|
||||||
|
1368 558 D Woonsocket PW Public Works - Streets Public Works
|
||||||
|
Radio Technicians
|
||||||
|
DEC HEX Mode Alpha Tag Description Tag
|
||||||
|
1 001 D Radio Techs RISCON Radio Technicians Public Works
|
||||||
|
10125 278d D Radio Techs RISCON Radio Technicians Public Works
|
|
@ -12,6 +12,8 @@ type Talkgroup struct {
|
||||||
Learned bool `json:"learned"`
|
Learned bool `json:"learned"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Metadata map[string]interface{}
|
||||||
|
|
||||||
type Names struct {
|
type Names struct {
|
||||||
System string
|
System string
|
||||||
Talkgroup string
|
Talkgroup string
|
||||||
|
@ -24,13 +26,16 @@ type ID struct {
|
||||||
|
|
||||||
type IDs []ID
|
type IDs []ID
|
||||||
|
|
||||||
func (ids *IDs) Packed() []int64 {
|
func (t IDs) Tuples() database.TGTuples {
|
||||||
r := make([]int64, len(*ids))
|
sys := make([]uint32, len(t))
|
||||||
for i := range *ids {
|
tg := make([]uint32, len(t))
|
||||||
r[i] = (*ids)[i].Pack()
|
|
||||||
|
for i := range t {
|
||||||
|
sys[i] = t[i].System
|
||||||
|
tg[i] = t[i].Talkgroup
|
||||||
}
|
}
|
||||||
|
|
||||||
return r
|
return database.TGTuples{sys, tg}
|
||||||
}
|
}
|
||||||
|
|
||||||
type intId interface {
|
type intId interface {
|
||||||
|
@ -44,18 +49,6 @@ func TG[T intId, U intId](sys T, tgid U) ID {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t ID) Pack() int64 {
|
|
||||||
// P25 system IDs are 12 bits, so we can fit them in a signed 8 byte int (int64, pg INT8)
|
|
||||||
return int64((int64(t.System) << 32) | int64(t.Talkgroup))
|
|
||||||
}
|
|
||||||
|
|
||||||
func Unpack(id int64) ID {
|
|
||||||
return ID{
|
|
||||||
System: uint32(id >> 32),
|
|
||||||
Talkgroup: uint32(id & 0xffffffff),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t ID) String() string {
|
func (t ID) String() string {
|
||||||
return fmt.Sprintf("%d:%d", t.System, t.Talkgroup)
|
return fmt.Sprintf("%d:%d", t.System, t.Talkgroup)
|
||||||
|
|
||||||
|
|
|
@ -23,31 +23,10 @@ CREATE TABLE IF NOT EXISTS systems(
|
||||||
name TEXT NOT NULL
|
name TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION systg2id(_sys INTEGER, _tg INTEGER) RETURNS INT8 LANGUAGE plpgsql AS
|
|
||||||
$$
|
|
||||||
BEGIN
|
|
||||||
RETURN ((_sys::BIGINT << 32) | _tg);
|
|
||||||
END
|
|
||||||
$$;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION tgfromid(_id INT8) RETURNS INTEGER LANGUAGE plpgsql AS
|
|
||||||
$$
|
|
||||||
BEGIN
|
|
||||||
RETURN (_id & x'ffffffff'::BIGINT);
|
|
||||||
END
|
|
||||||
$$;
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION sysfromid(_id INT8) RETURNS INTEGER LANGUAGE plpgsql AS
|
|
||||||
$$
|
|
||||||
BEGIN
|
|
||||||
RETURN (_id >> 32);
|
|
||||||
END
|
|
||||||
$$;
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS talkgroups(
|
CREATE TABLE IF NOT EXISTS talkgroups(
|
||||||
id INT8 PRIMARY KEY,
|
id UUID PRIMARY KEY,
|
||||||
system_id INT4 REFERENCES systems(id) NOT NULL GENERATED ALWAYS AS (id >> 32) STORED,
|
system_id INT4 REFERENCES systems(id) NOT NULL,
|
||||||
tgid INT4 NOT NULL GENERATED ALWAYS AS (id & x'ffffffff'::BIGINT) STORED,
|
tgid INT4 NOT NULL,
|
||||||
name TEXT,
|
name TEXT,
|
||||||
alpha_tag TEXT,
|
alpha_tag TEXT,
|
||||||
tg_group TEXT,
|
tg_group TEXT,
|
||||||
|
@ -56,9 +35,12 @@ CREATE TABLE IF NOT EXISTS talkgroups(
|
||||||
tags TEXT[] NOT NULL DEFAULT '{}',
|
tags TEXT[] NOT NULL DEFAULT '{}',
|
||||||
alert BOOLEAN NOT NULL DEFAULT 'true',
|
alert BOOLEAN NOT NULL DEFAULT 'true',
|
||||||
alert_config JSONB,
|
alert_config JSONB,
|
||||||
weight REAL NOT NULL DEFAULT 1.0
|
weight REAL NOT NULL DEFAULT 1.0,
|
||||||
|
UNIQUE (system_id, tgid)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
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 talkgroups_learned(
|
||||||
|
|
|
@ -8,30 +8,21 @@ WHERE tags && ARRAY[$1];
|
||||||
|
|
||||||
-- name: GetTalkgroupIDsByTags :many
|
-- name: GetTalkgroupIDsByTags :many
|
||||||
SELECT system_id, tgid FROM talkgroups
|
SELECT system_id, tgid FROM talkgroups
|
||||||
WHERE (tags @> ARRAY[sqlc.arg(anyTags)])
|
WHERE (tags @> ARRAY[@any_tags])
|
||||||
AND (tags && ARRAY[sqlc.arg(allTags)])
|
AND (tags && ARRAY[@all_tags])
|
||||||
AND NOT (tags @> ARRAY[sqlc.arg(notTags)]);
|
AND NOT (tags @> ARRAY[@not_tags]);
|
||||||
|
|
||||||
-- name: GetTalkgroupTags :one
|
-- name: GetTalkgroupTags :one
|
||||||
SELECT tags FROM talkgroups
|
SELECT tags FROM talkgroups
|
||||||
WHERE id = systg2id($1, $2);
|
WHERE system_id = @system_id AND tgid = @tg_id;
|
||||||
|
|
||||||
-- name: SetTalkgroupTags :exec
|
-- name: SetTalkgroupTags :exec
|
||||||
UPDATE talkgroups SET tags = $3
|
UPDATE talkgroups SET tags = @tags
|
||||||
WHERE id = systg2id($1, $2);
|
WHERE system_id = @system_id AND tgid = @tg_id;
|
||||||
|
|
||||||
-- name: BulkSetTalkgroupTags :exec
|
|
||||||
UPDATE talkgroups SET tags = $2
|
|
||||||
WHERE id = ANY($1);
|
|
||||||
|
|
||||||
-- name: GetTalkgroup :one
|
-- name: GetTalkgroup :one
|
||||||
SELECT sqlc.embed(talkgroups) FROM talkgroups
|
SELECT sqlc.embed(talkgroups) FROM talkgroups
|
||||||
WHERE id = systg2id(sqlc.arg(system_id), sqlc.arg(tgid));
|
WHERE (system_id, tgid) = (@system_id, @tg_id);
|
||||||
|
|
||||||
-- name: GetTalkgroupsByPackedIDs :many
|
|
||||||
SELECT sqlc.embed(tg), sqlc.embed(sys) FROM talkgroups tg
|
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
|
||||||
WHERE tg.id = ANY($1::INT8[]);
|
|
||||||
|
|
||||||
-- name: GetTalkgroupWithLearned :one
|
-- name: GetTalkgroupWithLearned :one
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -39,35 +30,17 @@ sqlc.embed(tg), sqlc.embed(sys),
|
||||||
FALSE learned
|
FALSE learned
|
||||||
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.id = systg2id(sqlc.arg(system_id), sqlc.arg(tgid))
|
WHERE (tg.system_id, tg.tgid) = (@system_id, @tgid)
|
||||||
UNION
|
UNION
|
||||||
SELECT
|
SELECT
|
||||||
tgl.id::INT8, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
NULL::UUID, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
||||||
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
||||||
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
||||||
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
||||||
TRUE learned
|
TRUE learned
|
||||||
FROM talkgroups_learned tgl
|
FROM talkgroups_learned tgl
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
JOIN systems sys ON tgl.system_id = sys.id
|
||||||
WHERE tgl.system_id = sqlc.arg(system_id) AND tgl.tgid = sqlc.arg(tgid) AND ignored IS NOT TRUE;
|
WHERE tgl.system_id = @system_id AND tgl.tgid = @tgid AND ignored IS NOT TRUE;
|
||||||
|
|
||||||
-- name: GetTalkgroupsWithLearnedByPackedIDs :many
|
|
||||||
SELECT
|
|
||||||
sqlc.embed(tg), sqlc.embed(sys),
|
|
||||||
FALSE learned
|
|
||||||
FROM talkgroups tg
|
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
|
||||||
WHERE tg.id = ANY($1::INT8[])
|
|
||||||
UNION
|
|
||||||
SELECT
|
|
||||||
tgl.id::INT8, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
|
||||||
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
|
||||||
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
|
||||||
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
|
||||||
TRUE learned
|
|
||||||
FROM talkgroups_learned tgl
|
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
|
||||||
WHERE systg2id(tgl.system_id, tgl.tgid) = ANY($1::INT8[]) AND ignored IS NOT TRUE;
|
|
||||||
|
|
||||||
-- name: GetTalkgroupsWithLearnedBySystem :many
|
-- name: GetTalkgroupsWithLearnedBySystem :many
|
||||||
SELECT
|
SELECT
|
||||||
|
@ -78,7 +51,7 @@ JOIN systems sys ON tg.system_id = sys.id
|
||||||
WHERE tg.system_id = @system
|
WHERE tg.system_id = @system
|
||||||
UNION
|
UNION
|
||||||
SELECT
|
SELECT
|
||||||
tgl.id::INT8, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
NULL::UUID, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
||||||
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
||||||
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
||||||
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
||||||
|
@ -95,7 +68,7 @@ FROM talkgroups tg
|
||||||
JOIN systems sys ON tg.system_id = sys.id
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
UNION
|
UNION
|
||||||
SELECT
|
SELECT
|
||||||
tgl.id::INT8, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
NULL::UUID, tgl.system_id::INT4, tgl.tgid::INT4, tgl.name,
|
||||||
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
tgl.alpha_tag, tgl.alpha_tag, NULL::INTEGER, NULL::JSONB,
|
||||||
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
CASE WHEN tgl.alpha_tag IS NULL THEN NULL ELSE ARRAY[tgl.alpha_tag] END,
|
||||||
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
TRUE, NULL::JSONB, 1.0, sys.id, sys.name,
|
||||||
|
@ -105,7 +78,7 @@ 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
|
||||||
SELECT name FROM systems WHERE id = sqlc.arg(system_id);
|
SELECT name FROM systems WHERE id = @system_id;
|
||||||
|
|
||||||
-- name: UpdateTalkgroup :one
|
-- name: UpdateTalkgroup :one
|
||||||
UPDATE talkgroups
|
UPDATE talkgroups
|
||||||
|
@ -119,5 +92,5 @@ SET
|
||||||
alert = COALESCE(sqlc.narg('alert'), alert),
|
alert = COALESCE(sqlc.narg('alert'), alert),
|
||||||
alert_config = COALESCE(sqlc.narg('alert_config'), alert_config),
|
alert_config = COALESCE(sqlc.narg('alert_config'), alert_config),
|
||||||
weight = COALESCE(sqlc.narg('weight'), weight)
|
weight = COALESCE(sqlc.narg('weight'), weight)
|
||||||
WHERE id = @id
|
WHERE id = sqlc.narg('id') OR (system_id = sqlc.narg('system_id') AND tgid = sqlc.narg('tgid'))
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
|
@ -11,6 +11,9 @@ sql:
|
||||||
query_parameter_limit: 3
|
query_parameter_limit: 3
|
||||||
emit_json_tags: true
|
emit_json_tags: true
|
||||||
emit_interface: true
|
emit_interface: true
|
||||||
|
initialisms:
|
||||||
|
- id
|
||||||
|
- tgid
|
||||||
emit_pointers_for_null_types: true
|
emit_pointers_for_null_types: true
|
||||||
overrides:
|
overrides:
|
||||||
- db_type: "uuid"
|
- db_type: "uuid"
|
||||||
|
@ -32,3 +35,8 @@ sql:
|
||||||
import: "dynatron.me/x/stillbox/pkg/alerting/rules"
|
import: "dynatron.me/x/stillbox/pkg/alerting/rules"
|
||||||
type: "AlertRules"
|
type: "AlertRules"
|
||||||
nullable: true
|
nullable: true
|
||||||
|
- column: "talkgroups.metadata"
|
||||||
|
go_type:
|
||||||
|
import: "dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
|
type: "Metadata"
|
||||||
|
nullable: true
|
||||||
|
|
24
util/dumpdb.sh
Normal file
24
util/dumpdb.sh
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
config=config.yaml
|
||||||
|
pgformat="-Fc"
|
||||||
|
ext=pgdump
|
||||||
|
|
||||||
|
while getopts ":p" arg; do
|
||||||
|
case $arg in
|
||||||
|
c)
|
||||||
|
config=$OPTARG
|
||||||
|
;;
|
||||||
|
p)
|
||||||
|
pgformat="-Fp"
|
||||||
|
ext=sql
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
filen=`date "+backups/%Y%m%d_%H%M%S.${ext}"`
|
||||||
|
|
||||||
|
mkdir -p backups/
|
||||||
|
dbstring=`yq -r .db.connect "${config}"`
|
||||||
|
pg_dump "${pgformat}" -f "${filen}" -T calls "${dbstring}"
|
||||||
|
echo "backed up to ${filen}"
|
Loading…
Reference in a new issue