From 720a3695a47bf50f0d80cb729a7cf38f0115304c Mon Sep 17 00:00:00 2001 From: gearnode Date: Thu, 30 Jan 2025 14:31:49 -0800 Subject: [PATCH] Add vendor data model Signed-off-by: gearnode --- pkg/api/console/v1/schema.graphql | 25 + pkg/api/console/v1/schema/schema.go | 813 ++++++++++++++++++ pkg/api/console/v1/types/types.go | 18 + pkg/api/console/v1/types/vendor.go | 49 ++ pkg/api/console/v1/v1_resolver.go | 12 + .../coredata/migrations/20250130T142900Z.sql | 6 + .../coredata/migrations/20250139T143000Z.sql | 1 + pkg/probo/coredata/vendor.go | 107 +++ pkg/probo/probo.go | 27 + 9 files changed, 1058 insertions(+) create mode 100644 pkg/api/console/v1/types/vendor.go create mode 100644 pkg/probo/coredata/migrations/20250130T142900Z.sql create mode 100644 pkg/probo/coredata/migrations/20250139T143000Z.sql create mode 100644 pkg/probo/coredata/vendor.go diff --git a/pkg/api/console/v1/schema.graphql b/pkg/api/console/v1/schema.graphql index d651695af..d1e1507cb 100644 --- a/pkg/api/console/v1/schema.graphql +++ b/pkg/api/console/v1/schema.graphql @@ -39,6 +39,31 @@ type Organization implements Node { before: CursorKey ): FrameworkConnection! @goField(forceResolver: true) + vendors( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): VendorConnection! @goField(forceResolver: true) + + + createdAt: Datetime! + updatedAt: Datetime! +} + +type VendorConnection { + edges: [VendorEdge!]! + pageInfo: PageInfo! +} + +type VendorEdge { + cursor: CursorKey! + node: Vendor! +} + +type Vendor { + id: ID! + name: String! createdAt: Datetime! updatedAt: Datetime! } diff --git a/pkg/api/console/v1/schema/schema.go b/pkg/api/console/v1/schema/schema.go index 5652b3f4f..e7c1d2b80 100644 --- a/pkg/api/console/v1/schema/schema.go +++ b/pkg/api/console/v1/schema/schema.go @@ -134,6 +134,7 @@ type ComplexityRoot struct { ID func(childComplexity int) int Name func(childComplexity int) int UpdatedAt func(childComplexity int) int + Vendors func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey) int } PageInfo struct { @@ -165,6 +166,23 @@ type ComplexityRoot struct { Cursor func(childComplexity int) int Node func(childComplexity int) int } + + Vendor struct { + CreatedAt func(childComplexity int) int + ID func(childComplexity int) int + Name func(childComplexity int) int + UpdatedAt func(childComplexity int) int + } + + VendorConnection struct { + Edges func(childComplexity int) int + PageInfo func(childComplexity int) int + } + + VendorEdge struct { + Cursor func(childComplexity int) int + Node func(childComplexity int) int + } } type ControlResolver interface { @@ -176,6 +194,7 @@ type FrameworkResolver interface { } type OrganizationResolver interface { Frameworks(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.FrameworkConnection, error) + Vendors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.VendorConnection, error) } type QueryResolver interface { Node(ctx context.Context, id gid.GID) (types.Node, error) @@ -538,6 +557,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Organization.UpdatedAt(childComplexity), true + case "Organization.vendors": + if e.complexity.Organization.Vendors == nil { + break + } + + args, err := ec.field_Organization_vendors_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Organization.Vendors(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey)), true + case "PageInfo.endCursor": if e.complexity.PageInfo.EndCursor == nil { break @@ -653,6 +684,62 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.TaskEdge.Node(childComplexity), true + case "Vendor.createdAt": + if e.complexity.Vendor.CreatedAt == nil { + break + } + + return e.complexity.Vendor.CreatedAt(childComplexity), true + + case "Vendor.id": + if e.complexity.Vendor.ID == nil { + break + } + + return e.complexity.Vendor.ID(childComplexity), true + + case "Vendor.name": + if e.complexity.Vendor.Name == nil { + break + } + + return e.complexity.Vendor.Name(childComplexity), true + + case "Vendor.updatedAt": + if e.complexity.Vendor.UpdatedAt == nil { + break + } + + return e.complexity.Vendor.UpdatedAt(childComplexity), true + + case "VendorConnection.edges": + if e.complexity.VendorConnection.Edges == nil { + break + } + + return e.complexity.VendorConnection.Edges(childComplexity), true + + case "VendorConnection.pageInfo": + if e.complexity.VendorConnection.PageInfo == nil { + break + } + + return e.complexity.VendorConnection.PageInfo(childComplexity), true + + case "VendorEdge.cursor": + if e.complexity.VendorEdge.Cursor == nil { + break + } + + return e.complexity.VendorEdge.Cursor(childComplexity), true + + case "VendorEdge.node": + if e.complexity.VendorEdge.Node == nil { + break + } + + return e.complexity.VendorEdge.Node(childComplexity), true + } return 0, false } @@ -783,6 +870,31 @@ type Organization implements Node { before: CursorKey ): FrameworkConnection! @goField(forceResolver: true) + vendors( + first: Int + after: CursorKey + last: Int + before: CursorKey + ): VendorConnection! @goField(forceResolver: true) + + + createdAt: Datetime! + updatedAt: Datetime! +} + +type VendorConnection { + edges: [VendorEdge!]! + pageInfo: PageInfo! +} + +type VendorEdge { + cursor: CursorKey! + node: Vendor! +} + +type Vendor { + id: ID! + name: String! createdAt: Datetime! updatedAt: Datetime! } @@ -1228,6 +1340,83 @@ func (ec *executionContext) field_Organization_frameworks_argsBefore( return zeroVal, nil } +func (ec *executionContext) field_Organization_vendors_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := ec.field_Organization_vendors_argsFirst(ctx, rawArgs) + if err != nil { + return nil, err + } + args["first"] = arg0 + arg1, err := ec.field_Organization_vendors_argsAfter(ctx, rawArgs) + if err != nil { + return nil, err + } + args["after"] = arg1 + arg2, err := ec.field_Organization_vendors_argsLast(ctx, rawArgs) + if err != nil { + return nil, err + } + args["last"] = arg2 + arg3, err := ec.field_Organization_vendors_argsBefore(ctx, rawArgs) + if err != nil { + return nil, err + } + args["before"] = arg3 + return args, nil +} +func (ec *executionContext) field_Organization_vendors_argsFirst( + ctx context.Context, + rawArgs map[string]any, +) (*int, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("first")) + if tmp, ok := rawArgs["first"]; ok { + return ec.unmarshalOInt2ᚖint(ctx, tmp) + } + + var zeroVal *int + return zeroVal, nil +} + +func (ec *executionContext) field_Organization_vendors_argsAfter( + ctx context.Context, + rawArgs map[string]any, +) (*page.CursorKey, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("after")) + if tmp, ok := rawArgs["after"]; ok { + return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, tmp) + } + + var zeroVal *page.CursorKey + return zeroVal, nil +} + +func (ec *executionContext) field_Organization_vendors_argsLast( + ctx context.Context, + rawArgs map[string]any, +) (*int, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("last")) + if tmp, ok := rawArgs["last"]; ok { + return ec.unmarshalOInt2ᚖint(ctx, tmp) + } + + var zeroVal *int + return zeroVal, nil +} + +func (ec *executionContext) field_Organization_vendors_argsBefore( + ctx context.Context, + rawArgs map[string]any, +) (*page.CursorKey, error) { + ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("before")) + if tmp, ok := rawArgs["before"]; ok { + return ec.unmarshalOCursorKey2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, tmp) + } + + var zeroVal *page.CursorKey + return zeroVal, nil +} + func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -3197,6 +3386,55 @@ func (ec *executionContext) fieldContext_Organization_frameworks(ctx context.Con return fc, nil } +func (ec *executionContext) _Organization_vendors(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Organization_vendors(ctx, field) + if err != nil { + return graphql.Null + } + 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().Vendors(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) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*types.VendorConnection) + fc.Result = res + return ec.marshalNVendorConnection2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐVendorConnection(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Organization_vendors(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Organization", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "edges": + return ec.fieldContext_VendorConnection_edges(ctx, field) + case "pageInfo": + return ec.fieldContext_VendorConnection_pageInfo(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type VendorConnection", field.Name) + }, + } + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_Organization_vendors_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _Organization_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Organization_createdAt(ctx, field) if err != nil { @@ -3994,6 +4232,336 @@ func (ec *executionContext) fieldContext_TaskEdge_node(_ context.Context, field return fc, nil } +func (ec *executionContext) _Vendor_id(ctx context.Context, field graphql.CollectedField, obj *types.Vendor) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Vendor_id(ctx, field) + if err != nil { + return graphql.Null + } + 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 obj.ID, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(gid.GID) + fc.Result = res + return ec.marshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋgidᚐGID(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Vendor_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Vendor", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Vendor_name(ctx context.Context, field graphql.CollectedField, obj *types.Vendor) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Vendor_name(ctx, field) + if err != nil { + return graphql.Null + } + 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 obj.Name, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Vendor_name(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Vendor", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Vendor_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.Vendor) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Vendor_createdAt(ctx, field) + if err != nil { + return graphql.Null + } + 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 obj.CreatedAt, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(time.Time) + fc.Result = res + return ec.marshalNDatetime2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Vendor_createdAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Vendor", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Datetime does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Vendor_updatedAt(ctx context.Context, field graphql.CollectedField, obj *types.Vendor) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Vendor_updatedAt(ctx, field) + if err != nil { + return graphql.Null + } + 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 obj.UpdatedAt, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(time.Time) + fc.Result = res + return ec.marshalNDatetime2timeᚐTime(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Vendor_updatedAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Vendor", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Datetime does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _VendorConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.VendorConnection) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_VendorConnection_edges(ctx, field) + if err != nil { + return graphql.Null + } + 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 obj.Edges, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.([]*types.VendorEdge) + fc.Result = res + return ec.marshalNVendorEdge2ᚕᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐVendorEdgeᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_VendorConnection_edges(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "VendorConnection", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "cursor": + return ec.fieldContext_VendorEdge_cursor(ctx, field) + case "node": + return ec.fieldContext_VendorEdge_node(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type VendorEdge", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _VendorConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *types.VendorConnection) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_VendorConnection_pageInfo(ctx, field) + if err != nil { + return graphql.Null + } + 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 obj.PageInfo, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*types.PageInfo) + fc.Result = res + return ec.marshalNPageInfo2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐPageInfo(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_VendorConnection_pageInfo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "VendorConnection", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "hasNextPage": + return ec.fieldContext_PageInfo_hasNextPage(ctx, field) + case "hasPreviousPage": + return ec.fieldContext_PageInfo_hasPreviousPage(ctx, field) + case "startCursor": + return ec.fieldContext_PageInfo_startCursor(ctx, field) + case "endCursor": + return ec.fieldContext_PageInfo_endCursor(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _VendorEdge_cursor(ctx context.Context, field graphql.CollectedField, obj *types.VendorEdge) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_VendorEdge_cursor(ctx, field) + if err != nil { + return graphql.Null + } + 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 obj.Cursor, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(page.CursorKey) + fc.Result = res + return ec.marshalNCursorKey2githubᚗcomᚋgetproboᚋproboᚋpkgᚋproboᚋcoredataᚋpageᚐCursorKey(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_VendorEdge_cursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "VendorEdge", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type CursorKey does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _VendorEdge_node(ctx context.Context, field graphql.CollectedField, obj *types.VendorEdge) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_VendorEdge_node(ctx, field) + if err != nil { + return graphql.Null + } + 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 obj.Node, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + res := resTmp.(*types.Vendor) + fc.Result = res + return ec.marshalNVendor2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐVendor(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_VendorEdge_node(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "VendorEdge", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_Vendor_id(ctx, field) + case "name": + return ec.fieldContext_Vendor_name(ctx, field) + case "createdAt": + return ec.fieldContext_Vendor_createdAt(ctx, field) + case "updatedAt": + return ec.fieldContext_Vendor_updatedAt(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type Vendor", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) ___Directive_name(ctx context.Context, field graphql.CollectedField, obj *introspection.Directive) (ret graphql.Marshaler) { fc, err := ec.fieldContext___Directive_name(ctx, field) if err != nil { @@ -6328,6 +6896,37 @@ func (ec *executionContext) _Organization(ctx context.Context, sel ast.Selection continue } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + case "vendors": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + res = ec._Organization_vendors(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "createdAt": out.Values[i] = ec._Organization_createdAt(ctx, field, obj) @@ -6655,6 +7254,148 @@ func (ec *executionContext) _TaskEdge(ctx context.Context, sel ast.SelectionSet, return out } +var vendorImplementors = []string{"Vendor"} + +func (ec *executionContext) _Vendor(ctx context.Context, sel ast.SelectionSet, obj *types.Vendor) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, vendorImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Vendor") + case "id": + out.Values[i] = ec._Vendor_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "name": + out.Values[i] = ec._Vendor_name(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "createdAt": + out.Values[i] = ec._Vendor_createdAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "updatedAt": + out.Values[i] = ec._Vendor_updatedAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var vendorConnectionImplementors = []string{"VendorConnection"} + +func (ec *executionContext) _VendorConnection(ctx context.Context, sel ast.SelectionSet, obj *types.VendorConnection) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, vendorConnectionImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("VendorConnection") + case "edges": + out.Values[i] = ec._VendorConnection_edges(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "pageInfo": + out.Values[i] = ec._VendorConnection_pageInfo(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var vendorEdgeImplementors = []string{"VendorEdge"} + +func (ec *executionContext) _VendorEdge(ctx context.Context, sel ast.SelectionSet, obj *types.VendorEdge) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, vendorEdgeImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("VendorEdge") + case "cursor": + out.Values[i] = ec._VendorEdge_cursor(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "node": + out.Values[i] = ec._VendorEdge_node(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var __DirectiveImplementors = []string{"__Directive"} func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler { @@ -7446,6 +8187,78 @@ func (ec *executionContext) marshalNTaskEdge2ᚖgithubᚗcomᚋgetproboᚋprobo return ec._TaskEdge(ctx, sel, v) } +func (ec *executionContext) marshalNVendor2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐVendor(ctx context.Context, sel ast.SelectionSet, v *types.Vendor) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._Vendor(ctx, sel, v) +} + +func (ec *executionContext) marshalNVendorConnection2githubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐVendorConnection(ctx context.Context, sel ast.SelectionSet, v types.VendorConnection) graphql.Marshaler { + return ec._VendorConnection(ctx, sel, &v) +} + +func (ec *executionContext) marshalNVendorConnection2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐVendorConnection(ctx context.Context, sel ast.SelectionSet, v *types.VendorConnection) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._VendorConnection(ctx, sel, v) +} + +func (ec *executionContext) marshalNVendorEdge2ᚕᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐVendorEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*types.VendorEdge) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNVendorEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐVendorEdge(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNVendorEdge2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋapiᚋconsoleᚋv1ᚋtypesᚐVendorEdge(ctx context.Context, sel ast.SelectionSet, v *types.VendorEdge) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._VendorEdge(ctx, sel, v) +} + func (ec *executionContext) marshalN__Directive2githubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐDirective(ctx context.Context, sel ast.SelectionSet, v introspection.Directive) graphql.Marshaler { return ec.___Directive(ctx, sel, &v) } diff --git a/pkg/api/console/v1/types/types.go b/pkg/api/console/v1/types/types.go index 1d4a12487..6be765c50 100644 --- a/pkg/api/console/v1/types/types.go +++ b/pkg/api/console/v1/types/types.go @@ -106,6 +106,7 @@ type Organization struct { ID gid.GID `json:"id"` Name string `json:"name"` Frameworks *FrameworkConnection `json:"frameworks"` + Vendors *VendorConnection `json:"vendors"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } @@ -145,6 +146,23 @@ type TaskEdge struct { Node *Task `json:"node"` } +type Vendor struct { + ID gid.GID `json:"id"` + Name string `json:"name"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type VendorConnection struct { + Edges []*VendorEdge `json:"edges"` + PageInfo *PageInfo `json:"pageInfo"` +} + +type VendorEdge struct { + Cursor page.CursorKey `json:"cursor"` + Node *Vendor `json:"node"` +} + type ControlState string const ( diff --git a/pkg/api/console/v1/types/vendor.go b/pkg/api/console/v1/types/vendor.go new file mode 100644 index 000000000..7ff81a8ff --- /dev/null +++ b/pkg/api/console/v1/types/vendor.go @@ -0,0 +1,49 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package types + +import ( + "github.com/getprobo/probo/pkg/probo/coredata" + "github.com/getprobo/probo/pkg/probo/coredata/page" +) + +func NewVendorConnection(p *page.Page[*coredata.Vendor]) *VendorConnection { + var edges = make([]*VendorEdge, len(p.Data)) + + for i := range edges { + edges[i] = NewVendorEdge(p.Data[i]) + } + + return &VendorConnection{ + Edges: edges, + PageInfo: NewPageInfo(p), + } +} + +func NewVendorEdge(f *coredata.Vendor) *VendorEdge { + return &VendorEdge{ + Cursor: f.CursorKey(), + Node: NewVendor(f), + } +} + +func NewVendor(f *coredata.Vendor) *Vendor { + return &Vendor{ + ID: f.ID, + Name: f.Name, + CreatedAt: f.CreatedAt, + UpdatedAt: f.UpdatedAt, + } +} diff --git a/pkg/api/console/v1/v1_resolver.go b/pkg/api/console/v1/v1_resolver.go index 743f803d4..b5ad0bbea 100644 --- a/pkg/api/console/v1/v1_resolver.go +++ b/pkg/api/console/v1/v1_resolver.go @@ -64,6 +64,18 @@ func (r *organizationResolver) Frameworks(ctx context.Context, obj *types.Organi return types.NewFrameworkConnection(page), nil } +// Vendors is the resolver for the vendors field. +func (r *organizationResolver) Vendors(ctx context.Context, obj *types.Organization, first *int, after *page.CursorKey, last *int, before *page.CursorKey) (*types.VendorConnection, error) { + cursor := types.NewCursor(first, after, last, before) + + page, err := r.svc.ListOrganizationVendors(ctx, obj.ID, cursor) + if err != nil { + return nil, fmt.Errorf("cannot list organization frameworks: %w", err) + } + + return types.NewVendorConnection(page), nil +} + // Node is the resolver for the node field. func (r *queryResolver) Node(ctx context.Context, id gid.GID) (types.Node, error) { switch id.EntityType() { diff --git a/pkg/probo/coredata/migrations/20250130T142900Z.sql b/pkg/probo/coredata/migrations/20250130T142900Z.sql new file mode 100644 index 000000000..51b577676 --- /dev/null +++ b/pkg/probo/coredata/migrations/20250130T142900Z.sql @@ -0,0 +1,6 @@ +CREATE TABLE vendors ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + created_at TIMESTAMP WITH TIME ZONE, + updated_at TIMESTAMP WITH TIME ZONE +); diff --git a/pkg/probo/coredata/migrations/20250139T143000Z.sql b/pkg/probo/coredata/migrations/20250139T143000Z.sql new file mode 100644 index 000000000..f93e9e864 --- /dev/null +++ b/pkg/probo/coredata/migrations/20250139T143000Z.sql @@ -0,0 +1 @@ +ALTER TABLE vendors ADD COLUMN organization_id TEXT REFERENCES organizations(id) NOT NULL; diff --git a/pkg/probo/coredata/vendor.go b/pkg/probo/coredata/vendor.go new file mode 100644 index 000000000..d5927e5a8 --- /dev/null +++ b/pkg/probo/coredata/vendor.go @@ -0,0 +1,107 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package coredata + +import ( + "context" + "fmt" + "maps" + "time" + + "github.com/getprobo/probo/pkg/probo/coredata/gid" + "github.com/getprobo/probo/pkg/probo/coredata/page" + "github.com/jackc/pgx/v5" + "go.gearno.de/crypto/uuid" + "go.gearno.de/kit/pg" +) + +type ( + Vendor struct { + ID gid.GID + OrganizationID gid.GID + Name string + CreatedAt time.Time + UpdatedAt time.Time + } + + Vendors []*Vendor +) + +func (v Vendor) CursorKey() page.CursorKey { + return page.NewCursorKey(uuid.UUID(v.ID), v.CreatedAt) +} + +func (v *Vendor) scan(r pgx.Row) error { + return r.Scan( + &v.ID, + &v.OrganizationID, + &v.Name, + &v.CreatedAt, + &v.UpdatedAt, + ) +} + +func (v *Vendors) LoadByOrganizationID( + ctx context.Context, + conn pg.Conn, + scope *Scope, + organizationID gid.GID, + cursor *page.Cursor, +) error { + q := ` +SELECT + id, + organization_id, + name, + created_at, + updated_at +FROM + vendors +WHERE + %s + AND organization_id = @organization_id + AND %s +` + + q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + + args := pgx.NamedArgs{"organization_id": organizationID} + maps.Copy(args, cursor.SQLArguments()) + maps.Copy(args, cursor.SQLArguments()) + + r, err := conn.Query(ctx, q, args) + if err != nil { + return err + } + defer r.Close() + + vendors := Vendors{} + for r.Next() { + vendor := &Vendor{} + if err := vendor.scan(r); err != nil { + return err + } + + vendors = append(vendors, vendor) + } + + if err := r.Err(); err != nil { + return err + } + + *v = vendors + + return nil +} diff --git a/pkg/probo/probo.go b/pkg/probo/probo.go index 24aeaa063..68d376d62 100644 --- a/pkg/probo/probo.go +++ b/pkg/probo/probo.go @@ -203,3 +203,30 @@ func (s *Service) ListTaskEvidences( return page.NewPage(evidences, cursor), nil } + +func (s Service) ListOrganizationVendors( + ctx context.Context, + organizationID gid.GID, + cursor *page.Cursor, +) (*page.Page[*coredata.Vendor], error) { + var vendors coredata.Vendors + + err := s.pg.WithConn( + ctx, + func(conn pg.Conn) error { + return vendors.LoadByOrganizationID( + ctx, + conn, + s.scope, + organizationID, + cursor, + ) + }, + ) + + if err != nil { + return nil, err + } + + return page.NewPage(vendors, cursor), nil +}