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
+6 -2
View File
@@ -1,12 +1,16 @@
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)
}
+114 -7
View File
@@ -37,6 +37,24 @@ func (r *mutationResolver) AddOrganization(ctx context.Context, name string) (*m
return ToGqlOrganization(*org), nil
}
// AddUserToOrganization is the resolver for the addUserToOrganization field.
func (r *mutationResolver) AddUserToOrganization(ctx context.Context, organizationID string, userID string) (*model.Organization, error) {
sub := middleware.UserFromContext(ctx)
org := &domain.Organization{BaseAggregate: eventsourced.BaseAggregateFromString(organizationID)}
h, err := r.handler(ctx, org)
if err != nil {
return nil, err
}
_, err = h.Handle(ctx, &domain.AddUserToOrganization{
UserId: userID,
Initiator: sub,
})
if err != nil {
return nil, err
}
return ToGqlOrganization(*org), nil
}
// AddAPIKey is the resolver for the addAPIKey field.
func (r *mutationResolver) AddAPIKey(ctx context.Context, input *model.InputAPIKey) (*model.APIKey, error) {
sub := middleware.UserFromContext(ctx)
@@ -71,6 +89,41 @@ func (r *mutationResolver) AddAPIKey(ctx context.Context, input *model.InputAPIK
}, nil
}
// RemoveAPIKey is the resolver for the removeAPIKey field.
func (r *mutationResolver) RemoveAPIKey(ctx context.Context, organizationID string, keyName string) (*model.Organization, error) {
sub := middleware.UserFromContext(ctx)
org := &domain.Organization{BaseAggregate: eventsourced.BaseAggregateFromString(organizationID)}
h, err := r.handler(ctx, org)
if err != nil {
return nil, err
}
_, err = h.Handle(ctx, &domain.RemoveAPIKey{
KeyName: keyName,
Initiator: sub,
})
if err != nil {
return nil, err
}
return ToGqlOrganization(*org), nil
}
// RemoveOrganization is the resolver for the removeOrganization field.
func (r *mutationResolver) RemoveOrganization(ctx context.Context, organizationID string) (bool, error) {
sub := middleware.UserFromContext(ctx)
org := &domain.Organization{BaseAggregate: eventsourced.BaseAggregateFromString(organizationID)}
h, err := r.handler(ctx, org)
if err != nil {
return false, err
}
_, err = h.Handle(ctx, &domain.RemoveOrganization{
Initiator: sub,
})
if err != nil {
return false, err
}
return true, nil
}
// UpdateSubGraph is the resolver for the updateSubGraph field.
func (r *mutationResolver) UpdateSubGraph(ctx context.Context, input model.InputSubGraph) (*model.SubGraph, error) {
orgId := middleware.OrganizationFromContext(ctx)
@@ -183,13 +236,49 @@ func (r *queryResolver) Organizations(ctx context.Context) ([]*model.Organizatio
return ToGqlOrganizations(orgs), nil
}
// AllOrganizations is the resolver for the allOrganizations field.
func (r *queryResolver) AllOrganizations(ctx context.Context) ([]*model.Organization, error) {
// Check if user has admin role
if !middleware.UserHasRole(ctx, "admin") {
return nil, fmt.Errorf("unauthorized: admin role required")
}
orgs := r.Cache.AllOrganizations()
return ToGqlOrganizations(orgs), nil
}
// Supergraph is the resolver for the supergraph field.
func (r *queryResolver) Supergraph(ctx context.Context, ref string, isAfter *string) (model.Supergraph, error) {
orgId := middleware.OrganizationFromContext(ctx)
_, err := r.apiKeyCanAccessRef(ctx, ref, false)
if err != nil {
return nil, err
userId := middleware.UserFromContext(ctx)
r.Logger.Info("Supergraph query",
"ref", ref,
"orgId", orgId,
"userId", userId,
)
// If authenticated with API key (organization), check access
if orgId != "" {
_, err := r.apiKeyCanAccessRef(ctx, ref, false)
if err != nil {
r.Logger.Error("API key cannot access ref", "error", err, "ref", ref)
return nil, err
}
} else if userId != "" {
// For user authentication, check if user has access to ref through their organizations
userOrgs := r.Cache.OrganizationsByUser(userId)
if len(userOrgs) == 0 {
r.Logger.Error("User has no organizations", "userId", userId)
return nil, fmt.Errorf("user has no access to any organizations")
}
// Use the first organization's ID for querying
orgId = userOrgs[0].ID.String()
r.Logger.Info("Using organization from user context", "orgId", orgId)
} else {
return nil, fmt.Errorf("no authentication provided")
}
after := ""
if isAfter != nil {
after = *isAfter
@@ -241,16 +330,34 @@ func (r *queryResolver) Supergraph(ctx context.Context, ref string, isAfter *str
// LatestSchema is the resolver for the latestSchema field.
func (r *queryResolver) LatestSchema(ctx context.Context, ref string) (*model.SchemaUpdate, error) {
orgId := middleware.OrganizationFromContext(ctx)
userId := middleware.UserFromContext(ctx)
r.Logger.Info("LatestSchema query",
"ref", ref,
"orgId", orgId,
"userId", userId,
)
_, err := r.apiKeyCanAccessRef(ctx, ref, false)
if err != nil {
r.Logger.Error("API key cannot access ref", "error", err, "ref", ref)
return nil, err
// If authenticated with API key (organization), check access
if orgId != "" {
_, err := r.apiKeyCanAccessRef(ctx, ref, false)
if err != nil {
r.Logger.Error("API key cannot access ref", "error", err, "ref", ref)
return nil, err
}
} else if userId != "" {
// For user authentication, check if user has access to ref through their organizations
userOrgs := r.Cache.OrganizationsByUser(userId)
if len(userOrgs) == 0 {
r.Logger.Error("User has no organizations", "userId", userId)
return nil, fmt.Errorf("user has no access to any organizations")
}
// Use the first organization's ID for querying
// In a real-world scenario, you might want to check which org has access to this ref
orgId = userOrgs[0].ID.String()
r.Logger.Info("Using organization from user context", "orgId", orgId)
} else {
return nil, fmt.Errorf("no authentication provided")
}
// Get current services and schema