3
go.mod
3
go.mod
@@ -3,10 +3,12 @@ module github.com/getprobo/probo
|
||||
go 1.23.4
|
||||
|
||||
require (
|
||||
gearno.de/ref v0.0.0-20221013162104-a522beda40f4
|
||||
github.com/99designs/gqlgen v0.17.63
|
||||
github.com/jackc/pgx/v5 v5.7.1
|
||||
github.com/prometheus/client_golang v1.20.5
|
||||
github.com/vektah/gqlparser/v2 v2.5.21
|
||||
go.gearno.de/crypto/uuid v0.1.0
|
||||
go.gearno.de/kit v0.0.0-20241119134016-ee4f36699e6b
|
||||
go.opentelemetry.io/otel/trace v1.32.0
|
||||
)
|
||||
@@ -34,7 +36,6 @@ require (
|
||||
github.com/sosodev/duration v1.3.1 // indirect
|
||||
github.com/urfave/cli/v2 v2.27.5 // indirect
|
||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||
go.gearno.de/crypto/uuid v0.1.0 // indirect
|
||||
go.gearno.de/x/panicf v0.1.1 // indirect
|
||||
go.opentelemetry.io/otel v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -1,3 +1,5 @@
|
||||
gearno.de/ref v0.0.0-20221013162104-a522beda40f4 h1:tS3oPI89+Y33vZRR/DwbNTewefycz1UzEeDYgMKNdzY=
|
||||
gearno.de/ref v0.0.0-20221013162104-a522beda40f4/go.mod h1:yMxgb+Im8XTCLBBgpy8p5xE6/OaWkKyfypNd3HPQpLw=
|
||||
github.com/99designs/gqlgen v0.17.63 h1:HCdaYDPd9HqUXRchEvmE3EFzELRwLlaJ8DBuyC8Cqto=
|
||||
github.com/99designs/gqlgen v0.17.63/go.mod h1:sVCM2iwIZisJjTI/DEC3fpH+HFgxY1496ZJ+jbT9IjA=
|
||||
github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY=
|
||||
|
||||
@@ -22,3 +22,6 @@ models:
|
||||
Datetime:
|
||||
model:
|
||||
- "github.com/99designs/gqlgen/graphql.Time"
|
||||
CursorKey:
|
||||
model:
|
||||
- "github.com/getprobo/probo/pkg/api/console/v1/types.CursorKeyScalar"
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
package console_v1
|
||||
|
||||
import "github.com/getprobo/probo/pkg/probo"
|
||||
|
||||
type (
|
||||
Resolver struct{}
|
||||
Resolver struct {
|
||||
svc *probo.Service
|
||||
}
|
||||
)
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/99designs/gqlgen/graphql/introspection"
|
||||
"github.com/getprobo/probo/pkg/api/console/v1/types"
|
||||
"github.com/getprobo/probo/pkg/probo/coredata/page"
|
||||
gqlparser "github.com/vektah/gqlparser/v2"
|
||||
"github.com/vektah/gqlparser/v2/ast"
|
||||
)
|
||||
@@ -54,7 +55,7 @@ type ComplexityRoot struct {
|
||||
Description func(childComplexity int) int
|
||||
ID func(childComplexity int) int
|
||||
Name func(childComplexity int) int
|
||||
Tasks func(childComplexity int, first *int, after *string, last *int, before *string) int
|
||||
Tasks func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
|
||||
UpdatedAt func(childComplexity int) int
|
||||
}
|
||||
|
||||
@@ -69,7 +70,7 @@ type ComplexityRoot struct {
|
||||
}
|
||||
|
||||
Framework struct {
|
||||
Controls func(childComplexity int, first *int, after *string, last *int, before *string) int
|
||||
Controls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
|
||||
CreatedAt func(childComplexity int) int
|
||||
Description func(childComplexity int) int
|
||||
ID func(childComplexity int) int
|
||||
@@ -89,7 +90,7 @@ type ComplexityRoot struct {
|
||||
|
||||
Organization struct {
|
||||
CreatedAt func(childComplexity int) int
|
||||
Frameworks func(childComplexity int, first *int, after *string, last *int, before *string) int
|
||||
Frameworks func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int
|
||||
ID func(childComplexity int) int
|
||||
Name func(childComplexity int) int
|
||||
UpdatedAt func(childComplexity int) int
|
||||
@@ -125,13 +126,13 @@ type ComplexityRoot struct {
|
||||
}
|
||||
|
||||
type ControlResolver interface {
|
||||
Tasks(ctx context.Context, obj *types.Control, first *int, after *string, last *int, before *string) (*types.TaskConnection, error)
|
||||
Tasks(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TaskConnection, error)
|
||||
}
|
||||
type FrameworkResolver interface {
|
||||
Controls(ctx context.Context, obj *types.Framework, first *int, after *string, last *int, before *string) (*types.ControlConnection, error)
|
||||
Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ControlConnection, error)
|
||||
}
|
||||
type OrganizationResolver interface {
|
||||
Frameworks(ctx context.Context, obj *types.Organization, first *int, after *string, last *int, before *string) (*types.FrameworkConnection, error)
|
||||
Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error)
|
||||
}
|
||||
type QueryResolver interface {
|
||||
Node(ctx context.Context, id string) (types.Node, error)
|
||||
@@ -194,7 +195,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Control.Tasks(childComplexity, args["first"].(*int), args["after"].(*string), args["last"].(*int), args["before"].(*string)), true
|
||||
return e.complexity.Control.Tasks(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true
|
||||
|
||||
case "Control.updatedAt":
|
||||
if e.complexity.Control.UpdatedAt == nil {
|
||||
@@ -241,7 +242,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Framework.Controls(childComplexity, args["first"].(*int), args["after"].(*string), args["last"].(*int), args["before"].(*string)), true
|
||||
return e.complexity.Framework.Controls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true
|
||||
|
||||
case "Framework.createdAt":
|
||||
if e.complexity.Framework.CreatedAt == nil {
|
||||
@@ -323,7 +324,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Organization.Frameworks(childComplexity, args["first"].(*int), args["after"].(*string), args["last"].(*int), args["before"].(*string)), true
|
||||
return e.complexity.Organization.Frameworks(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true
|
||||
|
||||
case "Organization.id":
|
||||
if e.complexity.Organization.ID == nil {
|
||||
@@ -688,13 +689,13 @@ func (ec *executionContext) field_Control_tasks_argsFirst(
|
||||
func (ec *executionContext) field_Control_tasks_argsAfter(
|
||||
ctx context.Context,
|
||||
rawArgs map[string]any,
|
||||
) (*string, error) {
|
||||
) (*page.CursorKey, error) {
|
||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
|
||||
if tmp, ok := rawArgs["after"]; ok {
|
||||
return ec.unmarshalOCursorKey2ᚖstring(ctx, tmp)
|
||||
return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, tmp)
|
||||
}
|
||||
|
||||
var zeroVal *string
|
||||
var zeroVal *page.CursorKey
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
@@ -714,13 +715,13 @@ func (ec *executionContext) field_Control_tasks_argsLast(
|
||||
func (ec *executionContext) field_Control_tasks_argsBefore(
|
||||
ctx context.Context,
|
||||
rawArgs map[string]any,
|
||||
) (*string, error) {
|
||||
) (*page.CursorKey, error) {
|
||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
|
||||
if tmp, ok := rawArgs["before"]; ok {
|
||||
return ec.unmarshalOCursorKey2ᚖstring(ctx, tmp)
|
||||
return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, tmp)
|
||||
}
|
||||
|
||||
var zeroVal *string
|
||||
var zeroVal *page.CursorKey
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
@@ -765,13 +766,13 @@ func (ec *executionContext) field_Framework_controls_argsFirst(
|
||||
func (ec *executionContext) field_Framework_controls_argsAfter(
|
||||
ctx context.Context,
|
||||
rawArgs map[string]any,
|
||||
) (*string, error) {
|
||||
) (*page.CursorKey, error) {
|
||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
|
||||
if tmp, ok := rawArgs["after"]; ok {
|
||||
return ec.unmarshalOCursorKey2ᚖstring(ctx, tmp)
|
||||
return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, tmp)
|
||||
}
|
||||
|
||||
var zeroVal *string
|
||||
var zeroVal *page.CursorKey
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
@@ -791,13 +792,13 @@ func (ec *executionContext) field_Framework_controls_argsLast(
|
||||
func (ec *executionContext) field_Framework_controls_argsBefore(
|
||||
ctx context.Context,
|
||||
rawArgs map[string]any,
|
||||
) (*string, error) {
|
||||
) (*page.CursorKey, error) {
|
||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
|
||||
if tmp, ok := rawArgs["before"]; ok {
|
||||
return ec.unmarshalOCursorKey2ᚖstring(ctx, tmp)
|
||||
return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, tmp)
|
||||
}
|
||||
|
||||
var zeroVal *string
|
||||
var zeroVal *page.CursorKey
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
@@ -842,13 +843,13 @@ func (ec *executionContext) field_Organization_frameworks_argsFirst(
|
||||
func (ec *executionContext) field_Organization_frameworks_argsAfter(
|
||||
ctx context.Context,
|
||||
rawArgs map[string]any,
|
||||
) (*string, error) {
|
||||
) (*page.CursorKey, error) {
|
||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
|
||||
if tmp, ok := rawArgs["after"]; ok {
|
||||
return ec.unmarshalOCursorKey2ᚖstring(ctx, tmp)
|
||||
return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, tmp)
|
||||
}
|
||||
|
||||
var zeroVal *string
|
||||
var zeroVal *page.CursorKey
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
@@ -868,13 +869,13 @@ func (ec *executionContext) field_Organization_frameworks_argsLast(
|
||||
func (ec *executionContext) field_Organization_frameworks_argsBefore(
|
||||
ctx context.Context,
|
||||
rawArgs map[string]any,
|
||||
) (*string, error) {
|
||||
) (*page.CursorKey, error) {
|
||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
|
||||
if tmp, ok := rawArgs["before"]; ok {
|
||||
return ec.unmarshalOCursorKey2ᚖstring(ctx, tmp)
|
||||
return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, tmp)
|
||||
}
|
||||
|
||||
var zeroVal *string
|
||||
var zeroVal *page.CursorKey
|
||||
return zeroVal, nil
|
||||
}
|
||||
|
||||
@@ -1100,7 +1101,7 @@ func (ec *executionContext) _Control_tasks(ctx context.Context, field graphql.Co
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Control().Tasks(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*string), fc.Args["last"].(*int), fc.Args["before"].(*string))
|
||||
return ec.resolvers.Control().Tasks(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
@@ -1329,9 +1330,9 @@ func (ec *executionContext) _ControlEdge_cursor(ctx context.Context, field graph
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(string)
|
||||
res := resTmp.(page.CursorKey)
|
||||
fc.Result = res
|
||||
return ec.marshalNCursorKey2string(ctx, field.Selections, res)
|
||||
return ec.marshalNCursorKey2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_ControlEdge_cursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
@@ -1521,7 +1522,7 @@ func (ec *executionContext) _Framework_controls(ctx context.Context, field graph
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Framework().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*string), fc.Args["last"].(*int), fc.Args["before"].(*string))
|
||||
return ec.resolvers.Framework().Controls(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
@@ -1750,9 +1751,9 @@ func (ec *executionContext) _FrameworkEdge_cursor(ctx context.Context, field gra
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(string)
|
||||
res := resTmp.(page.CursorKey)
|
||||
fc.Result = res
|
||||
return ec.marshalNCursorKey2string(ctx, field.Selections, res)
|
||||
return ec.marshalNCursorKey2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_FrameworkEdge_cursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
@@ -1904,7 +1905,7 @@ func (ec *executionContext) _Organization_frameworks(ctx context.Context, field
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||
ctx = rctx // use context from middleware stack in children
|
||||
return ec.resolvers.Organization().Frameworks(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*string), fc.Args["last"].(*int), fc.Args["before"].(*string))
|
||||
return ec.resolvers.Organization().Frameworks(rctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey))
|
||||
})
|
||||
if err != nil {
|
||||
ec.Error(ctx, err)
|
||||
@@ -2114,9 +2115,9 @@ func (ec *executionContext) _PageInfo_startCursor(ctx context.Context, field gra
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*string)
|
||||
res := resTmp.(*page.CursorKey)
|
||||
fc.Result = res
|
||||
return ec.marshalOCursorKey2ᚖstring(ctx, field.Selections, res)
|
||||
return ec.marshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_PageInfo_startCursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
@@ -2149,9 +2150,9 @@ func (ec *executionContext) _PageInfo_endCursor(ctx context.Context, field graph
|
||||
if resTmp == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(*string)
|
||||
res := resTmp.(*page.CursorKey)
|
||||
fc.Result = res
|
||||
return ec.marshalOCursorKey2ᚖstring(ctx, field.Selections, res)
|
||||
return ec.marshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_PageInfo_endCursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
@@ -2585,9 +2586,9 @@ func (ec *executionContext) _TaskEdge_cursor(ctx context.Context, field graphql.
|
||||
}
|
||||
return graphql.Null
|
||||
}
|
||||
res := resTmp.(string)
|
||||
res := resTmp.(page.CursorKey)
|
||||
fc.Result = res
|
||||
return ec.marshalNCursorKey2string(ctx, field.Selections, res)
|
||||
return ec.marshalNCursorKey2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) fieldContext_TaskEdge_cursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
|
||||
@@ -5358,13 +5359,13 @@ func (ec *executionContext) marshalNControlEdge2ᚖgithubᚗcomᚋgetproboᚋpro
|
||||
return ec._ControlEdge(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNCursorKey2string(ctx context.Context, v any) (string, error) {
|
||||
res, err := graphql.UnmarshalString(v)
|
||||
func (ec *executionContext) unmarshalNCursorKey2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx context.Context, v any) (page.CursorKey, error) {
|
||||
res, err := types.UnmarshalCursorKeyScalar(v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNCursorKey2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler {
|
||||
res := graphql.MarshalString(v)
|
||||
func (ec *executionContext) marshalNCursorKey2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx context.Context, sel ast.SelectionSet, v page.CursorKey) graphql.Marshaler {
|
||||
res := types.MarshalCursorKeyScalar(v)
|
||||
if res == graphql.Null {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
|
||||
@@ -5837,19 +5838,19 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast
|
||||
return res
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalOCursorKey2ᚖstring(ctx context.Context, v any) (*string, error) {
|
||||
func (ec *executionContext) unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx context.Context, v any) (*page.CursorKey, error) {
|
||||
if v == nil {
|
||||
return nil, nil
|
||||
}
|
||||
res, err := graphql.UnmarshalString(v)
|
||||
res, err := types.UnmarshalCursorKeyScalar(v)
|
||||
return &res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalOCursorKey2ᚖstring(ctx context.Context, sel ast.SelectionSet, v *string) graphql.Marshaler {
|
||||
func (ec *executionContext) marshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx context.Context, sel ast.SelectionSet, v *page.CursorKey) graphql.Marshaler {
|
||||
if v == nil {
|
||||
return graphql.Null
|
||||
}
|
||||
res := graphql.MarshalString(*v)
|
||||
res := types.MarshalCursorKeyScalar(*v)
|
||||
return res
|
||||
}
|
||||
|
||||
|
||||
55
pkg/api/console/v1/types/cursorkey.go
Normal file
55
pkg/api/console/v1/types/cursorkey.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/99designs/gqlgen/graphql"
|
||||
"github.com/getprobo/probo/pkg/probo/coredata/page"
|
||||
)
|
||||
|
||||
func NewCursor(
|
||||
first *int,
|
||||
after *page.CursorKey,
|
||||
last *int,
|
||||
before *page.CursorKey,
|
||||
) *page.Cursor {
|
||||
var (
|
||||
size int
|
||||
from *page.CursorKey
|
||||
direction = page.Head
|
||||
)
|
||||
|
||||
if first != nil {
|
||||
size = *first
|
||||
direction = page.Head
|
||||
from = after
|
||||
} else if last != nil {
|
||||
size = *last
|
||||
direction = page.Tail
|
||||
from = before
|
||||
}
|
||||
|
||||
return page.NewCursor(size, from, direction)
|
||||
}
|
||||
|
||||
func MarshalCursorKeyScalar(ck page.CursorKey) graphql.Marshaler {
|
||||
return graphql.WriterFunc(func(w io.Writer) {
|
||||
w.Write([]byte(strconv.Quote(ck.String())))
|
||||
})
|
||||
}
|
||||
|
||||
func UnmarshalCursorKeyScalar(v interface{}) (page.CursorKey, error) {
|
||||
s, ok := v.(string)
|
||||
if !ok {
|
||||
return page.CursorKeyNil, errors.New("must be a string")
|
||||
}
|
||||
|
||||
ck, err := page.ParseCursorKey(s)
|
||||
if err != nil {
|
||||
return page.CursorKeyNil, err
|
||||
}
|
||||
|
||||
return ck, nil
|
||||
}
|
||||
30
pkg/api/console/v1/types/framework.go
Normal file
30
pkg/api/console/v1/types/framework.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/getprobo/probo/pkg/probo/coredata"
|
||||
"github.com/getprobo/probo/pkg/probo/coredata/page"
|
||||
)
|
||||
|
||||
func NewExecutionConnection(p *page.Page[*coredata.Framework]) *FrameworkConnection {
|
||||
var edges = make([]*FrameworkEdge, len(p.Data))
|
||||
|
||||
for i := range edges {
|
||||
edges[i] = NewFrameworkEdge(p.Data[i])
|
||||
}
|
||||
|
||||
return &FrameworkConnection{
|
||||
Edges: edges,
|
||||
PageInfo: NewPageInfo(p),
|
||||
}
|
||||
}
|
||||
|
||||
func NewFrameworkEdge(f *coredata.Framework) *FrameworkEdge {
|
||||
return &FrameworkEdge{
|
||||
Cursor: f.CursorKey(),
|
||||
Node: NewExecution(f),
|
||||
}
|
||||
}
|
||||
|
||||
func NewExecution(e *coredata.Framework) *Framework {
|
||||
return &Framework{}
|
||||
}
|
||||
25
pkg/api/console/v1/types/pageinfo.go
Normal file
25
pkg/api/console/v1/types/pageinfo.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"gearno.de/ref"
|
||||
"github.com/getprobo/probo/pkg/probo/coredata/page"
|
||||
)
|
||||
|
||||
func NewPageInfo[T page.Paginable](p *page.Page[T]) *PageInfo {
|
||||
var (
|
||||
startCursor *page.CursorKey
|
||||
endCursor *page.CursorKey
|
||||
)
|
||||
|
||||
if len(p.Data) > 0 {
|
||||
startCursor = ref.Ref(p.First().CursorKey())
|
||||
endCursor = ref.Ref(p.Last().CursorKey())
|
||||
}
|
||||
|
||||
return &PageInfo{
|
||||
HasNextPage: p.Info.HasNext,
|
||||
HasPreviousPage: p.Info.HasPrev,
|
||||
StartCursor: startCursor,
|
||||
EndCursor: endCursor,
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@ package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/getprobo/probo/pkg/probo/coredata/page"
|
||||
)
|
||||
|
||||
type Node interface {
|
||||
@@ -29,8 +31,8 @@ type ControlConnection struct {
|
||||
}
|
||||
|
||||
type ControlEdge struct {
|
||||
Cursor string `json:"cursor"`
|
||||
Node *Control `json:"node"`
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *Control `json:"node"`
|
||||
}
|
||||
|
||||
type Framework struct {
|
||||
@@ -51,8 +53,8 @@ type FrameworkConnection struct {
|
||||
}
|
||||
|
||||
type FrameworkEdge struct {
|
||||
Cursor string `json:"cursor"`
|
||||
Node *Framework `json:"node"`
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *Framework `json:"node"`
|
||||
}
|
||||
|
||||
type Organization struct {
|
||||
@@ -67,10 +69,10 @@ func (Organization) IsNode() {}
|
||||
func (this Organization) GetID() string { return this.ID }
|
||||
|
||||
type PageInfo struct {
|
||||
HasNextPage bool `json:"hasNextPage"`
|
||||
HasPreviousPage bool `json:"hasPreviousPage"`
|
||||
StartCursor *string `json:"startCursor,omitempty"`
|
||||
EndCursor *string `json:"endCursor,omitempty"`
|
||||
HasNextPage bool `json:"hasNextPage"`
|
||||
HasPreviousPage bool `json:"hasPreviousPage"`
|
||||
StartCursor *page.CursorKey `json:"startCursor,omitempty"`
|
||||
EndCursor *page.CursorKey `json:"endCursor,omitempty"`
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
@@ -92,6 +94,6 @@ type TaskConnection struct {
|
||||
}
|
||||
|
||||
type TaskEdge struct {
|
||||
Cursor string `json:"cursor"`
|
||||
Node *Task `json:"node"`
|
||||
Cursor page.CursorKey `json:"cursor"`
|
||||
Node *Task `json:"node"`
|
||||
}
|
||||
|
||||
@@ -10,21 +10,29 @@ import (
|
||||
|
||||
"github.com/getprobo/probo/pkg/api/console/v1/schema"
|
||||
"github.com/getprobo/probo/pkg/api/console/v1/types"
|
||||
"github.com/getprobo/probo/pkg/probo/coredata/page"
|
||||
)
|
||||
|
||||
// Tasks is the resolver for the tasks field.
|
||||
func (r *controlResolver) Tasks(ctx context.Context, obj *types.Control, first *int, after *string, last *int, before *string) (*types.TaskConnection, error) {
|
||||
func (r *controlResolver) Tasks(ctx context.Context, obj *types.Control, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.TaskConnection, error) {
|
||||
panic(fmt.Errorf("not implemented: Tasks - tasks"))
|
||||
}
|
||||
|
||||
// Controls is the resolver for the controls field.
|
||||
func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, first *int, after *string, last *int, before *string) (*types.ControlConnection, error) {
|
||||
func (r *frameworkResolver) Controls(ctx context.Context, obj *types.Framework, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.ControlConnection, error) {
|
||||
panic(fmt.Errorf("not implemented: Controls - controls"))
|
||||
}
|
||||
|
||||
// Frameworks is the resolver for the frameworks field.
|
||||
func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *string, last *int, before *string) (*types.FrameworkConnection, error) {
|
||||
panic(fmt.Errorf("not implemented: Frameworks - frameworks"))
|
||||
func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) {
|
||||
cursor := types.NewCursor(first, after, last, before)
|
||||
|
||||
page, err := r.svc.ListOrganizationFramework(ctx, "", cursor)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot list organization frameworks: %w", err)
|
||||
}
|
||||
|
||||
return types.NewExecutionConnection(page), nil
|
||||
}
|
||||
|
||||
// Node is the resolver for the node field.
|
||||
|
||||
@@ -2,31 +2,97 @@ package coredata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/getprobo/probo/pkg/probo/coredata/page"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/crypto/uuid"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
type (
|
||||
Framework struct {
|
||||
ID string
|
||||
ID uuid.UUID
|
||||
OrganizationID string
|
||||
ContentID string
|
||||
Name string
|
||||
Description string
|
||||
ContentRef string
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
Frameworks []*Framework
|
||||
)
|
||||
|
||||
func (f *Framework) CursorKey() page.CursorKey {
|
||||
return page.NewCursorKey(f.ID, f.CreatedAt)
|
||||
}
|
||||
|
||||
func (f *Framework) scan(r pgx.Row) error {
|
||||
return r.Scan(
|
||||
&f.ID,
|
||||
&f.OrganizationID,
|
||||
&f.ContentID,
|
||||
&f.Name,
|
||||
&f.Description,
|
||||
&f.ContentRef,
|
||||
&f.CreatedAt,
|
||||
&f.UpdatedAt,
|
||||
)
|
||||
}
|
||||
func (f *Frameworks) LoadByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Conn,
|
||||
organizationID string,
|
||||
cursor *page.Cursor,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
framework_id,
|
||||
organization_id,
|
||||
name,
|
||||
description,
|
||||
content_ref,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
frameworks
|
||||
WHERE
|
||||
organization_id = @organization_id
|
||||
AND %
|
||||
ORDER BY name ASC;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, cursor.SQLFragment())
|
||||
|
||||
args := pgx.NamedArgs{"organization_id": organizationID}
|
||||
maps.Copy(args, cursor.SQLArguments())
|
||||
|
||||
r, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
frameworks := Frameworks{}
|
||||
for r.Next() {
|
||||
framework := &Framework{}
|
||||
if err := framework.scan(r); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
frameworks = append(frameworks, framework)
|
||||
}
|
||||
defer r.Close()
|
||||
|
||||
if err = r.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*f = frameworks
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *Framework) LoadByID(
|
||||
ctx context.Context,
|
||||
@@ -37,7 +103,9 @@ func (f *Framework) LoadByID(
|
||||
SELECT
|
||||
framework_id,
|
||||
organization_id,
|
||||
content_id,
|
||||
name,
|
||||
description,
|
||||
content_ref,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
|
||||
98
pkg/probo/coredata/page/cursor.go
Normal file
98
pkg/probo/coredata/page/cursor.go
Normal file
@@ -0,0 +1,98 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type (
|
||||
Cursor struct {
|
||||
Size int
|
||||
Key *CursorKey
|
||||
Position Position
|
||||
}
|
||||
|
||||
Position int8
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultCursorSize = 25
|
||||
|
||||
Tail Position = iota
|
||||
Head
|
||||
)
|
||||
|
||||
func (p Position) ToDirection() string {
|
||||
switch p {
|
||||
case Tail:
|
||||
return "ASC"
|
||||
case Head:
|
||||
return "DESC"
|
||||
default:
|
||||
panic(fmt.Errorf("unknown direction: %d", p))
|
||||
}
|
||||
}
|
||||
|
||||
func NewCursor(size int, from *CursorKey, pos Position) *Cursor {
|
||||
if size == 0 {
|
||||
size = DefaultCursorSize
|
||||
}
|
||||
|
||||
return &Cursor{
|
||||
Size: size,
|
||||
Key: from,
|
||||
Position: pos,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cursor) SQLFragment() string {
|
||||
return `
|
||||
CASE
|
||||
WHEN @cursor_order = 'DESC' AND @cursor_from_id::TEXT IS NOT NULL THEN (
|
||||
(created_at <= @cursor_from_ts) AND NOT (created_at = @cursor_from_ts AND id > @cursor_from_id)
|
||||
)
|
||||
WHEN @cursor_order = 'ASC' AND @cursor_from_id::TEXT IS NOT NULL THEN (
|
||||
(created_at >= @cursor_from_ts) AND NOT (created_at = @cursor_from_ts AND id < @cursor_from_id)
|
||||
)
|
||||
ELSE TRUE
|
||||
END
|
||||
ORDER BY
|
||||
CASE
|
||||
WHEN @cursor_order = 'ASC' THEN created_at
|
||||
END ASC,
|
||||
CASE
|
||||
WHEN @cursor_order = 'ASC' THEN id
|
||||
END ASC,
|
||||
CASE
|
||||
WHEN @cursor_order = 'DESC' THEN created_at
|
||||
END DESC,
|
||||
CASE
|
||||
WHEN @cursor_order = 'DESC' THEN id
|
||||
END DESC
|
||||
LIMIT @cursor_limit
|
||||
`
|
||||
}
|
||||
|
||||
func (c *Cursor) SQLArguments() pgx.NamedArgs {
|
||||
var size = c.Size
|
||||
if c.Key == nil {
|
||||
size += 1
|
||||
} else {
|
||||
size += 2
|
||||
}
|
||||
|
||||
arguments := pgx.NamedArgs{
|
||||
"cursor_order": c.Position.ToDirection(),
|
||||
"cursor_limit": size,
|
||||
"cursor_from_id": nil,
|
||||
"cursor_from_ts": nil,
|
||||
}
|
||||
|
||||
if c.Key != nil {
|
||||
arguments["cursor_from_id"] = c.Key.ID()
|
||||
arguments["cursor_from_ts"] = c.Key.Timestamp()
|
||||
}
|
||||
|
||||
return arguments
|
||||
}
|
||||
105
pkg/probo/coredata/page/cursorkey.go
Normal file
105
pkg/probo/coredata/page/cursorkey.go
Normal file
@@ -0,0 +1,105 @@
|
||||
package page
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"go.gearno.de/crypto/uuid"
|
||||
)
|
||||
|
||||
type (
|
||||
CursorKey [byteLength]byte
|
||||
)
|
||||
|
||||
var (
|
||||
CursorKeyNil CursorKey
|
||||
|
||||
ErrInvalidFormat = errors.New("invalid format")
|
||||
)
|
||||
|
||||
const (
|
||||
byteLength = 28
|
||||
)
|
||||
|
||||
func ParseCursorKey(s string) (CursorKey, error) {
|
||||
b, err := base64.RawURLEncoding.DecodeString(s)
|
||||
if err != nil {
|
||||
return CursorKeyNil, ErrInvalidFormat
|
||||
}
|
||||
|
||||
ck, err := CursorKeyFromBytes(b)
|
||||
if err != nil {
|
||||
return CursorKeyNil, ErrInvalidFormat
|
||||
}
|
||||
|
||||
return ck, nil
|
||||
}
|
||||
|
||||
func CursorKeyFromBytes(b []byte) (CursorKey, error) {
|
||||
var ck CursorKey
|
||||
|
||||
if len(b) != byteLength {
|
||||
return CursorKeyNil, ErrInvalidFormat
|
||||
}
|
||||
|
||||
copy(ck[:], b)
|
||||
|
||||
return ck, nil
|
||||
}
|
||||
|
||||
func NewCursorKey(id uuid.UUID, t time.Time) CursorKey {
|
||||
var cursorKey CursorKey
|
||||
copy(cursorKey[:20], id[:])
|
||||
_ = binary.PutVarint(cursorKey[20:], t.UnixMicro())
|
||||
|
||||
return cursorKey
|
||||
}
|
||||
|
||||
func (ck CursorKey) Bytes() []byte {
|
||||
return ck[:]
|
||||
}
|
||||
|
||||
func (ck CursorKey) String() string {
|
||||
return base64.RawURLEncoding.EncodeToString(ck.Bytes())
|
||||
}
|
||||
|
||||
func (ck CursorKey) Timestamp() time.Time {
|
||||
return ck.ID().Timestamp()
|
||||
}
|
||||
|
||||
func (ck CursorKey) ID() uuid.UUID {
|
||||
id, _ := uuid.FromBytes(ck[:])
|
||||
return id
|
||||
}
|
||||
|
||||
func (ck CursorKey) MarshalText() ([]byte, error) {
|
||||
return []byte(ck.String()), nil
|
||||
}
|
||||
|
||||
func (ck *CursorKey) UnmarshalText(data []byte) error {
|
||||
ck2, err := ParseCursorKey(string(data))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*ck = ck2
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ck CursorKey) MarshalBinary() ([]byte, error) {
|
||||
return ck.Bytes(), nil
|
||||
}
|
||||
|
||||
func (ck *CursorKey) UnmarshalBinary(b []byte) error {
|
||||
ck2, err := CursorKeyFromBytes(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
*ck = ck2
|
||||
|
||||
return nil
|
||||
}
|
||||
101
pkg/probo/coredata/page/page.go
Normal file
101
pkg/probo/coredata/page/page.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package page
|
||||
|
||||
type (
|
||||
Paginable interface {
|
||||
CursorKey() CursorKey
|
||||
}
|
||||
|
||||
PageInfo struct {
|
||||
HasNext bool
|
||||
HasPrev bool
|
||||
}
|
||||
|
||||
Page[T Paginable] struct {
|
||||
Info *PageInfo
|
||||
Data []T
|
||||
}
|
||||
)
|
||||
|
||||
func (p *Page[T]) First() T {
|
||||
if len(p.Data) == 0 {
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
|
||||
return p.Data[0]
|
||||
}
|
||||
|
||||
func (p *Page[T]) Last() T {
|
||||
if len(p.Data) == 0 {
|
||||
var zero T
|
||||
return zero
|
||||
}
|
||||
|
||||
return p.Data[len(p.Data)-1]
|
||||
}
|
||||
|
||||
func NewPage[T Paginable](data []T, c *Cursor) *Page[T] {
|
||||
pi := &PageInfo{}
|
||||
|
||||
if len(data) == 0 {
|
||||
return &Page[T]{
|
||||
Info: pi,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
edges := data
|
||||
firstFromData := data[0]
|
||||
|
||||
switch c.Position {
|
||||
case Head:
|
||||
if c.Key != nil {
|
||||
if len(edges) == c.Size+2 {
|
||||
edges = edges[1 : len(edges)-1]
|
||||
} else {
|
||||
edges = edges[1:]
|
||||
}
|
||||
} else if c.Key == nil && len(edges) == c.Size+1 {
|
||||
edges = edges[0 : len(edges)-1]
|
||||
}
|
||||
|
||||
if c.Key != nil && *c.Key == firstFromData.CursorKey() {
|
||||
pi.HasPrev = true
|
||||
}
|
||||
|
||||
if c.Key != nil && c.Size+2 == len(data) {
|
||||
pi.HasNext = true
|
||||
} else if c.Key == nil && c.Size+1 == len(data) {
|
||||
pi.HasNext = true
|
||||
}
|
||||
case Tail:
|
||||
for i, j := 0, len(edges)-1; i < j; i, j = i+1, j-1 {
|
||||
edges[i], edges[j] = edges[j], edges[i]
|
||||
}
|
||||
|
||||
if c.Key != nil {
|
||||
if len(edges) == c.Size+2 {
|
||||
edges = edges[1 : len(edges)-1]
|
||||
} else {
|
||||
edges = edges[0 : len(edges)-1]
|
||||
}
|
||||
} else if c.Key == nil && len(edges) == c.Size+1 {
|
||||
edges = edges[1:]
|
||||
}
|
||||
|
||||
if c.Key != nil && *c.Key == firstFromData.CursorKey() {
|
||||
pi.HasNext = true
|
||||
}
|
||||
|
||||
if c.Key != nil && c.Size+2 == len(data) {
|
||||
pi.HasPrev = true
|
||||
} else if c.Key == nil && c.Size+1 == len(data) {
|
||||
pi.HasPrev = true
|
||||
}
|
||||
}
|
||||
|
||||
return &Page[T]{
|
||||
Info: pi,
|
||||
Data: edges,
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package probo
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/getprobo/probo/pkg/probo/coredata"
|
||||
"github.com/getprobo/probo/pkg/probo/coredata/page"
|
||||
"go.gearno.de/kit/pg"
|
||||
)
|
||||
|
||||
@@ -17,3 +19,44 @@ func NewService(ctx context.Context, pgClient *pg.Client) *Service {
|
||||
pg: pgClient,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) GetOrganization(
|
||||
ctx context.Context,
|
||||
organizationID string,
|
||||
) (*coredata.Organization, error) {
|
||||
organization := &coredata.Organization{}
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return organization.LoadByID(ctx, conn, organizationID)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return organization, nil
|
||||
}
|
||||
|
||||
func (s *Service) ListOrganizationFramework(
|
||||
ctx context.Context,
|
||||
organizationID string,
|
||||
cursor *page.Cursor,
|
||||
) (*page.Page[*coredata.Framework], error) {
|
||||
var frameworks coredata.Frameworks
|
||||
|
||||
err := s.pg.WithConn(
|
||||
ctx,
|
||||
func(conn pg.Conn) error {
|
||||
return frameworks.LoadByOrganizationID(ctx, conn, organizationID, cursor)
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return page.NewPage(frameworks, cursor), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user