Part of ongoing gobwebs ecosystem updates. This moved valid and cursor
modules to the gobwebs-graphql module.
Changelog-updated: moved graphql `valid` and `cursor` modules/objects to
the latest version of gobwebs-graphql
---
api/gqlgen.yml | 2 +-
api/graph/generated.go | 65 +++++-----
api/graph/model/cursor.go | 59 ---------
api/graph/model/models_gen.go | 115 ++++++++---------
api/graph/pagination.go | 17 +--
api/graph/pagination_test.go | 27 ++--
api/graph/schema.resolvers.go | 6 +-
go.mod | 2 +-
go.sum | 4 +-
helpers.go | 10 +-
helpers_test.go | 4 +-
valid/valid.go | 231 ----------------------------------
12 files changed, 128 insertions(+), 414 deletions(-)
delete mode 100644 api/graph/model/cursor.go
delete mode 100644 valid/valid.go
diff --git a/api/gqlgen.yml b/api/gqlgen.yml
index 6f9e4a4..4b2dd8d 100644
--- a/api/gqlgen.yml
+++ b/api/gqlgen.yml
@@ -52,7 +52,7 @@ autobind:
models:
Cursor:
model:
- - links/api/graph/model.Cursor
+ - netlandish.com/x/gobwebs-graphql/cursor.Cursor
ID:
model:
- github.com/99designs/gqlgen/graphql.ID
diff --git a/api/graph/generated.go b/api/graph/generated.go
index af1105f..9a3507d 100644
--- a/api/graph/generated.go
+++ b/api/graph/generated.go
@@ -20,6 +20,7 @@ import (
gqlparser "github.com/vektah/gqlparser/v2"
"github.com/vektah/gqlparser/v2/ast"
"netlandish.com/x/gobwebs-auditlog"
+ "netlandish.com/x/gobwebs-graphql/cursor"
)
// region ************************** generated!.gotpl **************************
@@ -16568,9 +16569,9 @@ func (ec *executionContext) _PageInfo_cursor(ctx context.Context, field graphql.
}
return graphql.Null
}
- res := resTmp.(model.Cursor)
+ res := resTmp.(cursor.Cursor)
fc.Result = res
- return ec.marshalNCursor2linksᚋapiᚋgraphᚋmodelᚐCursor(ctx, field.Selections, res)
+ return ec.marshalNCursor2netlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_PageInfo_cursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
@@ -25518,14 +25519,14 @@ func (ec *executionContext) unmarshalInputAuditLogInput(ctx context.Context, obj
it.ListingID = data
case "after":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
it.After = data
case "before":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
@@ -25696,14 +25697,14 @@ func (ec *executionContext) unmarshalInputGetAdminDomainInput(ctx context.Contex
it.Limit = data
case "after":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
it.After = data
case "before":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
@@ -25772,14 +25773,14 @@ func (ec *executionContext) unmarshalInputGetAdminOrganizationsInput(ctx context
it.Limit = data
case "after":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
it.After = data
case "before":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
@@ -25827,14 +25828,14 @@ func (ec *executionContext) unmarshalInputGetFeedInput(ctx context.Context, obj
it.Limit = data
case "after":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
it.After = data
case "before":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
@@ -25910,14 +25911,14 @@ func (ec *executionContext) unmarshalInputGetLinkInput(ctx context.Context, obj
it.Limit = data
case "after":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
it.After = data
case "before":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
@@ -26014,14 +26015,14 @@ func (ec *executionContext) unmarshalInputGetLinkShortInput(ctx context.Context,
it.Limit = data
case "after":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
it.After = data
case "before":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
@@ -26090,14 +26091,14 @@ func (ec *executionContext) unmarshalInputGetListingDetailInput(ctx context.Cont
it.Limit = data
case "after":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
it.After = data
case "before":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
@@ -26138,14 +26139,14 @@ func (ec *executionContext) unmarshalInputGetListingInput(ctx context.Context, o
it.Limit = data
case "after":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
it.After = data
case "before":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
@@ -26200,14 +26201,14 @@ func (ec *executionContext) unmarshalInputGetOrganizationsInput(ctx context.Cont
it.Limit = data
case "after":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
it.After = data
case "before":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
@@ -26255,14 +26256,14 @@ func (ec *executionContext) unmarshalInputGetPaymentInput(ctx context.Context, o
it.Limit = data
case "after":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
it.After = data
case "before":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
@@ -26331,14 +26332,14 @@ func (ec *executionContext) unmarshalInputGetTagsInput(ctx context.Context, obj
it.Service = data
case "after":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
it.After = data
case "before":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
@@ -26379,14 +26380,14 @@ func (ec *executionContext) unmarshalInputGetUserInput(ctx context.Context, obj
it.Limit = data
case "after":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
it.After = data
case "before":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
- data, err := ec.unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx, v)
+ data, err := ec.unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx, v)
if err != nil {
return it, err
}
@@ -32193,13 +32194,13 @@ func (ec *executionContext) marshalNCloudOrderType2linksᚋapiᚋgraphᚋmodel
return v
}
-func (ec *executionContext) unmarshalNCursor2linksᚋapiᚋgraphᚋmodelᚐCursor(ctx context.Context, v any) (model.Cursor, error) {
- var res model.Cursor
+func (ec *executionContext) unmarshalNCursor2netlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx context.Context, v any) (cursor.Cursor, error) {
+ var res cursor.Cursor
err := res.UnmarshalGQL(v)
return res, graphql.ErrorOnPath(ctx, err)
}
-func (ec *executionContext) marshalNCursor2linksᚋapiᚋgraphᚋmodelᚐCursor(ctx context.Context, sel ast.SelectionSet, v model.Cursor) graphql.Marshaler {
+func (ec *executionContext) marshalNCursor2netlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx context.Context, sel ast.SelectionSet, v cursor.Cursor) graphql.Marshaler {
return v
}
@@ -33601,16 +33602,16 @@ func (ec *executionContext) unmarshalOCompleteRegisterInput2ᚖlinksᚋapiᚋgra
return &res, graphql.ErrorOnPath(ctx, err)
}
-func (ec *executionContext) unmarshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx context.Context, v any) (*model.Cursor, error) {
+func (ec *executionContext) unmarshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx context.Context, v any) (*cursor.Cursor, error) {
if v == nil {
return nil, nil
}
- var res = new(model.Cursor)
+ var res = new(cursor.Cursor)
err := res.UnmarshalGQL(v)
return res, graphql.ErrorOnPath(ctx, err)
}
-func (ec *executionContext) marshalOCursor2ᚖlinksᚋapiᚋgraphᚋmodelᚐCursor(ctx context.Context, sel ast.SelectionSet, v *model.Cursor) graphql.Marshaler {
+func (ec *executionContext) marshalOCursor2ᚖnetlandishᚗcomᚋxᚋgobwebsᚑgraphqlᚋcursorᚐCursor(ctx context.Context, sel ast.SelectionSet, v *cursor.Cursor) graphql.Marshaler {
if v == nil {
return graphql.Null
}
diff --git a/api/graph/model/cursor.go b/api/graph/model/cursor.go
deleted file mode 100644
index f4e05eb..0000000
--- a/api/graph/model/cursor.go
@@ -1,59 +0,0 @@
-package model
-
-import (
- "encoding/base64"
- "encoding/json"
- "fmt"
- "io"
- "links/valid"
-
- "github.com/vektah/gqlparser/v2/gqlerror"
-)
-
-const (
- PaginationDefault int = 25
- PaginationMax int = 100
-)
-
-// Cursor is used to represent a graph cursor in the resolver
-// as well in the handler parsin data
-type Cursor struct {
- After int
- Before int
- Limit int
-}
-
-// errInvalidCursor carries a validation code so consumers running the error
-// through ParseInputErrors render a form error rather than falling through to
-// the raw error path, which the web tier reports as a 500.
-func errInvalidCursor() error {
- return &gqlerror.Error{
- Message: "Invalid cursor",
- Extensions: map[string]any{"code": valid.ErrValidationGlobalCode},
- }
-}
-
-func (c *Cursor) UnmarshalGQL(v any) error {
- enc, ok := v.(string)
- if !ok {
- return fmt.Errorf("cursor must be strings")
- }
- plain, err := base64.StdEncoding.DecodeString(enc)
- if plain == nil || err != nil {
- return errInvalidCursor()
- }
- if err = json.Unmarshal(plain, c); err != nil {
- return errInvalidCursor()
- }
- return nil
-}
-
-func (c Cursor) MarshalGQL(w io.Writer) {
- data, err := json.Marshal(c)
- if err != nil {
- panic(err)
- }
- w.Write([]byte("\""))
- w.Write([]byte(base64.StdEncoding.EncodeToString(data)))
- w.Write([]byte("\""))
-}
diff --git a/api/graph/model/models_gen.go b/api/graph/model/models_gen.go
index 79ddf39..fb3b6f5 100644
--- a/api/graph/model/models_gen.go
+++ b/api/graph/model/models_gen.go
@@ -12,6 +12,7 @@ import (
"github.com/99designs/gqlgen/graphql"
"netlandish.com/x/gobwebs-auditlog"
+ "netlandish.com/x/gobwebs-graphql/cursor"
)
type AddListingInput struct {
@@ -114,12 +115,12 @@ type AuditLogCursor struct {
}
type AuditLogInput struct {
- UserID *int `json:"userId,omitempty"`
- OrgSlug *string `json:"orgSlug,omitempty"`
- ListingID *int `json:"listingId,omitempty"`
- After *Cursor `json:"after,omitempty"`
- Before *Cursor `json:"before,omitempty"`
- Limit *int `json:"limit,omitempty"`
+ UserID *int `json:"userId,omitempty"`
+ OrgSlug *string `json:"orgSlug,omitempty"`
+ ListingID *int `json:"listingId,omitempty"`
+ After *cursor.Cursor `json:"after,omitempty"`
+ Before *cursor.Cursor `json:"before,omitempty"`
+ Limit *int `json:"limit,omitempty"`
}
type BookmarkCursor struct {
@@ -169,8 +170,8 @@ type EmailPostUser struct {
type GetAdminDomainInput struct {
Limit *int `json:"limit,omitempty"`
- After *Cursor `json:"after,omitempty"`
- Before *Cursor `json:"before,omitempty"`
+ After *cursor.Cursor `json:"after,omitempty"`
+ Before *cursor.Cursor `json:"before,omitempty"`
Search *string `json:"search,omitempty"`
FilterLevel *DomainLevel `json:"filterLevel,omitempty"`
FilterService *DomainService `json:"filterService,omitempty"`
@@ -179,17 +180,17 @@ type GetAdminDomainInput struct {
}
type GetAdminOrganizationsInput struct {
- Limit *int `json:"limit,omitempty"`
- After *Cursor `json:"after,omitempty"`
- Before *Cursor `json:"before,omitempty"`
- Search *string `json:"search,omitempty"`
- OwnerID *int `json:"ownerId,omitempty"`
+ Limit *int `json:"limit,omitempty"`
+ After *cursor.Cursor `json:"after,omitempty"`
+ Before *cursor.Cursor `json:"before,omitempty"`
+ Search *string `json:"search,omitempty"`
+ OwnerID *int `json:"ownerId,omitempty"`
}
type GetFeedInput struct {
Limit *int `json:"limit,omitempty"`
- After *Cursor `json:"after,omitempty"`
- Before *Cursor `json:"before,omitempty"`
+ After *cursor.Cursor `json:"after,omitempty"`
+ Before *cursor.Cursor `json:"before,omitempty"`
Tag *string `json:"tag,omitempty"`
ExcludeTag *string `json:"excludeTag,omitempty"`
Search *string `json:"search,omitempty"`
@@ -200,8 +201,8 @@ type GetFeedInput struct {
type GetLinkInput struct {
OrgSlug *string `json:"orgSlug,omitempty"`
Limit *int `json:"limit,omitempty"`
- After *Cursor `json:"after,omitempty"`
- Before *Cursor `json:"before,omitempty"`
+ After *cursor.Cursor `json:"after,omitempty"`
+ Before *cursor.Cursor `json:"before,omitempty"`
Tag *string `json:"tag,omitempty"`
ExcludeTag *string `json:"excludeTag,omitempty"`
Search *string `json:"search,omitempty"`
@@ -213,64 +214,64 @@ type GetLinkInput struct {
}
type GetLinkShortInput struct {
- OrgSlug string `json:"orgSlug"`
- Limit *int `json:"limit,omitempty"`
- After *Cursor `json:"after,omitempty"`
- Before *Cursor `json:"before,omitempty"`
- Tag *string `json:"tag,omitempty"`
- ExcludeTag *string `json:"excludeTag,omitempty"`
- DomainID *int `json:"domainId,omitempty"`
+ OrgSlug string `json:"orgSlug"`
+ Limit *int `json:"limit,omitempty"`
+ After *cursor.Cursor `json:"after,omitempty"`
+ Before *cursor.Cursor `json:"before,omitempty"`
+ Tag *string `json:"tag,omitempty"`
+ ExcludeTag *string `json:"excludeTag,omitempty"`
+ DomainID *int `json:"domainId,omitempty"`
}
type GetListingDetailInput struct {
- DomainID int `json:"domainId"`
- Slug *string `json:"slug,omitempty"`
- Limit *int `json:"limit,omitempty"`
- After *Cursor `json:"after,omitempty"`
- Before *Cursor `json:"before,omitempty"`
+ DomainID int `json:"domainId"`
+ Slug *string `json:"slug,omitempty"`
+ Limit *int `json:"limit,omitempty"`
+ After *cursor.Cursor `json:"after,omitempty"`
+ Before *cursor.Cursor `json:"before,omitempty"`
}
type GetListingInput struct {
- OrgSlug string `json:"orgSlug"`
- Limit *int `json:"limit,omitempty"`
- After *Cursor `json:"after,omitempty"`
- Before *Cursor `json:"before,omitempty"`
- Tag *string `json:"tag,omitempty"`
- ExcludeTag *string `json:"excludeTag,omitempty"`
- DomainID *int `json:"domainId,omitempty"`
+ OrgSlug string `json:"orgSlug"`
+ Limit *int `json:"limit,omitempty"`
+ After *cursor.Cursor `json:"after,omitempty"`
+ Before *cursor.Cursor `json:"before,omitempty"`
+ Tag *string `json:"tag,omitempty"`
+ ExcludeTag *string `json:"excludeTag,omitempty"`
+ DomainID *int `json:"domainId,omitempty"`
}
type GetOrganizationsInput struct {
- Limit *int `json:"limit,omitempty"`
- After *Cursor `json:"after,omitempty"`
- Before *Cursor `json:"before,omitempty"`
- Search *string `json:"search,omitempty"`
+ Limit *int `json:"limit,omitempty"`
+ After *cursor.Cursor `json:"after,omitempty"`
+ Before *cursor.Cursor `json:"before,omitempty"`
+ Search *string `json:"search,omitempty"`
}
type GetPaymentInput struct {
- OrgSlug *string `json:"orgSlug,omitempty"`
- Limit *int `json:"limit,omitempty"`
- After *Cursor `json:"after,omitempty"`
- Before *Cursor `json:"before,omitempty"`
- Interval *int `json:"interval,omitempty"`
- DateStart *string `json:"dateStart,omitempty"`
- DateEnd *string `json:"dateEnd,omitempty"`
- Filter *bool `json:"filter,omitempty"`
+ OrgSlug *string `json:"orgSlug,omitempty"`
+ Limit *int `json:"limit,omitempty"`
+ After *cursor.Cursor `json:"after,omitempty"`
+ Before *cursor.Cursor `json:"before,omitempty"`
+ Interval *int `json:"interval,omitempty"`
+ DateStart *string `json:"dateStart,omitempty"`
+ DateEnd *string `json:"dateEnd,omitempty"`
+ Filter *bool `json:"filter,omitempty"`
}
type GetTagsInput struct {
OrgSlug string `json:"orgSlug"`
Service *DomainService `json:"service,omitempty"`
- After *Cursor `json:"after,omitempty"`
- Before *Cursor `json:"before,omitempty"`
+ After *cursor.Cursor `json:"after,omitempty"`
+ Before *cursor.Cursor `json:"before,omitempty"`
Limit *int `json:"limit,omitempty"`
}
type GetUserInput struct {
- Limit *int `json:"limit,omitempty"`
- After *Cursor `json:"after,omitempty"`
- Before *Cursor `json:"before,omitempty"`
- Search *string `json:"search,omitempty"`
+ Limit *int `json:"limit,omitempty"`
+ After *cursor.Cursor `json:"after,omitempty"`
+ Before *cursor.Cursor `json:"before,omitempty"`
+ Search *string `json:"search,omitempty"`
}
type LinkInput struct {
@@ -371,9 +372,9 @@ type OrganizationStats struct {
// This is used in various cursor objects for pagination
type PageInfo struct {
- Cursor Cursor `json:"cursor"`
- HasNextPage bool `json:"hasNextPage"`
- HasPrevPage bool `json:"hasPrevPage"`
+ Cursor cursor.Cursor `json:"cursor"`
+ HasNextPage bool `json:"hasNextPage"`
+ HasPrevPage bool `json:"hasPrevPage"`
}
type Payment struct {
diff --git a/api/graph/pagination.go b/api/graph/pagination.go
index 09068e1..9727afa 100644
--- a/api/graph/pagination.go
+++ b/api/graph/pagination.go
@@ -5,6 +5,7 @@ import (
"links/api/graph/model"
sq "github.com/Masterminds/squirrel"
+ "netlandish.com/x/gobwebs-graphql/cursor"
"netlandish.com/x/gobwebs/database"
)
@@ -14,7 +15,7 @@ type contextKey struct {
name string
}
-// PaginationContext is used to override `model.PaginationMax` value
+// PaginationContext is used to override `cursor.PaginationMax` value
func PaginationContext(ctx context.Context, limit int) context.Context {
return context.WithValue(ctx, paginationCtxKey, limit)
}
@@ -23,12 +24,12 @@ func PaginationContext(ctx context.Context, limit int) context.Context {
func ForPaginationContext(ctx context.Context) int {
limit, ok := ctx.Value(paginationCtxKey).(int)
if !ok {
- return model.PaginationMax
+ return cursor.PaginationMax
}
return limit
}
-func PaginateResults[T any](items []T, limit int, before, after *model.Cursor,
+func PaginateResults[T any](items []T, limit int, before, after *cursor.Cursor,
getID func(T) int) ([]T, *model.PageInfo) {
overFetched := len(items) > limit
if overFetched {
@@ -57,7 +58,7 @@ func PaginateResults[T any](items []T, limit int, before, after *model.Cursor,
return items, &pageInfo
}
-func cursorField(before, after *model.Cursor, idField, orderDir string) sq.Sqlizer {
+func cursorField(before, after *cursor.Cursor, idField, orderDir string) sq.Sqlizer {
if after != nil {
if orderDir == "DESC" {
return sq.Lt{idField: after.After}
@@ -80,11 +81,11 @@ func QueryModel[T any](
opts *database.FilterOptions,
idField, orderDir string,
limit *int,
- before, after *model.Cursor,
+ before, after *cursor.Cursor,
getModels func(context.Context, *database.FilterOptions) ([]T, error),
getID func(T) int,
) ([]T, *model.PageInfo, error) {
- numElements := model.PaginationDefault
+ numElements := cursor.PaginationDefault
if after != nil {
opts.Filter = sq.And{
opts.Filter,
@@ -114,10 +115,10 @@ func QueryModel[T any](
// would drop the SQL LIMIT clause entirely in GetBuilder and then panic the
// slice reslice in PaginateResults.
if numElements <= 0 {
- numElements = model.PaginationDefault
+ numElements = cursor.PaginationDefault
}
- // maxLimit will default to model.PaginationMax
+ // maxLimit will default to cursor.PaginationMax
maxLimit := ForPaginationContext(ctx)
if numElements > maxLimit {
numElements = maxLimit
diff --git a/api/graph/pagination_test.go b/api/graph/pagination_test.go
index 6307936..86bae1c 100644
--- a/api/graph/pagination_test.go
+++ b/api/graph/pagination_test.go
@@ -7,6 +7,7 @@ import (
"testing"
"github.com/stretchr/testify/assert"
+ "netlandish.com/x/gobwebs-graphql/cursor"
"netlandish.com/x/gobwebs/database"
)
@@ -16,7 +17,7 @@ func TestPaginationForwardAndBackward(t *testing.T) {
allItems = append(allItems, i)
}
- getPage := func(after, before *model.Cursor) ([]int, *model.PageInfo) {
+ getPage := func(after, before *cursor.Cursor) ([]int, *model.PageInfo) {
var filtered []int
for _, id := range allItems {
// ORDER BY id DESC
@@ -38,7 +39,7 @@ func TestPaginationForwardAndBackward(t *testing.T) {
}
t.Run("Forward Pagination", func(t *testing.T) {
- var after *model.Cursor
+ var after *cursor.Cursor
seen := make(map[int]bool)
for {
page, info := getPage(after, nil)
@@ -52,13 +53,13 @@ func TestPaginationForwardAndBackward(t *testing.T) {
if !info.HasNextPage {
break
}
- after = &model.Cursor{After: info.Cursor.After, Limit: 5}
+ after = &cursor.Cursor{After: info.Cursor.After, Limit: 5}
}
assert.Equal(t, 26, len(seen))
})
t.Run("Backward Pagination", func(t *testing.T) {
- before := &model.Cursor{Before: 0}
+ before := &cursor.Cursor{Before: 0}
seen := make(map[int]bool)
for {
page, info := getPage(nil, before)
@@ -72,7 +73,7 @@ func TestPaginationForwardAndBackward(t *testing.T) {
if !info.HasPrevPage {
break
}
- before = &model.Cursor{Before: info.Cursor.Before, Limit: 5}
+ before = &cursor.Cursor{Before: info.Cursor.Before, Limit: 5}
}
assert.Equal(t, 26, len(seen))
})
@@ -84,7 +85,7 @@ func TestQueryModelClampsCursorLimit(t *testing.T) {
items = append(items, i)
}
- run := func(t *testing.T, limit *int, before, after *model.Cursor) int {
+ run := func(t *testing.T, limit *int, before, after *cursor.Cursor) int {
t.Helper()
var gotLimit int
getModels := func(_ context.Context, opts *database.FilterOptions) ([]int, error) {
@@ -103,22 +104,22 @@ func TestQueryModelClampsCursorLimit(t *testing.T) {
// A negative cursor limit used to leave opts.Limit at zero, which drops the SQL
// LIMIT clause entirely and then panics on the reslice in PaginateResults.
t.Run("negative after limit", func(t *testing.T) {
- assert.Equal(t, model.PaginationDefault+1,
- run(t, nil, nil, &model.Cursor{After: 150, Limit: -1}))
+ assert.Equal(t, cursor.PaginationDefault+1,
+ run(t, nil, nil, &cursor.Cursor{After: 150, Limit: -1}))
})
t.Run("zero before limit", func(t *testing.T) {
- assert.Equal(t, model.PaginationDefault+1,
- run(t, nil, &model.Cursor{Before: 150, Limit: 0}, nil))
+ assert.Equal(t, cursor.PaginationDefault+1,
+ run(t, nil, &cursor.Cursor{Before: 150, Limit: 0}, nil))
})
t.Run("negative explicit limit falls back to cursor", func(t *testing.T) {
negative := -5
- assert.Equal(t, 11, run(t, &negative, nil, &model.Cursor{After: 150, Limit: 10}))
+ assert.Equal(t, 11, run(t, &negative, nil, &cursor.Cursor{After: 150, Limit: 10}))
})
t.Run("oversized cursor limit still capped", func(t *testing.T) {
- assert.Equal(t, model.PaginationMax+1,
- run(t, nil, nil, &model.Cursor{After: 150, Limit: 10000}))
+ assert.Equal(t, cursor.PaginationMax+1,
+ run(t, nil, nil, &cursor.Cursor{After: 150, Limit: 10000}))
})
}
diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go
index bdb6ccc..1033be8 100644
--- a/api/graph/schema.resolvers.go
+++ b/api/graph/schema.resolvers.go
@@ -23,7 +23,6 @@ import (
"links/domain"
"links/internal/localizer"
"links/models"
- "links/valid"
"net/url"
"os"
"regexp"
@@ -40,8 +39,9 @@ import (
"golang.org/x/image/draw"
"golang.org/x/net/idna"
"netlandish.com/x/gobwebs"
- auditlog "netlandish.com/x/gobwebs-auditlog"
- oauth2 "netlandish.com/x/gobwebs-oauth2"
+ "netlandish.com/x/gobwebs-auditlog"
+ "netlandish.com/x/gobwebs-graphql/valid"
+ "netlandish.com/x/gobwebs-oauth2"
gaccounts "netlandish.com/x/gobwebs/accounts"
"netlandish.com/x/gobwebs/crypto"
"netlandish.com/x/gobwebs/database"
diff --git a/go.mod b/go.mod
index 4c3c217..e6b935e 100644
--- a/go.mod
+++ b/go.mod
@@ -36,7 +36,7 @@ require (
netlandish.com/x/gobwebs v0.1.15
netlandish.com/x/gobwebs-auditlog v0.3.2
netlandish.com/x/gobwebs-formguard v0.2.13
- netlandish.com/x/gobwebs-graphql v0.2.14
+ netlandish.com/x/gobwebs-graphql v0.3.0
netlandish.com/x/gobwebs-oauth2 v0.2.13
netlandish.com/x/gobwebs-ses-feedback v0.2.13
petersanchez.com/x/carrier v0.2.4
diff --git a/go.sum b/go.sum
index d2cccaf..4469686 100644
--- a/go.sum
+++ b/go.sum
@@ -2588,8 +2588,8 @@ netlandish.com/x/gobwebs-auditlog v0.3.2 h1:mG3y/gA9wLFbm172JAg3qhQXsviL7AEUw1TA
netlandish.com/x/gobwebs-auditlog v0.3.2/go.mod h1:Hp/tmzsy6Cum7w/s3xeA7iXHOkTVCxLYmU7mEXJZuL4=
netlandish.com/x/gobwebs-formguard v0.2.13 h1:bqolzDBFLYk9V3qglIXccMEefIoFlCHrte7UjRyN9EY=
netlandish.com/x/gobwebs-formguard v0.2.13/go.mod h1:3bH1yWdbFauqMr+CP5hmsOSt4VHIY4qfo4wPQfRnasg=
-netlandish.com/x/gobwebs-graphql v0.2.14 h1:rQ98bzgEat9/FSbKTqYwJi6juIQ2Rh1RTc50dG2XAl8=
-netlandish.com/x/gobwebs-graphql v0.2.14/go.mod h1:T45dDN3aIbpOX+rjAMyxa/yZuB3HZqgPAyEEypVWIiE=
+netlandish.com/x/gobwebs-graphql v0.3.0 h1:E8v+R4TYBezP1zgbXgK7aLFA+jQAi1VIlz6l+DucOis=
+netlandish.com/x/gobwebs-graphql v0.3.0/go.mod h1:ihf/UpV46TJSVgGw0avUeMw/YANC08BzOZJ+fvk+zyM=
netlandish.com/x/gobwebs-oauth2 v0.2.13 h1:Wco3mWNzXIq5PxdgrvWLXKO6kCH6G0GiHJgkyc9U7KI=
netlandish.com/x/gobwebs-oauth2 v0.2.13/go.mod h1:4+LM3EHqQ0gtONbVdExgZBNckI/Pmx23XELZiXv7fpw=
netlandish.com/x/gobwebs-ses-feedback v0.2.13 h1:PFKHqv4cOfOG+rx5zQnl/tmYYQRrW+SP1SaKJ29b1e8=
diff --git a/helpers.go b/helpers.go
index ceaf027..6617434 100644
--- a/helpers.go
+++ b/helpers.go
@@ -9,11 +9,9 @@ import (
"fmt"
"html/template"
"io"
- "links/api/graph/model"
"links/domain"
"links/internal/localizer"
"links/models"
- "links/valid"
"mime/multipart"
"net"
"net/http"
@@ -40,6 +38,8 @@ import (
"golang.org/x/time/rate"
"netlandish.com/x/gobwebs"
auditlog "netlandish.com/x/gobwebs-auditlog"
+ "netlandish.com/x/gobwebs-graphql/cursor"
+ "netlandish.com/x/gobwebs-graphql/valid"
"netlandish.com/x/gobwebs/config"
"netlandish.com/x/gobwebs/core"
"netlandish.com/x/gobwebs/crypto"
@@ -501,7 +501,7 @@ func ParsePendingBaseURLs(ctx context.Context, userAgent string) error {
}
func ValidCursor(v string) bool {
- var cur model.Cursor
+ var cur cursor.Cursor
return cur.UnmarshalGQL(v) == nil
}
@@ -530,7 +530,7 @@ func SafeRedirect(next, fallback string) string {
}
// GetPaginationParams returns the params needed for cursor pagination
-func GetPaginationParams(c echo.Context, pagvar, cursor string, exclude ...string) template.URL {
+func GetPaginationParams(c echo.Context, pagvar, cur string, exclude ...string) template.URL {
q := make(url.Values)
oldq := c.QueryParams()
for k, val := range oldq {
@@ -541,7 +541,7 @@ func GetPaginationParams(c echo.Context, pagvar, cursor string, exclude ...strin
q.Add(k, v)
}
}
- q.Set(pagvar, cursor)
+ q.Set(pagvar, cur)
return template.URL(q.Encode())
}
diff --git a/helpers_test.go b/helpers_test.go
index 2cf5938..710ddcf 100644
--- a/helpers_test.go
+++ b/helpers_test.go
@@ -2,7 +2,6 @@ package links
import (
"encoding/base64"
- "links/api/graph/model"
"net/http"
"net/http/httptest"
"net/url"
@@ -11,11 +10,12 @@ import (
"git.sr.ht/~emersion/gqlclient"
"github.com/labstack/echo/v4"
"github.com/stretchr/testify/require"
+ "netlandish.com/x/gobwebs-graphql/cursor"
)
func validCursor(t *testing.T) string {
t.Helper()
- cur := model.Cursor{After: 199071, Before: 207789, Limit: 25}
+ cur := cursor.Cursor{After: 199071, Before: 207789, Limit: 25}
rec := httptest.NewRecorder()
cur.MarshalGQL(rec.Body)
return rec.Body.String()[1 : rec.Body.Len()-1]
diff --git a/valid/valid.go b/valid/valid.go
deleted file mode 100644
index 30210d4..0000000
--- a/valid/valid.go
@@ -1,231 +0,0 @@
-package valid
-
-import (
- "context"
- "errors"
- "fmt"
-
- "github.com/99designs/gqlgen/graphql"
- "github.com/vektah/gqlparser/v2/gqlerror"
-)
-
-// ErrValidationCode ...
-const (
- ErrValidationCode int = 100
- ErrNotFoundCode int = 200
- ErrValidationGlobalCode int = 300
- ErrRestrictedCode int = 400
-)
-
-var (
- // ErrAuthorization ...
- ErrAuthorization = errors.New("Authorizatoin required")
-)
-
-// Validation ...
-type Validation struct {
- ctx context.Context
- input map[string]any
-}
-
-// ValidationError ...
-type ValidationError struct {
- valid *Validation
- err *gqlerror.Error
-}
-
-// Error Returns a new GraphQL error attached to the given field.
-func Error(ctx context.Context, field string, msg string) error {
- return &gqlerror.Error{
- Message: msg,
- Path: graphql.GetPath(ctx),
- Extensions: map[string]any{
- "field": field,
- },
- }
-}
-
-// Errorf Returns a new GraphQL error attached to the given field.
-func Errorf(ctx context.Context, field string, msg string, items ...any) error {
- return &gqlerror.Error{
- Message: fmt.Sprintf(msg, items...),
- Path: graphql.GetPath(ctx),
- Extensions: map[string]any{
- "field": field,
- },
- }
-}
-
-// New Creates a new validation context.
-func New(ctx context.Context) *Validation {
- return &Validation{
- ctx: ctx,
- }
-}
-
-// WithInput Adds an input map to a validation context.
-func (valid *Validation) WithInput(input map[string]any) *Validation {
- valid.input = input
- return valid
-}
-
-// Ok Returns true if no errors were found.
-func (valid *Validation) Ok() bool {
- return len(graphql.GetErrors(valid.ctx)) == 0
-}
-
-// Optional Fetches an item from the validation context, which must have an input
-// registered. If the field is not present, the callback is not run. Otherwise,
-// the function is called with the value for the user to conduct further
-// validation with.
-func (valid *Validation) Optional(name string, fn func(i any)) {
- if valid.input == nil {
- panic("Attempted to validate fields without input")
- }
- if o, ok := valid.input[name]; ok {
- if o == nil {
- return
- }
- fn(o)
- }
-}
-
-// OptionalString Fetches a string from the validation context, which must have an input
-// registered. If the field is not present, the callback is not run. If
-// present, but not a string, an error is recorded. Otherwise, the function is
-// called with the string for the user to conduct further validation with.
-func (valid *Validation) OptionalString(name string, fn func(s string)) {
- if valid.input == nil {
- panic("Attempted to validate fields without input")
- }
- if o, ok := valid.input[name]; ok {
- if o == nil {
- return
- }
- var val string
- switch s := o.(type) {
- case string:
- val = s
- case *string:
- val = *s
- default:
- valid.
- Error("Expected %s to be a string", name).
- WithField(name)
- return
- }
- fn(val)
- }
-}
-
-// NullableString Fetches a nullable string from the validation context, which must have an
-// input registered. If the field is not present, the callback is not run. If
-// present, but null, the function is called with null set to true. Otherwise,
-// the function is called with the string for the user to conduct further
-// validation with.
-func (valid *Validation) NullableString(name string, fn func(s *string)) {
- if valid.input == nil {
- panic("Attempted to validate fields without input")
- }
- if o, ok := valid.input[name]; ok {
- var val *string
- if o != nil {
- switch s := o.(type) {
- case string:
- val = &s
- case *string:
- val = s
- default:
- valid.
- Error("Expected %s to be a string", name).
- WithField(name)
- return
- }
- }
- fn(val)
- }
-}
-
-// OptionalBool Fetches a boolean from the validation context, which must have an input
-// registered. If the field is not present, the callback is not run. If
-// present, but not a boolean, an error is recorded. Otherwise, the function is
-// called with the boolean for the user to conduct further validation with.
-func (valid *Validation) OptionalBool(name string, fn func(b bool)) {
- if valid.input == nil {
- panic("Attempted to validate fields without input")
- }
- if o, ok := valid.input[name]; ok {
- if o == nil {
- return
- }
- var val bool
- switch b := o.(type) {
- case bool:
- val = b
- case *bool:
- val = *b
- default:
- valid.
- Error("Expected %s to be a bool", name).
- WithField(name)
- return
- }
- fn(val)
- }
-}
-
-// Creates a validation error unconditionally.
-func (valid *Validation) Error(msg string, items ...any) *ValidationError {
- err := &gqlerror.Error{
- Path: graphql.GetPath(valid.ctx),
- Message: fmt.Sprintf(msg, items...),
- }
- graphql.AddError(valid.ctx, err)
- return &ValidationError{
- valid: valid,
- err: err,
- }
-}
-
-// Expect Asserts that a condition is true, recording a GraphQL error with the given
-// message if not.
-func (valid *Validation) Expect(cond bool, msg string, items ...any) *ValidationError {
- if cond {
- return &ValidationError{valid: valid}
- }
- return valid.Error(msg, items...)
-}
-
-// WithField Associates a field name with an error.
-func (err *ValidationError) WithField(field string) *ValidationError {
- if err.err == nil {
- return err
- }
- if err.err.Extensions == nil {
- err.err.Extensions = make(map[string]any)
- }
- err.err.Extensions["field"] = field
- return err
-}
-
-// WithCode Associates an error message with an error code.
-func (err *ValidationError) WithCode(code int) *ValidationError {
- if err.err == nil {
- return err
- }
- if err.err.Extensions == nil {
- err.err.Extensions = make(map[string]any)
- }
- err.err.Extensions["code"] = code
- return err
-}
-
-// And Composes another assertion onto the same validation context which initially
-// created an error. Short-circuiting is used, such that if the earlier
-// condition failed, the new condition is not considered.
-func (err *ValidationError) And(cond bool, msg string, items ...any) *ValidationError {
- if err.err != nil {
- return err
- }
- return err.valid.Expect(cond, msg, items...)
-}
--
2.54.0