Fix pagination cursor dec/enc + finish impl
This commit is contained in:
@@ -505,15 +505,9 @@ endpoints:
|
||||
- type: uint
|
||||
name: limit
|
||||
title: Limit
|
||||
- type: uint
|
||||
name: offset
|
||||
title: Offset
|
||||
- type: uint
|
||||
name: page
|
||||
title: Page number (1-based)
|
||||
- type: uint
|
||||
name: perPage
|
||||
title: Returned items per page (default 50)
|
||||
- type: string
|
||||
name: pageCursor
|
||||
title: Page cursor
|
||||
- type: string
|
||||
name: sort
|
||||
title: Sort items
|
||||
@@ -903,15 +897,9 @@ endpoints:
|
||||
- type: uint
|
||||
name: limit
|
||||
title: Limit
|
||||
- type: uint
|
||||
name: offset
|
||||
title: Offset
|
||||
- type: uint
|
||||
name: page
|
||||
title: Page number (1-based)
|
||||
- type: uint
|
||||
name: perPage
|
||||
title: Returned items per page (default 50)
|
||||
- type: string
|
||||
name: pageCursor
|
||||
title: Page cursor
|
||||
- name: read
|
||||
path: "/{attachmentID}"
|
||||
method: GET
|
||||
|
||||
@@ -51,8 +51,6 @@ func (ctrl Attachment) List(ctx context.Context, r *request.AttachmentList) (int
|
||||
ModuleID: r.ModuleID,
|
||||
RecordID: r.RecordID,
|
||||
FieldName: r.FieldName,
|
||||
|
||||
//PageFilter: rh.Paging(r),
|
||||
}
|
||||
|
||||
set, filter, err := ctrl.attachment.With(ctx).Find(f)
|
||||
|
||||
@@ -78,14 +78,14 @@ func (ctrl *Record) List(ctx context.Context, r *request.RecordList) (interface{
|
||||
m *types.Module
|
||||
err error
|
||||
|
||||
rf = types.RecordFilter{
|
||||
f = types.RecordFilter{
|
||||
NamespaceID: r.NamespaceID,
|
||||
ModuleID: r.ModuleID,
|
||||
Deleted: filter.State(r.Deleted),
|
||||
}
|
||||
)
|
||||
|
||||
if err = rf.Sort.Set(r.Sort); err != nil {
|
||||
if err = f.Sort.Set(r.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -95,14 +95,22 @@ func (ctrl *Record) List(ctx context.Context, r *request.RecordList) (interface{
|
||||
|
||||
if r.Query != "" {
|
||||
// Query param takes preference
|
||||
rf.Query = r.Query
|
||||
f.Query = r.Query
|
||||
} else if r.Filter != "" {
|
||||
// Backward compatibility
|
||||
// Filter param is deprecated
|
||||
rf.Query = r.Filter
|
||||
f.Query = r.Filter
|
||||
}
|
||||
|
||||
rr, filter, err := ctrl.record.With(ctx).Find(rf)
|
||||
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rr, filter, err := ctrl.record.With(ctx).Find(f)
|
||||
|
||||
return ctrl.makeFilterPayload(ctx, m, rr, &filter, err)
|
||||
}
|
||||
|
||||
@@ -75,20 +75,10 @@ type (
|
||||
// Limit
|
||||
Limit uint
|
||||
|
||||
// Offset GET parameter
|
||||
// PageCursor GET parameter
|
||||
//
|
||||
// Offset
|
||||
Offset uint
|
||||
|
||||
// Page GET parameter
|
||||
//
|
||||
// Page number (1-based)
|
||||
Page uint
|
||||
|
||||
// PerPage GET parameter
|
||||
//
|
||||
// Returned items per page (default 50)
|
||||
PerPage uint
|
||||
// Page cursor
|
||||
PageCursor string
|
||||
}
|
||||
|
||||
AttachmentRead struct {
|
||||
@@ -232,9 +222,7 @@ func (r AttachmentList) Auditable() map[string]interface{} {
|
||||
"recordID": r.RecordID,
|
||||
"fieldName": r.FieldName,
|
||||
"limit": r.Limit,
|
||||
"offset": r.Offset,
|
||||
"page": r.Page,
|
||||
"perPage": r.PerPage,
|
||||
"pageCursor": r.PageCursor,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,18 +272,8 @@ func (r AttachmentList) GetLimit() uint {
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetOffset() uint {
|
||||
return r.Offset
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetPage() uint {
|
||||
return r.Page
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r AttachmentList) GetPerPage() uint {
|
||||
return r.PerPage
|
||||
func (r AttachmentList) GetPageCursor() string {
|
||||
return r.PageCursor
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
@@ -357,20 +335,8 @@ func (r *AttachmentList) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["offset"]; ok && len(val) > 0 {
|
||||
r.Offset, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["page"]; ok && len(val) > 0 {
|
||||
r.Page, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["perPage"]; ok && len(val) > 0 {
|
||||
r.PerPage, err = payload.ParseUint(val[0]), nil
|
||||
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
|
||||
r.PageCursor, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -88,20 +88,10 @@ type (
|
||||
// Limit
|
||||
Limit uint
|
||||
|
||||
// Offset GET parameter
|
||||
// PageCursor GET parameter
|
||||
//
|
||||
// Offset
|
||||
Offset uint
|
||||
|
||||
// Page GET parameter
|
||||
//
|
||||
// Page number (1-based)
|
||||
Page uint
|
||||
|
||||
// PerPage GET parameter
|
||||
//
|
||||
// Returned items per page (default 50)
|
||||
PerPage uint
|
||||
// Page cursor
|
||||
PageCursor string
|
||||
|
||||
// Sort GET parameter
|
||||
//
|
||||
@@ -519,9 +509,7 @@ func (r RecordList) Auditable() map[string]interface{} {
|
||||
"filter": r.Filter,
|
||||
"deleted": r.Deleted,
|
||||
"limit": r.Limit,
|
||||
"offset": r.Offset,
|
||||
"page": r.Page,
|
||||
"perPage": r.PerPage,
|
||||
"pageCursor": r.PageCursor,
|
||||
"sort": r.Sort,
|
||||
}
|
||||
}
|
||||
@@ -557,18 +545,8 @@ func (r RecordList) GetLimit() uint {
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r RecordList) GetOffset() uint {
|
||||
return r.Offset
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r RecordList) GetPage() uint {
|
||||
return r.Page
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r RecordList) GetPerPage() uint {
|
||||
return r.PerPage
|
||||
func (r RecordList) GetPageCursor() string {
|
||||
return r.PageCursor
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
@@ -617,20 +595,8 @@ func (r *RecordList) Fill(req *http.Request) (err error) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["offset"]; ok && len(val) > 0 {
|
||||
r.Offset, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["page"]; ok && len(val) > 0 {
|
||||
r.Page, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["perPage"]; ok && len(val) > 0 {
|
||||
r.PerPage, err = payload.ParseUint(val[0]), nil
|
||||
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
|
||||
r.PageCursor, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package types
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"encoding/json"
|
||||
"github.com/cortezaproject/corteza-server/pkg/filter"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -42,6 +43,9 @@ type (
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(*Attachment) (bool, error)
|
||||
|
||||
// Standard helpers for paging and sorting
|
||||
filter.Paging
|
||||
}
|
||||
|
||||
attachmentImageMeta struct {
|
||||
|
||||
@@ -85,7 +85,7 @@ func (p *PagingCursor) String() string {
|
||||
}
|
||||
|
||||
func (p *PagingCursor) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(struct {
|
||||
buf, err := json.Marshal(struct {
|
||||
K []string
|
||||
V []interface{}
|
||||
D []bool
|
||||
@@ -96,11 +96,26 @@ func (p *PagingCursor) MarshalJSON() ([]byte, error) {
|
||||
p.desc,
|
||||
p.Reverse,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
std := base64.StdEncoding
|
||||
dbuf := make([]byte, std.EncodedLen(len(buf)))
|
||||
std.Encode(dbuf, buf)
|
||||
|
||||
return append([]byte{'"'}, append(dbuf, '"')...), nil
|
||||
}
|
||||
|
||||
func (p *PagingCursor) Encode() string {
|
||||
b, _ := p.MarshalJSON()
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (p *PagingCursor) UnmarshalJSON(in []byte) error {
|
||||
var (
|
||||
aux *struct {
|
||||
aux struct {
|
||||
K []string
|
||||
V []interface{}
|
||||
D []bool
|
||||
@@ -108,7 +123,7 @@ func (p *PagingCursor) UnmarshalJSON(in []byte) error {
|
||||
}
|
||||
)
|
||||
|
||||
if err := json.Unmarshal(in, aux); err != nil {
|
||||
if err := json.Unmarshal(in, &aux); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -120,6 +135,20 @@ func (p *PagingCursor) UnmarshalJSON(in []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PagingCursor) Decode(cursor string) error {
|
||||
b, err := base64.StdEncoding.DecodeString(cursor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = p.UnmarshalJSON(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func parseCursor(in string) (p *PagingCursor, err error) {
|
||||
if len(in) == 0 {
|
||||
return nil, nil
|
||||
@@ -130,7 +159,8 @@ func parseCursor(in string) (p *PagingCursor, err error) {
|
||||
return nil, fmt.Errorf("could not decode cursor: %w", err)
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(buf, p); err != nil {
|
||||
p = &PagingCursor{}
|
||||
if err = p.UnmarshalJSON(buf); err != nil {
|
||||
return nil, fmt.Errorf("could not decode cursor: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,61 @@ func TestUserListAll(t *testing.T) {
|
||||
End()
|
||||
}
|
||||
|
||||
func TestUserListWithPaging(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearUsers()
|
||||
|
||||
h.secCtx()
|
||||
|
||||
seedCount := 40
|
||||
for i := 0; i < seedCount; i++ {
|
||||
h.createUserWithEmail(h.randEmail())
|
||||
}
|
||||
|
||||
h.allow(types.UserRBACResource.AppendWildcard(), "read")
|
||||
|
||||
var aux = struct {
|
||||
Response struct {
|
||||
Filter struct {
|
||||
NextPage *string
|
||||
PrevPage *string
|
||||
}
|
||||
}
|
||||
}{}
|
||||
|
||||
h.apiInit().
|
||||
Get("/users/").
|
||||
Query("limit", "13").
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(helpers.AssertNoErrors).
|
||||
Assert(jsonpath.Present(`$.response.filter`)).
|
||||
Assert(jsonpath.Present(`$.response.set`)).
|
||||
Assert(jsonpath.Len(`$.response.set`, 13)).
|
||||
Assert(jsonpath.Present(`$.response.filter.nextPage`)).
|
||||
End().
|
||||
JSON(&aux)
|
||||
|
||||
h.a.NotNil(aux.Response.Filter.NextPage)
|
||||
|
||||
h.apiInit().
|
||||
Debug().
|
||||
Get("/users/").
|
||||
Query("limit", "13").
|
||||
Query("pageCursor", *aux.Response.Filter.NextPage).
|
||||
Expect(t).
|
||||
Status(http.StatusOK).
|
||||
Assert(jsonpath.Len(`$.response.set`, 13)).
|
||||
Assert(jsonpath.Present(`$.response.filter.prevPage`)).
|
||||
Assert(jsonpath.Present(`$.response.filter.nextPage`)).
|
||||
End().
|
||||
JSON(&aux)
|
||||
|
||||
h.a.NotNil(aux.Response.Filter.PrevPage)
|
||||
h.a.NotNil(aux.Response.Filter.NextPage)
|
||||
|
||||
}
|
||||
|
||||
func TestUserList_filterForbidden(t *testing.T) {
|
||||
h := newHelper(t)
|
||||
h.clearUsers()
|
||||
|
||||
Reference in New Issue
Block a user