3
go.mod
3
go.mod
@@ -3,10 +3,12 @@ module github.com/getprobo/probo
|
|||||||
go 1.23.4
|
go 1.23.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
gearno.de/ref v0.0.0-20221013162104-a522beda40f4
|
||||||
github.com/99designs/gqlgen v0.17.63
|
github.com/99designs/gqlgen v0.17.63
|
||||||
github.com/jackc/pgx/v5 v5.7.1
|
github.com/jackc/pgx/v5 v5.7.1
|
||||||
github.com/prometheus/client_golang v1.20.5
|
github.com/prometheus/client_golang v1.20.5
|
||||||
github.com/vektah/gqlparser/v2 v2.5.21
|
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.gearno.de/kit v0.0.0-20241119134016-ee4f36699e6b
|
||||||
go.opentelemetry.io/otel/trace v1.32.0
|
go.opentelemetry.io/otel/trace v1.32.0
|
||||||
)
|
)
|
||||||
@@ -34,7 +36,6 @@ require (
|
|||||||
github.com/sosodev/duration v1.3.1 // indirect
|
github.com/sosodev/duration v1.3.1 // indirect
|
||||||
github.com/urfave/cli/v2 v2.27.5 // indirect
|
github.com/urfave/cli/v2 v2.27.5 // indirect
|
||||||
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // 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.gearno.de/x/panicf v0.1.1 // indirect
|
||||||
go.opentelemetry.io/otel v1.32.0 // indirect
|
go.opentelemetry.io/otel v1.32.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace 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 h1:HCdaYDPd9HqUXRchEvmE3EFzELRwLlaJ8DBuyC8Cqto=
|
||||||
github.com/99designs/gqlgen v0.17.63/go.mod h1:sVCM2iwIZisJjTI/DEC3fpH+HFgxY1496ZJ+jbT9IjA=
|
github.com/99designs/gqlgen v0.17.63/go.mod h1:sVCM2iwIZisJjTI/DEC3fpH+HFgxY1496ZJ+jbT9IjA=
|
||||||
github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY=
|
github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY=
|
||||||
|
|||||||
@@ -22,3 +22,6 @@ models:
|
|||||||
Datetime:
|
Datetime:
|
||||||
model:
|
model:
|
||||||
- "github.com/99designs/gqlgen/graphql.Time"
|
- "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
|
package console_v1
|
||||||
|
|
||||||
|
import "github.com/getprobo/probo/pkg/probo"
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Resolver struct{}
|
Resolver struct {
|
||||||
|
svc *probo.Service
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/99designs/gqlgen/graphql"
|
"github.com/99designs/gqlgen/graphql"
|
||||||
"github.com/99designs/gqlgen/graphql/introspection"
|
"github.com/99designs/gqlgen/graphql/introspection"
|
||||||
"github.com/getprobo/probo/pkg/api/console/v1/types"
|
"github.com/getprobo/probo/pkg/api/console/v1/types"
|
||||||
|
"github.com/getprobo/probo/pkg/probo/coredata/page"
|
||||||
gqlparser "github.com/vektah/gqlparser/v2"
|
gqlparser "github.com/vektah/gqlparser/v2"
|
||||||
"github.com/vektah/gqlparser/v2/ast"
|
"github.com/vektah/gqlparser/v2/ast"
|
||||||
)
|
)
|
||||||
@@ -54,7 +55,7 @@ type ComplexityRoot struct {
|
|||||||
Description func(childComplexity int) int
|
Description func(childComplexity int) int
|
||||||
ID func(childComplexity int) int
|
ID func(childComplexity int) int
|
||||||
Name 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
|
UpdatedAt func(childComplexity int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,7 +70,7 @@ type ComplexityRoot struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Framework 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
|
CreatedAt func(childComplexity int) int
|
||||||
Description func(childComplexity int) int
|
Description func(childComplexity int) int
|
||||||
ID func(childComplexity int) int
|
ID func(childComplexity int) int
|
||||||
@@ -89,7 +90,7 @@ type ComplexityRoot struct {
|
|||||||
|
|
||||||
Organization struct {
|
Organization struct {
|
||||||
CreatedAt func(childComplexity int) int
|
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
|
ID func(childComplexity int) int
|
||||||
Name func(childComplexity int) int
|
Name func(childComplexity int) int
|
||||||
UpdatedAt func(childComplexity int) int
|
UpdatedAt func(childComplexity int) int
|
||||||
@@ -125,13 +126,13 @@ type ComplexityRoot struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ControlResolver interface {
|
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 {
|
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 {
|
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 {
|
type QueryResolver interface {
|
||||||
Node(ctx context.Context, id string) (types.Node, error)
|
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 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":
|
case "Control.updatedAt":
|
||||||
if e.complexity.Control.UpdatedAt == nil {
|
if e.complexity.Control.UpdatedAt == nil {
|
||||||
@@ -241,7 +242,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
return 0, false
|
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":
|
case "Framework.createdAt":
|
||||||
if e.complexity.Framework.CreatedAt == nil {
|
if e.complexity.Framework.CreatedAt == nil {
|
||||||
@@ -323,7 +324,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
|||||||
return 0, false
|
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":
|
case "Organization.id":
|
||||||
if e.complexity.Organization.ID == nil {
|
if e.complexity.Organization.ID == nil {
|
||||||
@@ -688,13 +689,13 @@ func (ec *executionContext) field_Control_tasks_argsFirst(
|
|||||||
func (ec *executionContext) field_Control_tasks_argsAfter(
|
func (ec *executionContext) field_Control_tasks_argsAfter(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
rawArgs map[string]any,
|
rawArgs map[string]any,
|
||||||
) (*string, error) {
|
) (*page.CursorKey, error) {
|
||||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
|
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
|
||||||
if tmp, ok := rawArgs["after"]; ok {
|
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
|
return zeroVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -714,13 +715,13 @@ func (ec *executionContext) field_Control_tasks_argsLast(
|
|||||||
func (ec *executionContext) field_Control_tasks_argsBefore(
|
func (ec *executionContext) field_Control_tasks_argsBefore(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
rawArgs map[string]any,
|
rawArgs map[string]any,
|
||||||
) (*string, error) {
|
) (*page.CursorKey, error) {
|
||||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
|
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
|
||||||
if tmp, ok := rawArgs["before"]; ok {
|
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
|
return zeroVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,13 +766,13 @@ func (ec *executionContext) field_Framework_controls_argsFirst(
|
|||||||
func (ec *executionContext) field_Framework_controls_argsAfter(
|
func (ec *executionContext) field_Framework_controls_argsAfter(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
rawArgs map[string]any,
|
rawArgs map[string]any,
|
||||||
) (*string, error) {
|
) (*page.CursorKey, error) {
|
||||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
|
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
|
||||||
if tmp, ok := rawArgs["after"]; ok {
|
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
|
return zeroVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -791,13 +792,13 @@ func (ec *executionContext) field_Framework_controls_argsLast(
|
|||||||
func (ec *executionContext) field_Framework_controls_argsBefore(
|
func (ec *executionContext) field_Framework_controls_argsBefore(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
rawArgs map[string]any,
|
rawArgs map[string]any,
|
||||||
) (*string, error) {
|
) (*page.CursorKey, error) {
|
||||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
|
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
|
||||||
if tmp, ok := rawArgs["before"]; ok {
|
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
|
return zeroVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -842,13 +843,13 @@ func (ec *executionContext) field_Organization_frameworks_argsFirst(
|
|||||||
func (ec *executionContext) field_Organization_frameworks_argsAfter(
|
func (ec *executionContext) field_Organization_frameworks_argsAfter(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
rawArgs map[string]any,
|
rawArgs map[string]any,
|
||||||
) (*string, error) {
|
) (*page.CursorKey, error) {
|
||||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
|
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after"))
|
||||||
if tmp, ok := rawArgs["after"]; ok {
|
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
|
return zeroVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -868,13 +869,13 @@ func (ec *executionContext) field_Organization_frameworks_argsLast(
|
|||||||
func (ec *executionContext) field_Organization_frameworks_argsBefore(
|
func (ec *executionContext) field_Organization_frameworks_argsBefore(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
rawArgs map[string]any,
|
rawArgs map[string]any,
|
||||||
) (*string, error) {
|
) (*page.CursorKey, error) {
|
||||||
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
|
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before"))
|
||||||
if tmp, ok := rawArgs["before"]; ok {
|
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
|
return zeroVal, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1100,7 +1101,7 @@ func (ec *executionContext) _Control_tasks(ctx context.Context, field graphql.Co
|
|||||||
ctx = graphql.WithFieldContext(ctx, fc)
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||||
ctx = rctx // use context from middleware stack in children
|
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 {
|
if err != nil {
|
||||||
ec.Error(ctx, err)
|
ec.Error(ctx, err)
|
||||||
@@ -1329,9 +1330,9 @@ func (ec *executionContext) _ControlEdge_cursor(ctx context.Context, field graph
|
|||||||
}
|
}
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
}
|
}
|
||||||
res := resTmp.(string)
|
res := resTmp.(page.CursorKey)
|
||||||
fc.Result = res
|
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) {
|
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)
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||||
ctx = rctx // use context from middleware stack in children
|
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 {
|
if err != nil {
|
||||||
ec.Error(ctx, err)
|
ec.Error(ctx, err)
|
||||||
@@ -1750,9 +1751,9 @@ func (ec *executionContext) _FrameworkEdge_cursor(ctx context.Context, field gra
|
|||||||
}
|
}
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
}
|
}
|
||||||
res := resTmp.(string)
|
res := resTmp.(page.CursorKey)
|
||||||
fc.Result = res
|
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) {
|
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)
|
ctx = graphql.WithFieldContext(ctx, fc)
|
||||||
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
|
||||||
ctx = rctx // use context from middleware stack in children
|
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 {
|
if err != nil {
|
||||||
ec.Error(ctx, err)
|
ec.Error(ctx, err)
|
||||||
@@ -2114,9 +2115,9 @@ func (ec *executionContext) _PageInfo_startCursor(ctx context.Context, field gra
|
|||||||
if resTmp == nil {
|
if resTmp == nil {
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
}
|
}
|
||||||
res := resTmp.(*string)
|
res := resTmp.(*page.CursorKey)
|
||||||
fc.Result = res
|
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) {
|
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 {
|
if resTmp == nil {
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
}
|
}
|
||||||
res := resTmp.(*string)
|
res := resTmp.(*page.CursorKey)
|
||||||
fc.Result = res
|
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) {
|
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
|
return graphql.Null
|
||||||
}
|
}
|
||||||
res := resTmp.(string)
|
res := resTmp.(page.CursorKey)
|
||||||
fc.Result = res
|
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) {
|
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)
|
return ec._ControlEdge(ctx, sel, v)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) unmarshalNCursorKey2string(ctx context.Context, v any) (string, error) {
|
func (ec *executionContext) unmarshalNCursorKey2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx context.Context, v any) (page.CursorKey, error) {
|
||||||
res, err := graphql.UnmarshalString(v)
|
res, err := types.UnmarshalCursorKeyScalar(v)
|
||||||
return res, graphql.ErrorOnPath(ctx, err)
|
return res, graphql.ErrorOnPath(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ec *executionContext) marshalNCursorKey2string(ctx context.Context, sel ast.SelectionSet, v string) graphql.Marshaler {
|
func (ec *executionContext) marshalNCursorKey2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx context.Context, sel ast.SelectionSet, v page.CursorKey) graphql.Marshaler {
|
||||||
res := graphql.MarshalString(v)
|
res := types.MarshalCursorKeyScalar(v)
|
||||||
if res == graphql.Null {
|
if res == graphql.Null {
|
||||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||||
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
|
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
|
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 {
|
if v == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
res, err := graphql.UnmarshalString(v)
|
res, err := types.UnmarshalCursorKeyScalar(v)
|
||||||
return &res, graphql.ErrorOnPath(ctx, err)
|
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 {
|
if v == nil {
|
||||||
return graphql.Null
|
return graphql.Null
|
||||||
}
|
}
|
||||||
res := graphql.MarshalString(*v)
|
res := types.MarshalCursorKeyScalar(*v)
|
||||||
return res
|
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 (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/probo/coredata/page"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Node interface {
|
type Node interface {
|
||||||
@@ -29,7 +31,7 @@ type ControlConnection struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ControlEdge struct {
|
type ControlEdge struct {
|
||||||
Cursor string `json:"cursor"`
|
Cursor page.CursorKey `json:"cursor"`
|
||||||
Node *Control `json:"node"`
|
Node *Control `json:"node"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +53,7 @@ type FrameworkConnection struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type FrameworkEdge struct {
|
type FrameworkEdge struct {
|
||||||
Cursor string `json:"cursor"`
|
Cursor page.CursorKey `json:"cursor"`
|
||||||
Node *Framework `json:"node"`
|
Node *Framework `json:"node"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,8 +71,8 @@ func (this Organization) GetID() string { return this.ID }
|
|||||||
type PageInfo struct {
|
type PageInfo struct {
|
||||||
HasNextPage bool `json:"hasNextPage"`
|
HasNextPage bool `json:"hasNextPage"`
|
||||||
HasPreviousPage bool `json:"hasPreviousPage"`
|
HasPreviousPage bool `json:"hasPreviousPage"`
|
||||||
StartCursor *string `json:"startCursor,omitempty"`
|
StartCursor *page.CursorKey `json:"startCursor,omitempty"`
|
||||||
EndCursor *string `json:"endCursor,omitempty"`
|
EndCursor *page.CursorKey `json:"endCursor,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
@@ -92,6 +94,6 @@ type TaskConnection struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TaskEdge struct {
|
type TaskEdge struct {
|
||||||
Cursor string `json:"cursor"`
|
Cursor page.CursorKey `json:"cursor"`
|
||||||
Node *Task `json:"node"`
|
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/schema"
|
||||||
"github.com/getprobo/probo/pkg/api/console/v1/types"
|
"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.
|
// 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"))
|
panic(fmt.Errorf("not implemented: Tasks - tasks"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Controls is the resolver for the controls field.
|
// 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"))
|
panic(fmt.Errorf("not implemented: Controls - controls"))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Frameworks is the resolver for the frameworks field.
|
// 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) {
|
func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) {
|
||||||
panic(fmt.Errorf("not implemented: Frameworks - frameworks"))
|
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.
|
// Node is the resolver for the node field.
|
||||||
|
|||||||
@@ -2,31 +2,97 @@ package coredata
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"maps"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/probo/coredata/page"
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
|
"go.gearno.de/crypto/uuid"
|
||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
Framework struct {
|
Framework struct {
|
||||||
ID string
|
ID uuid.UUID
|
||||||
OrganizationID string
|
OrganizationID string
|
||||||
ContentID string
|
Name string
|
||||||
|
Description string
|
||||||
|
ContentRef string
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt 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 {
|
func (f *Framework) scan(r pgx.Row) error {
|
||||||
return r.Scan(
|
return r.Scan(
|
||||||
&f.ID,
|
&f.ID,
|
||||||
&f.OrganizationID,
|
&f.OrganizationID,
|
||||||
&f.ContentID,
|
&f.Name,
|
||||||
|
&f.Description,
|
||||||
|
&f.ContentRef,
|
||||||
&f.CreatedAt,
|
&f.CreatedAt,
|
||||||
&f.UpdatedAt,
|
&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(
|
func (f *Framework) LoadByID(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
@@ -37,7 +103,9 @@ func (f *Framework) LoadByID(
|
|||||||
SELECT
|
SELECT
|
||||||
framework_id,
|
framework_id,
|
||||||
organization_id,
|
organization_id,
|
||||||
content_id,
|
name,
|
||||||
|
description,
|
||||||
|
content_ref,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/getprobo/probo/pkg/probo/coredata"
|
||||||
|
"github.com/getprobo/probo/pkg/probo/coredata/page"
|
||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,3 +19,44 @@ func NewService(ctx context.Context, pgClient *pg.Client) *Service {
|
|||||||
pg: pgClient,
|
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