feat: add commands for managing organizations and users

Introduce `AddUserToOrganization`, `RemoveAPIKey`, and 
`RemoveOrganization` commands to enhance organization 
management. Implement validation for user addition and 
API key removal. Update GraphQL schema to support new 
mutations and add caching for the new events, ensuring 
that organizations and their relationships are accurately 
represented in the cache.
This commit is contained in:
2025-11-22 18:37:07 +01:00
parent 335a9f3b54
commit ffcf41b85a
14 changed files with 1500 additions and 30 deletions
+411 -10
View File
@@ -61,9 +61,12 @@ type ComplexityRoot struct {
}
Mutation struct {
AddAPIKey func(childComplexity int, input *model.InputAPIKey) int
AddOrganization func(childComplexity int, name string) int
UpdateSubGraph func(childComplexity int, input model.InputSubGraph) int
AddAPIKey func(childComplexity int, input *model.InputAPIKey) int
AddOrganization func(childComplexity int, name string) int
AddUserToOrganization func(childComplexity int, organizationID string, userID string) int
RemoveAPIKey func(childComplexity int, organizationID string, keyName string) int
RemoveOrganization func(childComplexity int, organizationID string) int
UpdateSubGraph func(childComplexity int, input model.InputSubGraph) int
}
Organization struct {
@@ -74,9 +77,10 @@ type ComplexityRoot struct {
}
Query struct {
LatestSchema func(childComplexity int, ref string) int
Organizations func(childComplexity int) int
Supergraph func(childComplexity int, ref string, isAfter *string) int
AllOrganizations func(childComplexity int) int
LatestSchema func(childComplexity int, ref string) int
Organizations func(childComplexity int) int
Supergraph func(childComplexity int, ref string, isAfter *string) int
}
SchemaUpdate struct {
@@ -119,11 +123,15 @@ type ComplexityRoot struct {
type MutationResolver interface {
AddOrganization(ctx context.Context, name string) (*model.Organization, error)
AddUserToOrganization(ctx context.Context, organizationID string, userID string) (*model.Organization, error)
AddAPIKey(ctx context.Context, input *model.InputAPIKey) (*model.APIKey, error)
RemoveAPIKey(ctx context.Context, organizationID string, keyName string) (*model.Organization, error)
RemoveOrganization(ctx context.Context, organizationID string) (bool, error)
UpdateSubGraph(ctx context.Context, input model.InputSubGraph) (*model.SubGraph, error)
}
type QueryResolver interface {
Organizations(ctx context.Context) ([]*model.Organization, error)
AllOrganizations(ctx context.Context) ([]*model.Organization, error)
Supergraph(ctx context.Context, ref string, isAfter *string) (model.Supergraph, error)
LatestSchema(ctx context.Context, ref string) (*model.SchemaUpdate, error)
}
@@ -215,6 +223,39 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
}
return e.complexity.Mutation.AddOrganization(childComplexity, args["name"].(string)), true
case "Mutation.addUserToOrganization":
if e.complexity.Mutation.AddUserToOrganization == nil {
break
}
args, err := ec.field_Mutation_addUserToOrganization_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.AddUserToOrganization(childComplexity, args["organizationId"].(string), args["userId"].(string)), true
case "Mutation.removeAPIKey":
if e.complexity.Mutation.RemoveAPIKey == nil {
break
}
args, err := ec.field_Mutation_removeAPIKey_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.RemoveAPIKey(childComplexity, args["organizationId"].(string), args["keyName"].(string)), true
case "Mutation.removeOrganization":
if e.complexity.Mutation.RemoveOrganization == nil {
break
}
args, err := ec.field_Mutation_removeOrganization_args(ctx, rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.RemoveOrganization(childComplexity, args["organizationId"].(string)), true
case "Mutation.updateSubGraph":
if e.complexity.Mutation.UpdateSubGraph == nil {
break
@@ -252,6 +293,12 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin
return e.complexity.Organization.Users(childComplexity), true
case "Query.allOrganizations":
if e.complexity.Query.AllOrganizations == nil {
break
}
return e.complexity.Query.AllOrganizations(childComplexity), true
case "Query.latestSchema":
if e.complexity.Query.LatestSchema == nil {
break
@@ -532,13 +579,17 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er
var sources = []*ast.Source{
{Name: "../schema.graphqls", Input: `type Query {
organizations: [Organization!]! @auth(user: true)
supergraph(ref: String!, isAfter: String): Supergraph! @auth(organization: true)
latestSchema(ref: String!): SchemaUpdate! @auth(organization: true)
allOrganizations: [Organization!]! @auth(user: true)
supergraph(ref: String!, isAfter: String): Supergraph! @auth(user: true, organization: true)
latestSchema(ref: String!): SchemaUpdate! @auth(user: true, organization: true)
}
type Mutation {
addOrganization(name: String!): Organization! @auth(user: true)
addUserToOrganization(organizationId: ID!, userId: String!): Organization! @auth(user: true)
addAPIKey(input: InputAPIKey): APIKey! @auth(user: true)
removeAPIKey(organizationId: ID!, keyName: String!): Organization! @auth(user: true)
removeOrganization(organizationId: ID!): Boolean! @auth(user: true)
updateSubGraph(input: InputSubGraph!): SubGraph! @auth(organization: true)
}
@@ -663,6 +714,49 @@ func (ec *executionContext) field_Mutation_addOrganization_args(ctx context.Cont
return args, nil
}
func (ec *executionContext) field_Mutation_addUserToOrganization_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "organizationId", ec.unmarshalNID2string)
if err != nil {
return nil, err
}
args["organizationId"] = arg0
arg1, err := graphql.ProcessArgField(ctx, rawArgs, "userId", ec.unmarshalNString2string)
if err != nil {
return nil, err
}
args["userId"] = arg1
return args, nil
}
func (ec *executionContext) field_Mutation_removeAPIKey_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "organizationId", ec.unmarshalNID2string)
if err != nil {
return nil, err
}
args["organizationId"] = arg0
arg1, err := graphql.ProcessArgField(ctx, rawArgs, "keyName", ec.unmarshalNString2string)
if err != nil {
return nil, err
}
args["keyName"] = arg1
return args, nil
}
func (ec *executionContext) field_Mutation_removeOrganization_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := graphql.ProcessArgField(ctx, rawArgs, "organizationId", ec.unmarshalNID2string)
if err != nil {
return nil, err
}
args["organizationId"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_updateSubGraph_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
@@ -1057,6 +1151,75 @@ func (ec *executionContext) fieldContext_Mutation_addOrganization(ctx context.Co
return fc, nil
}
func (ec *executionContext) _Mutation_addUserToOrganization(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Mutation_addUserToOrganization,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().AddUserToOrganization(ctx, fc.Args["organizationId"].(string), fc.Args["userId"].(string))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal *model.Organization
return zeroVal, err
}
if ec.directives.Auth == nil {
var zeroVal *model.Organization
return zeroVal, errors.New("directive auth is not implemented")
}
return ec.directives.Auth(ctx, nil, directive0, user, nil)
}
next = directive1
return next
},
ec.marshalNOrganization2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization,
true,
true,
)
}
func (ec *executionContext) fieldContext_Mutation_addUserToOrganization(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_Organization_id(ctx, field)
case "name":
return ec.fieldContext_Organization_name(ctx, field)
case "users":
return ec.fieldContext_Organization_users(ctx, field)
case "apiKeys":
return ec.fieldContext_Organization_apiKeys(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_addUserToOrganization_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_addAPIKey(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
@@ -1132,6 +1295,134 @@ func (ec *executionContext) fieldContext_Mutation_addAPIKey(ctx context.Context,
return fc, nil
}
func (ec *executionContext) _Mutation_removeAPIKey(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Mutation_removeAPIKey,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().RemoveAPIKey(ctx, fc.Args["organizationId"].(string), fc.Args["keyName"].(string))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal *model.Organization
return zeroVal, err
}
if ec.directives.Auth == nil {
var zeroVal *model.Organization
return zeroVal, errors.New("directive auth is not implemented")
}
return ec.directives.Auth(ctx, nil, directive0, user, nil)
}
next = directive1
return next
},
ec.marshalNOrganization2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization,
true,
true,
)
}
func (ec *executionContext) fieldContext_Mutation_removeAPIKey(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_Organization_id(ctx, field)
case "name":
return ec.fieldContext_Organization_name(ctx, field)
case "users":
return ec.fieldContext_Organization_users(ctx, field)
case "apiKeys":
return ec.fieldContext_Organization_apiKeys(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_removeAPIKey_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_removeOrganization(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Mutation_removeOrganization,
func(ctx context.Context) (any, error) {
fc := graphql.GetFieldContext(ctx)
return ec.resolvers.Mutation().RemoveOrganization(ctx, fc.Args["organizationId"].(string))
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal bool
return zeroVal, err
}
if ec.directives.Auth == nil {
var zeroVal bool
return zeroVal, errors.New("directive auth is not implemented")
}
return ec.directives.Auth(ctx, nil, directive0, user, nil)
}
next = directive1
return next
},
ec.marshalNBoolean2bool,
true,
true,
)
}
func (ec *executionContext) fieldContext_Mutation_removeOrganization(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields")
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_removeOrganization_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Mutation_updateSubGraph(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
@@ -1400,6 +1691,63 @@ func (ec *executionContext) fieldContext_Query_organizations(_ context.Context,
return fc, nil
}
func (ec *executionContext) _Query_allOrganizations(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
ec.OperationContext,
field,
ec.fieldContext_Query_allOrganizations,
func(ctx context.Context) (any, error) {
return ec.resolvers.Query().AllOrganizations(ctx)
},
func(ctx context.Context, next graphql.Resolver) graphql.Resolver {
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal []*model.Organization
return zeroVal, err
}
if ec.directives.Auth == nil {
var zeroVal []*model.Organization
return zeroVal, errors.New("directive auth is not implemented")
}
return ec.directives.Auth(ctx, nil, directive0, user, nil)
}
next = directive1
return next
},
ec.marshalNOrganization2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganizationᚄ,
true,
true,
)
}
func (ec *executionContext) fieldContext_Query_allOrganizations(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Query",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "id":
return ec.fieldContext_Organization_id(ctx, field)
case "name":
return ec.fieldContext_Organization_name(ctx, field)
case "users":
return ec.fieldContext_Organization_users(ctx, field)
case "apiKeys":
return ec.fieldContext_Organization_apiKeys(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type Organization", field.Name)
},
}
return fc, nil
}
func (ec *executionContext) _Query_supergraph(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
return graphql.ResolveField(
ctx,
@@ -1414,6 +1762,11 @@ func (ec *executionContext) _Query_supergraph(ctx context.Context, field graphql
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal model.Supergraph
return zeroVal, err
}
organization, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal model.Supergraph
@@ -1423,7 +1776,7 @@ func (ec *executionContext) _Query_supergraph(ctx context.Context, field graphql
var zeroVal model.Supergraph
return zeroVal, errors.New("directive auth is not implemented")
}
return ec.directives.Auth(ctx, nil, directive0, nil, organization)
return ec.directives.Auth(ctx, nil, directive0, user, organization)
}
next = directive1
@@ -1473,6 +1826,11 @@ func (ec *executionContext) _Query_latestSchema(ctx context.Context, field graph
directive0 := next
directive1 := func(ctx context.Context) (any, error) {
user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal *model.SchemaUpdate
return zeroVal, err
}
organization, err := ec.unmarshalOBoolean2ᚖbool(ctx, true)
if err != nil {
var zeroVal *model.SchemaUpdate
@@ -1482,7 +1840,7 @@ func (ec *executionContext) _Query_latestSchema(ctx context.Context, field graph
var zeroVal *model.SchemaUpdate
return zeroVal, errors.New("directive auth is not implemented")
}
return ec.directives.Auth(ctx, nil, directive0, nil, organization)
return ec.directives.Auth(ctx, nil, directive0, user, organization)
}
next = directive1
@@ -3938,6 +4296,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "addUserToOrganization":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_addUserToOrganization(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "addAPIKey":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_addAPIKey(ctx, field)
@@ -3945,6 +4310,20 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "removeAPIKey":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_removeAPIKey(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "removeOrganization":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_removeOrganization(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
case "updateSubGraph":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_updateSubGraph(ctx, field)
@@ -4069,6 +4448,28 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) })
case "allOrganizations":
field := field
innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_allOrganizations(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&fs.Invalids, 1)
}
return res
}
rrm := func(ctx context.Context) graphql.Marshaler {
return ec.OperationContext.RootResolverMiddleware(ctx,
func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) })
}
out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return rrm(innerCtx) })
case "supergraph":
field := field