Merge pull request 'Pagination' (#49) from paginateTG into trunk
Reviewed-on: #49
This commit is contained in:
commit
80c06919f8
15 changed files with 491 additions and 37 deletions
|
@ -36,7 +36,7 @@ func PtrTo[T any](t T) *T {
|
|||
return &t
|
||||
}
|
||||
|
||||
func PtrOrNull[T comparable](val T) *T {
|
||||
func NilIfZero[T comparable](val T) *T {
|
||||
var zero T
|
||||
if val == zero {
|
||||
return nil
|
||||
|
@ -45,7 +45,7 @@ func PtrOrNull[T comparable](val T) *T {
|
|||
return &val
|
||||
}
|
||||
|
||||
func ZeroOr[T any](v *T) T {
|
||||
func ZeroIfNil[T any](v *T) T {
|
||||
var zero T
|
||||
if v == nil {
|
||||
return zero
|
||||
|
@ -53,3 +53,16 @@ func ZeroOr[T any](v *T) T {
|
|||
|
||||
return *v
|
||||
}
|
||||
|
||||
func DefaultIfNilOrZero[T comparable](v *T, def T) T {
|
||||
if v == nil {
|
||||
return def
|
||||
}
|
||||
|
||||
var zero T
|
||||
if *v == zero {
|
||||
return def
|
||||
}
|
||||
|
||||
return *v
|
||||
}
|
||||
|
|
8
internal/forms/testdata/urlenc3.http
vendored
Normal file
8
internal/forms/testdata/urlenc3.http
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
POST /api/talkgroup/ HTTP/1.1
|
||||
Host: xenon:3051
|
||||
User-Agent: curl/8.10.1
|
||||
Accept: */*
|
||||
Content-Length: 16
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
page=1&perPage=2&orderBy=id
|
|
@ -223,9 +223,17 @@ func (o *options) unmIterFields(r *http.Request, destStruct reflect.Value) error
|
|||
}
|
||||
destFieldVal.Set(reflect.ValueOf(ar))
|
||||
default:
|
||||
dvt := destFieldVal.Type()
|
||||
if dvt.Kind() == reflect.Ptr {
|
||||
dvt = dvt.Elem()
|
||||
}
|
||||
if reflect.ValueOf(ff).CanConvert(dvt) {
|
||||
setVal(destFieldVal, ff != "" || o.acceptBlank, ff)
|
||||
} else {
|
||||
panic(fmt.Errorf("unsupported type %T", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
|
||||
"dynatron.me/x/stillbox/pkg/alerting"
|
||||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
@ -115,6 +116,14 @@ var (
|
|||
TalkgroupGroup: "Wide Area",
|
||||
TalkgroupLabel: "Wide Area 1 FD/EMS Intercity",
|
||||
}
|
||||
|
||||
Pag1 = tgstore.Pagination{
|
||||
Pagination: common.Pagination{
|
||||
Page: common.PtrTo(1),
|
||||
PerPage: common.PtrTo(2),
|
||||
},
|
||||
OrderBy: common.PtrTo(tgstore.TGOrderID),
|
||||
}
|
||||
)
|
||||
|
||||
func makeRequest(fixture string) *http.Request {
|
||||
|
@ -222,6 +231,13 @@ func TestUnmarshal(t *testing.T) {
|
|||
expect: realSim,
|
||||
opts: []forms.Option{forms.WithAcceptBlank(), forms.WithParseLocalTime()},
|
||||
},
|
||||
{
|
||||
name: "urlencode pagination",
|
||||
r: makeRequest("urlenc3.http"),
|
||||
dest: &tgstore.Pagination{},
|
||||
expect: &Pag1,
|
||||
opts: []forms.Option{forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty()},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
|
|
|
@ -13,9 +13,16 @@ func (g GetTalkgroupWithLearnedRow) GetLearned() bool { return g
|
|||
func (g GetTalkgroupsWithLearnedRow) GetTalkgroup() Talkgroup { return g.Talkgroup }
|
||||
func (g GetTalkgroupsWithLearnedRow) GetSystem() System { return g.System }
|
||||
func (g GetTalkgroupsWithLearnedRow) GetLearned() bool { return g.Talkgroup.Learned }
|
||||
func (g GetTalkgroupsWithLearnedPRow) GetTalkgroup() Talkgroup { return g.Talkgroup }
|
||||
func (g GetTalkgroupsWithLearnedPRow) GetSystem() System { return g.System }
|
||||
func (g GetTalkgroupsWithLearnedPRow) GetLearned() bool { return g.Talkgroup.Learned }
|
||||
func (g GetTalkgroupsWithLearnedBySystemRow) GetTalkgroup() Talkgroup { return g.Talkgroup }
|
||||
func (g GetTalkgroupsWithLearnedBySystemRow) GetSystem() System { return g.System }
|
||||
func (g GetTalkgroupsWithLearnedBySystemRow) GetLearned() bool { return g.Talkgroup.Learned }
|
||||
|
||||
func (g GetTalkgroupsWithLearnedBySystemPRow) GetTalkgroup() Talkgroup { return g.Talkgroup }
|
||||
func (g GetTalkgroupsWithLearnedBySystemPRow) GetSystem() System { return g.System }
|
||||
func (g GetTalkgroupsWithLearnedBySystemPRow) GetLearned() bool { return g.Talkgroup.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 }
|
||||
|
|
|
@ -1244,6 +1244,127 @@ func (_c *Store_GetTalkgroupsWithLearnedBySystem_Call) RunAndReturn(run func(con
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetTalkgroupsWithLearnedBySystemP provides a mock function with given fields: ctx, system, offset, perPage
|
||||
func (_m *Store) GetTalkgroupsWithLearnedBySystemP(ctx context.Context, system int32, offset int32, perPage int32) ([]database.GetTalkgroupsWithLearnedBySystemPRow, error) {
|
||||
ret := _m.Called(ctx, system, offset, perPage)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetTalkgroupsWithLearnedBySystemP")
|
||||
}
|
||||
|
||||
var r0 []database.GetTalkgroupsWithLearnedBySystemPRow
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int32, int32, int32) ([]database.GetTalkgroupsWithLearnedBySystemPRow, error)); ok {
|
||||
return rf(ctx, system, offset, perPage)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int32, int32, int32) []database.GetTalkgroupsWithLearnedBySystemPRow); ok {
|
||||
r0 = rf(ctx, system, offset, perPage)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]database.GetTalkgroupsWithLearnedBySystemPRow)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int32, int32, int32) error); ok {
|
||||
r1 = rf(ctx, system, offset, perPage)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_GetTalkgroupsWithLearnedBySystemP_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTalkgroupsWithLearnedBySystemP'
|
||||
type Store_GetTalkgroupsWithLearnedBySystemP_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetTalkgroupsWithLearnedBySystemP is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - system int32
|
||||
// - offset int32
|
||||
// - perPage int32
|
||||
func (_e *Store_Expecter) GetTalkgroupsWithLearnedBySystemP(ctx interface{}, system interface{}, offset interface{}, perPage interface{}) *Store_GetTalkgroupsWithLearnedBySystemP_Call {
|
||||
return &Store_GetTalkgroupsWithLearnedBySystemP_Call{Call: _e.mock.On("GetTalkgroupsWithLearnedBySystemP", ctx, system, offset, perPage)}
|
||||
}
|
||||
|
||||
func (_c *Store_GetTalkgroupsWithLearnedBySystemP_Call) Run(run func(ctx context.Context, system int32, offset int32, perPage int32)) *Store_GetTalkgroupsWithLearnedBySystemP_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(int32), args[2].(int32), args[3].(int32))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_GetTalkgroupsWithLearnedBySystemP_Call) Return(_a0 []database.GetTalkgroupsWithLearnedBySystemPRow, _a1 error) *Store_GetTalkgroupsWithLearnedBySystemP_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_GetTalkgroupsWithLearnedBySystemP_Call) RunAndReturn(run func(context.Context, int32, int32, int32) ([]database.GetTalkgroupsWithLearnedBySystemPRow, error)) *Store_GetTalkgroupsWithLearnedBySystemP_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetTalkgroupsWithLearnedP provides a mock function with given fields: ctx, offset, perPage
|
||||
func (_m *Store) GetTalkgroupsWithLearnedP(ctx context.Context, offset int32, perPage int32) ([]database.GetTalkgroupsWithLearnedPRow, error) {
|
||||
ret := _m.Called(ctx, offset, perPage)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetTalkgroupsWithLearnedP")
|
||||
}
|
||||
|
||||
var r0 []database.GetTalkgroupsWithLearnedPRow
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int32, int32) ([]database.GetTalkgroupsWithLearnedPRow, error)); ok {
|
||||
return rf(ctx, offset, perPage)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, int32, int32) []database.GetTalkgroupsWithLearnedPRow); ok {
|
||||
r0 = rf(ctx, offset, perPage)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]database.GetTalkgroupsWithLearnedPRow)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, int32, int32) error); ok {
|
||||
r1 = rf(ctx, offset, perPage)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_GetTalkgroupsWithLearnedP_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetTalkgroupsWithLearnedP'
|
||||
type Store_GetTalkgroupsWithLearnedP_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetTalkgroupsWithLearnedP is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - offset int32
|
||||
// - perPage int32
|
||||
func (_e *Store_Expecter) GetTalkgroupsWithLearnedP(ctx interface{}, offset interface{}, perPage interface{}) *Store_GetTalkgroupsWithLearnedP_Call {
|
||||
return &Store_GetTalkgroupsWithLearnedP_Call{Call: _e.mock.On("GetTalkgroupsWithLearnedP", ctx, offset, perPage)}
|
||||
}
|
||||
|
||||
func (_c *Store_GetTalkgroupsWithLearnedP_Call) Run(run func(ctx context.Context, offset int32, perPage int32)) *Store_GetTalkgroupsWithLearnedP_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(int32), args[2].(int32))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_GetTalkgroupsWithLearnedP_Call) Return(_a0 []database.GetTalkgroupsWithLearnedPRow, _a1 error) *Store_GetTalkgroupsWithLearnedP_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_GetTalkgroupsWithLearnedP_Call) RunAndReturn(run func(context.Context, int32, int32) ([]database.GetTalkgroupsWithLearnedPRow, error)) *Store_GetTalkgroupsWithLearnedP_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetUserByID provides a mock function with given fields: ctx, id
|
||||
func (_m *Store) GetUserByID(ctx context.Context, id int) (database.User, error) {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
|
|
@ -30,6 +30,8 @@ type Querier interface {
|
|||
GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) ([]GetTalkgroupsWithAnyTagsRow, error)
|
||||
GetTalkgroupsWithLearned(ctx context.Context) ([]GetTalkgroupsWithLearnedRow, error)
|
||||
GetTalkgroupsWithLearnedBySystem(ctx context.Context, system int32) ([]GetTalkgroupsWithLearnedBySystemRow, error)
|
||||
GetTalkgroupsWithLearnedBySystemP(ctx context.Context, system int32, offset int32, perPage int32) ([]GetTalkgroupsWithLearnedBySystemPRow, error)
|
||||
GetTalkgroupsWithLearnedP(ctx context.Context, offset int32, perPage int32) ([]GetTalkgroupsWithLearnedPRow, error)
|
||||
GetUserByID(ctx context.Context, id int) (User, error)
|
||||
GetUserByUID(ctx context.Context, id int) (User, error)
|
||||
GetUserByUsername(ctx context.Context, username string) (User, error)
|
||||
|
|
|
@ -377,6 +377,112 @@ func (q *Queries) GetTalkgroupsWithLearnedBySystem(ctx context.Context, system i
|
|||
return items, nil
|
||||
}
|
||||
|
||||
const getTalkgroupsWithLearnedBySystemP = `-- name: GetTalkgroupsWithLearnedBySystemP :many
|
||||
SELECT
|
||||
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, tg.ignored, sys.id, sys.name
|
||||
FROM talkgroups tg
|
||||
JOIN systems sys ON tg.system_id = sys.id
|
||||
WHERE tg.system_id = $1
|
||||
ORDER BY tg.system_id ASC, tg.tgid ASC
|
||||
OFFSET $2 ROWS
|
||||
FETCH NEXT $3 ROWS ONLY
|
||||
`
|
||||
|
||||
type GetTalkgroupsWithLearnedBySystemPRow struct {
|
||||
Talkgroup Talkgroup `json:"talkgroup"`
|
||||
System System `json:"system"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTalkgroupsWithLearnedBySystemP(ctx context.Context, system int32, offset int32, perPage int32) ([]GetTalkgroupsWithLearnedBySystemPRow, error) {
|
||||
rows, err := q.db.Query(ctx, getTalkgroupsWithLearnedBySystemP, system, offset, perPage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetTalkgroupsWithLearnedBySystemPRow
|
||||
for rows.Next() {
|
||||
var i GetTalkgroupsWithLearnedBySystemPRow
|
||||
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.Talkgroup.Learned,
|
||||
&i.Talkgroup.Ignored,
|
||||
&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 getTalkgroupsWithLearnedP = `-- name: GetTalkgroupsWithLearnedP :many
|
||||
SELECT
|
||||
tg.id, tg.system_id, tg.tgid, tg.name, tg.alpha_tag, tg.tg_group, tg.frequency, tg.metadata, tg.tags, tg.alert, tg.alert_config, tg.weight, tg.learned, tg.ignored, sys.id, sys.name
|
||||
FROM talkgroups tg
|
||||
JOIN systems sys ON tg.system_id = sys.id
|
||||
WHERE ignored IS NOT TRUE
|
||||
ORDER BY tg.system_id ASC, tg.tgid ASC
|
||||
OFFSET $1 ROWS
|
||||
FETCH NEXT $2 ROWS ONLY
|
||||
`
|
||||
|
||||
type GetTalkgroupsWithLearnedPRow struct {
|
||||
Talkgroup Talkgroup `json:"talkgroup"`
|
||||
System System `json:"system"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetTalkgroupsWithLearnedP(ctx context.Context, offset int32, perPage int32) ([]GetTalkgroupsWithLearnedPRow, error) {
|
||||
rows, err := q.db.Query(ctx, getTalkgroupsWithLearnedP, offset, perPage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetTalkgroupsWithLearnedPRow
|
||||
for rows.Next() {
|
||||
var i GetTalkgroupsWithLearnedPRow
|
||||
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.Talkgroup.Learned,
|
||||
&i.Talkgroup.Ignored,
|
||||
&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 restoreTalkgroupVersion = `-- name: RestoreTalkgroupVersion :one
|
||||
INSERT INTO talkgroups(
|
||||
system_id,
|
||||
|
|
|
@ -59,6 +59,14 @@ func badRequest(err error) render.Renderer {
|
|||
}
|
||||
}
|
||||
|
||||
func badRequestErrText(err error) render.Renderer {
|
||||
return &errResponse{
|
||||
Err: err,
|
||||
Code: http.StatusBadRequest,
|
||||
Error: "Bad request: " + err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func recordNotFound(err error) render.Renderer {
|
||||
return &errResponse{
|
||||
Err: err,
|
||||
|
@ -67,7 +75,7 @@ func recordNotFound(err error) render.Renderer {
|
|||
}
|
||||
}
|
||||
|
||||
func errTextNotFound(err error) render.Renderer {
|
||||
func notFoundErrText(err error) render.Renderer {
|
||||
return &errResponse{
|
||||
Err: err,
|
||||
Code: http.StatusNotFound,
|
||||
|
@ -86,8 +94,9 @@ func internalError(err error) render.Renderer {
|
|||
type errResponder func(error) render.Renderer
|
||||
|
||||
var statusMapping = map[error]errResponder{
|
||||
tgstore.ErrNoSuchSystem: errTextNotFound,
|
||||
tgstore.ErrNotFound: errTextNotFound,
|
||||
tgstore.ErrNoSuchSystem: notFoundErrText,
|
||||
tgstore.ErrNotFound: notFoundErrText,
|
||||
tgstore.ErrInvalidOrderBy: badRequestErrText,
|
||||
pgx.ErrNoRows: recordNotFound,
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
const DefaultPerPage = 20
|
||||
|
||||
type talkgroupAPI struct {
|
||||
}
|
||||
|
||||
|
@ -19,10 +21,15 @@ func (tga *talkgroupAPI) Subrouter() http.Handler {
|
|||
r := chi.NewMux()
|
||||
|
||||
r.Get(`/{system:\d+}/{id:\d+}`, tga.get)
|
||||
r.Put(`/{system:\d+}/{id:\d+}`, tga.put)
|
||||
r.Put(`/{system:\d+}`, tga.putTalkgroups)
|
||||
r.Get(`/{system:\d+}/`, tga.get)
|
||||
r.Get("/", tga.get)
|
||||
|
||||
r.Put(`/{system:\d+}/{id:\d+}`, tga.put)
|
||||
r.Put(`/{system:\d+}`, tga.putTalkgroups)
|
||||
|
||||
r.Post(`/{system:\d+}/`, tga.postPaginated)
|
||||
r.Post(`/`, tga.postPaginated)
|
||||
|
||||
r.Post("/import", tga.tgImport)
|
||||
|
||||
return r
|
||||
|
@ -83,6 +90,42 @@ func (tga *talkgroupAPI) get(w http.ResponseWriter, r *http.Request) {
|
|||
respond(w, r, res)
|
||||
}
|
||||
|
||||
func (tga *talkgroupAPI) postPaginated(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
tgs := tgstore.FromCtx(ctx)
|
||||
|
||||
var p tgParams
|
||||
|
||||
err := decodeParams(&p, r)
|
||||
if err != nil {
|
||||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
input := &tgstore.Pagination{}
|
||||
err = forms.Unmarshal(r, input, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
|
||||
if err != nil {
|
||||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
var res interface{}
|
||||
switch {
|
||||
case p.System != nil:
|
||||
res, err = tgs.SystemTGs(ctx, int32(*p.System), tgstore.WithPagination(input, DefaultPerPage))
|
||||
default:
|
||||
// get all talkgroups
|
||||
res, err = tgs.TGs(ctx, nil, tgstore.WithPagination(input, DefaultPerPage))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
wErr(w, r, autoError(err))
|
||||
return
|
||||
}
|
||||
|
||||
respond(w, r, res)
|
||||
}
|
||||
|
||||
func (tga *talkgroupAPI) put(w http.ResponseWriter, r *http.Request) {
|
||||
var id tgParams
|
||||
err := decodeParams(&id, r)
|
||||
|
|
|
@ -73,9 +73,9 @@ func (s *DatabaseSink) toAddCallParams(call *calls.Call) database.AddCallParams
|
|||
System: call.System,
|
||||
Talkgroup: call.Talkgroup,
|
||||
CallDate: pgtype.Timestamptz{Time: call.DateTime, Valid: true},
|
||||
AudioName: common.PtrOrNull(call.AudioName),
|
||||
AudioName: common.NilIfZero(call.AudioName),
|
||||
AudioBlob: call.Audio,
|
||||
AudioType: common.PtrOrNull(call.AudioType),
|
||||
AudioType: common.NilIfZero(call.AudioType),
|
||||
Duration: call.Duration.MsInt32Ptr(),
|
||||
Frequency: call.Frequency,
|
||||
Frequencies: call.Frequencies,
|
||||
|
|
|
@ -83,9 +83,9 @@ func (car *CallUploadRequest) ToCall(submitter auth.UserID) (*calls.Call, error)
|
|||
Frequency: car.Frequency,
|
||||
Frequencies: car.Frequencies,
|
||||
Patches: car.Patches,
|
||||
TalkgroupLabel: common.PtrOrNull(car.TalkgroupLabel),
|
||||
TGAlphaTag: common.PtrOrNull(car.TalkgroupTag),
|
||||
TalkgroupGroup: common.PtrOrNull(car.TalkgroupGroup),
|
||||
TalkgroupLabel: common.NilIfZero(car.TalkgroupLabel),
|
||||
TGAlphaTag: common.NilIfZero(car.TalkgroupTag),
|
||||
TalkgroupGroup: common.NilIfZero(car.TalkgroupGroup),
|
||||
Source: car.Source,
|
||||
}, !car.DontStore)
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ type tgMap map[tgsp.ID]*tgsp.Talkgroup
|
|||
var (
|
||||
ErrNotFound = errors.New("talkgroup not found")
|
||||
ErrNoSuchSystem = errors.New("no such system")
|
||||
ErrInvalidOrderBy = errors.New("invalid pagination orderBy value")
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
|
@ -36,13 +37,13 @@ type Store interface {
|
|||
TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error)
|
||||
|
||||
// TGs retrieves many talkgroups from the Store.
|
||||
TGs(ctx context.Context, tgs tgsp.IDs) ([]*tgsp.Talkgroup, error)
|
||||
TGs(ctx context.Context, tgs tgsp.IDs, opts ...option) ([]*tgsp.Talkgroup, error)
|
||||
|
||||
// LearnTG learns the talkgroup from a Call.
|
||||
LearnTG(ctx context.Context, call *calls.Call) (*tgsp.Talkgroup, error)
|
||||
|
||||
// SystemTGs retrieves all Talkgroups associated with a System.
|
||||
SystemTGs(ctx context.Context, systemID int32) ([]*tgsp.Talkgroup, error)
|
||||
SystemTGs(ctx context.Context, systemID int32, opts ...option) ([]*tgsp.Talkgroup, error)
|
||||
|
||||
// SystemName retrieves a system name from the store. It returns the record and whether one was found.
|
||||
SystemName(ctx context.Context, id int) (string, bool)
|
||||
|
@ -63,6 +64,55 @@ type Store interface {
|
|||
HUP(*config.Config)
|
||||
}
|
||||
|
||||
type options struct {
|
||||
pagination *Pagination
|
||||
perPageDefault int
|
||||
}
|
||||
|
||||
func sOpt(opts []option) (o options) {
|
||||
for _, opt := range opts {
|
||||
opt(&o)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type option func(*options)
|
||||
|
||||
func WithPagination(p *Pagination, defPerPage int) option {
|
||||
return func(o *options) {
|
||||
o.pagination = p
|
||||
o.perPageDefault = defPerPage
|
||||
}
|
||||
}
|
||||
|
||||
type TGOrder string
|
||||
|
||||
const (
|
||||
TGOrderTGID TGOrder = "tgid"
|
||||
TGOrderGroup TGOrder = "group"
|
||||
TGOrderName TGOrder = "name"
|
||||
TGOrderID TGOrder = "id"
|
||||
)
|
||||
|
||||
func (t *TGOrder) IsValid() bool {
|
||||
if t == nil {
|
||||
return true
|
||||
}
|
||||
|
||||
switch *t {
|
||||
case TGOrderTGID, TGOrderGroup, TGOrderName, TGOrderID:
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
type Pagination struct {
|
||||
common.Pagination
|
||||
|
||||
OrderBy *TGOrder `json:"orderBy"`
|
||||
}
|
||||
|
||||
type storeCtxKey string
|
||||
|
||||
const StoreCtxKey storeCtxKey = "store"
|
||||
|
@ -148,20 +198,29 @@ func (t *cache) add(rec *tgsp.Talkgroup) {
|
|||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
t.addNoLock(rec)
|
||||
}
|
||||
|
||||
func (t *cache) addNoLock(rec *tgsp.Talkgroup) {
|
||||
tg := tgsp.TG(rec.System.ID, rec.Talkgroup.TGID)
|
||||
t.tgs[tg] = rec
|
||||
t.systems[int32(rec.System.ID)] = rec.System.Name
|
||||
}
|
||||
|
||||
type row interface {
|
||||
type rowType interface {
|
||||
database.GetTalkgroupsRow | database.GetTalkgroupsWithLearnedRow |
|
||||
database.GetTalkgroupsWithLearnedBySystemRow | database.GetTalkgroupWithLearnedRow
|
||||
database.GetTalkgroupsWithLearnedBySystemRow | database.GetTalkgroupWithLearnedRow |
|
||||
database.GetTalkgroupsWithLearnedBySystemPRow | database.GetTalkgroupsWithLearnedPRow
|
||||
row
|
||||
}
|
||||
|
||||
type row interface {
|
||||
GetTalkgroup() database.Talkgroup
|
||||
GetSystem() database.System
|
||||
GetLearned() bool
|
||||
}
|
||||
|
||||
func rowToTalkgroup[T row](r T) *tgsp.Talkgroup {
|
||||
func rowToTalkgroup[T rowType](r T) *tgsp.Talkgroup {
|
||||
return &tgsp.Talkgroup{
|
||||
Talkgroup: r.GetTalkgroup(),
|
||||
System: r.GetSystem(),
|
||||
|
@ -169,10 +228,13 @@ func rowToTalkgroup[T row](r T) *tgsp.Talkgroup {
|
|||
}
|
||||
}
|
||||
|
||||
func addToRowList[T row](t *cache, r []*tgsp.Talkgroup, tgRecords []T) []*tgsp.Talkgroup {
|
||||
func addToRowListS[T rowType](t *cache, r []*tgsp.Talkgroup, tgRecords []T) []*tgsp.Talkgroup {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
|
||||
for _, rec := range tgRecords {
|
||||
tg := rowToTalkgroup(rec)
|
||||
t.add(tg)
|
||||
t.addNoLock(tg)
|
||||
|
||||
r = append(r, tg)
|
||||
}
|
||||
|
@ -180,8 +242,25 @@ func addToRowList[T row](t *cache, r []*tgsp.Talkgroup, tgRecords []T) []*tgsp.T
|
|||
return r
|
||||
}
|
||||
|
||||
func (t *cache) TGs(ctx context.Context, tgs tgsp.IDs) ([]*tgsp.Talkgroup, error) {
|
||||
func addToRowList[T rowType](t *cache, tgRecords []T) []*tgsp.Talkgroup {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
r := make([]*tgsp.Talkgroup, 0, len(tgRecords))
|
||||
|
||||
for _, rec := range tgRecords {
|
||||
tg := rowToTalkgroup(rec)
|
||||
t.addNoLock(tg)
|
||||
|
||||
r = append(r, tg)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (t *cache) TGs(ctx context.Context, tgs tgsp.IDs, opts ...option) ([]*tgsp.Talkgroup, error) {
|
||||
db := database.FromCtx(ctx)
|
||||
r := make([]*tgsp.Talkgroup, 0, len(tgs))
|
||||
opt := sOpt(opts)
|
||||
var err error
|
||||
if tgs != nil {
|
||||
toGet := make(tgsp.IDs, 0, len(tgs))
|
||||
|
@ -194,20 +273,30 @@ func (t *cache) TGs(ctx context.Context, tgs tgsp.IDs) ([]*tgsp.Talkgroup, error
|
|||
}
|
||||
}
|
||||
|
||||
tgRecords, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedBySysTGID(ctx, toGet.Tuples())
|
||||
tgRecords, err := db.GetTalkgroupsWithLearnedBySysTGID(ctx, toGet.Tuples())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addToRowList(t, r, tgRecords), nil
|
||||
return addToRowList(t, tgRecords), nil
|
||||
}
|
||||
|
||||
// get all talkgroups
|
||||
|
||||
tgRecords, err := database.FromCtx(ctx).GetTalkgroupsWithLearned(ctx)
|
||||
if opt.pagination != nil {
|
||||
offset, perPage := opt.pagination.OffsetPerPage(opt.perPageDefault)
|
||||
tgRecords, err := db.GetTalkgroupsWithLearnedP(ctx, offset, perPage)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addToRowList(t, r, tgRecords), nil
|
||||
return addToRowListS(t, r, tgRecords), nil
|
||||
}
|
||||
|
||||
tgRecords, err := db.GetTalkgroupsWithLearned(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addToRowListS(t, r, tgRecords), nil
|
||||
}
|
||||
|
||||
func (t *cache) Load(ctx context.Context, tgs database.TGTuples) error {
|
||||
|
@ -236,14 +325,25 @@ func (t *cache) Weight(ctx context.Context, id tgsp.ID, tm time.Time) float64 {
|
|||
return float64(m)
|
||||
}
|
||||
|
||||
func (t *cache) SystemTGs(ctx context.Context, systemID int32) ([]*tgsp.Talkgroup, error) {
|
||||
recs, err := database.FromCtx(ctx).GetTalkgroupsWithLearnedBySystem(ctx, systemID)
|
||||
func (t *cache) SystemTGs(ctx context.Context, systemID int32, opts ...option) ([]*tgsp.Talkgroup, error) {
|
||||
db := database.FromCtx(ctx)
|
||||
opt := sOpt(opts)
|
||||
var err error
|
||||
if opt.pagination != nil {
|
||||
offset, perPage := opt.pagination.OffsetPerPage(opt.perPageDefault)
|
||||
recs, err := db.GetTalkgroupsWithLearnedBySystemP(ctx, systemID, offset, perPage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return addToRowList(t, recs), nil
|
||||
}
|
||||
|
||||
recs, err := db.GetTalkgroupsWithLearnedBySystem(ctx, systemID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := make([]*tgsp.Talkgroup, 0, len(recs))
|
||||
return addToRowList(t, r, recs), nil
|
||||
return addToRowList(t, recs), nil
|
||||
}
|
||||
|
||||
func (t *cache) TG(ctx context.Context, tg tgsp.ID) (*tgsp.Talkgroup, error) {
|
||||
|
|
|
@ -30,8 +30,8 @@ type Alias struct {
|
|||
func tgToAlias(tg *talkgroups.Talkgroup) Alias {
|
||||
return Alias{
|
||||
XMLName: xml.Name{Local: "alias"},
|
||||
Name: common.ZeroOr(tg.Name),
|
||||
Group: common.ZeroOr(tg.TGGroup),
|
||||
Name: common.ZeroIfNil(tg.Name),
|
||||
Group: common.ZeroIfNil(tg.TGGroup),
|
||||
List: "Stillbox",
|
||||
IDs: []ID{
|
||||
ID{
|
||||
|
|
|
@ -31,6 +31,17 @@ FROM talkgroups tg
|
|||
JOIN systems sys ON tg.system_id = sys.id
|
||||
WHERE (tg.system_id, tg.tgid) = (@system_id, @tgid);
|
||||
|
||||
-- name: GetTalkgroupsWithLearnedBySystemP :many
|
||||
SELECT
|
||||
sqlc.embed(tg), sqlc.embed(sys)
|
||||
FROM talkgroups tg
|
||||
JOIN systems sys ON tg.system_id = sys.id
|
||||
WHERE tg.system_id = @system
|
||||
ORDER BY tg.system_id ASC, tg.tgid ASC
|
||||
OFFSET sqlc.arg('offset') ROWS
|
||||
FETCH NEXT sqlc.arg('per_page') ROWS ONLY;
|
||||
;
|
||||
|
||||
-- name: GetTalkgroupsWithLearnedBySystem :many
|
||||
SELECT
|
||||
sqlc.embed(tg), sqlc.embed(sys)
|
||||
|
@ -45,6 +56,16 @@ FROM talkgroups tg
|
|||
JOIN systems sys ON tg.system_id = sys.id
|
||||
WHERE ignored IS NOT TRUE;
|
||||
|
||||
-- name: GetTalkgroupsWithLearnedP :many
|
||||
SELECT
|
||||
sqlc.embed(tg), sqlc.embed(sys)
|
||||
FROM talkgroups tg
|
||||
JOIN systems sys ON tg.system_id = sys.id
|
||||
WHERE ignored IS NOT TRUE
|
||||
ORDER BY tg.system_id ASC, tg.tgid ASC
|
||||
OFFSET sqlc.arg('offset') ROWS
|
||||
FETCH NEXT sqlc.arg('per_page') ROWS ONLY;
|
||||
|
||||
-- name: GetSystemName :one
|
||||
SELECT name FROM systems WHERE id = @system_id;
|
||||
|
||||
|
|
Loading…
Reference in a new issue