feat: organizations and API keys

This commit is contained in:
2023-04-27 07:09:10 +02:00
parent 504f40902e
commit 554a6c252f
22 changed files with 2469 additions and 199 deletions
+49
View File
@@ -0,0 +1,49 @@
package graph
import (
"gitlab.com/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/graph/model"
)
func ToGqlOrganizations(orgs []domain.Organization) []*model.Organization {
result := make([]*model.Organization, len(orgs))
for i, o := range orgs {
result[i] = ToGqlOrganization(o)
}
return result
}
func ToGqlOrganization(o domain.Organization) *model.Organization {
users := ToGqlUsers(o.Users)
apiKeys := ToGqlAPIKeys(o.APIKeys)
return &model.Organization{
ID: o.ID.String(),
Name: o.Name,
Users: users,
APIKeys: apiKeys,
}
}
func ToGqlUsers(users []string) []*model.User {
result := make([]*model.User, len(users))
for i, u := range users {
result[i] = &model.User{ID: u}
}
return result
}
func ToGqlAPIKeys(keys []domain.APIKey) []*model.APIKey {
result := make([]*model.APIKey, len(keys))
for i, k := range keys {
result[i] = &model.APIKey{
ID: apiKeyId(k.OrganizationId, k.Name),
Name: k.Name,
Key: &k.Key,
Organization: nil,
Refs: k.Refs,
Read: k.Read,
Publish: k.Publish,
}
}
return result
}
File diff suppressed because it is too large Load Diff
+29
View File
@@ -10,6 +10,24 @@ type Supergraph interface {
IsSupergraph()
}
type APIKey struct {
ID string `json:"id"`
Name string `json:"name"`
Key *string `json:"key,omitempty"`
Organization *Organization `json:"organization"`
Refs []string `json:"refs"`
Read bool `json:"read"`
Publish bool `json:"publish"`
}
type InputAPIKey struct {
Name string `json:"name"`
OrganizationID string `json:"organizationId"`
Refs []string `json:"refs"`
Read bool `json:"read"`
Publish bool `json:"publish"`
}
type InputSubGraph struct {
Ref string `json:"ref"`
Service string `json:"service"`
@@ -18,6 +36,13 @@ type InputSubGraph struct {
Sdl string `json:"sdl"`
}
type Organization struct {
ID string `json:"id"`
Name string `json:"name"`
Users []*User `json:"users"`
APIKeys []*APIKey `json:"apiKeys"`
}
type SubGraph struct {
ID string `json:"id"`
Service string `json:"service"`
@@ -42,3 +67,7 @@ type Unchanged struct {
}
func (Unchanged) IsSupergraph() {}
type User struct {
ID string `json:"id"`
}
+5
View File
@@ -2,6 +2,7 @@ package graph
import (
"context"
"fmt"
"github.com/apex/log"
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
@@ -29,3 +30,7 @@ type Resolver struct {
func (r *Resolver) handler(ctx context.Context, aggregate eventsourced.Aggregate) (eventsourced.CommandHandler, error) {
return eventsourced.NewHandler(ctx, aggregate, r.EventStore, eventsourced.WithEventPublisher(r.Publisher))
}
func apiKeyId(orgId, name string) string {
return fmt.Sprintf("%s-%s", orgId, name)
}
+35 -4
View File
@@ -1,10 +1,33 @@
type Query {
subGraphs(ref: String!): [SubGraph!]! @hasApiKey @deprecated(reason: "Use supergraph instead")
supergraph(ref: String!, isAfter: String): Supergraph! @hasApiKey
organizations: [Organization!]! @auth(user: true)
supergraph(ref: String!, isAfter: String): Supergraph! @auth(organization: true)
}
type Mutation {
updateSubGraph(input: InputSubGraph!): SubGraph! @hasApiKey
addOrganization(name: String!): Organization! @auth(user: true)
addAPIKey(input: InputAPIKey): APIKey! @auth(user: true)
updateSubGraph(input: InputSubGraph!): SubGraph! @auth(organization: true)
}
type Organization {
id: ID!
name: String!
users: [User!]!
apiKeys: [APIKey!]!
}
type User {
id: String!
}
type APIKey {
id: ID!
name: String!
key: String
organization: Organization!
refs: [String!]!
read: Boolean!
publish: Boolean!
}
union Supergraph = Unchanged | SubGraphs
@@ -30,6 +53,14 @@ type SubGraph {
changedAt: Time!
}
input InputAPIKey {
name: String!
organizationId: ID!
refs: [String!]!
read: Boolean!
publish: Boolean!
}
input InputSubGraph {
ref: String!
service: String!
@@ -40,4 +71,4 @@ input InputSubGraph {
scalar Time
directive @hasApiKey on FIELD_DEFINITION
directive @auth(user: Boolean, organization: Boolean) on FIELD_DEFINITION
+76 -19
View File
@@ -15,11 +15,71 @@ import (
"gitlab.com/unboundsoftware/schemas/domain"
"gitlab.com/unboundsoftware/schemas/graph/generated"
"gitlab.com/unboundsoftware/schemas/graph/model"
"gitlab.com/unboundsoftware/schemas/middleware"
"gitlab.com/unboundsoftware/schemas/rand"
)
// AddOrganization is the resolver for the addOrganization field.
func (r *mutationResolver) AddOrganization(ctx context.Context, name string) (*model.Organization, error) {
sub := middleware.UserFromContext(ctx)
org := &domain.Organization{}
h, err := r.handler(ctx, org)
if err != nil {
return nil, err
}
_, err = h.Handle(ctx, &domain.AddOrganization{
Name: name,
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)
org := &domain.Organization{BaseAggregate: eventsourced.BaseAggregateFromString(input.OrganizationID)}
h, err := r.handler(ctx, org)
if err != nil {
return nil, err
}
key := fmt.Sprintf("us_ak_%s", rand.String(16))
_, err = h.Handle(ctx, &domain.AddAPIKey{
Name: input.Name,
Key: key,
Refs: input.Refs,
Read: input.Read,
Publish: input.Publish,
Initiator: sub,
})
if err != nil {
return nil, err
}
return &model.APIKey{
ID: apiKeyId(input.OrganizationID, input.Name),
Name: input.Name,
Key: &key,
Organization: &model.Organization{
ID: input.OrganizationID,
Name: org.Name,
},
Refs: input.Refs,
Read: input.Read,
Publish: input.Publish,
}, nil
}
// UpdateSubGraph is the resolver for the updateSubGraph field.
func (r *mutationResolver) UpdateSubGraph(ctx context.Context, input model.InputSubGraph) (*model.SubGraph, error) {
subGraphId := r.Cache.SubGraphId(input.Ref, input.Service)
orgId := middleware.OrganizationFromContext(ctx)
key, err := middleware.ApiKeyFromContext(ctx)
if err != nil {
return nil, err
}
apiKey := r.Cache.ApiKeyByKey(key)
subGraphId := r.Cache.SubGraphId(orgId, input.Ref, input.Service)
subGraph := &domain.SubGraph{}
if subGraphId != "" {
subGraph.BaseAggregate = eventsourced.BaseAggregateFromString(subGraphId)
@@ -34,7 +94,7 @@ func (r *mutationResolver) UpdateSubGraph(ctx context.Context, input model.Input
return r.toGqlSubGraph(subGraph), nil
}
serviceSDLs := []string{input.Sdl}
services, _ := r.Cache.Services(input.Ref, "")
services, _ := r.Cache.Services(orgId, input.Ref, "")
for _, id := range services {
sg, err := r.fetchSubGraph(ctx, id)
if err != nil {
@@ -49,12 +109,13 @@ func (r *mutationResolver) UpdateSubGraph(ctx context.Context, input model.Input
return nil, err
}
_, err = handler.Handle(ctx, domain.UpdateSubGraph{
Ref: input.Ref,
Service: input.Service,
Url: input.URL,
WSUrl: input.WsURL,
Sdl: input.Sdl,
Initiator: "Fetch name from API-key?",
OrganizationId: orgId,
Ref: input.Ref,
Service: input.Service,
Url: input.URL,
WSUrl: input.WsURL,
Sdl: input.Sdl,
Initiator: apiKey.Name,
})
if err != nil {
return nil, err
@@ -62,25 +123,21 @@ func (r *mutationResolver) UpdateSubGraph(ctx context.Context, input model.Input
return r.toGqlSubGraph(subGraph), nil
}
// SubGraphs is the resolver for the subGraphs field.
func (r *queryResolver) SubGraphs(ctx context.Context, ref string) ([]*model.SubGraph, error) {
res, err := r.Supergraph(ctx, ref, nil)
if err != nil {
return nil, err
}
if s, ok := res.(*model.SubGraphs); ok {
return s.SubGraphs, nil
}
return nil, fmt.Errorf("unexpected response")
// Organizations is the resolver for the organizations field.
func (r *queryResolver) Organizations(ctx context.Context) ([]*model.Organization, error) {
sub := middleware.UserFromContext(ctx)
orgs := r.Cache.OrganizationsByUser(sub)
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)
after := ""
if isAfter != nil {
after = *isAfter
}
services, lastUpdate := r.Cache.Services(ref, after)
services, lastUpdate := r.Cache.Services(orgId, ref, after)
if after == lastUpdate {
return &model.Unchanged{
ID: lastUpdate,