Settings store
This commit is contained in:
parent
640fbcaa6f
commit
051bae5dd2
8 changed files with 362 additions and 0 deletions
|
@ -947,6 +947,53 @@ func (_c *Store_DeleteIncident_Call) RunAndReturn(run func(context.Context, uuid
|
|||
return _c
|
||||
}
|
||||
|
||||
// DeleteSetting provides a mock function with given fields: ctx, name
|
||||
func (_m *Store) DeleteSetting(ctx context.Context, name string) error {
|
||||
ret := _m.Called(ctx, name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteSetting")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||
r0 = rf(ctx, name)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Store_DeleteSetting_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteSetting'
|
||||
type Store_DeleteSetting_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeleteSetting is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - name string
|
||||
func (_e *Store_Expecter) DeleteSetting(ctx interface{}, name interface{}) *Store_DeleteSetting_Call {
|
||||
return &Store_DeleteSetting_Call{Call: _e.mock.On("DeleteSetting", ctx, name)}
|
||||
}
|
||||
|
||||
func (_c *Store_DeleteSetting_Call) Run(run func(ctx context.Context, name string)) *Store_DeleteSetting_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_DeleteSetting_Call) Return(_a0 error) *Store_DeleteSetting_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_DeleteSetting_Call) RunAndReturn(run func(context.Context, string) error) *Store_DeleteSetting_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteShare provides a mock function with given fields: ctx, id
|
||||
func (_m *Store) DeleteShare(ctx context.Context, id string) error {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
@ -1989,6 +2036,65 @@ func (_c *Store_GetIncidentTalkgroups_Call) RunAndReturn(run func(context.Contex
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetSetting provides a mock function with given fields: ctx, name
|
||||
func (_m *Store) GetSetting(ctx context.Context, name string) ([]byte, error) {
|
||||
ret := _m.Called(ctx, name)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetSetting")
|
||||
}
|
||||
|
||||
var r0 []byte
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) ([]byte, error)); ok {
|
||||
return rf(ctx, name)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string) []byte); ok {
|
||||
r0 = rf(ctx, name)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]byte)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||
r1 = rf(ctx, name)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_GetSetting_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSetting'
|
||||
type Store_GetSetting_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetSetting is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - name string
|
||||
func (_e *Store_Expecter) GetSetting(ctx interface{}, name interface{}) *Store_GetSetting_Call {
|
||||
return &Store_GetSetting_Call{Call: _e.mock.On("GetSetting", ctx, name)}
|
||||
}
|
||||
|
||||
func (_c *Store_GetSetting_Call) Run(run func(ctx context.Context, name string)) *Store_GetSetting_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_GetSetting_Call) Return(_a0 []byte, _a1 error) *Store_GetSetting_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_GetSetting_Call) RunAndReturn(run func(context.Context, string) ([]byte, error)) *Store_GetSetting_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetShare provides a mock function with given fields: ctx, id
|
||||
func (_m *Store) GetShare(ctx context.Context, id string) (database.Share, error) {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
@ -3804,6 +3910,55 @@ func (_c *Store_SetCallTranscript_Call) RunAndReturn(run func(context.Context, u
|
|||
return _c
|
||||
}
|
||||
|
||||
// SetSetting provides a mock function with given fields: ctx, name, updatedBy, value
|
||||
func (_m *Store) SetSetting(ctx context.Context, name string, updatedBy *int32, value []byte) error {
|
||||
ret := _m.Called(ctx, name, updatedBy, value)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for SetSetting")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, string, *int32, []byte) error); ok {
|
||||
r0 = rf(ctx, name, updatedBy, value)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Store_SetSetting_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SetSetting'
|
||||
type Store_SetSetting_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// SetSetting is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - name string
|
||||
// - updatedBy *int32
|
||||
// - value []byte
|
||||
func (_e *Store_Expecter) SetSetting(ctx interface{}, name interface{}, updatedBy interface{}, value interface{}) *Store_SetSetting_Call {
|
||||
return &Store_SetSetting_Call{Call: _e.mock.On("SetSetting", ctx, name, updatedBy, value)}
|
||||
}
|
||||
|
||||
func (_c *Store_SetSetting_Call) Run(run func(ctx context.Context, name string, updatedBy *int32, value []byte)) *Store_SetSetting_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(string), args[2].(*int32), args[3].([]byte))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_SetSetting_Call) Return(_a0 error) *Store_SetSetting_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_SetSetting_Call) RunAndReturn(run func(context.Context, string, *int32, []byte) error) *Store_SetSetting_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetTalkgroupTags provides a mock function with given fields: ctx, tags, systemID, tGID
|
||||
func (_m *Store) SetTalkgroupTags(ctx context.Context, tags []string, systemID int32, tGID int32) error {
|
||||
ret := _m.Called(ctx, tags, systemID, tGID)
|
||||
|
|
|
@ -26,6 +26,7 @@ type Querier interface {
|
|||
DeleteAPIKey(ctx context.Context, apiKey string) error
|
||||
DeleteCall(ctx context.Context, id uuid.UUID) error
|
||||
DeleteIncident(ctx context.Context, id uuid.UUID) error
|
||||
DeleteSetting(ctx context.Context, name string) error
|
||||
DeleteShare(ctx context.Context, id string) error
|
||||
DeleteSystem(ctx context.Context, id int) error
|
||||
DeleteTalkgroup(ctx context.Context, systemID int32, tGID int32) error
|
||||
|
@ -43,6 +44,7 @@ type Querier interface {
|
|||
GetIncidentCalls(ctx context.Context, id uuid.UUID) ([]GetIncidentCallsRow, error)
|
||||
GetIncidentOwner(ctx context.Context, id uuid.UUID) (int, error)
|
||||
GetIncidentTalkgroups(ctx context.Context, incidentID uuid.UUID) ([]GetIncidentTalkgroupsRow, error)
|
||||
GetSetting(ctx context.Context, name string) ([]byte, error)
|
||||
GetShare(ctx context.Context, id string) (Share, error)
|
||||
GetSharesP(ctx context.Context, arg GetSharesPParams) ([]GetSharesPRow, error)
|
||||
GetSharesPCount(ctx context.Context, owner *int32) (int64, error)
|
||||
|
@ -71,6 +73,7 @@ type Querier interface {
|
|||
RestoreTalkgroupVersion(ctx context.Context, versionIds int) (Talkgroup, error)
|
||||
SetAppPrefs(ctx context.Context, appName string, prefs []byte, uid int) error
|
||||
SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error
|
||||
SetSetting(ctx context.Context, name string, updatedBy *int32, value []byte) error
|
||||
SetTalkgroupTags(ctx context.Context, tags []string, systemID int32, tGID int32) error
|
||||
StoreDeletedTGVersion(ctx context.Context, systemID *int32, tGID *int32, submitter *int32) error
|
||||
StoreTGVersion(ctx context.Context, arg []StoreTGVersionParams) *StoreTGVersionBatchResults
|
||||
|
|
42
pkg/database/settings.sql.go
Normal file
42
pkg/database/settings.sql.go
Normal file
|
@ -0,0 +1,42 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.27.0
|
||||
// source: settings.sql
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const deleteSetting = `-- name: DeleteSetting :exec
|
||||
DELETE FROM settings WHERE name = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSetting(ctx context.Context, name string) error {
|
||||
_, err := q.db.Exec(ctx, deleteSetting, name)
|
||||
return err
|
||||
}
|
||||
|
||||
const getSetting = `-- name: GetSetting :one
|
||||
SELECT value FROM settings WHERE name = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetSetting(ctx context.Context, name string) ([]byte, error) {
|
||||
row := q.db.QueryRow(ctx, getSetting, name)
|
||||
var value []byte
|
||||
err := row.Scan(&value)
|
||||
return value, err
|
||||
}
|
||||
|
||||
const setSetting = `-- name: SetSetting :exec
|
||||
INSERT INTO settings (name, updated_by, value) VALUES ($1, $2, $3)
|
||||
ON CONFLICT (name) DO UPDATE SET
|
||||
value = $3,
|
||||
updated_by = $2
|
||||
`
|
||||
|
||||
func (q *Queries) SetSetting(ctx context.Context, name string, updatedBy *int32, value []byte) error {
|
||||
_, err := q.db.Exec(ctx, setSetting, name, updatedBy, value)
|
||||
return err
|
||||
}
|
|
@ -23,6 +23,7 @@ const (
|
|||
ResourceShare = "Share"
|
||||
ResourceAPIKey = "APIKey"
|
||||
ResourceCallStats = "CallStats"
|
||||
ResourceSetting = "Setting"
|
||||
|
||||
ActionRead = "read"
|
||||
ActionCreate = "create"
|
||||
|
@ -97,3 +98,9 @@ func (s *SystemServiceSubject) String() string {
|
|||
func (s *SystemServiceSubject) GetRoles() []string {
|
||||
return []string{RoleSystem}
|
||||
}
|
||||
|
||||
func IsSystemService(sub Subject) bool {
|
||||
_, is := sub.(*SystemServiceSubject)
|
||||
|
||||
return is
|
||||
}
|
||||
|
|
|
@ -50,6 +50,9 @@ var Policy = &restrict.PolicyDefinition{
|
|||
entities.ResourceCallStats: {
|
||||
&restrict.Permission{Action: entities.ActionRead},
|
||||
},
|
||||
entities.ResourceSetting: {
|
||||
&restrict.Permission{Action: entities.ActionRead},
|
||||
},
|
||||
},
|
||||
},
|
||||
entities.RoleSubmitter: {
|
||||
|
@ -108,6 +111,12 @@ var Policy = &restrict.PolicyDefinition{
|
|||
&restrict.Permission{Action: entities.ActionCreate},
|
||||
&restrict.Permission{Action: entities.ActionDelete},
|
||||
},
|
||||
entities.ResourceSetting: {
|
||||
&restrict.Permission{Action: entities.ActionRead},
|
||||
&restrict.Permission{Action: entities.ActionCreate},
|
||||
&restrict.Permission{Action: entities.ActionUpdate},
|
||||
&restrict.Permission{Action: entities.ActionDelete},
|
||||
},
|
||||
},
|
||||
},
|
||||
entities.RoleSystem: {
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"dynatron.me/x/stillbox/pkg/rbac/policy"
|
||||
"dynatron.me/x/stillbox/pkg/rest"
|
||||
"dynatron.me/x/stillbox/pkg/services"
|
||||
"dynatron.me/x/stillbox/pkg/settings"
|
||||
"dynatron.me/x/stillbox/pkg/shares"
|
||||
"dynatron.me/x/stillbox/pkg/sinks"
|
||||
"dynatron.me/x/stillbox/pkg/sources"
|
||||
|
@ -57,6 +58,7 @@ type Server struct {
|
|||
share shares.Service
|
||||
rbac rbac.RBAC
|
||||
stats stats.Stats
|
||||
settings settings.Store
|
||||
}
|
||||
|
||||
func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
|
||||
|
@ -110,6 +112,7 @@ func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
|
|||
incidents: incstore.NewStore(),
|
||||
rbac: rbacSvc,
|
||||
stats: statsSvc,
|
||||
settings: settings.New(),
|
||||
}
|
||||
|
||||
if cfg.DB.Partition.Enabled {
|
||||
|
@ -176,6 +179,7 @@ func (s *Server) fillCtx(ctx context.Context) context.Context {
|
|||
ctx = shares.CtxWithStore(ctx, s.share)
|
||||
ctx = rbac.CtxWithRBAC(ctx, s.rbac)
|
||||
ctx = stats.CtxWithStats(ctx, s.stats)
|
||||
ctx = settings.CtxWithStore(ctx, s.settings)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
|
131
pkg/settings/settings.go
Normal file
131
pkg/settings/settings.go
Normal file
|
@ -0,0 +1,131 @@
|
|||
package settings
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/cache"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
"dynatron.me/x/stillbox/pkg/rbac/entities"
|
||||
"dynatron.me/x/stillbox/pkg/services"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoSetting = errors.New("no such setting")
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
// Get gets a setting and unmarshals it into dst.
|
||||
Get(ctx context.Context, name string, dest interface{}) error
|
||||
|
||||
// Set sets a setting.
|
||||
Set(ctx context.Context, name string, val interface{}) error
|
||||
|
||||
// Delete removes a setting.
|
||||
Delete(ctx context.Context, name string) error
|
||||
}
|
||||
|
||||
type postgresStore struct {
|
||||
c cache.Cache[string, []byte]
|
||||
}
|
||||
|
||||
type storeCtxKey string
|
||||
|
||||
const StoreCtxKey storeCtxKey = "store"
|
||||
|
||||
func CtxWithStore(ctx context.Context, s Store) context.Context {
|
||||
return services.WithValue(ctx, StoreCtxKey, s)
|
||||
}
|
||||
|
||||
func FromCtx(ctx context.Context) Store {
|
||||
s, ok := services.Value(ctx, StoreCtxKey).(Store)
|
||||
if !ok {
|
||||
panic("no settings store in context")
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func New() *postgresStore {
|
||||
s := &postgresStore{
|
||||
c: cache.New[string, []byte](),
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *postgresStore) Get(ctx context.Context, name string, dest interface{}) error {
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceSetting), rbac.WithActions(entities.ActionRead))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ci, has := s.c.Get(name)
|
||||
if !has {
|
||||
db := database.FromCtx(ctx)
|
||||
|
||||
ci, err = db.GetSetting(ctx, name)
|
||||
if err != nil {
|
||||
if database.IsNoRows(err) {
|
||||
return ErrNoSetting
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
s.c.Set(name, ci)
|
||||
}
|
||||
|
||||
return json.Unmarshal(ci, dest)
|
||||
}
|
||||
|
||||
func (s *postgresStore) Set(ctx context.Context, name string, val interface{}) error {
|
||||
subj, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceSetting), rbac.WithActions(entities.ActionCreate, entities.ActionUpdate))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := json.Marshal(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var uid *int32
|
||||
switch u := subj.(type) {
|
||||
case *entities.SystemServiceSubject:
|
||||
// uid remains null
|
||||
case *users.User:
|
||||
u, err := users.FromSubject(subj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
uid = u.ID.Int32Ptr()
|
||||
default:
|
||||
return rbac.ErrBadSubject
|
||||
}
|
||||
|
||||
db := database.FromCtx(ctx)
|
||||
|
||||
err = db.SetSetting(ctx, name, uid, b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.c.Set(name, b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *postgresStore) Delete(ctx context.Context, name string) error {
|
||||
_, err := rbac.Check(ctx, rbac.UseResource(entities.ResourceSetting), rbac.WithActions(entities.ActionDelete))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.c.Delete(name)
|
||||
return database.FromCtx(ctx).DeleteSetting(ctx, name)
|
||||
}
|
11
sql/postgres/queries/settings.sql
Normal file
11
sql/postgres/queries/settings.sql
Normal file
|
@ -0,0 +1,11 @@
|
|||
-- name: GetSetting :one
|
||||
SELECT value FROM settings WHERE name = @name;
|
||||
|
||||
-- name: SetSetting :exec
|
||||
INSERT INTO settings (name, updated_by, value) VALUES (@name, @updated_by, @value)
|
||||
ON CONFLICT (name) DO UPDATE SET
|
||||
value = @value,
|
||||
updated_by = @updated_by;
|
||||
|
||||
-- name: DeleteSetting :exec
|
||||
DELETE FROM settings WHERE name = @name;
|
Loading…
Add table
Reference in a new issue