wip pre-batch

This commit is contained in:
Daniel 2024-11-20 07:26:59 -05:00
parent 4f63b4b26c
commit f909723f7d
8 changed files with 277 additions and 10 deletions

View file

@ -1771,6 +1771,63 @@ func (_c *Store_UpdateTalkgroup_Call) RunAndReturn(run func(context.Context, dat
return _c return _c
} }
// UpsertTalkgroup provides a mock function with given fields: ctx, arg
func (_m *Store) UpsertTalkgroup(ctx context.Context, arg database.UpsertTalkgroupParams) (database.Talkgroup, error) {
ret := _m.Called(ctx, arg)
if len(ret) == 0 {
panic("no return value specified for UpsertTalkgroup")
}
var r0 database.Talkgroup
var r1 error
if rf, ok := ret.Get(0).(func(context.Context, database.UpsertTalkgroupParams) (database.Talkgroup, error)); ok {
return rf(ctx, arg)
}
if rf, ok := ret.Get(0).(func(context.Context, database.UpsertTalkgroupParams) database.Talkgroup); ok {
r0 = rf(ctx, arg)
} else {
r0 = ret.Get(0).(database.Talkgroup)
}
if rf, ok := ret.Get(1).(func(context.Context, database.UpsertTalkgroupParams) error); ok {
r1 = rf(ctx, arg)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// Store_UpsertTalkgroup_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpsertTalkgroup'
type Store_UpsertTalkgroup_Call struct {
*mock.Call
}
// UpsertTalkgroup is a helper method to define mock.On call
// - ctx context.Context
// - arg database.UpsertTalkgroupParams
func (_e *Store_Expecter) UpsertTalkgroup(ctx interface{}, arg interface{}) *Store_UpsertTalkgroup_Call {
return &Store_UpsertTalkgroup_Call{Call: _e.mock.On("UpsertTalkgroup", ctx, arg)}
}
func (_c *Store_UpsertTalkgroup_Call) Run(run func(ctx context.Context, arg database.UpsertTalkgroupParams)) *Store_UpsertTalkgroup_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context), args[1].(database.UpsertTalkgroupParams))
})
return _c
}
func (_c *Store_UpsertTalkgroup_Call) Return(_a0 database.Talkgroup, _a1 error) *Store_UpsertTalkgroup_Call {
_c.Call.Return(_a0, _a1)
return _c
}
func (_c *Store_UpsertTalkgroup_Call) RunAndReturn(run func(context.Context, database.UpsertTalkgroupParams) (database.Talkgroup, error)) *Store_UpsertTalkgroup_Call {
_c.Call.Return(run)
return _c
}
// NewStore creates a new instance of Store. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // NewStore creates a new instance of Store. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
// The first argument is typically a *testing.T value. // The first argument is typically a *testing.T value.
func NewStore(t interface { func NewStore(t interface {

View file

@ -39,6 +39,7 @@ type Querier interface {
SetTalkgroupTags(ctx context.Context, tags []string, systemID int32, tGID int32) 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)
UpsertTalkgroup(ctx context.Context, arg UpsertTalkgroupParams) (Talkgroup, error)
} }
var _ Querier = (*Queries)(nil) var _ Querier = (*Queries)(nil)

View file

@ -57,7 +57,7 @@ INSERT INTO talkgroups (
) VALUES( ) VALUES(
$1, $1,
$2, $2,
't' TRUE
) )
` `
@ -419,8 +419,9 @@ SET
tags = COALESCE($6, tags), tags = COALESCE($6, tags),
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 OR (system_id = $11 AND tgid = $12) learned = COALESCE($10, learned)
WHERE id = $11 OR (system_id = $12 AND tgid = $13)
RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned
` `
@ -434,6 +435,7 @@ type UpdateTalkgroupParams struct {
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"`
Learned *bool `json:"learned"`
ID *int32 `json:"id"` ID *int32 `json:"id"`
SystemID *int32 `json:"system_id"` SystemID *int32 `json:"system_id"`
TGID *int32 `json:"tgid"` TGID *int32 `json:"tgid"`
@ -450,6 +452,7 @@ func (q *Queries) UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams
arg.Alert, arg.Alert,
arg.AlertConfig, arg.AlertConfig,
arg.Weight, arg.Weight,
arg.Learned,
arg.ID, arg.ID,
arg.SystemID, arg.SystemID,
arg.TGID, arg.TGID,
@ -472,3 +475,84 @@ func (q *Queries) UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams
) )
return i, err return i, err
} }
const upsertTalkgroup = `-- name: UpsertTalkgroup :one
INSERT INTO talkgroups AS tg (
system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned
) VALUES (
$1,
$2,
$3,
$4,
$5,
$6,
$7,
$8,
$9,
$10,
$11,
$12
)
ON CONFLICT (system_id, tgid) DO UPDATE
SET
name = COALESCE($3, tg.name),
alpha_tag = COALESCE($4, tg.alpha_tag),
tg_group = COALESCE($5, tg.tg_group),
frequency = COALESCE($6, tg.frequency),
metadata = COALESCE($7, tg.metadata),
tags = COALESCE($8, tg.tags),
alert = COALESCE($9, tg.alert),
alert_config = COALESCE($10, tg.alert_config),
weight = COALESCE($11, tg.weight),
learned = COALESCE($12, tg.learned)
RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned
`
type UpsertTalkgroupParams struct {
SystemID int32 `json:"system_id"`
TGID int32 `json:"tg_id"`
Name *string `json:"name"`
AlphaTag *string `json:"alpha_tag"`
TGGroup *string `json:"tg_group"`
Frequency *int32 `json:"frequency"`
Metadata jsontypes.Metadata `json:"metadata"`
Tags []string `json:"tags"`
Alert *bool `json:"alert"`
AlertConfig rules.AlertRules `json:"alert_config"`
Weight *float32 `json:"weight"`
Learned *bool `json:"learned"`
}
func (q *Queries) UpsertTalkgroup(ctx context.Context, arg UpsertTalkgroupParams) (Talkgroup, error) {
row := q.db.QueryRow(ctx, upsertTalkgroup,
arg.SystemID,
arg.TGID,
arg.Name,
arg.AlphaTag,
arg.TGGroup,
arg.Frequency,
arg.Metadata,
arg.Tags,
arg.Alert,
arg.AlertConfig,
arg.Weight,
arg.Learned,
)
var i Talkgroup
err := row.Scan(
&i.ID,
&i.SystemID,
&i.TGID,
&i.Name,
&i.AlphaTag,
&i.TGGroup,
&i.Frequency,
&i.Metadata,
&i.Tags,
&i.Alert,
&i.AlertConfig,
&i.Weight,
&i.Learned,
)
return i, err
}

View file

@ -42,7 +42,6 @@ type errResponse struct {
func (e *errResponse) Render(w http.ResponseWriter, r *http.Request) error { func (e *errResponse) Render(w http.ResponseWriter, r *http.Request) error {
switch e.Code { switch e.Code {
case http.StatusNotFound:
default: default:
log.Error().Str("path", r.URL.Path).Err(e.Err).Int("code", e.Code).Str("msg", e.Error).Msg("request failed") log.Error().Str("path", r.URL.Path).Err(e.Err).Int("code", e.Code).Str("msg", e.Error).Msg("request failed")
} }
@ -71,7 +70,7 @@ func recordNotFound(err error) render.Renderer {
func internalError(err error) render.Renderer { func internalError(err error) render.Renderer {
return &errResponse{ return &errResponse{
Err: err, Err: err,
Code: http.StatusNotFound, Code: http.StatusInternalServerError,
Error: "Internal server error", Error: "Internal server error",
} }
} }

View file

@ -17,9 +17,10 @@ type talkgroupAPI struct {
func (tga *talkgroupAPI) Subrouter() http.Handler { func (tga *talkgroupAPI) Subrouter() http.Handler {
r := chi.NewMux() r := chi.NewMux()
r.Get("/{system:\\d+}/{id:\\d+}", tga.get) r.Get(`/{system:\d+}/{id:\d+}`, tga.get)
r.Put("/{system:\\d+}/{id:\\d+}", tga.put) r.Put(`/{system:\d+}/{id:\d+}`, tga.put)
r.Get("/{system:\\d+}/", tga.get) r.Put(`/{system:\d+}`, tga.putTalkgroups);
r.Get(`/{system:\d+}/`, tga.get)
r.Get("/", tga.get) r.Get("/", tga.get)
r.Post("/import", tga.tgImport) r.Post("/import", tga.tgImport)
@ -50,6 +51,7 @@ func (t tgParams) ToID() talkgroups.ID {
} }
} }
func (tga *talkgroupAPI) get(w http.ResponseWriter, r *http.Request) { func (tga *talkgroupAPI) get(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
tgs := talkgroups.StoreFrom(ctx) tgs := talkgroups.StoreFrom(ctx)
@ -99,6 +101,8 @@ func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) {
return return
} }
input.Learned = nil // ignore for this call
record, err := tgs.UpdateTG(ctx, input) record, err := tgs.UpdateTG(ctx, input)
if err != nil { if err != nil {
wErr(w, r, autoError(err)) wErr(w, r, autoError(err))
@ -123,3 +127,36 @@ func (tga *talkgroupAPI) tgImport(w http.ResponseWriter, r *http.Request) {
respond(w, r, recs) respond(w, r, recs)
} }
func (tga *talkgroupAPI) putTalkgroups(w http.ResponseWriter, r *http.Request) {
var id tgParams
err := decodeParams(&id, r)
if err != nil {
wErr(w, r, badRequest(err))
return
}
if id.System == nil { // don't think this would ever happen
wErr(w, r, badRequest(talkgroups.ErrNoSuchSystem))
return
}
ctx := r.Context()
tgs := talkgroups.StoreFrom(ctx)
var input []database.UpsertTalkgroupParams
err = forms.Unmarshal(r, &input, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
if err != nil {
wErr(w, r, badRequest(err))
return
}
record, err := tgs.UpsertTGs(ctx, *id.System, input)
if err != nil {
wErr(w, r, autoError(err))
return
}
respond(w, r, record)
}

View file

@ -110,6 +110,7 @@ func (rr *radioReferenceImporter) importTalkgroups(ctx context.Context, sys int,
gn := groupName // must take a copy gn := groupName // must take a copy
tgs = append(tgs, talkgroups.Talkgroup{ tgs = append(tgs, talkgroups.Talkgroup{
Talkgroup: database.Talkgroup{ Talkgroup: database.Talkgroup{
ID: len(tgs), // need unique ID for the UI to track
TGID: int32(tgt.Talkgroup), TGID: int32(tgt.Talkgroup),
SystemID: int32(tgt.System), SystemID: int32(tgt.System),
Name: &fields[4], Name: &fields[4],

View file

@ -3,9 +3,11 @@ package talkgroups
import ( import (
"context" "context"
"errors" "errors"
"strings"
"sync" "sync"
"time" "time"
"dynatron.me/x/stillbox/internal/common"
"dynatron.me/x/stillbox/pkg/config" "dynatron.me/x/stillbox/pkg/config"
"dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/database"
@ -24,6 +26,9 @@ type Store interface {
// UpdateTG updates a talkgroup record. // UpdateTG updates a talkgroup record.
UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*Talkgroup, error) UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*Talkgroup, error)
// UpsertTGs upserts a slice of talkgroups.
UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*Talkgroup, error)
// TG retrieves a Talkgroup from the Store. // TG retrieves a Talkgroup from the Store.
TG(ctx context.Context, tg ID) (*Talkgroup, error) TG(ctx context.Context, tg ID) (*Talkgroup, error)
@ -305,3 +310,54 @@ func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupPara
return record, nil return record, nil
} }
func (t *cache) UpsertTGs(ctx context.Context, system int, input []database.UpsertTalkgroupParams) ([]*Talkgroup, error) {
db := database.FromCtx(ctx)
sysName, hasSys := t.SystemName(ctx, system)
if !hasSys {
return nil, ErrNoSuchSystem
}
sys := database.System{
ID: system,
Name: sysName,
}
tgs := make([]*Talkgroup, 0, len(input))
err := db.InTx(ctx, func(db database.Store) error {
for _, tgu := range input {
// normalize tags
for i, tag := range tgu.Tags {
tgu.Tags[i] = strings.ToLower(tag)
}
tgu.SystemID = int32(system)
tgu.Learned = common.PtrTo(false)
tg, err := db.UpsertTalkgroup(ctx, tgu)
if err != nil {
return err
}
tgs = append(tgs, &Talkgroup{
Talkgroup: tg,
System: sys,
Learned: tg.Learned,
})
}
return nil
}, pgx.TxOptions{})
if err != nil {
return nil, err
}
// update the cache
t.Lock()
defer t.Unlock()
for _, tg := range tgs {
t.tgs[TG(tg.SystemID, tg.TGID)] = tg
}
return tgs, nil
}

View file

@ -86,10 +86,42 @@ SET
tags = COALESCE(sqlc.narg('tags'), tags), tags = COALESCE(sqlc.narg('tags'), tags),
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),
learned = COALESCE(sqlc.narg('learned'), learned)
WHERE id = sqlc.narg('id') OR (system_id = sqlc.narg('system_id') AND tgid = sqlc.narg('tgid')) WHERE id = sqlc.narg('id') OR (system_id = sqlc.narg('system_id') AND tgid = sqlc.narg('tgid'))
RETURNING *; RETURNING *;
-- name: UpsertTalkgroup :one
INSERT INTO talkgroups AS tg (
system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight, learned
) VALUES (
@system_id,
@tg_id,
sqlc.narg('name'),
sqlc.narg('alpha_tag'),
sqlc.narg('tg_group'),
sqlc.narg('frequency'),
sqlc.narg('metadata'),
sqlc.narg('tags'),
sqlc.narg('alert'),
sqlc.narg('alert_config'),
sqlc.narg('weight'),
sqlc.narg('learned')
)
ON CONFLICT (system_id, tgid) DO UPDATE
SET
name = COALESCE(sqlc.narg('name'), tg.name),
alpha_tag = COALESCE(sqlc.narg('alpha_tag'), tg.alpha_tag),
tg_group = COALESCE(sqlc.narg('tg_group'), tg.tg_group),
frequency = COALESCE(sqlc.narg('frequency'), tg.frequency),
metadata = COALESCE(sqlc.narg('metadata'), tg.metadata),
tags = COALESCE(sqlc.narg('tags'), tg.tags),
alert = COALESCE(sqlc.narg('alert'), tg.alert),
alert_config = COALESCE(sqlc.narg('alert_config'), tg.alert_config),
weight = COALESCE(sqlc.narg('weight'), tg.weight),
learned = COALESCE(sqlc.narg('learned'), tg.learned)
RETURNING *;
-- name: AddTalkgroupWithLearnedFlag :exec -- name: AddTalkgroupWithLearnedFlag :exec
INSERT INTO talkgroups ( INSERT INTO talkgroups (
system_id, system_id,
@ -98,7 +130,7 @@ INSERT INTO talkgroups (
) VALUES( ) VALUES(
@system_id, @system_id,
@tgid, @tgid,
't' TRUE
); );
-- name: AddLearnedTalkgroup :one -- name: AddLearnedTalkgroup :one