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
|
||||
}
|
||||
|
||||
// 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
|
||||
func (_m *Store) GetSystemName(ctx context.Context, systemID int) (string, error) {
|
||||
ret := _m.Called(ctx, systemID)
|
||||
|
|
|
@ -42,6 +42,8 @@ type Querier interface {
|
|||
GetIncidentOwner(ctx context.Context, id uuid.UUID) (int, error)
|
||||
GetIncidentTalkgroups(ctx context.Context, incidentID uuid.UUID) ([]GetIncidentTalkgroupsRow, 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)
|
||||
GetTalkgroup(ctx context.Context, systemID int32, tGID int32) (GetTalkgroupRow, 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
|
||||
}
|
||||
|
||||
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
|
||||
DELETE FROM shares WHERE expiration < NOW()
|
||||
`
|
||||
|
|
|
@ -121,6 +121,7 @@ func (sa *shareAPI) Subrouter() http.Handler {
|
|||
|
||||
r.Post(`/create`, sa.createShare)
|
||||
r.Delete(`/{id:[A-Za-z0-9_-]{20,}}`, sa.deleteShare)
|
||||
r.Post(`/`, sa.listShares)
|
||||
|
||||
return r
|
||||
}
|
||||
|
@ -156,6 +157,34 @@ func (sa *shareAPI) createShare(w http.ResponseWriter, r *http.Request) {
|
|||
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) {
|
||||
ctx := r.Context()
|
||||
shs := shares.FromCtx(ctx)
|
||||
|
|
|
@ -3,7 +3,9 @@ package shares
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/common"
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/rbac"
|
||||
|
@ -12,13 +14,21 @@ import (
|
|||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type SharesParams struct {
|
||||
common.Pagination
|
||||
Direction *common.SortDirection `json:"dir"`
|
||||
}
|
||||
|
||||
type Shares interface {
|
||||
// NewShare creates a new share.
|
||||
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)
|
||||
|
||||
// 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(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)
|
||||
}
|
||||
|
||||
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 {
|
||||
return database.FromCtx(ctx).PruneShares(ctx)
|
||||
}
|
||||
|
|
|
@ -24,3 +24,30 @@ DELETE FROM shares WHERE id = @id;
|
|||
|
||||
-- name: PruneShares :exec
|
||||
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