Compare commits
7 commits
ea2cbfede5
...
3b8a50d3e8
Author | SHA1 | Date | |
---|---|---|---|
3b8a50d3e8 | |||
3f133e152a | |||
c4df60a89a | |||
c1a258f63c | |||
65cddbcc0d | |||
fb2387c212 | |||
170970e92d |
25 changed files with 685 additions and 188 deletions
|
@ -57,7 +57,7 @@ func main() {
|
||||||
loginForm.Add("username", *username)
|
loginForm.Add("username", *username)
|
||||||
loginForm.Add("password", *password)
|
loginForm.Add("password", *password)
|
||||||
|
|
||||||
loginReq, err := http.NewRequest("POST", "http"+secureSuffix()+"://"+*addr+"/login", strings.NewReader(loginForm.Encode()))
|
loginReq, err := http.NewRequest("POST", "http"+secureSuffix()+"://"+*addr+"/api/login", strings.NewReader(loginForm.Encode()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,7 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
u := url.URL{Scheme: "ws" + secureSuffix(), Host: *addr, Path: "/ws"}
|
u := url.URL{Scheme: "ws" + secureSuffix(), Host: *addr, Path: "/api/ws"}
|
||||||
log.Printf("connecting to %s", u.String())
|
log.Printf("connecting to %s", u.String())
|
||||||
|
|
||||||
dialer := websocket.Dialer{
|
dialer := websocket.Dialer{
|
||||||
|
|
|
@ -31,7 +31,7 @@ func newItem[K comparable](id K, options *options[K]) *item[K] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *item[K]) score(id K) Score[K] {
|
func (i *item[K]) score() Score[K] {
|
||||||
recentCount, count := i.computeCounts()
|
recentCount, count := i.computeCounts()
|
||||||
if recentCount < i.options.countThreshold {
|
if recentCount < i.options.countThreshold {
|
||||||
return Score[K]{}
|
return Score[K]{}
|
||||||
|
|
|
@ -192,7 +192,7 @@ func (s *Scorer[K]) addToItem(item *item[K], tm time.Time) {
|
||||||
func (s *Scorer[K]) Score() Scores[K] {
|
func (s *Scorer[K]) Score() Scores[K] {
|
||||||
var scores Scores[K]
|
var scores Scores[K]
|
||||||
for id, item := range s.items {
|
for id, item := range s.items {
|
||||||
score := item.score(id)
|
score := item.score()
|
||||||
score.ID = id
|
score.ID = id
|
||||||
scores = append(scores, score)
|
scores = append(scores, score)
|
||||||
}
|
}
|
||||||
|
|
|
@ -169,12 +169,9 @@ func (as *alerter) eval(ctx context.Context, now time.Time, testMode bool) ([]Al
|
||||||
for _, s := range as.scores {
|
for _, s := range as.scores {
|
||||||
origScore := s.Score
|
origScore := s.Score
|
||||||
tgr, err := as.tgCache.TG(ctx, s.ID)
|
tgr, err := as.tgCache.TG(ctx, s.ID)
|
||||||
if err == nil {
|
if err == nil && !tgr.Talkgroup.Alert {
|
||||||
if !tgr.Talkgroup.Alert {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
s.Score *= float64(tgr.Talkgroup.Weight)
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.Score > as.cfg.AlertThreshold || testMode {
|
if s.Score > as.cfg.AlertThreshold || testMode {
|
||||||
if old, inCache := as.alertCache[s.ID]; !inCache || now.Sub(old.Timestamp) > as.renotify {
|
if old, inCache := as.alertCache[s.ID]; !inCache || now.Sub(old.Timestamp) > as.renotify {
|
||||||
|
|
|
@ -69,20 +69,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.GetTalkgroupsByPackedIDs(ctx, as.packedScoredTGs())
|
tgs, err := db.GetTalkgroupsWithLearnedByPackedIDs(ctx, as.packedScoredTGs())
|
||||||
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.GetTalkgroupsByPackedIDsRow, len(tgs))
|
tgMap := make(map[talkgroups.ID]database.GetTalkgroupsWithLearnedByPackedIDsRow, len(tgs))
|
||||||
for _, t := range tgs {
|
for _, t := range tgs {
|
||||||
tgMap[talkgroups.ID{System: uint32(t.System.ID), Talkgroup: uint32(t.Talkgroup.ID)}] = t
|
tgMap[talkgroups.ID{System: uint32(t.System.ID), Talkgroup: uint32(t.Talkgroup.Tgid)}] = t
|
||||||
}
|
}
|
||||||
|
|
||||||
renderData := struct {
|
renderData := struct {
|
||||||
TGs map[talkgroups.ID]database.GetTalkgroupsByPackedIDsRow
|
TGs map[talkgroups.ID]database.GetTalkgroupsWithLearnedByPackedIDsRow
|
||||||
Scores trending.Scores[talkgroups.ID]
|
Scores trending.Scores[talkgroups.ID]
|
||||||
LastScore time.Time
|
LastScore time.Time
|
||||||
Simulation *Simulation
|
Simulation *Simulation
|
||||||
|
|
127
pkg/api/api.go
Normal file
127
pkg/api/api.go
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-viper/mapstructure/v2"
|
||||||
|
"github.com/jackc/pgx/v5"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type API interface {
|
||||||
|
Subrouter() http.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
type api struct {
|
||||||
|
tgs talkgroups.Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(tgs talkgroups.Store) API {
|
||||||
|
s := &api{
|
||||||
|
tgs: tgs,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *api) Subrouter() http.Handler {
|
||||||
|
r := chi.NewMux()
|
||||||
|
|
||||||
|
r.Get("/talkgroup/{system:\\d+}/{id:\\d+}", a.talkgroup)
|
||||||
|
r.Get("/talkgroup/{system:\\d+}/", a.talkgroup)
|
||||||
|
r.Get("/talkgroup/", a.talkgroup)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
var statusMapping = map[error]int{
|
||||||
|
talkgroups.ErrNotFound: http.StatusNotFound,
|
||||||
|
pgx.ErrNoRows: http.StatusNotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpCode(err error) int {
|
||||||
|
c, ok := statusMapping[err]
|
||||||
|
if ok {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
for e, c := range statusMapping { // check if err wraps an error we know about
|
||||||
|
if errors.Is(err, e) {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *api) writeResponse(w http.ResponseWriter, r *http.Request, data interface{}, err error) {
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Str("path", r.URL.Path).Err(err).Msg("request failed")
|
||||||
|
http.Error(w, err.Error(), httpCode(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
enc := json.NewEncoder(w)
|
||||||
|
err = enc.Encode(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Str("path", r.URL.Path).Err(err).Msg("response marshal failed")
|
||||||
|
http.Error(w, err.Error(), httpCode(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeParams(d interface{}, r *http.Request) error {
|
||||||
|
params := chi.RouteContext(r.Context()).URLParams
|
||||||
|
m := make(map[string]string, len(params.Keys))
|
||||||
|
|
||||||
|
for i, k := range params.Keys {
|
||||||
|
m[k] = params.Values[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
dec, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
|
||||||
|
Metadata: nil,
|
||||||
|
Result: d,
|
||||||
|
TagName: "param",
|
||||||
|
WeaklyTypedInput: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dec.Decode(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *api) badReq(w http.ResponseWriter, err error) {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *api) talkgroup(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
p := struct {
|
||||||
|
System *int `param:"system"`
|
||||||
|
ID *int `param:"id"`
|
||||||
|
}{}
|
||||||
|
|
||||||
|
err := decodeParams(&p, r)
|
||||||
|
if err != nil {
|
||||||
|
a.badReq(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var res interface{}
|
||||||
|
switch {
|
||||||
|
case p.System != nil && p.ID != nil:
|
||||||
|
res, err = a.tgs.TG(ctx, talkgroups.TG(*p.System, *p.ID))
|
||||||
|
case p.System != nil:
|
||||||
|
res, err = a.tgs.SystemTGs(ctx, int32(*p.System))
|
||||||
|
default:
|
||||||
|
res, err = a.tgs.TGs(ctx, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.writeResponse(w, r, res, err)
|
||||||
|
}
|
|
@ -4,7 +4,10 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/config"
|
"dynatron.me/x/stillbox/pkg/config"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/jwtauth/v5"
|
"github.com/go-chi/jwtauth/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -66,3 +69,20 @@ func ErrorResponse(w http.ResponseWriter, err error) {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Auth) PublicRoutes(r chi.Router) {
|
||||||
|
r.Post("/api/login", a.routeAuth)
|
||||||
|
r.Get("/api/login", a.routeLogin)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Auth) PrivateRoutes(r chi.Router) {
|
||||||
|
r.Get("/refresh", a.routeRefresh)
|
||||||
|
}
|
||||||
|
|
||||||
|
//go:embed login.html
|
||||||
|
var loginPage []byte
|
||||||
|
|
||||||
|
func (a *Auth) routeLogin(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Add("Content-Type", "text/html")
|
||||||
|
_, _ = w.Write(loginPage)
|
||||||
|
}
|
||||||
|
|
|
@ -110,14 +110,6 @@ func (a *Auth) newToken(uid int32) string {
|
||||||
return tokenString
|
return tokenString
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) PublicRoutes(r chi.Router) {
|
|
||||||
r.Post("/login", a.routeAuth)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Auth) PrivateRoutes(r chi.Router) {
|
|
||||||
r.Get("/refresh", a.routeRefresh)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Auth) allowInsecureCookie(r *http.Request) bool {
|
func (a *Auth) allowInsecureCookie(r *http.Request) bool {
|
||||||
host := strings.Split(r.Host, ":")
|
host := strings.Split(r.Host, ":")
|
||||||
v, has := a.cfg.AllowInsecure[host[0]]
|
v, has := a.cfg.AllowInsecure[host[0]]
|
||||||
|
|
17
pkg/auth/login.html
Normal file
17
pkg/auth/login.html
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Login</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<form action="/login" method="POST">
|
||||||
|
<label for="username">Username: </label>
|
||||||
|
<input type="text" name="username" />
|
||||||
|
<label for="password">Password: </label>
|
||||||
|
<input type="password" name="password" />
|
||||||
|
<input type="submit" value="Login" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/pb"
|
"dynatron.me/x/stillbox/pkg/pb"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups"
|
"dynatron.me/x/stillbox/pkg/talkgroups"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -32,6 +33,7 @@ func (d CallDuration) Seconds() int32 {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Call struct {
|
type Call struct {
|
||||||
|
ID uuid.UUID
|
||||||
Audio []byte
|
Audio []byte
|
||||||
AudioName string
|
AudioName string
|
||||||
AudioType string
|
AudioType string
|
||||||
|
@ -68,6 +70,7 @@ func Make(call *Call, dontStore bool) (*Call, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
call.shouldStore = dontStore
|
call.shouldStore = dontStore
|
||||||
|
call.ID = uuid.New()
|
||||||
|
|
||||||
return call, nil
|
return call, nil
|
||||||
}
|
}
|
||||||
|
@ -92,6 +95,7 @@ func toInt32Slice(s []int) []int32 {
|
||||||
|
|
||||||
func (c *Call) ToPB() *pb.Call {
|
func (c *Call) ToPB() *pb.Call {
|
||||||
return &pb.Call{
|
return &pb.Call{
|
||||||
|
Id: c.ID.String(),
|
||||||
AudioName: c.AudioName,
|
AudioName: c.AudioName,
|
||||||
AudioType: c.AudioType,
|
AudioType: c.AudioType,
|
||||||
DateTime: timestamppb.New(c.DateTime),
|
DateTime: timestamppb.New(c.DateTime),
|
||||||
|
|
|
@ -52,7 +52,7 @@ func (q *Queries) AddAlert(ctx context.Context, arg AddAlertParams) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
const addCall = `-- name: AddCall :one
|
const addCall = `-- name: AddCall :exec
|
||||||
INSERT INTO calls (
|
INSERT INTO calls (
|
||||||
id,
|
id,
|
||||||
submitter,
|
submitter,
|
||||||
|
@ -71,11 +71,29 @@ INSERT INTO calls (
|
||||||
tg_alpha_tag,
|
tg_alpha_tag,
|
||||||
tg_group,
|
tg_group,
|
||||||
source
|
source
|
||||||
) VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
) VALUES (
|
||||||
RETURNING id
|
$1,
|
||||||
|
$2,
|
||||||
|
$3,
|
||||||
|
$4,
|
||||||
|
$5,
|
||||||
|
$6,
|
||||||
|
$7,
|
||||||
|
$8,
|
||||||
|
$9,
|
||||||
|
$10,
|
||||||
|
$11,
|
||||||
|
$12,
|
||||||
|
$13,
|
||||||
|
$14,
|
||||||
|
$15,
|
||||||
|
$16,
|
||||||
|
$17
|
||||||
|
)
|
||||||
`
|
`
|
||||||
|
|
||||||
type AddCallParams struct {
|
type AddCallParams struct {
|
||||||
|
ID uuid.UUID `json:"id"`
|
||||||
Submitter *int32 `json:"submitter"`
|
Submitter *int32 `json:"submitter"`
|
||||||
System int `json:"system"`
|
System int `json:"system"`
|
||||||
Talkgroup int `json:"talkgroup"`
|
Talkgroup int `json:"talkgroup"`
|
||||||
|
@ -94,8 +112,9 @@ type AddCallParams struct {
|
||||||
Source int `json:"source"`
|
Source int `json:"source"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) AddCall(ctx context.Context, arg AddCallParams) (uuid.UUID, error) {
|
func (q *Queries) AddCall(ctx context.Context, arg AddCallParams) error {
|
||||||
row := q.db.QueryRow(ctx, addCall,
|
_, err := q.db.Exec(ctx, addCall,
|
||||||
|
arg.ID,
|
||||||
arg.Submitter,
|
arg.Submitter,
|
||||||
arg.System,
|
arg.System,
|
||||||
arg.Talkgroup,
|
arg.Talkgroup,
|
||||||
|
@ -113,9 +132,7 @@ func (q *Queries) AddCall(ctx context.Context, arg AddCallParams) (uuid.UUID, er
|
||||||
arg.TgGroup,
|
arg.TgGroup,
|
||||||
arg.Source,
|
arg.Source,
|
||||||
)
|
)
|
||||||
var id uuid.UUID
|
return err
|
||||||
err := row.Scan(&id)
|
|
||||||
return id, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDatabaseSize = `-- name: GetDatabaseSize :one
|
const getDatabaseSize = `-- name: GetDatabaseSize :one
|
||||||
|
|
11
pkg/database/extend.go
Normal file
11
pkg/database/extend.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package database
|
||||||
|
|
||||||
|
func (d GetTalkgroupsWithLearnedByPackedIDsRow) GetTalkgroup() Talkgroup { return d.Talkgroup }
|
||||||
|
func (d GetTalkgroupsWithLearnedByPackedIDsRow) GetSystem() System { return d.System }
|
||||||
|
func (d GetTalkgroupsWithLearnedByPackedIDsRow) GetLearned() bool { return d.Learned }
|
||||||
|
func (g GetTalkgroupsWithLearnedRow) GetTalkgroup() Talkgroup { return g.Talkgroup }
|
||||||
|
func (g GetTalkgroupsWithLearnedRow) GetSystem() System { return g.System }
|
||||||
|
func (g GetTalkgroupsWithLearnedRow) GetLearned() bool { return g.Learned }
|
||||||
|
func (g GetTalkgroupsWithLearnedBySystemRow) GetTalkgroup() Talkgroup { return g.Talkgroup }
|
||||||
|
func (g GetTalkgroupsWithLearnedBySystemRow) GetSystem() System { return g.System }
|
||||||
|
func (g GetTalkgroupsWithLearnedBySystemRow) GetLearned() bool { return g.Learned }
|
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
type Querier interface {
|
type Querier interface {
|
||||||
AddAlert(ctx context.Context, arg AddAlertParams) error
|
AddAlert(ctx context.Context, arg AddAlertParams) error
|
||||||
AddCall(ctx context.Context, arg AddCallParams) (uuid.UUID, error)
|
AddCall(ctx context.Context, arg AddCallParams) error
|
||||||
BulkSetTalkgroupTags(ctx context.Context, iD int64, tags []string) 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)
|
||||||
|
@ -26,10 +26,12 @@ type Querier interface {
|
||||||
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, sys int, tg int) ([]string, error)
|
||||||
GetTalkgroupWithLearned(ctx context.Context, systemID int, tgid int) (GetTalkgroupWithLearnedRow, error)
|
GetTalkgroupWithLearned(ctx context.Context, systemID int, tgid int) (GetTalkgroupWithLearnedRow, error)
|
||||||
GetTalkgroupWithLearnedByPackedIDs(ctx context.Context, dollar_1 []int64) ([]GetTalkgroupWithLearnedByPackedIDsRow, error)
|
|
||||||
GetTalkgroupsByPackedIDs(ctx context.Context, dollar_1 []int64) ([]GetTalkgroupsByPackedIDsRow, 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)
|
||||||
|
GetTalkgroupsWithLearnedByPackedIDs(ctx context.Context, dollar_1 []int64) ([]GetTalkgroupsWithLearnedByPackedIDsRow, 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)
|
||||||
|
|
|
@ -151,67 +151,6 @@ func (q *Queries) GetTalkgroupWithLearned(ctx context.Context, systemID int, tgi
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
const getTalkgroupWithLearnedByPackedIDs = `-- name: GetTalkgroupWithLearnedByPackedIDs :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 GetTalkgroupWithLearnedByPackedIDsRow struct {
|
|
||||||
Talkgroup Talkgroup `json:"talkgroup"`
|
|
||||||
System System `json:"system"`
|
|
||||||
Learned bool `json:"learned"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetTalkgroupWithLearnedByPackedIDs(ctx context.Context, dollar_1 []int64) ([]GetTalkgroupWithLearnedByPackedIDsRow, error) {
|
|
||||||
rows, err := q.db.Query(ctx, getTalkgroupWithLearnedByPackedIDs, dollar_1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
var items []GetTalkgroupWithLearnedByPackedIDsRow
|
|
||||||
for rows.Next() {
|
|
||||||
var i GetTalkgroupWithLearnedByPackedIDsRow
|
|
||||||
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 getTalkgroupsByPackedIDs = `-- name: GetTalkgroupsByPackedIDs :many
|
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
|
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 systems sys ON tg.system_id = sys.id
|
||||||
|
@ -342,6 +281,188 @@ func (q *Queries) GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) (
|
||||||
return items, nil
|
return items, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTalkgroupsWithLearned = `-- name: GetTalkgroupsWithLearned :many
|
||||||
|
SELECT
|
||||||
|
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, sys.id, sys.name,
|
||||||
|
FALSE learned
|
||||||
|
FROM talkgroups tg
|
||||||
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
|
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 ignored IS NOT TRUE
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetTalkgroupsWithLearnedRow struct {
|
||||||
|
Talkgroup Talkgroup `json:"talkgroup"`
|
||||||
|
System System `json:"system"`
|
||||||
|
Learned bool `json:"learned"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetTalkgroupsWithLearned(ctx context.Context) ([]GetTalkgroupsWithLearnedRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getTalkgroupsWithLearned)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetTalkgroupsWithLearnedRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetTalkgroupsWithLearnedRow
|
||||||
|
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 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.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 getTalkgroupsWithLearnedBySystem = `-- name: GetTalkgroupsWithLearnedBySystem :many
|
||||||
|
SELECT
|
||||||
|
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, sys.id, sys.name,
|
||||||
|
FALSE learned
|
||||||
|
FROM talkgroups tg
|
||||||
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
|
WHERE tg.system_id = $1
|
||||||
|
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 tgl.system_id = $1 AND ignored IS NOT TRUE
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetTalkgroupsWithLearnedBySystemRow struct {
|
||||||
|
Talkgroup Talkgroup `json:"talkgroup"`
|
||||||
|
System System `json:"system"`
|
||||||
|
Learned bool `json:"learned"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetTalkgroupsWithLearnedBySystem(ctx context.Context, system int32) ([]GetTalkgroupsWithLearnedBySystemRow, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getTalkgroupsWithLearnedBySystem, system)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []GetTalkgroupsWithLearnedBySystemRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i GetTalkgroupsWithLearnedBySystemRow
|
||||||
|
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 setTalkgroupTags = `-- name: SetTalkgroupTags :exec
|
const setTalkgroupTags = `-- name: SetTalkgroupTags :exec
|
||||||
UPDATE talkgroups SET tags = $3
|
UPDATE talkgroups SET tags = $3
|
||||||
WHERE id = systg2id($1, $2)
|
WHERE id = systg2id($1, $2)
|
||||||
|
|
|
@ -43,7 +43,46 @@ JOIN systems sys ON tgl.system_id = sys.id
|
||||||
WHERE tgl.system_id = $1 AND tgl.tgid = $2 AND ignored IS NOT TRUE
|
WHERE tgl.system_id = $1 AND tgl.tgid = $2 AND ignored IS NOT TRUE
|
||||||
`
|
`
|
||||||
|
|
||||||
|
const getTalkgroupsWithLearnedBySystemTest = `-- name: GetTalkgroupsWithLearnedBySystem :many
|
||||||
|
SELECT
|
||||||
|
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, sys.id, sys.name,
|
||||||
|
FALSE learned
|
||||||
|
FROM talkgroups tg
|
||||||
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
|
WHERE tg.system_id = $1
|
||||||
|
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 tg.system_id = $1 AND ignored IS NOT TRUE
|
||||||
|
`
|
||||||
|
|
||||||
|
const getTalkgroupsWithLearnedTest = `-- name: GetTalkgroupsWithLearned :many
|
||||||
|
SELECT
|
||||||
|
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, sys.id, sys.name,
|
||||||
|
FALSE learned
|
||||||
|
FROM talkgroups tg
|
||||||
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
|
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 ignored IS NOT TRUE
|
||||||
|
`
|
||||||
|
|
||||||
func TestQueryColumnsMatch(t *testing.T) {
|
func TestQueryColumnsMatch(t *testing.T) {
|
||||||
require.Equal(t, getTalkgroupWithLearnedByPackedIDsTest, getTalkgroupWithLearnedByPackedIDs)
|
require.Equal(t, getTalkgroupsWithLearnedByPackedIDsTest, getTalkgroupWithLearnedByPackedIDs)
|
||||||
require.Equal(t, getTalkgroupWithLearnedTest, getTalkgroupWithLearned)
|
require.Equal(t, getTalkgroupWithLearnedTest, getTalkgroupWithLearned)
|
||||||
|
require.Equal(t, getTalkgroupsWithLearnedBySystemTest, getTalkgroupsWithLearnedBySystem)
|
||||||
|
require.Equal(t, getTalkgroupsWithLearnedTest, getTalkgroupsWithLearned)
|
||||||
}
|
}
|
||||||
|
|
|
@ -183,5 +183,5 @@ func (conn *wsConn) writeToClient(w io.WriteCloser, msg ToClient) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *wsManager) PrivateRoutes(r chi.Router) {
|
func (n *wsManager) PrivateRoutes(r chi.Router) {
|
||||||
r.HandleFunc("/ws", n.serveWS)
|
r.HandleFunc("/api/ws", n.serveWS)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.33.0
|
// protoc-gen-go v1.33.0
|
||||||
// protoc v5.28.2
|
// protoc v5.28.3
|
||||||
// source: stillbox.proto
|
// source: stillbox.proto
|
||||||
|
|
||||||
package pb
|
package pb
|
||||||
|
@ -288,18 +288,19 @@ type Call struct {
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
|
|
||||||
AudioName string `protobuf:"bytes,1,opt,name=audioName,proto3" json:"audioName,omitempty"`
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
AudioType string `protobuf:"bytes,2,opt,name=audioType,proto3" json:"audioType,omitempty"`
|
AudioName string `protobuf:"bytes,2,opt,name=audioName,proto3" json:"audioName,omitempty"`
|
||||||
DateTime *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=date_time,json=dateTime,proto3" json:"date_time,omitempty"`
|
AudioType string `protobuf:"bytes,3,opt,name=audioType,proto3" json:"audioType,omitempty"`
|
||||||
System int32 `protobuf:"varint,4,opt,name=system,proto3" json:"system,omitempty"`
|
DateTime *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=date_time,json=dateTime,proto3" json:"date_time,omitempty"`
|
||||||
Talkgroup int32 `protobuf:"varint,5,opt,name=talkgroup,proto3" json:"talkgroup,omitempty"`
|
System int32 `protobuf:"varint,5,opt,name=system,proto3" json:"system,omitempty"`
|
||||||
Source int32 `protobuf:"varint,6,opt,name=source,proto3" json:"source,omitempty"`
|
Talkgroup int32 `protobuf:"varint,6,opt,name=talkgroup,proto3" json:"talkgroup,omitempty"`
|
||||||
Frequency int64 `protobuf:"varint,7,opt,name=frequency,proto3" json:"frequency,omitempty"`
|
Source int32 `protobuf:"varint,7,opt,name=source,proto3" json:"source,omitempty"`
|
||||||
Frequencies []int64 `protobuf:"varint,8,rep,packed,name=frequencies,proto3" json:"frequencies,omitempty"`
|
Frequency int64 `protobuf:"varint,8,opt,name=frequency,proto3" json:"frequency,omitempty"`
|
||||||
Patches []int32 `protobuf:"varint,9,rep,packed,name=patches,proto3" json:"patches,omitempty"`
|
Frequencies []int64 `protobuf:"varint,9,rep,packed,name=frequencies,proto3" json:"frequencies,omitempty"`
|
||||||
Sources []int32 `protobuf:"varint,10,rep,packed,name=sources,proto3" json:"sources,omitempty"`
|
Patches []int32 `protobuf:"varint,10,rep,packed,name=patches,proto3" json:"patches,omitempty"`
|
||||||
Duration *int32 `protobuf:"varint,11,opt,name=duration,proto3,oneof" json:"duration,omitempty"`
|
Sources []int32 `protobuf:"varint,11,rep,packed,name=sources,proto3" json:"sources,omitempty"`
|
||||||
Audio []byte `protobuf:"bytes,12,opt,name=audio,proto3" json:"audio,omitempty"`
|
Duration *int32 `protobuf:"varint,12,opt,name=duration,proto3,oneof" json:"duration,omitempty"`
|
||||||
|
Audio []byte `protobuf:"bytes,13,opt,name=audio,proto3" json:"audio,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *Call) Reset() {
|
func (x *Call) Reset() {
|
||||||
|
@ -334,6 +335,13 @@ func (*Call) Descriptor() ([]byte, []int) {
|
||||||
return file_stillbox_proto_rawDescGZIP(), []int{2}
|
return file_stillbox_proto_rawDescGZIP(), []int{2}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *Call) GetId() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Id
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func (x *Call) GetAudioName() string {
|
func (x *Call) GetAudioName() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.AudioName
|
return x.AudioName
|
||||||
|
@ -1187,29 +1195,30 @@ var file_stillbox_proto_rawDesc = []byte{
|
||||||
0x6f, 0x75, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x06, 0x74, 0x67, 0x49, 0x6e, 0x66,
|
0x6f, 0x75, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x48, 0x00, 0x52, 0x06, 0x74, 0x67, 0x49, 0x6e, 0x66,
|
||||||
0x6f, 0x42, 0x12, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x72, 0x65, 0x73,
|
0x6f, 0x42, 0x12, 0x0a, 0x10, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e, 0x64, 0x5f, 0x72, 0x65, 0x73,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x61, 0x6e,
|
||||||
0x64, 0x5f, 0x69, 0x64, 0x22, 0x81, 0x03, 0x0a, 0x04, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x1c, 0x0a,
|
0x64, 0x5f, 0x69, 0x64, 0x22, 0x91, 0x03, 0x0a, 0x04, 0x43, 0x61, 0x6c, 0x6c, 0x12, 0x0e, 0x0a,
|
||||||
0x09, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x1c, 0x0a,
|
||||||
|
0x09, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09,
|
||||||
0x52, 0x09, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61,
|
0x52, 0x09, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61,
|
||||||
0x75, 0x64, 0x69, 0x6f, 0x54, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
|
0x75, 0x64, 0x69, 0x6f, 0x54, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09,
|
||||||
0x61, 0x75, 0x64, 0x69, 0x6f, 0x54, 0x79, 0x70, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x64, 0x61, 0x74,
|
0x61, 0x75, 0x64, 0x69, 0x6f, 0x54, 0x79, 0x70, 0x65, 0x12, 0x37, 0x0a, 0x09, 0x64, 0x61, 0x74,
|
||||||
0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
|
0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67,
|
||||||
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
|
0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54,
|
||||||
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69,
|
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x08, 0x64, 0x61, 0x74, 0x65, 0x54, 0x69,
|
||||||
0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x04, 0x20, 0x01,
|
0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x18, 0x05, 0x20, 0x01,
|
||||||
0x28, 0x05, 0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x61,
|
0x28, 0x05, 0x52, 0x06, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x61,
|
||||||
0x6c, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74,
|
0x6c, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x09, 0x74,
|
||||||
0x61, 0x6c, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72,
|
0x61, 0x6c, 0x6b, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72,
|
||||||
0x63, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
0x63, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65,
|
||||||
0x12, 0x1c, 0x0a, 0x09, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x07, 0x20,
|
0x12, 0x1c, 0x0a, 0x09, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x18, 0x08, 0x20,
|
||||||
0x01, 0x28, 0x03, 0x52, 0x09, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x20,
|
0x01, 0x28, 0x03, 0x52, 0x09, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79, 0x12, 0x20,
|
||||||
0x0a, 0x0b, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x08, 0x20,
|
0x0a, 0x0b, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73, 0x18, 0x09, 0x20,
|
||||||
0x03, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73,
|
0x03, 0x28, 0x03, 0x52, 0x0b, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x69, 0x65, 0x73,
|
||||||
0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28,
|
0x12, 0x18, 0x0a, 0x07, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28,
|
||||||
0x05, 0x52, 0x07, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x6f,
|
0x05, 0x52, 0x07, 0x70, 0x61, 0x74, 0x63, 0x68, 0x65, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x6f,
|
||||||
0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x05, 0x52, 0x07, 0x73, 0x6f, 0x75,
|
0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x05, 0x52, 0x07, 0x73, 0x6f, 0x75,
|
||||||
0x72, 0x63, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
0x72, 0x63, 0x65, 0x73, 0x12, 0x1f, 0x0a, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
0x18, 0x0b, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69,
|
0x18, 0x0c, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x08, 0x64, 0x75, 0x72, 0x61, 0x74, 0x69,
|
||||||
0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x18, 0x0c,
|
0x6f, 0x6e, 0x88, 0x01, 0x01, 0x12, 0x14, 0x0a, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x18, 0x0d,
|
||||||
0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x42, 0x0b, 0x0a, 0x09, 0x5f,
|
0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x61, 0x75, 0x64, 0x69, 0x6f, 0x42, 0x0b, 0x0a, 0x09, 0x5f,
|
||||||
0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3e, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c,
|
0x64, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x3e, 0x0a, 0x05, 0x48, 0x65, 0x6c, 0x6c,
|
||||||
0x6f, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f,
|
0x6f, 0x12, 0x35, 0x0a, 0x0b, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x66, 0x6f,
|
||||||
|
|
|
@ -24,18 +24,19 @@ message CommandResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
message Call {
|
message Call {
|
||||||
string audioName = 1;
|
string id = 1;
|
||||||
string audioType = 2;
|
string audioName = 2;
|
||||||
google.protobuf.Timestamp date_time = 3;
|
string audioType = 3;
|
||||||
int32 system = 4;
|
google.protobuf.Timestamp date_time = 4;
|
||||||
int32 talkgroup = 5;
|
int32 system = 5;
|
||||||
int32 source = 6;
|
int32 talkgroup = 6;
|
||||||
int64 frequency = 7;
|
int32 source = 7;
|
||||||
repeated int64 frequencies = 8;
|
int64 frequency = 8;
|
||||||
repeated int32 patches = 9;
|
repeated int64 frequencies = 9;
|
||||||
repeated int32 sources = 10;
|
repeated int32 patches = 10;
|
||||||
optional int32 duration = 11;
|
repeated int32 sources = 11;
|
||||||
bytes audio = 12;
|
optional int32 duration = 12;
|
||||||
|
bytes audio = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Hello {
|
message Hello {
|
||||||
|
|
|
@ -36,6 +36,7 @@ func (s *Server) setupRoutes() {
|
||||||
s.nex.PrivateRoutes(r)
|
s.nex.PrivateRoutes(r)
|
||||||
s.auth.PrivateRoutes(r)
|
s.auth.PrivateRoutes(r)
|
||||||
s.alerter.PrivateRoutes(r)
|
s.alerter.PrivateRoutes(r)
|
||||||
|
r.Mount("/api", s.api.Subrouter())
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dynatron.me/x/stillbox/pkg/alerting"
|
"dynatron.me/x/stillbox/pkg/alerting"
|
||||||
|
"dynatron.me/x/stillbox/pkg/api"
|
||||||
"dynatron.me/x/stillbox/pkg/auth"
|
"dynatron.me/x/stillbox/pkg/auth"
|
||||||
"dynatron.me/x/stillbox/pkg/config"
|
"dynatron.me/x/stillbox/pkg/config"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
|
@ -36,6 +37,7 @@ type Server struct {
|
||||||
notifier notify.Notifier
|
notifier notify.Notifier
|
||||||
hup chan os.Signal
|
hup chan os.Signal
|
||||||
tgs talkgroups.Store
|
tgs talkgroups.Store
|
||||||
|
api api.API
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, cfg *config.Config) (*Server, error) {
|
func New(ctx context.Context, cfg *config.Config) (*Server, error) {
|
||||||
|
@ -59,6 +61,7 @@ func New(ctx context.Context, cfg *config.Config) (*Server, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
tgCache := talkgroups.NewCache()
|
tgCache := talkgroups.NewCache()
|
||||||
|
api := api.New(tgCache)
|
||||||
|
|
||||||
srv := &Server{
|
srv := &Server{
|
||||||
auth: authenticator,
|
auth: authenticator,
|
||||||
|
@ -70,6 +73,7 @@ func New(ctx context.Context, cfg *config.Config) (*Server, error) {
|
||||||
alerter: alerting.New(cfg.Alerting, tgCache, alerting.WithNotifier(notifier)),
|
alerter: alerting.New(cfg.Alerting, tgCache, alerting.WithNotifier(notifier)),
|
||||||
notifier: notifier,
|
notifier: notifier,
|
||||||
tgs: tgCache,
|
tgs: tgCache,
|
||||||
|
api: api,
|
||||||
}
|
}
|
||||||
|
|
||||||
srv.sinks.Register("database", sinks.NewDatabaseSink(srv.db), true)
|
srv.sinks.Register("database", sinks.NewDatabaseSink(srv.db), true)
|
||||||
|
|
|
@ -26,12 +26,12 @@ func (s *DatabaseSink) Call(ctx context.Context, call *calls.Call) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
dbCall, err := s.db.AddCall(ctx, s.toAddCallParams(call))
|
err := s.db.AddCall(ctx, s.toAddCallParams(call))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("add call: %w", err)
|
return fmt.Errorf("add call: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Str("id", dbCall.String()).Int("system", call.System).Int("tgid", call.Talkgroup).Msg("stored")
|
log.Debug().Str("id", call.ID.String()).Int("system", call.System).Int("tgid", call.Talkgroup).Msg("stored")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -42,6 +42,7 @@ func (s *DatabaseSink) SinkType() string {
|
||||||
|
|
||||||
func (s *DatabaseSink) toAddCallParams(call *calls.Call) database.AddCallParams {
|
func (s *DatabaseSink) toAddCallParams(call *calls.Call) database.AddCallParams {
|
||||||
return database.AddCallParams{
|
return database.AddCallParams{
|
||||||
|
ID: call.ID,
|
||||||
Submitter: call.Submitter.Int32Ptr(),
|
Submitter: call.Submitter.Int32Ptr(),
|
||||||
System: call.System,
|
System: call.System,
|
||||||
Talkgroup: call.Talkgroup,
|
Talkgroup: call.Talkgroup,
|
||||||
|
|
|
@ -15,11 +15,17 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tgMap map[ID]Talkgroup
|
type tgMap map[ID]*Talkgroup
|
||||||
|
|
||||||
type Store interface {
|
type Store interface {
|
||||||
// TG retrieves a Talkgroup from the Store.
|
// TG retrieves a Talkgroup from the Store.
|
||||||
TG(ctx context.Context, tg ID) (Talkgroup, error)
|
TG(ctx context.Context, tg ID) (*Talkgroup, error)
|
||||||
|
|
||||||
|
// TGs retrieves many talkgroups from the Store.
|
||||||
|
TGs(ctx context.Context, tgs IDs) ([]*Talkgroup, error)
|
||||||
|
|
||||||
|
// SystemTGs retrieves all Talkgroups associated with a System.
|
||||||
|
SystemTGs(ctx context.Context, systemID int32) ([]*Talkgroup, error)
|
||||||
|
|
||||||
// SystemName retrieves a system name from the store. It returns the record and whether one was found.
|
// SystemName retrieves a system name from the store. It returns the record and whether one was found.
|
||||||
SystemName(ctx context.Context, id int) (string, bool)
|
SystemName(ctx context.Context, id int) (string, bool)
|
||||||
|
@ -117,7 +123,7 @@ func (t *cache) Hint(ctx context.Context, tgs []ID) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) add(rec Talkgroup) error {
|
func (t *cache) add(rec *Talkgroup) error {
|
||||||
t.Lock()
|
t.Lock()
|
||||||
defer t.Unlock()
|
defer t.Unlock()
|
||||||
|
|
||||||
|
@ -128,16 +134,70 @@ func (t *cache) add(rec Talkgroup) error {
|
||||||
return t.AlertConfig.UnmarshalTGRules(tg, rec.Talkgroup.AlertConfig)
|
return t.AlertConfig.UnmarshalTGRules(tg, rec.Talkgroup.AlertConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rowToTalkgroup(r database.GetTalkgroupWithLearnedByPackedIDsRow) Talkgroup {
|
type row interface {
|
||||||
return Talkgroup{
|
database.GetTalkgroupsWithLearnedByPackedIDsRow | database.GetTalkgroupsWithLearnedRow |
|
||||||
Talkgroup: r.Talkgroup,
|
database.GetTalkgroupsWithLearnedBySystemRow
|
||||||
System: r.System,
|
GetTalkgroup() database.Talkgroup
|
||||||
Learned: r.Learned,
|
GetSystem() database.System
|
||||||
|
GetLearned() bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func rowToTalkgroup[T row](r T) *Talkgroup {
|
||||||
|
return &Talkgroup{
|
||||||
|
Talkgroup: r.GetTalkgroup(),
|
||||||
|
System: r.GetSystem(),
|
||||||
|
Learned: r.GetLearned(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addToRowList[T row](t *cache, r []*Talkgroup, tgRecords []T) ([]*Talkgroup, error) {
|
||||||
|
for _, rec := range tgRecords {
|
||||||
|
tg := rowToTalkgroup(rec)
|
||||||
|
err := t.add(tg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r = append(r, tg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *cache) TGs(ctx context.Context, tgs IDs) ([]*Talkgroup, error) {
|
||||||
|
r := make([]*Talkgroup, 0, len(tgs))
|
||||||
|
var err error
|
||||||
|
if tgs != nil {
|
||||||
|
toGet := make(IDs, 0, len(tgs))
|
||||||
|
t.RLock()
|
||||||
|
for _, id := range tgs {
|
||||||
|
rec, has := t.tgs[id]
|
||||||
|
if has {
|
||||||
|
r = append(r, rec)
|
||||||
|
} else {
|
||||||
|
toGet = append(toGet, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.RUnlock()
|
||||||
|
|
||||||
|
tgRecords, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedByPackedIDs(ctx, toGet.Packed())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return addToRowList(t, r, tgRecords)
|
||||||
|
}
|
||||||
|
|
||||||
|
// get all talkgroups
|
||||||
|
|
||||||
|
tgRecords, err := database.FromCtx(ctx).GetTalkgroupsWithLearned(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return addToRowList(t, r, tgRecords)
|
||||||
|
}
|
||||||
|
|
||||||
func (t *cache) Load(ctx context.Context, tgs []int64) error {
|
func (t *cache) Load(ctx context.Context, tgs []int64) error {
|
||||||
tgRecords, err := database.FromCtx(ctx).GetTalkgroupWithLearnedByPackedIDs(ctx, tgs)
|
tgRecords, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedByPackedIDs(ctx, tgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -168,7 +228,17 @@ func (t *cache) Weight(ctx context.Context, id ID, tm time.Time) float64 {
|
||||||
return float64(m)
|
return float64(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *cache) TG(ctx context.Context, tg ID) (Talkgroup, error) {
|
func (t *cache) SystemTGs(ctx context.Context, systemID int32) ([]*Talkgroup, error) {
|
||||||
|
recs, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedBySystem(ctx, systemID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
r := make([]*Talkgroup, 0, len(recs))
|
||||||
|
return addToRowList(t, r, recs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *cache) TG(ctx context.Context, tg ID) (*Talkgroup, error) {
|
||||||
t.RLock()
|
t.RLock()
|
||||||
rec, has := t.tgs[tg]
|
rec, has := t.tgs[tg]
|
||||||
t.RUnlock()
|
t.RUnlock()
|
||||||
|
@ -177,18 +247,18 @@ func (t *cache) TG(ctx context.Context, tg ID) (Talkgroup, error) {
|
||||||
return rec, nil
|
return rec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
recs, err := database.FromCtx(ctx).GetTalkgroupWithLearnedByPackedIDs(ctx, []int64{tg.Pack()})
|
recs, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedByPackedIDs(ctx, []int64{tg.Pack()})
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
case pgx.ErrNoRows:
|
case pgx.ErrNoRows:
|
||||||
return Talkgroup{}, ErrNotFound
|
return nil, ErrNotFound
|
||||||
default:
|
default:
|
||||||
log.Error().Err(err).Msg("TG() cache add db get")
|
log.Error().Err(err).Msg("TG() cache add db get")
|
||||||
return Talkgroup{}, errors.Join(ErrNotFound, err)
|
return nil, errors.Join(ErrNotFound, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(recs) < 1 {
|
if len(recs) < 1 {
|
||||||
return Talkgroup{}, ErrNotFound
|
return nil, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.add(rowToTalkgroup(recs[0]))
|
err = t.add(rowToTalkgroup(recs[0]))
|
||||||
|
|
|
@ -13,8 +13,19 @@ type Talkgroup struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ID struct {
|
type ID struct {
|
||||||
System uint32
|
System uint32 `json:"sys"`
|
||||||
Talkgroup uint32
|
Talkgroup uint32 `json:"tg"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type IDs []ID
|
||||||
|
|
||||||
|
func (ids *IDs) Packed() []int64 {
|
||||||
|
r := make([]int64, len(*ids))
|
||||||
|
for i := range *ids {
|
||||||
|
r[i] = (*ids)[i].Pack()
|
||||||
|
}
|
||||||
|
|
||||||
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func TG[T int | uint | int64 | uint64 | int32 | uint32](sys, tgid T) ID {
|
func TG[T int | uint | int64 | uint64 | int32 | uint32](sys, tgid T) ID {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
-- name: AddCall :one
|
-- name: AddCall :exec
|
||||||
INSERT INTO calls (
|
INSERT INTO calls (
|
||||||
id,
|
id,
|
||||||
submitter,
|
submitter,
|
||||||
|
@ -17,8 +17,25 @@ INSERT INTO calls (
|
||||||
tg_alpha_tag,
|
tg_alpha_tag,
|
||||||
tg_group,
|
tg_group,
|
||||||
source
|
source
|
||||||
) VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
) VALUES (
|
||||||
RETURNING id;
|
@id,
|
||||||
|
@submitter,
|
||||||
|
@system,
|
||||||
|
@talkgroup,
|
||||||
|
@call_date,
|
||||||
|
@audio_name,
|
||||||
|
@audio_blob,
|
||||||
|
@audio_type,
|
||||||
|
@audio_url,
|
||||||
|
@duration,
|
||||||
|
@frequency,
|
||||||
|
@frequencies,
|
||||||
|
@patches,
|
||||||
|
@tg_label,
|
||||||
|
@tg_alpha_tag,
|
||||||
|
@tg_group,
|
||||||
|
@source
|
||||||
|
);
|
||||||
|
|
||||||
-- name: SetCallTranscript :exec
|
-- name: SetCallTranscript :exec
|
||||||
UPDATE calls SET transcript = $2 WHERE id = $1;
|
UPDATE calls SET transcript = $2 WHERE id = $1;
|
||||||
|
|
|
@ -51,7 +51,7 @@ 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 = sqlc.arg(system_id) AND tgl.tgid = sqlc.arg(tgid) AND ignored IS NOT TRUE;
|
||||||
|
|
||||||
-- name: GetTalkgroupWithLearnedByPackedIDs :many
|
-- name: GetTalkgroupsWithLearnedByPackedIDs :many
|
||||||
SELECT
|
SELECT
|
||||||
sqlc.embed(tg), sqlc.embed(sys),
|
sqlc.embed(tg), sqlc.embed(sys),
|
||||||
FALSE learned
|
FALSE learned
|
||||||
|
@ -69,5 +69,41 @@ FROM talkgroups_learned tgl
|
||||||
JOIN systems sys ON tgl.system_id = sys.id
|
JOIN systems sys ON tgl.system_id = sys.id
|
||||||
WHERE systg2id(tgl.system_id, tgl.tgid) = ANY($1::INT8[]) AND ignored IS NOT TRUE;
|
WHERE systg2id(tgl.system_id, tgl.tgid) = ANY($1::INT8[]) AND ignored IS NOT TRUE;
|
||||||
|
|
||||||
|
-- name: GetTalkgroupsWithLearnedBySystem :many
|
||||||
|
SELECT
|
||||||
|
sqlc.embed(tg), sqlc.embed(sys),
|
||||||
|
FALSE learned
|
||||||
|
FROM talkgroups tg
|
||||||
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
|
WHERE tg.system_id = @system
|
||||||
|
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 tgl.system_id = @system AND ignored IS NOT TRUE;
|
||||||
|
|
||||||
|
-- name: GetTalkgroupsWithLearned :many
|
||||||
|
SELECT
|
||||||
|
sqlc.embed(tg), sqlc.embed(sys),
|
||||||
|
FALSE learned
|
||||||
|
FROM talkgroups tg
|
||||||
|
JOIN systems sys ON tg.system_id = sys.id
|
||||||
|
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 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 = sqlc.arg(system_id);
|
||||||
|
|
Loading…
Reference in a new issue