RBAC #102
15 changed files with 569 additions and 0 deletions
1
go.mod
1
go.mod
|
@ -56,6 +56,7 @@ require (
|
||||||
github.com/lestrrat-go/iter v1.0.2 // indirect
|
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||||
github.com/lestrrat-go/jwx/v2 v2.1.3 // indirect
|
github.com/lestrrat-go/jwx/v2 v2.1.3 // indirect
|
||||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||||
|
github.com/matoous/go-nanoid v1.5.1 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -116,6 +116,8 @@ github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNB
|
||||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
|
github.com/matoous/go-nanoid v1.5.1 h1:aCjdvTyO9LLnTIi0fgdXhOPPvOHjpXN6Ik9DaNjIct4=
|
||||||
|
github.com/matoous/go-nanoid v1.5.1/go.mod h1:zyD2a71IubI24efhpvkJz+ZwfwagzgSO6UNiFsZKN7U=
|
||||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
|
|
@ -502,6 +502,53 @@ func (_c *Store_CreatePartition_Call) RunAndReturn(run func(context.Context, str
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateShare provides a mock function with given fields: ctx, arg
|
||||||
|
func (_m *Store) CreateShare(ctx context.Context, arg database.CreateShareParams) error {
|
||||||
|
ret := _m.Called(ctx, arg)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for CreateShare")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, database.CreateShareParams) error); ok {
|
||||||
|
r0 = rf(ctx, arg)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store_CreateShare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateShare'
|
||||||
|
type Store_CreateShare_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateShare is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - arg database.CreateShareParams
|
||||||
|
func (_e *Store_Expecter) CreateShare(ctx interface{}, arg interface{}) *Store_CreateShare_Call {
|
||||||
|
return &Store_CreateShare_Call{Call: _e.mock.On("CreateShare", ctx, arg)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_CreateShare_Call) Run(run func(ctx context.Context, arg database.CreateShareParams)) *Store_CreateShare_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(database.CreateShareParams))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_CreateShare_Call) Return(_a0 error) *Store_CreateShare_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_CreateShare_Call) RunAndReturn(run func(context.Context, database.CreateShareParams) error) *Store_CreateShare_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// CreateSystem provides a mock function with given fields: ctx, iD, name
|
// CreateSystem provides a mock function with given fields: ctx, iD, name
|
||||||
func (_m *Store) CreateSystem(ctx context.Context, iD int, name string) error {
|
func (_m *Store) CreateSystem(ctx context.Context, iD int, name string) error {
|
||||||
ret := _m.Called(ctx, iD, name)
|
ret := _m.Called(ctx, iD, name)
|
||||||
|
@ -795,6 +842,53 @@ func (_c *Store_DeleteIncident_Call) RunAndReturn(run func(context.Context, uuid
|
||||||
return _c
|
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)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for DeleteShare")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string) error); ok {
|
||||||
|
r0 = rf(ctx, id)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store_DeleteShare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteShare'
|
||||||
|
type Store_DeleteShare_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteShare is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - id string
|
||||||
|
func (_e *Store_Expecter) DeleteShare(ctx interface{}, id interface{}) *Store_DeleteShare_Call {
|
||||||
|
return &Store_DeleteShare_Call{Call: _e.mock.On("DeleteShare", ctx, id)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_DeleteShare_Call) Run(run func(ctx context.Context, id string)) *Store_DeleteShare_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(string))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_DeleteShare_Call) Return(_a0 error) *Store_DeleteShare_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_DeleteShare_Call) RunAndReturn(run func(context.Context, string) error) *Store_DeleteShare_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// DeleteSystem provides a mock function with given fields: ctx, id
|
// DeleteSystem provides a mock function with given fields: ctx, id
|
||||||
func (_m *Store) DeleteSystem(ctx context.Context, id int) error {
|
func (_m *Store) DeleteSystem(ctx context.Context, id int) error {
|
||||||
ret := _m.Called(ctx, id)
|
ret := _m.Called(ctx, id)
|
||||||
|
@ -1436,6 +1530,63 @@ func (_c *Store_GetIncidentCalls_Call) RunAndReturn(run func(context.Context, uu
|
||||||
return _c
|
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)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for GetShare")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 database.Share
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string) (database.Share, error)); ok {
|
||||||
|
return rf(ctx, id)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, string) database.Share); ok {
|
||||||
|
r0 = rf(ctx, id)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(database.Share)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, string) error); ok {
|
||||||
|
r1 = rf(ctx, id)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store_GetShare_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetShare'
|
||||||
|
type Store_GetShare_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetShare is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - id string
|
||||||
|
func (_e *Store_Expecter) GetShare(ctx interface{}, id interface{}) *Store_GetShare_Call {
|
||||||
|
return &Store_GetShare_Call{Call: _e.mock.On("GetShare", ctx, id)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_GetShare_Call) Run(run func(ctx context.Context, id string)) *Store_GetShare_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(string))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_GetShare_Call) Return(_a0 database.Share, _a1 error) *Store_GetShare_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_GetShare_Call) RunAndReturn(run func(context.Context, string) (database.Share, error)) *Store_GetShare_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// GetSystemName provides a mock function with given fields: ctx, systemID
|
// GetSystemName provides a mock function with given fields: ctx, systemID
|
||||||
func (_m *Store) GetSystemName(ctx context.Context, systemID int) (string, error) {
|
func (_m *Store) GetSystemName(ctx context.Context, systemID int) (string, error) {
|
||||||
ret := _m.Called(ctx, systemID)
|
ret := _m.Called(ctx, systemID)
|
||||||
|
@ -2887,6 +3038,52 @@ func (_c *Store_ListIncidentsP_Call) RunAndReturn(run func(context.Context, data
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PruneShares provides a mock function with given fields: ctx
|
||||||
|
func (_m *Store) PruneShares(ctx context.Context) error {
|
||||||
|
ret := _m.Called(ctx)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for PruneShares")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context) error); ok {
|
||||||
|
r0 = rf(ctx)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Error(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store_PruneShares_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'PruneShares'
|
||||||
|
type Store_PruneShares_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// PruneShares is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
func (_e *Store_Expecter) PruneShares(ctx interface{}) *Store_PruneShares_Call {
|
||||||
|
return &Store_PruneShares_Call{Call: _e.mock.On("PruneShares", ctx)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_PruneShares_Call) Run(run func(ctx context.Context)) *Store_PruneShares_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_PruneShares_Call) Return(_a0 error) *Store_PruneShares_Call {
|
||||||
|
_c.Call.Return(_a0)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_PruneShares_Call) RunAndReturn(run func(context.Context) error) *Store_PruneShares_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
// RemoveFromIncident provides a mock function with given fields: ctx, iD, callIds
|
// RemoveFromIncident provides a mock function with given fields: ctx, iD, callIds
|
||||||
func (_m *Store) RemoveFromIncident(ctx context.Context, iD uuid.UUID, callIds []uuid.UUID) error {
|
func (_m *Store) RemoveFromIncident(ctx context.Context, iD uuid.UUID, callIds []uuid.UUID) error {
|
||||||
ret := _m.Called(ctx, iD, callIds)
|
ret := _m.Called(ctx, iD, callIds)
|
||||||
|
|
|
@ -80,6 +80,14 @@ type Setting struct {
|
||||||
Value []byte `json:"value,omitempty"`
|
Value []byte `json:"value,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Share struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
EntityType string `json:"entity_type,omitempty"`
|
||||||
|
EntityID uuid.UUID `json:"entity_id,omitempty"`
|
||||||
|
Owner int `json:"owner,omitempty"`
|
||||||
|
Expiration pgtype.Timestamptz `json:"expiration,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type SweptCall struct {
|
type SweptCall struct {
|
||||||
ID uuid.UUID `json:"id,omitempty"`
|
ID uuid.UUID `json:"id,omitempty"`
|
||||||
Submitter *int32 `json:"submitter,omitempty"`
|
Submitter *int32 `json:"submitter,omitempty"`
|
||||||
|
|
|
@ -19,10 +19,12 @@ type Querier interface {
|
||||||
CleanupSweptCalls(ctx context.Context, rangeStart pgtype.Timestamptz, rangeEnd pgtype.Timestamptz) (int64, error)
|
CleanupSweptCalls(ctx context.Context, rangeStart pgtype.Timestamptz, rangeEnd pgtype.Timestamptz) (int64, 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)
|
||||||
CreateIncident(ctx context.Context, arg CreateIncidentParams) (Incident, error)
|
CreateIncident(ctx context.Context, arg CreateIncidentParams) (Incident, error)
|
||||||
|
CreateShare(ctx context.Context, arg CreateShareParams) error
|
||||||
CreateSystem(ctx context.Context, iD int, name string) error
|
CreateSystem(ctx context.Context, iD int, name string) error
|
||||||
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
|
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
|
||||||
DeleteAPIKey(ctx context.Context, apiKey string) error
|
DeleteAPIKey(ctx context.Context, apiKey string) error
|
||||||
DeleteIncident(ctx context.Context, id uuid.UUID) error
|
DeleteIncident(ctx context.Context, id uuid.UUID) error
|
||||||
|
DeleteShare(ctx context.Context, id string) error
|
||||||
DeleteSystem(ctx context.Context, id int) error
|
DeleteSystem(ctx context.Context, id int) error
|
||||||
DeleteTalkgroup(ctx context.Context, systemID int32, tGID int32) error
|
DeleteTalkgroup(ctx context.Context, systemID int32, tGID int32) error
|
||||||
DeleteUser(ctx context.Context, username string) error
|
DeleteUser(ctx context.Context, username string) error
|
||||||
|
@ -33,6 +35,7 @@ type Querier interface {
|
||||||
GetDatabaseSize(ctx context.Context) (string, error)
|
GetDatabaseSize(ctx context.Context) (string, error)
|
||||||
GetIncident(ctx context.Context, id uuid.UUID) (Incident, error)
|
GetIncident(ctx context.Context, id uuid.UUID) (Incident, error)
|
||||||
GetIncidentCalls(ctx context.Context, id uuid.UUID) ([]GetIncidentCallsRow, error)
|
GetIncidentCalls(ctx context.Context, id uuid.UUID) ([]GetIncidentCallsRow, error)
|
||||||
|
GetShare(ctx context.Context, id string) (Share, error)
|
||||||
GetSystemName(ctx context.Context, systemID int) (string, error)
|
GetSystemName(ctx context.Context, systemID int) (string, error)
|
||||||
GetTalkgroup(ctx context.Context, systemID int32, tGID int32) (GetTalkgroupRow, error)
|
GetTalkgroup(ctx context.Context, systemID int32, tGID int32) (GetTalkgroupRow, error)
|
||||||
GetTalkgroupIDsByTags(ctx context.Context, anyTags []string, allTags []string, notTags []string) ([]GetTalkgroupIDsByTagsRow, error)
|
GetTalkgroupIDsByTags(ctx context.Context, anyTags []string, allTags []string, notTags []string) ([]GetTalkgroupIDsByTagsRow, error)
|
||||||
|
@ -54,6 +57,7 @@ type Querier interface {
|
||||||
ListCallsP(ctx context.Context, arg ListCallsPParams) ([]ListCallsPRow, error)
|
ListCallsP(ctx context.Context, arg ListCallsPParams) ([]ListCallsPRow, error)
|
||||||
ListIncidentsCount(ctx context.Context, start pgtype.Timestamptz, end pgtype.Timestamptz, filter *string) (int64, error)
|
ListIncidentsCount(ctx context.Context, start pgtype.Timestamptz, end pgtype.Timestamptz, filter *string) (int64, error)
|
||||||
ListIncidentsP(ctx context.Context, arg ListIncidentsPParams) ([]ListIncidentsPRow, error)
|
ListIncidentsP(ctx context.Context, arg ListIncidentsPParams) ([]ListIncidentsPRow, error)
|
||||||
|
PruneShares(ctx context.Context) error
|
||||||
RemoveFromIncident(ctx context.Context, iD uuid.UUID, callIds []uuid.UUID) error
|
RemoveFromIncident(ctx context.Context, iD uuid.UUID, callIds []uuid.UUID) error
|
||||||
RestoreTalkgroupVersion(ctx context.Context, versionIds int) (Talkgroup, error)
|
RestoreTalkgroupVersion(ctx context.Context, versionIds int) (Talkgroup, error)
|
||||||
SetAppPrefs(ctx context.Context, appName string, prefs []byte, uid int) error
|
SetAppPrefs(ctx context.Context, appName string, prefs []byte, uid int) error
|
||||||
|
|
84
pkg/database/share.sql.go
Normal file
84
pkg/database/share.sql.go
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.27.0
|
||||||
|
// source: share.sql
|
||||||
|
|
||||||
|
package database
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
)
|
||||||
|
|
||||||
|
const createShare = `-- name: CreateShare :exec
|
||||||
|
INSERT INTO shares (
|
||||||
|
id,
|
||||||
|
entity_type,
|
||||||
|
entity_id,
|
||||||
|
owner,
|
||||||
|
expiration
|
||||||
|
) VALUES ($1, $2, $3, $4, $5)
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateShareParams struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
EntityType string `json:"entity_type"`
|
||||||
|
EntityID uuid.UUID `json:"entity_id"`
|
||||||
|
Owner int `json:"owner"`
|
||||||
|
Expiration pgtype.Timestamptz `json:"expiration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateShare(ctx context.Context, arg CreateShareParams) error {
|
||||||
|
_, err := q.db.Exec(ctx, createShare,
|
||||||
|
arg.ID,
|
||||||
|
arg.EntityType,
|
||||||
|
arg.EntityID,
|
||||||
|
arg.Owner,
|
||||||
|
arg.Expiration,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteShare = `-- name: DeleteShare :exec
|
||||||
|
DELETE FROM shares WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteShare(ctx context.Context, id string) error {
|
||||||
|
_, err := q.db.Exec(ctx, deleteShare, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getShare = `-- name: GetShare :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
entity_type,
|
||||||
|
entity_id,
|
||||||
|
owner,
|
||||||
|
expiration
|
||||||
|
FROM shares
|
||||||
|
WHERE id = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetShare(ctx context.Context, id string) (Share, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getShare, id)
|
||||||
|
var i Share
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.EntityType,
|
||||||
|
&i.EntityID,
|
||||||
|
&i.Owner,
|
||||||
|
&i.Expiration,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const pruneShares = `-- name: PruneShares :exec
|
||||||
|
DELETE FROM shares WHERE expiration < NOW()
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) PruneShares(ctx context.Context) error {
|
||||||
|
_, err := q.db.Exec(ctx, pruneShares)
|
||||||
|
return err
|
||||||
|
}
|
1
pkg/rbac/rbac.go
Normal file
1
pkg/rbac/rbac.go
Normal file
|
@ -0,0 +1 @@
|
||||||
|
package rbac
|
|
@ -37,6 +37,7 @@ func (a *api) Subrouter() http.Handler {
|
||||||
r.Mount("/call", new(callsAPI).Subrouter())
|
r.Mount("/call", new(callsAPI).Subrouter())
|
||||||
r.Mount("/user", new(usersAPI).Subrouter())
|
r.Mount("/user", new(usersAPI).Subrouter())
|
||||||
r.Mount("/incident", newIncidentsAPI(&a.baseURL).Subrouter())
|
r.Mount("/incident", newIncidentsAPI(&a.baseURL).Subrouter())
|
||||||
|
r.Mount("/share", newShareHandler(&a.baseURL).Subrouter())
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
39
pkg/rest/share.go
Normal file
39
pkg/rest/share.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/internal/common"
|
||||||
|
"dynatron.me/x/stillbox/internal/forms"
|
||||||
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
|
"dynatron.me/x/stillbox/pkg/incidents"
|
||||||
|
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
||||||
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type shareAPI struct {
|
||||||
|
baseURL *url.URL
|
||||||
|
}
|
||||||
|
|
||||||
|
func newShareHandler(baseURL *url.URL) API {
|
||||||
|
return &shareAPI{baseURL}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ia *shareAPI) Subrouter() http.Handler {
|
||||||
|
r := chi.NewMux()
|
||||||
|
|
||||||
|
//r.Get(`/{id:[A-Za-z0-9_-]{20,}}`, ia.getShare)
|
||||||
|
//r.Post('/create', ia.createShare)
|
||||||
|
//r.Delete(`/{id:[A-Za-z0-9_-]{20,}}`, ia.deleteShare)
|
||||||
|
//r.Get(`/`, ia.getShares)
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"dynatron.me/x/stillbox/pkg/nexus"
|
"dynatron.me/x/stillbox/pkg/nexus"
|
||||||
"dynatron.me/x/stillbox/pkg/notify"
|
"dynatron.me/x/stillbox/pkg/notify"
|
||||||
"dynatron.me/x/stillbox/pkg/rest"
|
"dynatron.me/x/stillbox/pkg/rest"
|
||||||
|
"dynatron.me/x/stillbox/pkg/share"
|
||||||
"dynatron.me/x/stillbox/pkg/sinks"
|
"dynatron.me/x/stillbox/pkg/sinks"
|
||||||
"dynatron.me/x/stillbox/pkg/sources"
|
"dynatron.me/x/stillbox/pkg/sources"
|
||||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||||
|
@ -48,6 +49,7 @@ type Server struct {
|
||||||
users users.Store
|
users users.Store
|
||||||
calls callstore.Store
|
calls callstore.Store
|
||||||
incidents incstore.Store
|
incidents incstore.Store
|
||||||
|
share share.Service
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
|
func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
|
||||||
|
@ -85,6 +87,7 @@ func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
|
||||||
tgs: tgCache,
|
tgs: tgCache,
|
||||||
sinks: sinks.NewSinkManager(),
|
sinks: sinks.NewSinkManager(),
|
||||||
rest: api,
|
rest: api,
|
||||||
|
share: share.NewService(),
|
||||||
users: users.NewStore(),
|
users: users.NewStore(),
|
||||||
calls: callstore.NewStore(),
|
calls: callstore.NewStore(),
|
||||||
incidents: incstore.NewStore(),
|
incidents: incstore.NewStore(),
|
||||||
|
@ -141,6 +144,7 @@ func (s *Server) addStoresTo(ctx context.Context) context.Context {
|
||||||
ctx = users.CtxWithStore(ctx, s.users)
|
ctx = users.CtxWithStore(ctx, s.users)
|
||||||
ctx = callstore.CtxWithStore(ctx, s.calls)
|
ctx = callstore.CtxWithStore(ctx, s.calls)
|
||||||
ctx = incstore.CtxWithStore(ctx, s.incidents)
|
ctx = incstore.CtxWithStore(ctx, s.incidents)
|
||||||
|
ctx = share.CtxWithStore(ctx, s.share.Store())
|
||||||
|
|
||||||
return ctx
|
return ctx
|
||||||
}
|
}
|
||||||
|
@ -159,6 +163,7 @@ func (s *Server) Go(ctx context.Context) error {
|
||||||
|
|
||||||
go s.nex.Go(ctx)
|
go s.nex.Go(ctx)
|
||||||
go s.alerter.Go(ctx)
|
go s.alerter.Go(ctx)
|
||||||
|
go s.share.Go(ctx)
|
||||||
|
|
||||||
if pm := s.partman; pm != nil {
|
if pm := s.partman; pm != nil {
|
||||||
go pm.Go(ctx)
|
go pm.Go(ctx)
|
||||||
|
|
49
pkg/share/service.go
Normal file
49
pkg/share/service.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
package share
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PruneInterval = time.Hour * 4
|
||||||
|
)
|
||||||
|
|
||||||
|
type Service interface {
|
||||||
|
Store() Store
|
||||||
|
|
||||||
|
Go(ctx context.Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
type service struct {
|
||||||
|
store Store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Store() Store {
|
||||||
|
return s.store
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *service) Go(ctx context.Context) {
|
||||||
|
tick := time.NewTicker(PruneInterval)
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <- tick.C:
|
||||||
|
err := s.store.Prune(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("share prune failed")
|
||||||
|
}
|
||||||
|
case <-ctx.Done():
|
||||||
|
tick.Stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService() *service {
|
||||||
|
return &service{
|
||||||
|
store: NewStore(),
|
||||||
|
}
|
||||||
|
}
|
61
pkg/share/share.go
Normal file
61
pkg/share/share.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package share
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/matoous/go-nanoid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SlugLength = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
type EntityType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
EntityIncident EntityType = "incident"
|
||||||
|
EntityCall EntityType = "call"
|
||||||
|
)
|
||||||
|
|
||||||
|
// If an incident is shared, all calls that are part of it must be shared too, but this can be through the incident share (/share/bLaH/callID[.mp3])
|
||||||
|
|
||||||
|
type Share struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Type EntityType `json:"entityType"`
|
||||||
|
EntityID uuid.UUID `json:"entityID"`
|
||||||
|
Expiration *jsontypes.Time `json:"expiration"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewShare creates a new share.
|
||||||
|
func NewShare(ctx context.Context, shType EntityType, shID uuid.UUID, exp *time.Duration) (id string, err error) {
|
||||||
|
id, err = gonanoid.ID(SlugLength)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
store := FromCtx(ctx)
|
||||||
|
|
||||||
|
var expT *jsontypes.Time
|
||||||
|
if exp != nil {
|
||||||
|
tt := time.Now().Add(*exp)
|
||||||
|
expT = (*jsontypes.Time)(&tt)
|
||||||
|
}
|
||||||
|
|
||||||
|
share := &Share{
|
||||||
|
ID: id,
|
||||||
|
Type: shType,
|
||||||
|
EntityID: shID,
|
||||||
|
Expiration: expT,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = store.Create(ctx, share)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return id, nil
|
||||||
|
}
|
85
pkg/share/store.go
Normal file
85
pkg/share/store.go
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
package share
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Store interface {
|
||||||
|
// Get retreives a share record.
|
||||||
|
Get(ctx context.Context, id string) (*Share, error)
|
||||||
|
|
||||||
|
// Create stores a new share record.
|
||||||
|
Create(ctx context.Context, share *Share) error
|
||||||
|
|
||||||
|
// Delete deletes a share record.
|
||||||
|
Delete(ctx context.Context, id string) error
|
||||||
|
|
||||||
|
// Prune removes expired share records.
|
||||||
|
Prune(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type postgresStore struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func recToShare(share database.Share) *Share {
|
||||||
|
return &Share{
|
||||||
|
ID: share.ID,
|
||||||
|
Type: EntityType(share.EntityType),
|
||||||
|
EntityID: share.EntityID,
|
||||||
|
Expiration: jsontypes.TimePtrFromTSTZ(share.Expiration),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *postgresStore) Get(ctx context.Context, id string) (*Share, error) {
|
||||||
|
db := database.FromCtx(ctx)
|
||||||
|
rec, err := db.GetShare(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return recToShare(rec), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *postgresStore) Create(ctx context.Context, share *Share) error {
|
||||||
|
db := database.FromCtx(ctx)
|
||||||
|
err := db.CreateShare(ctx, database.CreateShareParams{
|
||||||
|
ID: share.ID,
|
||||||
|
EntityType: string(share.Type),
|
||||||
|
EntityID: share.EntityID,
|
||||||
|
Expiration: share.Expiration.PGTypeTSTZ(),
|
||||||
|
})
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *postgresStore) Delete(ctx context.Context, id string) error {
|
||||||
|
return database.FromCtx(ctx).DeleteShare(ctx, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *postgresStore) Prune(ctx context.Context) error {
|
||||||
|
return database.FromCtx(ctx).PruneShares(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStore() *postgresStore {
|
||||||
|
return new(postgresStore)
|
||||||
|
}
|
||||||
|
|
||||||
|
type storeCtxKey string
|
||||||
|
|
||||||
|
const StoreCtxKey storeCtxKey = "store"
|
||||||
|
|
||||||
|
func CtxWithStore(ctx context.Context, s Store) context.Context {
|
||||||
|
return context.WithValue(ctx, StoreCtxKey, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FromCtx(ctx context.Context) Store {
|
||||||
|
s, ok := ctx.Value(StoreCtxKey).(Store)
|
||||||
|
if !ok {
|
||||||
|
return NewStore()
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
|
@ -163,3 +163,11 @@ CREATE TABLE IF NOT EXISTS incidents_calls(
|
||||||
FOREIGN KEY (calls_tbl_id, call_date) REFERENCES calls(id, call_date),
|
FOREIGN KEY (calls_tbl_id, call_date) REFERENCES calls(id, call_date),
|
||||||
PRIMARY KEY (incident_id, call_id)
|
PRIMARY KEY (incident_id, call_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS shares(
|
||||||
|
id TEXT PRIMARY KEY,
|
||||||
|
entity_type TEXT NOT NULL,
|
||||||
|
entity_id UUID NOT NULL,
|
||||||
|
owner INTEGER NOT NULL REFERENCES users(id),
|
||||||
|
expiration TIMESTAMPTZ NULL
|
||||||
|
);
|
||||||
|
|
24
sql/postgres/queries/share.sql
Normal file
24
sql/postgres/queries/share.sql
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
-- name: GetShare :one
|
||||||
|
SELECT
|
||||||
|
id,
|
||||||
|
entity_type,
|
||||||
|
entity_id,
|
||||||
|
owner,
|
||||||
|
expiration
|
||||||
|
FROM shares
|
||||||
|
WHERE id = @id;
|
||||||
|
|
||||||
|
-- name: CreateShare :exec
|
||||||
|
INSERT INTO shares (
|
||||||
|
id,
|
||||||
|
entity_type,
|
||||||
|
entity_id,
|
||||||
|
owner,
|
||||||
|
expiration
|
||||||
|
) VALUES (@id, @entity_type, @entity_id, @owner, sqlc.narg('expiration'));
|
||||||
|
|
||||||
|
-- name: DeleteShare :exec
|
||||||
|
DELETE FROM shares WHERE id = @id;
|
||||||
|
|
||||||
|
-- name: PruneShares :exec
|
||||||
|
DELETE FROM shares WHERE expiration < NOW();
|
Loading…
Reference in a new issue