From 6e1640e4b4b6169d9b25ad2ebc87513f1601eb18 Mon Sep 17 00:00:00 2001 From: Daniel Ponte Date: Sun, 10 Nov 2024 10:13:38 -0500 Subject: [PATCH] PUT talkgroup --- pkg/api/api.go | 25 +++++++----- pkg/api/talkgroups.go | 47 +++++++++------------- pkg/database/extend.go | 3 ++ pkg/database/querier.go | 1 + pkg/database/talkgroups.sql.go | 60 +++++++++++++++++++++++++++++ pkg/talkgroups/cache.go | 30 ++++++++++++++- pkg/talkgroups/talkgroup.go | 7 ++++ sql/postgres/queries/talkgroups.sql | 16 +++++++- 8 files changed, 149 insertions(+), 40 deletions(-) diff --git a/pkg/api/api.go b/pkg/api/api.go index 4e22860..9910d9f 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -34,30 +34,36 @@ func (a *api) Subrouter() http.Handler { return r } -var statusMapping = map[error]int{ - talkgroups.ErrNotFound: http.StatusNotFound, - pgx.ErrNoRows: http.StatusNotFound, +type errResponse struct { + text string + code int } -func httpCode(err error) int { +var statusMapping = map[error]errResponse{ + talkgroups.ErrNotFound: {talkgroups.ErrNotFound.Error(), http.StatusNotFound}, + pgx.ErrNoRows: {"no such record", http.StatusNotFound}, +} + +func httpCode(err error) (string, int) { c, ok := statusMapping[err] if ok { - return c + return c.text, c.code } for e, c := range statusMapping { // check if err wraps an error we know about if errors.Is(err, e) { - return c + return c.text, c.code } } - return http.StatusInternalServerError + return err.Error(), http.StatusInternalServerError } func 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)) + text, code := httpCode(err) + http.Error(w, text, code) return } @@ -66,7 +72,8 @@ func writeResponse(w http.ResponseWriter, r *http.Request, data interface{}, err 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)) + text, code := httpCode(err) + http.Error(w, text, code) return } } diff --git a/pkg/api/talkgroups.go b/pkg/api/talkgroups.go index e16f8f3..e699b78 100644 --- a/pkg/api/talkgroups.go +++ b/pkg/api/talkgroups.go @@ -1,10 +1,11 @@ package api import ( - "fmt" + "encoding/json" "net/http" "dynatron.me/x/stillbox/internal/forms" + "dynatron.me/x/stillbox/pkg/database" "dynatron.me/x/stillbox/pkg/talkgroups" "github.com/go-chi/chi/v5" @@ -80,37 +81,27 @@ func (tga *talkgroupAPI) putTalkgroup(w http.ResponseWriter, r *http.Request) { badReq(w, err) return } - /* - ctx := r.Context() - tgs := talkgroups.StoreFrom(ctx) - tg, err := tgs.TG(ctx, id.ToID()) - switch err { - case nil: - case talkgroups.ErrNotFound: - reqErr(w, err, http.StatusNotFound) - return - default: - reqErr(w, err, http.StatusInternalServerError) - } - */ + ctx := r.Context() + tgs := talkgroups.StoreFrom(ctx) - input := struct { - Name *string `form:"name"` - AlphaTag *string `form:"alpha_tag"` - TgGroup *string `form:"tg_group"` - Frequency *int32 `form:"frequency"` - Metadata []byte `form:"metadata"` - Tags []string `form:"tags"` - Alert *bool `form:"alert"` - AlertConfig []byte `form:"alert_config"` - Weight *float32 `form:"weight"` - }{} + input := database.UpdateTalkgroupParams{} - err = forms.Unmarshal(r, &input, forms.WithAcceptBlank(), forms.WithOmitEmpty()) + err = forms.Unmarshal(r, &input, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty()) if err != nil { - reqErr(w, err, http.StatusBadRequest) + writeResponse(w, r, nil, err) return } - fmt.Fprintf(w, "%+v\n", input) + input.ID = id.ToID().Pack() + + record, err := tgs.UpdateTG(ctx, input) + if err != nil { + writeResponse(w, r, nil, err) + return + } + + err = json.NewEncoder(w).Encode(record) + if err != nil { + writeResponse(w, r, nil, err) + } } diff --git a/pkg/database/extend.go b/pkg/database/extend.go index 3a96cd1..5f165a0 100644 --- a/pkg/database/extend.go +++ b/pkg/database/extend.go @@ -9,3 +9,6 @@ func (g GetTalkgroupsWithLearnedRow) GetLearned() bool { retur func (g GetTalkgroupsWithLearnedBySystemRow) GetTalkgroup() Talkgroup { return g.Talkgroup } func (g GetTalkgroupsWithLearnedBySystemRow) GetSystem() System { return g.System } 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 } diff --git a/pkg/database/querier.go b/pkg/database/querier.go index ba1ace1..6a814c3 100644 --- a/pkg/database/querier.go +++ b/pkg/database/querier.go @@ -39,6 +39,7 @@ type Querier interface { SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error SetTalkgroupTags(ctx context.Context, sys int, tg int, tags []string) error UpdatePassword(ctx context.Context, username string, password string) error + UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams) (Talkgroup, error) } var _ Querier = (*Queries)(nil) diff --git a/pkg/database/talkgroups.sql.go b/pkg/database/talkgroups.sql.go index b32deb3..81d224f 100644 --- a/pkg/database/talkgroups.sql.go +++ b/pkg/database/talkgroups.sql.go @@ -472,3 +472,63 @@ func (q *Queries) SetTalkgroupTags(ctx context.Context, sys int, tg int, tags [] _, err := q.db.Exec(ctx, setTalkgroupTags, sys, tg, tags) return err } + +const updateTalkgroup = `-- name: UpdateTalkgroup :one +UPDATE talkgroups +SET + name = COALESCE($1, name), + alpha_tag = COALESCE($2, alpha_tag), + tg_group = COALESCE($3, tg_group), + frequency = COALESCE($4, frequency), + metadata = COALESCE($5, metadata), + tags = COALESCE($6, tags), + alert = COALESCE($7, alert), + alert_config = COALESCE($8, alert_config), + weight = COALESCE($9, weight) +WHERE id = $10 +RETURNING id, system_id, tgid, name, alpha_tag, tg_group, frequency, metadata, tags, alert, alert_config, weight +` + +type UpdateTalkgroupParams struct { + Name *string `json:"name"` + AlphaTag *string `json:"alpha_tag"` + TgGroup *string `json:"tg_group"` + Frequency *int32 `json:"frequency"` + Metadata []byte `json:"metadata"` + Tags []string `json:"tags"` + Alert *bool `json:"alert"` + AlertConfig []byte `json:"alert_config"` + Weight *float32 `json:"weight"` + ID int64 `json:"id"` +} + +func (q *Queries) UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams) (Talkgroup, error) { + row := q.db.QueryRow(ctx, updateTalkgroup, + arg.Name, + arg.AlphaTag, + arg.TgGroup, + arg.Frequency, + arg.Metadata, + arg.Tags, + arg.Alert, + arg.AlertConfig, + arg.Weight, + arg.ID, + ) + 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, + ) + return i, err +} diff --git a/pkg/talkgroups/cache.go b/pkg/talkgroups/cache.go index 14c2237..7ada361 100644 --- a/pkg/talkgroups/cache.go +++ b/pkg/talkgroups/cache.go @@ -17,7 +17,15 @@ import ( type tgMap map[ID]*Talkgroup +var ( + ErrNotFound = errors.New("talkgroup not found") + ErrNoSuchSystem = errors.New("no such system") +) + type Store interface { + // UpdateTG updates a talkgroup record. + UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*Talkgroup, error) + // TG retrieves a Talkgroup from the Store. TG(ctx context.Context, tg ID) (*Talkgroup, error) @@ -213,8 +221,6 @@ func (t *cache) Load(ctx context.Context, tgs []int64) error { return nil } -var ErrNotFound = errors.New("talkgroup not found") - func (t *cache) Weight(ctx context.Context, id ID, tm time.Time) float64 { tg, err := t.TG(ctx, id) if err != nil { @@ -290,3 +296,23 @@ func (t *cache) SystemName(ctx context.Context, id int) (name string, has bool) return n, has } + +func (t *cache) UpdateTG(ctx context.Context, input database.UpdateTalkgroupParams) (*Talkgroup, error) { + sysName, has := t.SystemName(ctx, int(Unpack(input.ID).System)) + if !has { + return nil, ErrNoSuchSystem + } + + tg, err := database.FromCtx(ctx).UpdateTalkgroup(ctx, input) + if err != nil { + return nil, err + } + + record := &Talkgroup{ + Talkgroup: tg, + System: database.System{ID: int(tg.SystemID), Name: sysName}, + } + t.add(record) + + return record, nil +} diff --git a/pkg/talkgroups/talkgroup.go b/pkg/talkgroups/talkgroup.go index 288e488..e3cca71 100644 --- a/pkg/talkgroups/talkgroup.go +++ b/pkg/talkgroups/talkgroup.go @@ -49,6 +49,13 @@ func (t ID) Pack() int64 { 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 { return fmt.Sprintf("%d:%d", t.System, t.Talkgroup) diff --git a/sql/postgres/queries/talkgroups.sql b/sql/postgres/queries/talkgroups.sql index d201d0e..732a848 100644 --- a/sql/postgres/queries/talkgroups.sql +++ b/sql/postgres/queries/talkgroups.sql @@ -104,6 +104,20 @@ FROM talkgroups_learned tgl JOIN systems sys ON tgl.system_id = sys.id WHERE ignored IS NOT TRUE; - -- name: GetSystemName :one SELECT name FROM systems WHERE id = sqlc.arg(system_id); + +-- name: UpdateTalkgroup :one +UPDATE talkgroups +SET + name = COALESCE(sqlc.narg('name'), name), + alpha_tag = COALESCE(sqlc.narg('alpha_tag'), alpha_tag), + tg_group = COALESCE(sqlc.narg('tg_group'), tg_group), + frequency = COALESCE(sqlc.narg('frequency'), frequency), + metadata = COALESCE(sqlc.narg('metadata'), metadata), + tags = COALESCE(sqlc.narg('tags'), tags), + alert = COALESCE(sqlc.narg('alert'), alert), + alert_config = COALESCE(sqlc.narg('alert_config'), alert_config), + weight = COALESCE(sqlc.narg('weight'), weight) +WHERE id = @id +RETURNING *;