Implement new paging with resource check on filter
This commit is contained in:
@@ -41,6 +41,12 @@ type (
|
||||
|
||||
// Standard paging fields & helpers
|
||||
rh.PageFilter
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(user *Attachment) (bool, error)
|
||||
}
|
||||
|
||||
attachmentImageMeta struct {
|
||||
|
||||
@@ -54,6 +54,12 @@ type (
|
||||
// Resource permission check filter
|
||||
IsReadable *permissions.ResourceFilter `json:"-"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(user *Chart) (bool, error)
|
||||
|
||||
Deleted rh.FilterState `json:"deleted"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -38,6 +38,12 @@ type (
|
||||
// Resource permission check filter
|
||||
IsReadable *permissions.ResourceFilter `json:"-"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(user *Module) (bool, error)
|
||||
|
||||
Deleted rh.FilterState `json:"deleted"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -36,6 +36,12 @@ type (
|
||||
// Resource permission check filter
|
||||
IsReadable *permissions.ResourceFilter `json:"-"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(user *Namespace) (bool, error)
|
||||
|
||||
Deleted rh.FilterState `json:"deleted"`
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,12 @@ type (
|
||||
// Resource permission check filter
|
||||
IsReadable *permissions.ResourceFilter `json:"-"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(user *Page) (bool, error)
|
||||
|
||||
Deleted rh.FilterState `json:"deleted"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -57,6 +57,12 @@ type (
|
||||
// Standard paging fields & helpers
|
||||
rh.PageFilter
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(user *Record) (bool, error)
|
||||
|
||||
Deleted rh.FilterState `json:"deleted"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -39,44 +39,139 @@ func (s Store) Search{{ pubIdent $.Types.Plural }}(ctx context.Context, f {{ $.T
|
||||
{{ if $.Search.DisablePaging }}
|
||||
scap := DefaultSliceCapacity
|
||||
{{ else }}
|
||||
q = ApplyPaging(q, f.PageFilter)
|
||||
|
||||
scap := f.PerPage
|
||||
if scap == 0 {
|
||||
scap = DefaultSliceCapacity
|
||||
}
|
||||
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return nil, f, err
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
var (
|
||||
set = make([]*{{ $.Types.GoType }}, 0, scap)
|
||||
res *{{ $.Types.GoType }}
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
{{- if not $.Search.DisablePaging }}
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return err
|
||||
}
|
||||
{{- end }}
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internal{{ pubIdent $.Types.Singular }}RowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
{{- if $.Search.DisablePaging }}
|
||||
fetch = func() error {
|
||||
var (
|
||||
res *{{ $.Types.GoType }}
|
||||
rows, err = s.Query(ctx, q)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
for rows.Next() {
|
||||
if res, err = s.internal{{ pubIdent $.Types.Singular }}RowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}
|
||||
{{ else }}
|
||||
// @todo this offset needs to be removed and replaced with key-based-paging
|
||||
fetchPage = func(offset, limit uint) (fetched, skipped uint, err error) {
|
||||
var (
|
||||
res *{{ $.Types.GoType }}
|
||||
chk bool
|
||||
)
|
||||
|
||||
if limit > 0 {
|
||||
q = q.Limit(uint64(limit))
|
||||
}
|
||||
|
||||
if offset > 0 {
|
||||
q = q.Offset(uint64(offset))
|
||||
}
|
||||
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
fetched++
|
||||
if res, err = s.internal{{ pubIdent $.Types.Singular }}RowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If check function is set, call it and act accordingly
|
||||
if f.Check != nil {
|
||||
if chk, err = f.Check(res); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
} else if !chk {
|
||||
// did not pass the check
|
||||
// go with the next row
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
|
||||
// make sure we do not fetch more than requested!
|
||||
if f.Limit > 0 && uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
fetch = func() error {
|
||||
var (
|
||||
fetched uint
|
||||
|
||||
// starting offset & limit are from filter arg
|
||||
// note that this will have to be improved with key-based pagination
|
||||
offset, limit = calculatePaging(f.PageFilter)
|
||||
)
|
||||
|
||||
for refetch := 0; refetch < MaxRefetches; refetch++ {
|
||||
if fetched, _, err = fetchPage(offset, limit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if limit is not set or we've already collected enough resources
|
||||
// we can break the loop right away
|
||||
if limit == 0 || fetched == 0 || uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
// we've skipped fetched resources (due to check() fn)
|
||||
// and we still have less results (in set) than required by limit
|
||||
// inc offset by number of fetched items
|
||||
offset += fetched
|
||||
|
||||
if limit < MinRefetchLimit {
|
||||
limit = MinRefetchLimit
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
{{ end -}}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
|
||||
@@ -31,30 +31,34 @@ func (s Store) SearchActionlogs(ctx context.Context, f actionlog.Filter) (action
|
||||
scap := DefaultSliceCapacity
|
||||
|
||||
var (
|
||||
set = make([]*actionlog.Action, 0, scap)
|
||||
res *actionlog.Action
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalActionlogRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
set = make([]*actionlog.Action, 0, scap)
|
||||
fetch = func() error {
|
||||
var (
|
||||
res *actionlog.Action
|
||||
rows, err = s.Query(ctx, q)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
}
|
||||
for rows.Next() {
|
||||
if res, err = s.internalActionlogRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
return err
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// CreateActionlog creates one or more rows in actionlog table
|
||||
|
||||
@@ -28,41 +28,110 @@ func (s Store) SearchApplications(ctx context.Context, f types.ApplicationFilter
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
q = ApplyPaging(q, f.PageFilter)
|
||||
|
||||
scap := f.PerPage
|
||||
if scap == 0 {
|
||||
scap = DefaultSliceCapacity
|
||||
}
|
||||
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
var (
|
||||
set = make([]*types.Application, 0, scap)
|
||||
res *types.Application
|
||||
)
|
||||
// @todo this offset needs to be removed and replaced with key-based-paging
|
||||
fetchPage = func(offset, limit uint) (fetched, skipped uint, err error) {
|
||||
var (
|
||||
res *types.Application
|
||||
chk bool
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return err
|
||||
}
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalApplicationRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
if limit > 0 {
|
||||
q = q.Limit(uint64(limit))
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
if offset > 0 {
|
||||
q = q.Offset(uint64(offset))
|
||||
}
|
||||
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
fetched++
|
||||
if res, err = s.internalApplicationRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If check function is set, call it and act accordingly
|
||||
if f.Check != nil {
|
||||
if chk, err = f.Check(res); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
} else if !chk {
|
||||
// did not pass the check
|
||||
// go with the next row
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
|
||||
// make sure we do not fetch more than requested!
|
||||
if f.Limit > 0 && uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
fetch = func() error {
|
||||
var (
|
||||
fetched uint
|
||||
|
||||
// starting offset & limit are from filter arg
|
||||
// note that this will have to be improved with key-based pagination
|
||||
offset, limit = calculatePaging(f.PageFilter)
|
||||
)
|
||||
|
||||
for refetch := 0; refetch < MaxRefetches; refetch++ {
|
||||
if fetched, _, err = fetchPage(offset, limit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if limit is not set or we've already collected enough resources
|
||||
// we can break the loop right away
|
||||
if limit == 0 || fetched == 0 || uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
// we've skipped fetched resources (due to check() fn)
|
||||
// and we still have less results (in set) than required by limit
|
||||
// inc offset by number of fetched items
|
||||
offset += fetched
|
||||
|
||||
if limit < MinRefetchLimit {
|
||||
limit = MinRefetchLimit
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// LookupApplicationByID searches for application by ID
|
||||
|
||||
@@ -28,41 +28,110 @@ func (s Store) SearchAttachments(ctx context.Context, f types.AttachmentFilter)
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
q = ApplyPaging(q, f.PageFilter)
|
||||
|
||||
scap := f.PerPage
|
||||
if scap == 0 {
|
||||
scap = DefaultSliceCapacity
|
||||
}
|
||||
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
var (
|
||||
set = make([]*types.Attachment, 0, scap)
|
||||
res *types.Attachment
|
||||
)
|
||||
// @todo this offset needs to be removed and replaced with key-based-paging
|
||||
fetchPage = func(offset, limit uint) (fetched, skipped uint, err error) {
|
||||
var (
|
||||
res *types.Attachment
|
||||
chk bool
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return err
|
||||
}
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalAttachmentRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
if limit > 0 {
|
||||
q = q.Limit(uint64(limit))
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
if offset > 0 {
|
||||
q = q.Offset(uint64(offset))
|
||||
}
|
||||
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
fetched++
|
||||
if res, err = s.internalAttachmentRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If check function is set, call it and act accordingly
|
||||
if f.Check != nil {
|
||||
if chk, err = f.Check(res); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
} else if !chk {
|
||||
// did not pass the check
|
||||
// go with the next row
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
|
||||
// make sure we do not fetch more than requested!
|
||||
if f.Limit > 0 && uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
fetch = func() error {
|
||||
var (
|
||||
fetched uint
|
||||
|
||||
// starting offset & limit are from filter arg
|
||||
// note that this will have to be improved with key-based pagination
|
||||
offset, limit = calculatePaging(f.PageFilter)
|
||||
)
|
||||
|
||||
for refetch := 0; refetch < MaxRefetches; refetch++ {
|
||||
if fetched, _, err = fetchPage(offset, limit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if limit is not set or we've already collected enough resources
|
||||
// we can break the loop right away
|
||||
if limit == 0 || fetched == 0 || uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
// we've skipped fetched resources (due to check() fn)
|
||||
// and we still have less results (in set) than required by limit
|
||||
// inc offset by number of fetched items
|
||||
offset += fetched
|
||||
|
||||
if limit < MinRefetchLimit {
|
||||
limit = MinRefetchLimit
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// LookupAttachmentByID searches for attachment by its ID
|
||||
|
||||
@@ -28,41 +28,110 @@ func (s Store) SearchComposeCharts(ctx context.Context, f types.ChartFilter) (ty
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
q = ApplyPaging(q, f.PageFilter)
|
||||
|
||||
scap := f.PerPage
|
||||
if scap == 0 {
|
||||
scap = DefaultSliceCapacity
|
||||
}
|
||||
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
var (
|
||||
set = make([]*types.Chart, 0, scap)
|
||||
res *types.Chart
|
||||
)
|
||||
// @todo this offset needs to be removed and replaced with key-based-paging
|
||||
fetchPage = func(offset, limit uint) (fetched, skipped uint, err error) {
|
||||
var (
|
||||
res *types.Chart
|
||||
chk bool
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return err
|
||||
}
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalComposeChartRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
if limit > 0 {
|
||||
q = q.Limit(uint64(limit))
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
if offset > 0 {
|
||||
q = q.Offset(uint64(offset))
|
||||
}
|
||||
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
fetched++
|
||||
if res, err = s.internalComposeChartRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If check function is set, call it and act accordingly
|
||||
if f.Check != nil {
|
||||
if chk, err = f.Check(res); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
} else if !chk {
|
||||
// did not pass the check
|
||||
// go with the next row
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
|
||||
// make sure we do not fetch more than requested!
|
||||
if f.Limit > 0 && uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
fetch = func() error {
|
||||
var (
|
||||
fetched uint
|
||||
|
||||
// starting offset & limit are from filter arg
|
||||
// note that this will have to be improved with key-based pagination
|
||||
offset, limit = calculatePaging(f.PageFilter)
|
||||
)
|
||||
|
||||
for refetch := 0; refetch < MaxRefetches; refetch++ {
|
||||
if fetched, _, err = fetchPage(offset, limit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if limit is not set or we've already collected enough resources
|
||||
// we can break the loop right away
|
||||
if limit == 0 || fetched == 0 || uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
// we've skipped fetched resources (due to check() fn)
|
||||
// and we still have less results (in set) than required by limit
|
||||
// inc offset by number of fetched items
|
||||
offset += fetched
|
||||
|
||||
if limit < MinRefetchLimit {
|
||||
limit = MinRefetchLimit
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// LookupComposeChartByID searches for compose chart by ID
|
||||
|
||||
@@ -28,41 +28,110 @@ func (s Store) SearchComposeModules(ctx context.Context, f types.ModuleFilter) (
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
q = ApplyPaging(q, f.PageFilter)
|
||||
|
||||
scap := f.PerPage
|
||||
if scap == 0 {
|
||||
scap = DefaultSliceCapacity
|
||||
}
|
||||
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
var (
|
||||
set = make([]*types.Module, 0, scap)
|
||||
res *types.Module
|
||||
)
|
||||
// @todo this offset needs to be removed and replaced with key-based-paging
|
||||
fetchPage = func(offset, limit uint) (fetched, skipped uint, err error) {
|
||||
var (
|
||||
res *types.Module
|
||||
chk bool
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return err
|
||||
}
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalComposeModuleRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
if limit > 0 {
|
||||
q = q.Limit(uint64(limit))
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
if offset > 0 {
|
||||
q = q.Offset(uint64(offset))
|
||||
}
|
||||
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
fetched++
|
||||
if res, err = s.internalComposeModuleRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If check function is set, call it and act accordingly
|
||||
if f.Check != nil {
|
||||
if chk, err = f.Check(res); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
} else if !chk {
|
||||
// did not pass the check
|
||||
// go with the next row
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
|
||||
// make sure we do not fetch more than requested!
|
||||
if f.Limit > 0 && uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
fetch = func() error {
|
||||
var (
|
||||
fetched uint
|
||||
|
||||
// starting offset & limit are from filter arg
|
||||
// note that this will have to be improved with key-based pagination
|
||||
offset, limit = calculatePaging(f.PageFilter)
|
||||
)
|
||||
|
||||
for refetch := 0; refetch < MaxRefetches; refetch++ {
|
||||
if fetched, _, err = fetchPage(offset, limit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if limit is not set or we've already collected enough resources
|
||||
// we can break the loop right away
|
||||
if limit == 0 || fetched == 0 || uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
// we've skipped fetched resources (due to check() fn)
|
||||
// and we still have less results (in set) than required by limit
|
||||
// inc offset by number of fetched items
|
||||
offset += fetched
|
||||
|
||||
if limit < MinRefetchLimit {
|
||||
limit = MinRefetchLimit
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// LookupComposeModuleByHandle searches for compose module by handle (case-insensitive)
|
||||
|
||||
@@ -28,41 +28,110 @@ func (s Store) SearchComposeNamespaces(ctx context.Context, f types.NamespaceFil
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
q = ApplyPaging(q, f.PageFilter)
|
||||
|
||||
scap := f.PerPage
|
||||
if scap == 0 {
|
||||
scap = DefaultSliceCapacity
|
||||
}
|
||||
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
var (
|
||||
set = make([]*types.Namespace, 0, scap)
|
||||
res *types.Namespace
|
||||
)
|
||||
// @todo this offset needs to be removed and replaced with key-based-paging
|
||||
fetchPage = func(offset, limit uint) (fetched, skipped uint, err error) {
|
||||
var (
|
||||
res *types.Namespace
|
||||
chk bool
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return err
|
||||
}
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalComposeNamespaceRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
if limit > 0 {
|
||||
q = q.Limit(uint64(limit))
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
if offset > 0 {
|
||||
q = q.Offset(uint64(offset))
|
||||
}
|
||||
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
fetched++
|
||||
if res, err = s.internalComposeNamespaceRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If check function is set, call it and act accordingly
|
||||
if f.Check != nil {
|
||||
if chk, err = f.Check(res); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
} else if !chk {
|
||||
// did not pass the check
|
||||
// go with the next row
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
|
||||
// make sure we do not fetch more than requested!
|
||||
if f.Limit > 0 && uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
fetch = func() error {
|
||||
var (
|
||||
fetched uint
|
||||
|
||||
// starting offset & limit are from filter arg
|
||||
// note that this will have to be improved with key-based pagination
|
||||
offset, limit = calculatePaging(f.PageFilter)
|
||||
)
|
||||
|
||||
for refetch := 0; refetch < MaxRefetches; refetch++ {
|
||||
if fetched, _, err = fetchPage(offset, limit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if limit is not set or we've already collected enough resources
|
||||
// we can break the loop right away
|
||||
if limit == 0 || fetched == 0 || uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
// we've skipped fetched resources (due to check() fn)
|
||||
// and we still have less results (in set) than required by limit
|
||||
// inc offset by number of fetched items
|
||||
offset += fetched
|
||||
|
||||
if limit < MinRefetchLimit {
|
||||
limit = MinRefetchLimit
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// LookupComposeNamespaceBySlug searches for namespace by slug (case-insensitive)
|
||||
|
||||
@@ -28,41 +28,110 @@ func (s Store) SearchComposePages(ctx context.Context, f types.PageFilter) (type
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
q = ApplyPaging(q, f.PageFilter)
|
||||
|
||||
scap := f.PerPage
|
||||
if scap == 0 {
|
||||
scap = DefaultSliceCapacity
|
||||
}
|
||||
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
var (
|
||||
set = make([]*types.Page, 0, scap)
|
||||
res *types.Page
|
||||
)
|
||||
// @todo this offset needs to be removed and replaced with key-based-paging
|
||||
fetchPage = func(offset, limit uint) (fetched, skipped uint, err error) {
|
||||
var (
|
||||
res *types.Page
|
||||
chk bool
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return err
|
||||
}
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalComposePageRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
if limit > 0 {
|
||||
q = q.Limit(uint64(limit))
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
if offset > 0 {
|
||||
q = q.Offset(uint64(offset))
|
||||
}
|
||||
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
fetched++
|
||||
if res, err = s.internalComposePageRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If check function is set, call it and act accordingly
|
||||
if f.Check != nil {
|
||||
if chk, err = f.Check(res); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
} else if !chk {
|
||||
// did not pass the check
|
||||
// go with the next row
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
|
||||
// make sure we do not fetch more than requested!
|
||||
if f.Limit > 0 && uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
fetch = func() error {
|
||||
var (
|
||||
fetched uint
|
||||
|
||||
// starting offset & limit are from filter arg
|
||||
// note that this will have to be improved with key-based pagination
|
||||
offset, limit = calculatePaging(f.PageFilter)
|
||||
)
|
||||
|
||||
for refetch := 0; refetch < MaxRefetches; refetch++ {
|
||||
if fetched, _, err = fetchPage(offset, limit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if limit is not set or we've already collected enough resources
|
||||
// we can break the loop right away
|
||||
if limit == 0 || fetched == 0 || uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
// we've skipped fetched resources (due to check() fn)
|
||||
// and we still have less results (in set) than required by limit
|
||||
// inc offset by number of fetched items
|
||||
offset += fetched
|
||||
|
||||
if limit < MinRefetchLimit {
|
||||
limit = MinRefetchLimit
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// LookupComposePageByHandle searches for page chart by handle (case-insensitive)
|
||||
|
||||
@@ -31,30 +31,34 @@ func (s Store) SearchCredentials(ctx context.Context, f types.CredentialsFilter)
|
||||
scap := DefaultSliceCapacity
|
||||
|
||||
var (
|
||||
set = make([]*types.Credentials, 0, scap)
|
||||
res *types.Credentials
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalCredentialsRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
set = make([]*types.Credentials, 0, scap)
|
||||
fetch = func() error {
|
||||
var (
|
||||
res *types.Credentials
|
||||
rows, err = s.Query(ctx, q)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
}
|
||||
for rows.Next() {
|
||||
if res, err = s.internalCredentialsRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
return err
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// LookupCredentialsByID searches for credentials by ID
|
||||
|
||||
@@ -27,28 +27,36 @@ func Count(ctx context.Context, db *sqlx.DB, q squirrel.SelectBuilder) (count ui
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// FetchPaged fetches paged rows
|
||||
func ApplyPaging(q squirrel.SelectBuilder, p rh.PageFilter) squirrel.SelectBuilder {
|
||||
if p.Limit+p.Offset == 0 {
|
||||
func calculatePaging(p rh.PageFilter) (o uint, l uint) {
|
||||
o, l = p.Offset, p.Limit
|
||||
|
||||
if o+l == 0 {
|
||||
// When both, offset & limit are 0,
|
||||
// calculate both values from page/perPage params
|
||||
if p.PerPage > 0 {
|
||||
p.Limit = p.PerPage
|
||||
l = p.PerPage
|
||||
}
|
||||
|
||||
if p.Page < 1 {
|
||||
p.Page = 1
|
||||
}
|
||||
|
||||
p.Offset = (p.Page - 1) * p.PerPage
|
||||
o = (p.Page - 1) * p.PerPage
|
||||
}
|
||||
|
||||
if p.Limit > 0 {
|
||||
q = q.Limit(uint64(p.Limit))
|
||||
return
|
||||
}
|
||||
|
||||
// FetchPaged fetches paged rows
|
||||
func ApplyPaging(q squirrel.SelectBuilder, p rh.PageFilter) squirrel.SelectBuilder {
|
||||
o, l := calculatePaging(p)
|
||||
|
||||
if o > 0 {
|
||||
q = q.Offset(uint64(o))
|
||||
}
|
||||
|
||||
if p.Offset > 0 {
|
||||
q = q.Offset(uint64(p.Offset))
|
||||
if l > 0 {
|
||||
q = q.Limit(uint64(l))
|
||||
}
|
||||
|
||||
return q
|
||||
|
||||
@@ -28,30 +28,34 @@ func (s Store) SearchRbacRules(ctx context.Context, f permissions.RuleFilter) (p
|
||||
scap := DefaultSliceCapacity
|
||||
|
||||
var (
|
||||
set = make([]*permissions.Rule, 0, scap)
|
||||
res *permissions.Rule
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalRbacRuleRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
set = make([]*permissions.Rule, 0, scap)
|
||||
fetch = func() error {
|
||||
var (
|
||||
res *permissions.Rule
|
||||
rows, err = s.Query(ctx, q)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
}
|
||||
for rows.Next() {
|
||||
if res, err = s.internalRbacRuleRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
return err
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// CreateRbacRule creates one or more rows in rbac_rules table
|
||||
|
||||
@@ -78,6 +78,9 @@ const (
|
||||
TxRetryHardLimit = 100
|
||||
|
||||
DefaultSliceCapacity = 1000
|
||||
|
||||
MinRefetchLimit = 10
|
||||
MaxRefetches = 100
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -28,41 +28,110 @@ func (s Store) SearchReminders(ctx context.Context, f types.ReminderFilter) (typ
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
q = ApplyPaging(q, f.PageFilter)
|
||||
|
||||
scap := f.PerPage
|
||||
if scap == 0 {
|
||||
scap = DefaultSliceCapacity
|
||||
}
|
||||
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
var (
|
||||
set = make([]*types.Reminder, 0, scap)
|
||||
res *types.Reminder
|
||||
)
|
||||
// @todo this offset needs to be removed and replaced with key-based-paging
|
||||
fetchPage = func(offset, limit uint) (fetched, skipped uint, err error) {
|
||||
var (
|
||||
res *types.Reminder
|
||||
chk bool
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return err
|
||||
}
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalReminderRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
if limit > 0 {
|
||||
q = q.Limit(uint64(limit))
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
if offset > 0 {
|
||||
q = q.Offset(uint64(offset))
|
||||
}
|
||||
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
fetched++
|
||||
if res, err = s.internalReminderRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If check function is set, call it and act accordingly
|
||||
if f.Check != nil {
|
||||
if chk, err = f.Check(res); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
} else if !chk {
|
||||
// did not pass the check
|
||||
// go with the next row
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
|
||||
// make sure we do not fetch more than requested!
|
||||
if f.Limit > 0 && uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
fetch = func() error {
|
||||
var (
|
||||
fetched uint
|
||||
|
||||
// starting offset & limit are from filter arg
|
||||
// note that this will have to be improved with key-based pagination
|
||||
offset, limit = calculatePaging(f.PageFilter)
|
||||
)
|
||||
|
||||
for refetch := 0; refetch < MaxRefetches; refetch++ {
|
||||
if fetched, _, err = fetchPage(offset, limit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if limit is not set or we've already collected enough resources
|
||||
// we can break the loop right away
|
||||
if limit == 0 || fetched == 0 || uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
// we've skipped fetched resources (due to check() fn)
|
||||
// and we still have less results (in set) than required by limit
|
||||
// inc offset by number of fetched items
|
||||
offset += fetched
|
||||
|
||||
if limit < MinRefetchLimit {
|
||||
limit = MinRefetchLimit
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// LookupReminderByID searches for reminder by its ID
|
||||
|
||||
@@ -28,30 +28,34 @@ func (s Store) SearchRoleMembers(ctx context.Context, f types.RoleMemberFilter)
|
||||
scap := DefaultSliceCapacity
|
||||
|
||||
var (
|
||||
set = make([]*types.RoleMember, 0, scap)
|
||||
res *types.RoleMember
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalRoleMemberRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
set = make([]*types.RoleMember, 0, scap)
|
||||
fetch = func() error {
|
||||
var (
|
||||
res *types.RoleMember
|
||||
rows, err = s.Query(ctx, q)
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
}
|
||||
for rows.Next() {
|
||||
if res, err = s.internalRoleMemberRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
return err
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// CreateRoleMember creates one or more rows in role_members table
|
||||
|
||||
+92
-23
@@ -28,41 +28,110 @@ func (s Store) SearchRoles(ctx context.Context, f types.RoleFilter) (types.RoleS
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
q = ApplyPaging(q, f.PageFilter)
|
||||
|
||||
scap := f.PerPage
|
||||
if scap == 0 {
|
||||
scap = DefaultSliceCapacity
|
||||
}
|
||||
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
var (
|
||||
set = make([]*types.Role, 0, scap)
|
||||
res *types.Role
|
||||
)
|
||||
// @todo this offset needs to be removed and replaced with key-based-paging
|
||||
fetchPage = func(offset, limit uint) (fetched, skipped uint, err error) {
|
||||
var (
|
||||
res *types.Role
|
||||
chk bool
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return err
|
||||
}
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalRoleRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
if limit > 0 {
|
||||
q = q.Limit(uint64(limit))
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
if offset > 0 {
|
||||
q = q.Offset(uint64(offset))
|
||||
}
|
||||
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
fetched++
|
||||
if res, err = s.internalRoleRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If check function is set, call it and act accordingly
|
||||
if f.Check != nil {
|
||||
if chk, err = f.Check(res); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
} else if !chk {
|
||||
// did not pass the check
|
||||
// go with the next row
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
|
||||
// make sure we do not fetch more than requested!
|
||||
if f.Limit > 0 && uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
fetch = func() error {
|
||||
var (
|
||||
fetched uint
|
||||
|
||||
// starting offset & limit are from filter arg
|
||||
// note that this will have to be improved with key-based pagination
|
||||
offset, limit = calculatePaging(f.PageFilter)
|
||||
)
|
||||
|
||||
for refetch := 0; refetch < MaxRefetches; refetch++ {
|
||||
if fetched, _, err = fetchPage(offset, limit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if limit is not set or we've already collected enough resources
|
||||
// we can break the loop right away
|
||||
if limit == 0 || fetched == 0 || uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
// we've skipped fetched resources (due to check() fn)
|
||||
// and we still have less results (in set) than required by limit
|
||||
// inc offset by number of fetched items
|
||||
offset += fetched
|
||||
|
||||
if limit < MinRefetchLimit {
|
||||
limit = MinRefetchLimit
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// LookupRoleByID searches for role by ID
|
||||
|
||||
+92
-23
@@ -28,41 +28,110 @@ func (s Store) SearchSettings(ctx context.Context, f types.SettingsFilter) (type
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
q = ApplyPaging(q, f.PageFilter)
|
||||
|
||||
scap := f.PerPage
|
||||
if scap == 0 {
|
||||
scap = DefaultSliceCapacity
|
||||
}
|
||||
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
var (
|
||||
set = make([]*types.SettingValue, 0, scap)
|
||||
res *types.SettingValue
|
||||
)
|
||||
// @todo this offset needs to be removed and replaced with key-based-paging
|
||||
fetchPage = func(offset, limit uint) (fetched, skipped uint, err error) {
|
||||
var (
|
||||
res *types.SettingValue
|
||||
chk bool
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return err
|
||||
}
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalSettingRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
if limit > 0 {
|
||||
q = q.Limit(uint64(limit))
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
if offset > 0 {
|
||||
q = q.Offset(uint64(offset))
|
||||
}
|
||||
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
fetched++
|
||||
if res, err = s.internalSettingRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If check function is set, call it and act accordingly
|
||||
if f.Check != nil {
|
||||
if chk, err = f.Check(res); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
} else if !chk {
|
||||
// did not pass the check
|
||||
// go with the next row
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
|
||||
// make sure we do not fetch more than requested!
|
||||
if f.Limit > 0 && uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
fetch = func() error {
|
||||
var (
|
||||
fetched uint
|
||||
|
||||
// starting offset & limit are from filter arg
|
||||
// note that this will have to be improved with key-based pagination
|
||||
offset, limit = calculatePaging(f.PageFilter)
|
||||
)
|
||||
|
||||
for refetch := 0; refetch < MaxRefetches; refetch++ {
|
||||
if fetched, _, err = fetchPage(offset, limit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if limit is not set or we've already collected enough resources
|
||||
// we can break the loop right away
|
||||
if limit == 0 || fetched == 0 || uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
// we've skipped fetched resources (due to check() fn)
|
||||
// and we still have less results (in set) than required by limit
|
||||
// inc offset by number of fetched items
|
||||
offset += fetched
|
||||
|
||||
if limit < MinRefetchLimit {
|
||||
limit = MinRefetchLimit
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// LookupSettingByNameOwnedBy searches for settings by name and owner
|
||||
|
||||
+92
-23
@@ -28,41 +28,110 @@ func (s Store) SearchUsers(ctx context.Context, f types.UserFilter) (types.UserS
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
q = ApplyPaging(q, f.PageFilter)
|
||||
|
||||
scap := f.PerPage
|
||||
if scap == 0 {
|
||||
scap = DefaultSliceCapacity
|
||||
}
|
||||
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return nil, f, err
|
||||
}
|
||||
|
||||
var (
|
||||
set = make([]*types.User, 0, scap)
|
||||
res *types.User
|
||||
)
|
||||
// @todo this offset needs to be removed and replaced with key-based-paging
|
||||
fetchPage = func(offset, limit uint) (fetched, skipped uint, err error) {
|
||||
var (
|
||||
res *types.User
|
||||
chk bool
|
||||
)
|
||||
|
||||
return set, f, func() error {
|
||||
if f.Count, err = Count(ctx, s.db, q); err != nil || f.Count == 0 {
|
||||
return err
|
||||
}
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
if res, err = s.internalUserRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
return fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return err
|
||||
if limit > 0 {
|
||||
q = q.Limit(uint64(limit))
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
if offset > 0 {
|
||||
q = q.Offset(uint64(offset))
|
||||
}
|
||||
|
||||
rows, err := s.Query(ctx, q)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
for rows.Next() {
|
||||
fetched++
|
||||
if res, err = s.internalUserRowScanner(rows, rows.Err()); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after scan error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// If check function is set, call it and act accordingly
|
||||
if f.Check != nil {
|
||||
if chk, err = f.Check(res); err != nil {
|
||||
if cerr := rows.Close(); cerr != nil {
|
||||
err = fmt.Errorf("could not close rows (%v) after check error: %w", cerr, err)
|
||||
}
|
||||
|
||||
return
|
||||
} else if !chk {
|
||||
// did not pass the check
|
||||
// go with the next row
|
||||
skipped++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
set = append(set, res)
|
||||
|
||||
// make sure we do not fetch more than requested!
|
||||
if f.Limit > 0 && uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
err = rows.Close()
|
||||
return
|
||||
}
|
||||
|
||||
return rows.Close()
|
||||
}()
|
||||
fetch = func() error {
|
||||
var (
|
||||
fetched uint
|
||||
|
||||
// starting offset & limit are from filter arg
|
||||
// note that this will have to be improved with key-based pagination
|
||||
offset, limit = calculatePaging(f.PageFilter)
|
||||
)
|
||||
|
||||
for refetch := 0; refetch < MaxRefetches; refetch++ {
|
||||
if fetched, _, err = fetchPage(offset, limit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if limit is not set or we've already collected enough resources
|
||||
// we can break the loop right away
|
||||
if limit == 0 || fetched == 0 || uint(len(set)) >= f.Limit {
|
||||
break
|
||||
}
|
||||
|
||||
// we've skipped fetched resources (due to check() fn)
|
||||
// and we still have less results (in set) than required by limit
|
||||
// inc offset by number of fetched items
|
||||
offset += fetched
|
||||
|
||||
if limit < MinRefetchLimit {
|
||||
limit = MinRefetchLimit
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
)
|
||||
|
||||
return set, f, fetch()
|
||||
}
|
||||
|
||||
// LookupUserByID searches for user by ID
|
||||
|
||||
+17
-14
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/Masterminds/squirrel"
|
||||
"github.com/cortezaproject/corteza-server/pkg/permissions"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rh"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
)
|
||||
@@ -17,13 +16,13 @@ func (s Store) convertUserFilter(f types.UserFilter) (query squirrel.SelectBuild
|
||||
query = s.QueryUsers()
|
||||
|
||||
// Returns user filter (flt) wrapped in IF() function with cnd as condition (when cnd != nil)
|
||||
whereMasked := func(cnd *permissions.ResourceFilter, flt squirrel.Sqlizer) squirrel.Sqlizer {
|
||||
if cnd != nil {
|
||||
return rh.SquirrelFunction("IF", cnd, flt, squirrel.Expr("false"))
|
||||
} else {
|
||||
return flt
|
||||
}
|
||||
}
|
||||
//whereMasked := func(cnd *permissions.ResourceFilter, flt squirrel.Sqlizer) squirrel.Sqlizer {
|
||||
// if cnd != nil {
|
||||
// return rh.SquirrelFunction("IF", cnd, flt, squirrel.Expr("false"))
|
||||
// } else {
|
||||
// return flt
|
||||
// }
|
||||
//}
|
||||
|
||||
query = rh.FilterNullByState(query, "usr.deleted_at", f.Deleted)
|
||||
query = rh.FilterNullByState(query, "usr.suspended_at", f.Suspended)
|
||||
@@ -48,15 +47,19 @@ func (s Store) convertUserFilter(f types.UserFilter) (query squirrel.SelectBuild
|
||||
query = query.Where(squirrel.Or{
|
||||
squirrel.Like{"usr.username": qs},
|
||||
squirrel.Like{"usr.handle": qs},
|
||||
whereMasked(f.IsEmailUnmaskable, squirrel.Like{"usr.email": qs}),
|
||||
whereMasked(f.IsNameUnmaskable, squirrel.Like{"usr.name": qs}),
|
||||
//whereMasked(f.IsEmailUnmaskable, squirrel.Like{"usr.email": qs}),
|
||||
//whereMasked(f.IsNameUnmaskable, squirrel.Like{"usr.name": qs}),
|
||||
})
|
||||
}
|
||||
|
||||
if f.Email != "" {
|
||||
query = query.Where(whereMasked(f.IsEmailUnmaskable, squirrel.Eq{"usr.email": f.Email}))
|
||||
query = query.Where(squirrel.Eq{"usr.email": f.Email})
|
||||
}
|
||||
|
||||
//if f.Email != "" {
|
||||
// query = query.Where(whereMasked(f.IsEmailUnmaskable, squirrel.Eq{"usr.email": f.Email}))
|
||||
//}
|
||||
|
||||
if f.Username != "" {
|
||||
query = query.Where(squirrel.Eq{"usr.username": f.Username})
|
||||
}
|
||||
@@ -69,9 +72,9 @@ func (s Store) convertUserFilter(f types.UserFilter) (query squirrel.SelectBuild
|
||||
query = query.Where(squirrel.Eq{"usr.kind": f.Kind})
|
||||
}
|
||||
|
||||
if f.IsReadable != nil {
|
||||
query = query.Where(f.IsReadable)
|
||||
}
|
||||
//if f.IsReadable != nil {
|
||||
// query = query.Where(f.IsReadable)
|
||||
//}
|
||||
|
||||
var orderBy []string
|
||||
if orderBy, err = rh.ParseOrder(f.Sort, s.UserColumns()...); err != nil {
|
||||
|
||||
+130
-59
@@ -4,9 +4,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza-server/pkg/id"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rand"
|
||||
"github.com/cortezaproject/corteza-server/pkg/rh"
|
||||
"github.com/cortezaproject/corteza-server/system/types"
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
"github.com/stretchr/testify/require"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -21,26 +24,52 @@ type (
|
||||
func testUsers(t *testing.T, tmp interface{}) {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
req = require.New(t)
|
||||
|
||||
//err error
|
||||
user *types.User
|
||||
|
||||
s = tmp.(usersStoreAdt)
|
||||
|
||||
makeNew = func(nn ...string) *types.User {
|
||||
// minimum data set for new user
|
||||
name := strings.Join(nn, "")
|
||||
return &types.User{
|
||||
ID: id.Next(),
|
||||
CreatedAt: time.Now(),
|
||||
Email: "user-crud" + name + "@crust.test",
|
||||
Username: "username_" + name,
|
||||
Handle: "handle_" + name,
|
||||
}
|
||||
}
|
||||
|
||||
truncAndCreate = func(t *testing.T) (*require.Assertions, *types.User) {
|
||||
req := require.New(t)
|
||||
req.NoError(s.TruncateUsers(ctx))
|
||||
user := makeNew()
|
||||
req.NoError(s.CreateUser(ctx, user))
|
||||
return req, user
|
||||
}
|
||||
|
||||
truncAddFill = func(t *testing.T, l int) (*require.Assertions, types.UserSet) {
|
||||
req := require.New(t)
|
||||
req.NoError(s.TruncateUsers(ctx))
|
||||
|
||||
set := make([]*types.User, l)
|
||||
|
||||
for i := 0; i < l; i++ {
|
||||
set[i] = makeNew(string(rand.Bytes(10)))
|
||||
}
|
||||
|
||||
req.NoError(s.CreateUser(ctx, set...))
|
||||
return req, set
|
||||
}
|
||||
)
|
||||
|
||||
t.Run("create", func(t *testing.T) {
|
||||
user = &types.User{
|
||||
ID: 42,
|
||||
CreatedAt: time.Now(),
|
||||
Email: "user-crud@crust.test",
|
||||
Username: "UserCRUD",
|
||||
Handle: "usercrud",
|
||||
}
|
||||
req.NoError(s.CreateUser(ctx, user))
|
||||
req := require.New(t)
|
||||
req.NoError(s.CreateUser(ctx, makeNew()))
|
||||
})
|
||||
|
||||
t.Run("lookup by ID", func(t *testing.T) {
|
||||
req, user := truncAndCreate(t)
|
||||
|
||||
fetched, err := s.LookupUserByID(ctx, user.ID)
|
||||
req.NoError(err)
|
||||
req.Equal(user.Email, fetched.Email)
|
||||
@@ -54,13 +83,7 @@ func testUsers(t *testing.T, tmp interface{}) {
|
||||
})
|
||||
|
||||
t.Run("update", func(t *testing.T) {
|
||||
user = &types.User{
|
||||
ID: 42,
|
||||
CreatedAt: time.Now(),
|
||||
Email: "user-crud+2@crust.test",
|
||||
Username: "UserCRUD+2",
|
||||
Handle: "usercrud+2",
|
||||
}
|
||||
req, user := truncAndCreate(t)
|
||||
req.NoError(s.UpdateUser(ctx, user))
|
||||
})
|
||||
|
||||
@@ -94,80 +117,128 @@ func testUsers(t *testing.T, tmp interface{}) {
|
||||
//})
|
||||
|
||||
t.Run("lookup by email", func(t *testing.T) {
|
||||
req, user := truncAndCreate(t)
|
||||
|
||||
fetched, err := s.LookupUserByEmail(ctx, user.Email)
|
||||
req.NoError(err)
|
||||
req.Equal(user.Email, fetched.Email)
|
||||
})
|
||||
|
||||
t.Run("lookup by handle", func(t *testing.T) {
|
||||
req, user := truncAndCreate(t)
|
||||
|
||||
fetched, err := s.LookupUserByHandle(ctx, user.Handle)
|
||||
req.NoError(err)
|
||||
req.Equal(user.ID, fetched.ID)
|
||||
})
|
||||
|
||||
t.Run("lookup by nonexisting handle", func(t *testing.T) {
|
||||
req, _ := truncAndCreate(t)
|
||||
|
||||
fetched, err := s.LookupUserByHandle(ctx, "no such handle")
|
||||
req.EqualError(err, "not found")
|
||||
req.Nil(fetched)
|
||||
})
|
||||
|
||||
t.Run("lookup by username", func(t *testing.T) {
|
||||
req, user := truncAndCreate(t)
|
||||
|
||||
fetched, err := s.LookupUserByUsername(ctx, user.Username)
|
||||
req.NoError(err)
|
||||
req.Equal(user.ID, fetched.ID)
|
||||
})
|
||||
|
||||
t.Run("search by ID", func(t *testing.T) {
|
||||
set, f, err := s.SearchUsers(ctx, types.UserFilter{UserID: []uint64{user.ID}})
|
||||
req.NoError(err)
|
||||
req.Equal([]uint64{user.ID}, f.UserID)
|
||||
req.Len(set, 1)
|
||||
req.Equal(uint(1), f.Count)
|
||||
//req.Equal(set[0].ID, user.ID)
|
||||
})
|
||||
t.Run("search", func(t *testing.T) {
|
||||
t.Run("by ID", func(t *testing.T) {
|
||||
req, prefill := truncAddFill(t, 5)
|
||||
|
||||
t.Run("search by email", func(t *testing.T) {
|
||||
set, f, err := s.SearchUsers(ctx, types.UserFilter{Email: user.Email})
|
||||
req.NoError(err)
|
||||
req.Len(set, 1)
|
||||
req.Equal(uint(1), f.Count)
|
||||
})
|
||||
set, f, err := s.SearchUsers(ctx, types.UserFilter{UserID: []uint64{prefill[0].ID}})
|
||||
req.NoError(err)
|
||||
req.Equal([]uint64{prefill[0].ID}, f.UserID)
|
||||
req.Len(set, 1)
|
||||
req.Equal(uint(1), f.Count)
|
||||
//req.Equal(set[0].ID, user.ID)
|
||||
})
|
||||
|
||||
t.Run("search by username", func(t *testing.T) {
|
||||
set, f, err := s.SearchUsers(ctx, types.UserFilter{Username: user.Username})
|
||||
req.NoError(err)
|
||||
req.Len(set, 1)
|
||||
req.Equal(uint(1), f.Count)
|
||||
})
|
||||
t.Run("by email", func(t *testing.T) {
|
||||
req, prefill := truncAddFill(t, 5)
|
||||
|
||||
t.Run("search by query", func(t *testing.T) {
|
||||
set, f, err := s.SearchUsers(ctx, types.UserFilter{Query: user.Handle})
|
||||
req.NoError(err)
|
||||
req.Len(set, 1)
|
||||
req.Equal(uint(1), f.Count)
|
||||
})
|
||||
set, f, err := s.SearchUsers(ctx, types.UserFilter{Email: prefill[0].Email})
|
||||
req.NoError(err)
|
||||
req.Len(set, 1)
|
||||
req.Equal(uint(1), f.Count)
|
||||
})
|
||||
|
||||
t.Run("search by username", func(t *testing.T) {
|
||||
set, f, err := s.SearchUsers(ctx, types.UserFilter{Username: "no such username"})
|
||||
req.NoError(err)
|
||||
req.Len(set, 0)
|
||||
req.Equal(uint(0), f.Count)
|
||||
})
|
||||
t.Run("by username", func(t *testing.T) {
|
||||
req, prefill := truncAddFill(t, 5)
|
||||
set, f, err := s.SearchUsers(ctx, types.UserFilter{Username: prefill[0].Username})
|
||||
req.NoError(err)
|
||||
req.Len(set, 1)
|
||||
req.Equal(uint(1), f.Count)
|
||||
})
|
||||
|
||||
t.Run("search with masked details", func(t *testing.T) {
|
||||
t.Skip("not implemented")
|
||||
})
|
||||
t.Run("by query", func(t *testing.T) {
|
||||
req, prefill := truncAddFill(t, 5)
|
||||
set, f, err := s.SearchUsers(ctx, types.UserFilter{Query: prefill[0].Handle})
|
||||
req.NoError(err)
|
||||
req.Len(set, 1)
|
||||
req.Equal(uint(1), f.Count)
|
||||
})
|
||||
|
||||
t.Run("search by role", func(t *testing.T) {
|
||||
t.Skip("not implemented")
|
||||
})
|
||||
t.Run("by username", func(t *testing.T) {
|
||||
req, _ := truncAddFill(t, 5)
|
||||
set, f, err := s.SearchUsers(ctx, types.UserFilter{Username: "no such username"})
|
||||
req.NoError(err)
|
||||
req.Len(set, 0)
|
||||
req.Equal(uint(0), f.Count)
|
||||
})
|
||||
|
||||
t.Run("ordered search", func(t *testing.T) {
|
||||
t.Skip("not implemented")
|
||||
t.Run("with check", func(t *testing.T) {
|
||||
req, prefill := truncAddFill(t, 5)
|
||||
set, _, err := s.SearchUsers(ctx, types.UserFilter{
|
||||
Check: func(user *types.User) (bool, error) {
|
||||
// simple check that matches with the first user from prefill
|
||||
return user.ID == prefill[0].ID, nil
|
||||
},
|
||||
})
|
||||
req.NoError(err)
|
||||
req.Len(set, 1)
|
||||
req.Equal(prefill[0].ID, set[0].ID)
|
||||
})
|
||||
|
||||
t.Run("with check and paging", func(t *testing.T) {
|
||||
req, prefill := truncAddFill(t, 5)
|
||||
set, _, err := s.SearchUsers(ctx, types.UserFilter{
|
||||
// This will cause paging to run multiple queries
|
||||
// until it collects all data
|
||||
PageFilter: rh.PageFilter{Limit: 2},
|
||||
Check: func(user *types.User) (bool, error) {
|
||||
// simple check that matches with the 4th user from prefill
|
||||
return user.ID == prefill[4].ID, nil
|
||||
},
|
||||
})
|
||||
req.NoError(err)
|
||||
req.Len(set, 1)
|
||||
req.Equal(prefill[4].ID, set[0].ID)
|
||||
})
|
||||
|
||||
t.Run("with masked details", func(t *testing.T) {
|
||||
t.Skip("not implemented")
|
||||
})
|
||||
|
||||
t.Run("by role", func(t *testing.T) {
|
||||
t.Skip("not implemented")
|
||||
})
|
||||
|
||||
t.Run("search", func(t *testing.T) {
|
||||
t.Skip("not implemented")
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("count", func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
|
||||
f = types.UserFilter{}
|
||||
c1, c2 uint
|
||||
err error
|
||||
|
||||
@@ -140,6 +140,10 @@ func (svc accessControl) FilterUsersWithUnmaskableName(ctx context.Context) *per
|
||||
return svc.filter(ctx, types.UserPermissionResource, "unmask.name", permissions.Deny)
|
||||
}
|
||||
|
||||
func (svc accessControl) CanReadUser(ctx context.Context, u *types.User) bool {
|
||||
return svc.can(ctx, u.PermissionResource(), "read")
|
||||
}
|
||||
|
||||
func (svc accessControl) CanUpdateUser(ctx context.Context, u *types.User) bool {
|
||||
return svc.can(ctx, u.PermissionResource(), "update")
|
||||
}
|
||||
|
||||
+20
-7
@@ -59,6 +59,7 @@ type (
|
||||
userAccessController interface {
|
||||
CanAccess(context.Context) bool
|
||||
CanCreateUser(context.Context) bool
|
||||
CanReadUser(context.Context, *types.User) bool
|
||||
CanUpdateUser(context.Context, *types.User) bool
|
||||
CanDeleteUser(context.Context, *types.User) bool
|
||||
CanSuspendUser(context.Context, *types.User) bool
|
||||
@@ -253,11 +254,23 @@ func (svc user) proc(u *types.User, err error) (*types.User, error) {
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// Find interacts with backend storage and
|
||||
//
|
||||
// @todo rename to Search() for consistency
|
||||
func (svc user) Find(filter types.UserFilter) (uu types.UserSet, f types.UserFilter, err error) {
|
||||
var (
|
||||
uaProps = &userActionProps{filter: &filter}
|
||||
)
|
||||
|
||||
filter.Check = func(user *types.User) (bool, error) {
|
||||
if !svc.ac.CanReadUser(svc.ctx, user) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
svc.handlePrivateData(user)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
if filter.Deleted > 0 {
|
||||
// If list with deleted users is requested
|
||||
@@ -270,13 +283,13 @@ func (svc user) Find(filter types.UserFilter) (uu types.UserSet, f types.UserFil
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare filter for email unmasking check
|
||||
filter.IsEmailUnmaskable = svc.ac.FilterUsersWithUnmaskableEmail(svc.ctx)
|
||||
|
||||
// Prepare filter for name unmasking check
|
||||
filter.IsNameUnmaskable = svc.ac.FilterUsersWithUnmaskableName(svc.ctx)
|
||||
|
||||
filter.IsReadable = svc.ac.FilterReadableUsers(svc.ctx)
|
||||
//// Prepare filter for email unmasking check
|
||||
//filter.IsEmailUnmaskable = svc.ac.FilterUsersWithUnmaskableEmail(svc.ctx)
|
||||
//
|
||||
//// Prepare filter for name unmasking check
|
||||
//filter.IsNameUnmaskable = svc.ac.FilterUsersWithUnmaskableName(svc.ctx)
|
||||
//
|
||||
//filter.IsReadable = svc.ac.FilterReadableUsers(svc.ctx)
|
||||
|
||||
uu, f, err = svc.store.SearchUsers(svc.ctx, filter)
|
||||
if err != nil {
|
||||
|
||||
@@ -45,8 +45,15 @@ type (
|
||||
rh.PageFilter
|
||||
|
||||
// Resource permission check filter
|
||||
// deprecated
|
||||
IsReadable *permissions.ResourceFilter `json:"-"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(user *Application) (bool, error)
|
||||
|
||||
Deleted rh.FilterState `json:"deleted"`
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,12 @@ type (
|
||||
Filter string `json:"filter"`
|
||||
Sort string `json:"sort"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(user *Attachment) (bool, error)
|
||||
|
||||
// Standard paging fields & helpers
|
||||
rh.PageFilter
|
||||
}
|
||||
|
||||
@@ -40,6 +40,12 @@ type (
|
||||
|
||||
Sort string `json:"sort"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(user *Reminder) (bool, error)
|
||||
|
||||
// Standard paging fields & helpers
|
||||
rh.PageFilter
|
||||
}
|
||||
|
||||
@@ -37,7 +37,14 @@ type (
|
||||
rh.PageFilter
|
||||
|
||||
// Resource permission check filter
|
||||
// deprecated
|
||||
IsReadable *permissions.ResourceFilter `json:"-"`
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(user *Role) (bool, error)
|
||||
}
|
||||
|
||||
RoleMetrics struct {
|
||||
|
||||
@@ -29,6 +29,12 @@ type (
|
||||
OwnedBy uint64 `json:"ownedBy"`
|
||||
|
||||
rh.PageFilter
|
||||
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(user *SettingValue) (bool, error)
|
||||
}
|
||||
|
||||
SettingsKV map[string]types.JSONText
|
||||
|
||||
@@ -54,14 +54,11 @@ type (
|
||||
|
||||
Sort string `json:"sort"`
|
||||
|
||||
// Can we use email for searching or is it supposed to be masked for the current user?
|
||||
IsEmailUnmaskable *permissions.ResourceFilter `json:"-"`
|
||||
|
||||
// Can we use name for searching or is it supposed to be masked for the current user?
|
||||
IsNameUnmaskable *permissions.ResourceFilter `json:"-"`
|
||||
|
||||
// Resource permission check filter
|
||||
IsReadable *permissions.ResourceFilter `json:"-"`
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(user *User) (bool, error)
|
||||
|
||||
// Standard paging fields & helpers
|
||||
rh.PageFilter
|
||||
|
||||
Reference in New Issue
Block a user