Shares list endpoint
This commit is contained in:
parent
04977b5468
commit
d16ad6a4ad
6 changed files with 304 additions and 1 deletions
|
@ -1924,6 +1924,122 @@ func (_c *Store_GetShare_Call) RunAndReturn(run func(context.Context, string) (d
|
||||||
return _c
|
return _c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetSharesP provides a mock function with given fields: ctx, arg
|
||||||
|
func (_m *Store) GetSharesP(ctx context.Context, arg database.GetSharesPParams) ([]database.Share, error) {
|
||||||
|
ret := _m.Called(ctx, arg)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for GetSharesP")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 []database.Share
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, database.GetSharesPParams) ([]database.Share, error)); ok {
|
||||||
|
return rf(ctx, arg)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, database.GetSharesPParams) []database.Share); ok {
|
||||||
|
r0 = rf(ctx, arg)
|
||||||
|
} else {
|
||||||
|
if ret.Get(0) != nil {
|
||||||
|
r0 = ret.Get(0).([]database.Share)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, database.GetSharesPParams) error); ok {
|
||||||
|
r1 = rf(ctx, arg)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store_GetSharesP_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSharesP'
|
||||||
|
type Store_GetSharesP_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSharesP is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - arg database.GetSharesPParams
|
||||||
|
func (_e *Store_Expecter) GetSharesP(ctx interface{}, arg interface{}) *Store_GetSharesP_Call {
|
||||||
|
return &Store_GetSharesP_Call{Call: _e.mock.On("GetSharesP", ctx, arg)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_GetSharesP_Call) Run(run func(ctx context.Context, arg database.GetSharesPParams)) *Store_GetSharesP_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(database.GetSharesPParams))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_GetSharesP_Call) Return(_a0 []database.Share, _a1 error) *Store_GetSharesP_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_GetSharesP_Call) RunAndReturn(run func(context.Context, database.GetSharesPParams) ([]database.Share, error)) *Store_GetSharesP_Call {
|
||||||
|
_c.Call.Return(run)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSharesPCount provides a mock function with given fields: ctx, owner
|
||||||
|
func (_m *Store) GetSharesPCount(ctx context.Context, owner *int32) (int64, error) {
|
||||||
|
ret := _m.Called(ctx, owner)
|
||||||
|
|
||||||
|
if len(ret) == 0 {
|
||||||
|
panic("no return value specified for GetSharesPCount")
|
||||||
|
}
|
||||||
|
|
||||||
|
var r0 int64
|
||||||
|
var r1 error
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, *int32) (int64, error)); ok {
|
||||||
|
return rf(ctx, owner)
|
||||||
|
}
|
||||||
|
if rf, ok := ret.Get(0).(func(context.Context, *int32) int64); ok {
|
||||||
|
r0 = rf(ctx, owner)
|
||||||
|
} else {
|
||||||
|
r0 = ret.Get(0).(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rf, ok := ret.Get(1).(func(context.Context, *int32) error); ok {
|
||||||
|
r1 = rf(ctx, owner)
|
||||||
|
} else {
|
||||||
|
r1 = ret.Error(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return r0, r1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store_GetSharesPCount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetSharesPCount'
|
||||||
|
type Store_GetSharesPCount_Call struct {
|
||||||
|
*mock.Call
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSharesPCount is a helper method to define mock.On call
|
||||||
|
// - ctx context.Context
|
||||||
|
// - owner *int32
|
||||||
|
func (_e *Store_Expecter) GetSharesPCount(ctx interface{}, owner interface{}) *Store_GetSharesPCount_Call {
|
||||||
|
return &Store_GetSharesPCount_Call{Call: _e.mock.On("GetSharesPCount", ctx, owner)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_GetSharesPCount_Call) Run(run func(ctx context.Context, owner *int32)) *Store_GetSharesPCount_Call {
|
||||||
|
_c.Call.Run(func(args mock.Arguments) {
|
||||||
|
run(args[0].(context.Context), args[1].(*int32))
|
||||||
|
})
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_GetSharesPCount_Call) Return(_a0 int64, _a1 error) *Store_GetSharesPCount_Call {
|
||||||
|
_c.Call.Return(_a0, _a1)
|
||||||
|
return _c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_c *Store_GetSharesPCount_Call) RunAndReturn(run func(context.Context, *int32) (int64, error)) *Store_GetSharesPCount_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)
|
||||||
|
|
|
@ -42,6 +42,8 @@ type Querier interface {
|
||||||
GetIncidentOwner(ctx context.Context, id uuid.UUID) (int, error)
|
GetIncidentOwner(ctx context.Context, id uuid.UUID) (int, error)
|
||||||
GetIncidentTalkgroups(ctx context.Context, incidentID uuid.UUID) ([]GetIncidentTalkgroupsRow, error)
|
GetIncidentTalkgroups(ctx context.Context, incidentID uuid.UUID) ([]GetIncidentTalkgroupsRow, error)
|
||||||
GetShare(ctx context.Context, id string) (Share, error)
|
GetShare(ctx context.Context, id string) (Share, error)
|
||||||
|
GetSharesP(ctx context.Context, arg GetSharesPParams) ([]Share, error)
|
||||||
|
GetSharesPCount(ctx context.Context, owner *int32) (int64, 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)
|
||||||
|
|
|
@ -79,6 +79,79 @@ func (q *Queries) GetShare(ctx context.Context, id string) (Share, error) {
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getSharesP = `-- name: GetSharesP :many
|
||||||
|
SELECT
|
||||||
|
s.id,
|
||||||
|
s.entity_type,
|
||||||
|
s.entity_id,
|
||||||
|
s.entity_date,
|
||||||
|
s.owner,
|
||||||
|
s.expiration
|
||||||
|
FROM shares s
|
||||||
|
WHERE
|
||||||
|
CASE WHEN $1::INTEGER IS NOT NULL THEN
|
||||||
|
s.owner = $1 ELSE TRUE END
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN $2::TEXT = 'asc' THEN s.entity_date END ASC,
|
||||||
|
CASE WHEN $2::TEXT = 'desc' THEN s.entity_date END DESC
|
||||||
|
OFFSET $3 ROWS
|
||||||
|
FETCH NEXT $4 ROWS ONLY
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetSharesPParams struct {
|
||||||
|
Owner *int32 `json:"owner"`
|
||||||
|
Direction string `json:"direction"`
|
||||||
|
Offset int32 `json:"offset"`
|
||||||
|
PerPage int32 `json:"perPage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetSharesP(ctx context.Context, arg GetSharesPParams) ([]Share, error) {
|
||||||
|
rows, err := q.db.Query(ctx, getSharesP,
|
||||||
|
arg.Owner,
|
||||||
|
arg.Direction,
|
||||||
|
arg.Offset,
|
||||||
|
arg.PerPage,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []Share
|
||||||
|
for rows.Next() {
|
||||||
|
var i Share
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.EntityType,
|
||||||
|
&i.EntityID,
|
||||||
|
&i.EntityDate,
|
||||||
|
&i.Owner,
|
||||||
|
&i.Expiration,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSharesPCount = `-- name: GetSharesPCount :one
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM shares s
|
||||||
|
WHERE
|
||||||
|
CASE WHEN $1::INTEGER IS NOT NULL THEN
|
||||||
|
s.owner = $1 ELSE TRUE END
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetSharesPCount(ctx context.Context, owner *int32) (int64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getSharesPCount, owner)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
const pruneShares = `-- name: PruneShares :exec
|
const pruneShares = `-- name: PruneShares :exec
|
||||||
DELETE FROM shares WHERE expiration < NOW()
|
DELETE FROM shares WHERE expiration < NOW()
|
||||||
`
|
`
|
||||||
|
|
|
@ -121,6 +121,7 @@ func (sa *shareAPI) Subrouter() http.Handler {
|
||||||
|
|
||||||
r.Post(`/create`, sa.createShare)
|
r.Post(`/create`, sa.createShare)
|
||||||
r.Delete(`/{id:[A-Za-z0-9_-]{20,}}`, sa.deleteShare)
|
r.Delete(`/{id:[A-Za-z0-9_-]{20,}}`, sa.deleteShare)
|
||||||
|
r.Post(`/`, sa.listShares)
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
@ -156,6 +157,34 @@ func (sa *shareAPI) createShare(w http.ResponseWriter, r *http.Request) {
|
||||||
respond(w, r, sh)
|
respond(w, r, sh)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (sa *shareAPI) listShares(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ctx := r.Context()
|
||||||
|
shs := shares.FromCtx(ctx)
|
||||||
|
|
||||||
|
p := shares.SharesParams{}
|
||||||
|
err := forms.Unmarshal(r, &p, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
|
||||||
|
if err != nil {
|
||||||
|
wErr(w, r, badRequest(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
shRes, count, err := shs.Shares(ctx, p)
|
||||||
|
if err != nil {
|
||||||
|
wErr(w, r, autoError(err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := struct {
|
||||||
|
Shares []*shares.Share `json:"shares"`
|
||||||
|
TotalCount int `json:"totalCount"`
|
||||||
|
}{
|
||||||
|
Shares: shRes,
|
||||||
|
TotalCount: count,
|
||||||
|
}
|
||||||
|
|
||||||
|
respond(w, r, &response)
|
||||||
|
}
|
||||||
|
|
||||||
func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
|
func (sa *shareAPI) routeShare(w http.ResponseWriter, r *http.Request) {
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
shs := shares.FromCtx(ctx)
|
shs := shares.FromCtx(ctx)
|
||||||
|
|
|
@ -3,7 +3,9 @@ package shares
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"dynatron.me/x/stillbox/internal/common"
|
||||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||||
"dynatron.me/x/stillbox/pkg/database"
|
"dynatron.me/x/stillbox/pkg/database"
|
||||||
"dynatron.me/x/stillbox/pkg/rbac"
|
"dynatron.me/x/stillbox/pkg/rbac"
|
||||||
|
@ -12,13 +14,21 @@ import (
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type SharesParams struct {
|
||||||
|
common.Pagination
|
||||||
|
Direction *common.SortDirection `json:"dir"`
|
||||||
|
}
|
||||||
|
|
||||||
type Shares interface {
|
type Shares interface {
|
||||||
// NewShare creates a new share.
|
// NewShare creates a new share.
|
||||||
NewShare(ctx context.Context, sh CreateShareParams) (*Share, error)
|
NewShare(ctx context.Context, sh CreateShareParams) (*Share, error)
|
||||||
|
|
||||||
// Share retreives a share record.
|
// Share retrieves a share record.
|
||||||
GetShare(ctx context.Context, id string) (*Share, error)
|
GetShare(ctx context.Context, id string) (*Share, error)
|
||||||
|
|
||||||
|
// Shares retrieves shares visible by the context Subject.
|
||||||
|
Shares(ctx context.Context, p SharesParams) (shares []*Share, totalCount int, err error)
|
||||||
|
|
||||||
// Create stores a new share record.
|
// Create stores a new share record.
|
||||||
Create(ctx context.Context, share *Share) error
|
Create(ctx context.Context, share *Share) error
|
||||||
|
|
||||||
|
@ -98,6 +108,52 @@ func (s *postgresStore) Delete(ctx context.Context, id string) error {
|
||||||
return database.FromCtx(ctx).DeleteShare(ctx, id)
|
return database.FromCtx(ctx).DeleteShare(ctx, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *postgresStore) Shares(ctx context.Context, p SharesParams) (shares []*Share, totalCount int, err error) {
|
||||||
|
sub := entities.SubjectFrom(ctx)
|
||||||
|
|
||||||
|
// ersatz RBAC
|
||||||
|
owner := common.PtrTo(int32(-1)) // invalid UID
|
||||||
|
switch s := sub.(type) {
|
||||||
|
case *users.User:
|
||||||
|
if !s.IsAdmin {
|
||||||
|
owner = s.ID.Int32Ptr()
|
||||||
|
} else {
|
||||||
|
owner = nil
|
||||||
|
}
|
||||||
|
case *entities.SystemServiceSubject:
|
||||||
|
owner = nil
|
||||||
|
default:
|
||||||
|
return nil, 0, rbac.ErrAccessDenied(rbac.ErrNotAuthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
db := database.FromCtx(ctx)
|
||||||
|
|
||||||
|
count, err := db.GetSharesPCount(ctx, owner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, fmt.Errorf("shares count: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset, perPage := p.Pagination.OffsetPerPage(100)
|
||||||
|
dbParam := database.GetSharesPParams{
|
||||||
|
Owner: owner,
|
||||||
|
Direction: p.Direction.DirString(common.DirAsc),
|
||||||
|
Offset: offset,
|
||||||
|
PerPage: perPage,
|
||||||
|
}
|
||||||
|
|
||||||
|
shs, err := db.GetSharesP(ctx, dbParam)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
shares = make([]*Share, 0, len(shs))
|
||||||
|
for _, v := range shs {
|
||||||
|
shares = append(shares, recToShare(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
return shares, int(count), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *postgresStore) Prune(ctx context.Context) error {
|
func (s *postgresStore) Prune(ctx context.Context) error {
|
||||||
return database.FromCtx(ctx).PruneShares(ctx)
|
return database.FromCtx(ctx).PruneShares(ctx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,3 +24,30 @@ DELETE FROM shares WHERE id = @id;
|
||||||
|
|
||||||
-- name: PruneShares :exec
|
-- name: PruneShares :exec
|
||||||
DELETE FROM shares WHERE expiration < NOW();
|
DELETE FROM shares WHERE expiration < NOW();
|
||||||
|
|
||||||
|
-- name: GetSharesP :many
|
||||||
|
SELECT
|
||||||
|
s.id,
|
||||||
|
s.entity_type,
|
||||||
|
s.entity_id,
|
||||||
|
s.entity_date,
|
||||||
|
s.owner,
|
||||||
|
s.expiration
|
||||||
|
FROM shares s
|
||||||
|
WHERE
|
||||||
|
CASE WHEN sqlc.narg('owner')::INTEGER IS NOT NULL THEN
|
||||||
|
s.owner = @owner ELSE TRUE END
|
||||||
|
ORDER BY
|
||||||
|
CASE WHEN @direction::TEXT = 'asc' THEN s.entity_date END ASC,
|
||||||
|
CASE WHEN @direction::TEXT = 'desc' THEN s.entity_date END DESC
|
||||||
|
OFFSET sqlc.arg('offset') ROWS
|
||||||
|
FETCH NEXT sqlc.arg('per_page') ROWS ONLY
|
||||||
|
;
|
||||||
|
|
||||||
|
-- name: GetSharesPCount :one
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM shares s
|
||||||
|
WHERE
|
||||||
|
CASE WHEN sqlc.narg('owner')::INTEGER IS NOT NULL THEN
|
||||||
|
s.owner = @owner ELSE TRUE END
|
||||||
|
;
|
||||||
|
|
Loading…
Add table
Reference in a new issue