diff --git a/cache/cache.go b/cache/cache.go index 6de4796..f43aeaf 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -8,58 +8,138 @@ import ( "github.com/sparetimecoders/goamqp" "gitlab.com/unboundsoftware/schemas/domain" + "gitlab.com/unboundsoftware/schemas/hash" ) -const subGraphKey = "%s<->%s" - type Cache struct { - services map[string]map[string]struct{} - subGraphs map[string]string - lastUpdate map[string]string - logger log.Interface + organizations map[string]domain.Organization + users map[string][]string + apiKeys map[string]domain.APIKey + services map[string]map[string]map[string]struct{} + subGraphs map[string]string + lastUpdate map[string]string + logger log.Interface } -func (c *Cache) Services(ref, lastUpdate string) ([]string, string) { +func (c *Cache) OrganizationByAPIKey(apiKey string) *domain.Organization { + key, exists := c.apiKeys[apiKey] + if !exists { + return nil + } + org, exists := c.organizations[key.OrganizationId] + if !exists { + return nil + } + return &org +} + +func (c *Cache) OrganizationsByUser(sub string) []domain.Organization { + orgIds := c.users[sub] + orgs := make([]domain.Organization, len(orgIds)) + for i, id := range orgIds { + orgs[i] = c.organizations[id] + } + return orgs +} + +func (c *Cache) ApiKeyByKey(key string) *domain.APIKey { + k, exists := c.apiKeys[hash.String(key)] + if !exists { + return nil + } + return &k +} + +func (c *Cache) Services(orgId, ref, lastUpdate string) ([]string, string) { + key := refKey(orgId, ref) var services []string - if lastUpdate == "" || c.lastUpdate[ref] > lastUpdate { - for k := range c.services[ref] { + if lastUpdate == "" || c.lastUpdate[key] > lastUpdate { + for k := range c.services[orgId][ref] { services = append(services, k) } } - return services, c.lastUpdate[ref] + return services, c.lastUpdate[key] } -func (c *Cache) SubGraphId(ref, service string) string { - return c.subGraphs[fmt.Sprintf(subGraphKey, ref, service)] +func (c *Cache) SubGraphId(orgId, ref, service string) string { + return c.subGraphs[subGraphKey(orgId, ref, service)] } func (c *Cache) Update(msg any, _ goamqp.Headers) (any, error) { switch m := msg.(type) { + case *domain.OrganizationAdded: + o := domain.Organization{} + m.UpdateOrganization(&o) + c.organizations[m.ID.String()] = o + c.addUser(m.Initiator, o) + case *domain.APIKeyAdded: + key := domain.APIKey{ + Name: m.Name, + OrganizationId: m.OrganizationId, + Key: m.Key, + Refs: m.Refs, + Read: m.Read, + Publish: m.Publish, + CreatedBy: m.Initiator, + CreatedAt: m.When(), + } + c.apiKeys[m.Key] = key + org := c.organizations[m.OrganizationId] + org.APIKeys = append(org.APIKeys, key) + c.organizations[m.OrganizationId] = org case *domain.SubGraphUpdated: - if _, exists := c.services[m.Ref]; !exists { - c.services[m.Ref] = make(map[string]struct{}) + c.updateSubGraph(m.OrganizationId, m.Ref, m.ID.String(), m.Service, m.Time) + case *domain.Organization: + c.organizations[m.ID.String()] = *m + c.addUser(m.CreatedBy, *m) + for _, k := range m.APIKeys { + c.apiKeys[k.Key] = k } - c.services[m.Ref][m.ID.String()] = struct{}{} - c.subGraphs[fmt.Sprintf(subGraphKey, m.Ref, m.Service)] = m.ID.String() - c.lastUpdate[m.Ref] = m.Time.Format(time.RFC3339Nano) case *domain.SubGraph: - if _, exists := c.services[m.Ref]; !exists { - c.services[m.Ref] = make(map[string]struct{}) - } - c.services[m.Ref][m.ID.String()] = struct{}{} - c.subGraphs[fmt.Sprintf(subGraphKey, m.Ref, m.Service)] = m.ID.String() - c.lastUpdate[m.Ref] = m.ChangedAt.Format(time.RFC3339Nano) + c.updateSubGraph(m.OrganizationId, m.Ref, m.ID.String(), m.Service, m.ChangedAt) default: c.logger.Warnf("unexpected message received: %+v", msg) } return nil, nil } -func New(logger log.Interface) *Cache { - return &Cache{ - subGraphs: make(map[string]string), - services: make(map[string]map[string]struct{}), - lastUpdate: make(map[string]string), - logger: logger, +func (c *Cache) updateSubGraph(orgId string, ref string, subGraphId string, service string, updated time.Time) { + if _, exists := c.services[orgId]; !exists { + c.services[orgId] = make(map[string]map[string]struct{}) + } + if _, exists := c.services[orgId][ref]; !exists { + c.services[orgId][ref] = make(map[string]struct{}) + } + c.services[orgId][ref][subGraphId] = struct{}{} + c.subGraphs[subGraphKey(orgId, ref, service)] = subGraphId + c.lastUpdate[refKey(orgId, ref)] = updated.Format(time.RFC3339Nano) +} + +func (c *Cache) addUser(sub string, organization domain.Organization) { + user, exists := c.users[sub] + if !exists { + c.users[sub] = []string{organization.ID.String()} + } else { + c.users[sub] = append(user, organization.ID.String()) } } + +func New(logger log.Interface) *Cache { + return &Cache{ + organizations: make(map[string]domain.Organization), + users: make(map[string][]string), + apiKeys: make(map[string]domain.APIKey), + services: make(map[string]map[string]map[string]struct{}), + subGraphs: make(map[string]string), + lastUpdate: make(map[string]string), + logger: logger, + } +} + +func refKey(orgId string, ref string) string { + return fmt.Sprintf("%s<->%s", orgId, ref) +} + +func subGraphKey(orgId string, ref string, service string) string { + return fmt.Sprintf("%s<->%s<->%s", orgId, ref, service) +} diff --git a/cmd/service/service.go b/cmd/service/service.go index 466e4eb..e120dd6 100644 --- a/cmd/service/service.go +++ b/cmd/service/service.go @@ -35,10 +35,11 @@ import ( type CLI struct { AmqpURL string `name:"amqp-url" env:"AMQP_URL" help:"URL to use to connect to RabbitMQ" default:"amqp://user:password@localhost:5672/"` Port int `name:"port" env:"PORT" help:"Listen-port for GraphQL API" default:"8080"` - APIKey string `name:"api-key" env:"API_KEY" help:"The API-key that is required"` LogLevel string `name:"log-level" env:"LOG_LEVEL" help:"The level of logging to use (debug, info, warn, error, fatal)" default:"info"` DatabaseURL string `name:"postgres-url" env:"POSTGRES_URL" help:"URL to use to connect to Postgres" default:"postgres://postgres:postgres@:5432/schemas?sslmode=disable"` DatabaseDriverName string `name:"db-driver" env:"DB_DRIVER" help:"Driver to use to connect to db" default:"postgres"` + Issuer string `name:"issuer" env:"ISSUER" help:"The JWT token issuer to use" default:"unbound.eu.auth0.com"` + StrictSSL bool `name:"strict-ssl" env:"STRICT_SSL" help:"Should strict SSL handling be enabled" default:"true"` SentryConfig } @@ -88,16 +89,31 @@ func start(closeEvents chan error, logger *log.Entry, connectToAmqpFunc func(url db.DB, pg.WithEventTypes( &domain.SubGraphUpdated{}, + &domain.OrganizationAdded{}, + &domain.APIKeyAdded{}, ), ) if err != nil { return fmt.Errorf("failed to create eventstore: %v", err) } + + if err := store.RunEventStoreMigrations(db); err != nil { + return fmt.Errorf("event migrations: %w", err) + } + publisher, err := goamqp.NewPublisher( goamqp.Route{ Type: domain.SubGraphUpdated{}, Key: "SubGraph.Updated", }, + goamqp.Route{ + Type: domain.OrganizationAdded{}, + Key: "Organization.Added", + }, + goamqp.Route{ + Type: domain.APIKeyAdded{}, + Key: "Organization.APIKeyAdded", + }, ) if err != nil { return fmt.Errorf("failed to create publisher: %v", err) @@ -112,19 +128,11 @@ func start(closeEvents chan error, logger *log.Entry, connectToAmqpFunc func(url } serviceCache := cache.New(logger) - roots, err := eventStore.GetAggregateRoots(rootCtx, reflect.TypeOf(domain.SubGraph{})) - if err != nil { - return err + if err := loadOrganizations(rootCtx, eventStore, serviceCache); err != nil { + return fmt.Errorf("caching organizations: %w", err) } - for _, root := range roots { - subGraph := &domain.SubGraph{BaseAggregate: eventsourced.BaseAggregateFromString(root.String())} - if _, err := eventsourced.NewHandler(rootCtx, subGraph, eventStore); err != nil { - return err - } - _, err := serviceCache.Update(subGraph, nil) - if err != nil { - return err - } + if err := loadSubGraphs(rootCtx, eventStore, serviceCache); err != nil { + return fmt.Errorf("caching subgraphs: %w", err) } setups := []goamqp.Setup{ goamqp.UseLogger(logger.Errorf), @@ -132,6 +140,8 @@ func start(closeEvents chan error, logger *log.Entry, connectToAmqpFunc func(url goamqp.WithPrefetchLimit(20), goamqp.EventStreamPublisher(publisher), goamqp.TransientEventStreamConsumer("SubGraph.Updated", serviceCache.Update, domain.SubGraphUpdated{}), + goamqp.TransientEventStreamConsumer("Organization.Added", serviceCache.Update, domain.OrganizationAdded{}), + goamqp.TransientEventStreamConsumer("Organization.APIKeyAdded", serviceCache.Update, domain.APIKeyAdded{}), } if err := conn.Start(rootCtx, setups...); err != nil { return fmt.Errorf("failed to setup AMQP: %v", err) @@ -200,8 +210,10 @@ func start(closeEvents chan error, logger *log.Entry, connectToAmqpFunc func(url Resolvers: resolver, Complexity: generated.ComplexityRoot{}, } - apiKeyMiddleware := middleware.NewApiKey(cli.APIKey, logger) - config.Directives.HasApiKey = apiKeyMiddleware.Directive + apiKeyMiddleware := middleware.NewApiKey() + mw := middleware.NewAuth0("https://schemas.unbound.se", cli.Issuer, cli.StrictSSL) + authMiddleware := middleware.NewAuth(serviceCache) + config.Directives.Auth = authMiddleware.Directive srv := handler.NewDefaultServer(generated.NewExecutableSchema( config, )) @@ -209,7 +221,15 @@ func start(closeEvents chan error, logger *log.Entry, connectToAmqpFunc func(url sentryHandler := sentryhttp.New(sentryhttp.Options{Repanic: true}) mux.Handle("/", sentryHandler.HandleFunc(playground.Handler("GraphQL playground", "/query"))) mux.Handle("/health", http.HandlerFunc(healthFunc)) - mux.Handle("/query", cors.AllowAll().Handler(sentryHandler.Handle(apiKeyMiddleware.Handler(srv)))) + mux.Handle("/query", cors.AllowAll().Handler( + sentryHandler.Handle( + mw.Middleware().CheckJWT( + apiKeyMiddleware.Handler( + authMiddleware.Handler(srv), + ), + ), + ), + )) logger.Infof("connect to http://localhost:%d/ for GraphQL playground", cli.Port) @@ -223,6 +243,42 @@ func start(closeEvents chan error, logger *log.Entry, connectToAmqpFunc func(url return nil } +func loadOrganizations(ctx context.Context, eventStore eventsourced.EventStore, serviceCache *cache.Cache) error { + roots, err := eventStore.GetAggregateRoots(ctx, reflect.TypeOf(domain.Organization{})) + if err != nil { + return err + } + for _, root := range roots { + organization := &domain.Organization{BaseAggregate: eventsourced.BaseAggregateFromString(root.String())} + if _, err := eventsourced.NewHandler(ctx, organization, eventStore); err != nil { + return err + } + _, err := serviceCache.Update(organization, nil) + if err != nil { + return err + } + } + return nil +} + +func loadSubGraphs(ctx context.Context, eventStore eventsourced.EventStore, serviceCache *cache.Cache) error { + roots, err := eventStore.GetAggregateRoots(ctx, reflect.TypeOf(domain.SubGraph{})) + if err != nil { + return err + } + for _, root := range roots { + subGraph := &domain.SubGraph{BaseAggregate: eventsourced.BaseAggregateFromString(root.String())} + if _, err := eventsourced.NewHandler(ctx, subGraph, eventStore); err != nil { + return err + } + _, err := serviceCache.Update(subGraph, nil) + if err != nil { + return err + } + } + return nil +} + func healthFunc(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte("OK")) } diff --git a/domain/aggregates.go b/domain/aggregates.go index 3754ce9..e7422ae 100644 --- a/domain/aggregates.go +++ b/domain/aggregates.go @@ -8,19 +8,67 @@ import ( "gitlab.com/unboundsoftware/eventsourced/eventsourced" ) -type SubGraph struct { +type Organization struct { eventsourced.BaseAggregate - Ref string - Service string - Url *string - WSUrl *string - Sdl string + Name string + Users []string + APIKeys []APIKey CreatedBy string CreatedAt time.Time ChangedBy string ChangedAt time.Time } +func (o *Organization) Apply(event eventsourced.Event) error { + switch e := event.(type) { + case *OrganizationAdded: + e.UpdateOrganization(o) + case *APIKeyAdded: + o.APIKeys = append(o.APIKeys, APIKey{ + Name: e.Name, + OrganizationId: o.ID.String(), + Key: e.Key, + Refs: e.Refs, + Read: e.Read, + Publish: e.Publish, + CreatedBy: e.Initiator, + CreatedAt: e.When(), + }) + o.ChangedBy = e.Initiator + o.ChangedAt = e.When() + default: + return fmt.Errorf("unexpected event type: %+v", event) + } + return nil +} + +var _ eventsourced.Aggregate = &Organization{} + +type APIKey struct { + Name string + OrganizationId string + Key string + Refs []string + Read bool + Publish bool + CreatedBy string + CreatedAt time.Time +} + +type SubGraph struct { + eventsourced.BaseAggregate + OrganizationId string + Ref string + Service string + Url *string + WSUrl *string + Sdl string + CreatedBy string + CreatedAt time.Time + ChangedBy string + ChangedAt time.Time +} + func (s *SubGraph) Apply(event eventsourced.Event) error { switch e := event.(type) { case *SubGraphUpdated: @@ -28,6 +76,9 @@ func (s *SubGraph) Apply(event eventsourced.Event) error { s.CreatedBy = e.Initiator s.CreatedAt = e.When() } + if s.OrganizationId == "" { + s.OrganizationId = e.OrganizationId + } s.ChangedBy = e.Initiator s.ChangedAt = e.When() s.Ref = e.Ref diff --git a/domain/commands.go b/domain/commands.go index f5f227f..55fede4 100644 --- a/domain/commands.go +++ b/domain/commands.go @@ -6,17 +6,78 @@ import ( "strings" "gitlab.com/unboundsoftware/eventsourced/eventsourced" + + "gitlab.com/unboundsoftware/schemas/hash" ) -type UpdateSubGraph struct { - Ref string - Service string - Url *string - WSUrl *string - Sdl string +type AddOrganization struct { + Name string Initiator string } +func (a AddOrganization) Validate(_ context.Context, aggregate eventsourced.Aggregate) error { + if aggregate.Identity() != nil { + return fmt.Errorf("organization already exists") + } + if len(a.Name) == 0 { + return fmt.Errorf("name is required") + } + return nil +} + +func (a AddOrganization) Event(context.Context) eventsourced.Event { + return &OrganizationAdded{ + Name: a.Name, + Initiator: a.Initiator, + } +} + +var _ eventsourced.Command = AddOrganization{} + +type AddAPIKey struct { + Name string + Key string + Refs []string + Read bool + Publish bool + Initiator string +} + +func (a AddAPIKey) Validate(_ context.Context, aggregate eventsourced.Aggregate) error { + if aggregate.Identity() == nil { + return fmt.Errorf("organization does not exist") + } + for _, k := range aggregate.(*Organization).APIKeys { + if k.Name == a.Name { + return fmt.Errorf("a key named '%s' already exist", a.Name) + } + } + return nil +} + +func (a AddAPIKey) Event(context.Context) eventsourced.Event { + return &APIKeyAdded{ + Name: a.Name, + Key: hash.String(a.Key), + Refs: a.Refs, + Read: a.Read, + Publish: a.Publish, + Initiator: a.Initiator, + } +} + +var _ eventsourced.Command = AddAPIKey{} + +type UpdateSubGraph struct { + OrganizationId string + Ref string + Service string + Url *string + WSUrl *string + Sdl string + Initiator string +} + func (u UpdateSubGraph) Validate(_ context.Context, aggregate eventsourced.Aggregate) error { switch a := aggregate.(type) { case *SubGraph: @@ -40,12 +101,13 @@ func (u UpdateSubGraph) Validate(_ context.Context, aggregate eventsourced.Aggre func (u UpdateSubGraph) Event(context.Context) eventsourced.Event { return &SubGraphUpdated{ - Ref: u.Ref, - Service: u.Service, - Url: u.Url, - WSUrl: u.WSUrl, - Sdl: u.Sdl, - Initiator: u.Initiator, + OrganizationId: u.OrganizationId, + Ref: u.Ref, + Service: u.Service, + Url: u.Url, + WSUrl: u.WSUrl, + Sdl: u.Sdl, + Initiator: u.Initiator, } } diff --git a/domain/commands_test.go b/domain/commands_test.go new file mode 100644 index 0000000..c62e4e4 --- /dev/null +++ b/domain/commands_test.go @@ -0,0 +1,63 @@ +package domain + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "gitlab.com/unboundsoftware/eventsourced/eventsourced" +) + +func TestAddAPIKey_Event(t *testing.T) { + type fields struct { + Name string + Key string + Refs []string + Read bool + Publish bool + Initiator string + } + type args struct { + in0 context.Context + } + tests := []struct { + name string + fields fields + args args + want eventsourced.Event + }{ + { + name: "event", + fields: fields{ + Name: "test", + Key: "us_ak_1234567890123456", + Refs: []string{"Example@dev"}, + Read: true, + Publish: true, + Initiator: "jim@example.org", + }, + args: args{}, + want: &APIKeyAdded{ + Name: "test", + Key: "dXNfYWtfMTIzNDU2Nzg5MDEyMzQ1NuOwxEKY/BwUmvv0yJlvuSQnrkHkZJuTTKSVmRt4UrhV", + Refs: []string{"Example@dev"}, + Read: true, + Publish: true, + Initiator: "jim@example.org", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a := AddAPIKey{ + Name: tt.fields.Name, + Key: tt.fields.Key, + Refs: tt.fields.Refs, + Read: tt.fields.Read, + Publish: tt.fields.Publish, + Initiator: tt.fields.Initiator, + } + assert.Equalf(t, tt.want, a.Event(tt.args.in0), "Event(%v)", tt.args.in0) + }) + } +} diff --git a/domain/events.go b/domain/events.go index 8b8086e..e7f73f4 100644 --- a/domain/events.go +++ b/domain/events.go @@ -2,13 +2,48 @@ package domain import "gitlab.com/unboundsoftware/eventsourced/eventsourced" +type OrganizationAdded struct { + eventsourced.EventAggregateId + eventsourced.EventTime + Name string `json:"name"` + Initiator string `json:"initiator"` +} + +func (a *OrganizationAdded) UpdateOrganization(o *Organization) { + o.Name = a.Name + o.Users = []string{a.Initiator} + o.CreatedBy = a.Initiator + o.CreatedAt = a.When() + o.ChangedBy = a.Initiator + o.ChangedAt = a.When() +} + +type APIKeyAdded struct { + eventsourced.EventAggregateId + eventsourced.EventTime + OrganizationId string `json:"organizationId"` + Name string `json:"name"` + Key string `json:"key"` + Refs []string `json:"refs"` + Read bool `json:"read"` + Publish bool `json:"publish"` + Initiator string `json:"initiator"` +} + +func (a *APIKeyAdded) EnrichFromAggregate(aggregate eventsourced.Aggregate) { + a.OrganizationId = aggregate.Identity().String() +} + +var _ eventsourced.EnrichableEvent = &APIKeyAdded{} + type SubGraphUpdated struct { eventsourced.EventAggregateId eventsourced.EventTime - Ref string - Service string - Url *string - WSUrl *string - Sdl string - Initiator string + OrganizationId string `json:"organizationId"` + Ref string `json:"ref"` + Service string `json:"service"` + Url *string `json:"url"` + WSUrl *string `json:"wsUrl"` + Sdl string `json:"sdl"` + Initiator string `json:"initiator"` } diff --git a/go.mod b/go.mod index 48ba257..94996be 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,12 @@ require ( github.com/Khan/genqlient v0.5.0 github.com/alecthomas/kong v0.7.1 github.com/apex/log v1.9.0 + github.com/auth0/go-jwt-middleware/v2 v2.1.0 github.com/getsentry/sentry-go v0.20.0 + github.com/golang-jwt/jwt/v4 v4.5.0 github.com/jmoiron/sqlx v1.3.5 + github.com/pkg/errors v0.9.1 + github.com/pressly/goose/v3 v3.10.0 github.com/rs/cors v1.9.0 github.com/sparetimecoders/goamqp v0.1.3 github.com/stretchr/testify v1.8.2 @@ -32,7 +36,6 @@ require ( github.com/kr/text v0.2.0 // indirect github.com/lib/pq v1.10.8 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/qri-io/jsonpointer v0.1.1 // indirect github.com/qri-io/jsonschema v0.2.1 // indirect @@ -44,10 +47,10 @@ require ( github.com/tidwall/sjson v1.2.5 // indirect github.com/urfave/cli/v2 v2.24.4 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect - golang.org/x/mod v0.8.0 // indirect + golang.org/x/mod v0.9.0 // indirect golang.org/x/sys v0.6.0 // indirect golang.org/x/text v0.8.0 // indirect - golang.org/x/tools v0.6.0 // indirect + golang.org/x/tools v0.7.0 // indirect golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 7743875..4252082 100644 --- a/go.sum +++ b/go.sum @@ -25,6 +25,8 @@ github.com/aphistic/golf v0.0.0-20180712155816-02c07f170c5a/go.mod h1:3NqKYiepwy github.com/aphistic/sweet v0.2.0/go.mod h1:fWDlIh/isSE9n6EPsRmC0det+whmX6dJid3stzu0Xys= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/auth0/go-jwt-middleware/v2 v2.1.0 h1:VU4LsC3aFPoqXVyEp8EixU6FNM+ZNIjECszRTvtGQI8= +github.com/auth0/go-jwt-middleware/v2 v2.1.0/go.mod h1:CpzcJoleayAACpv+vt0AP8/aYn5TDngsqzLapV1nM4c= github.com/aws/aws-sdk-go v1.20.6/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aybabtme/rgbterm v0.0.0-20170906152045-cc83f3b3ce59/go.mod h1:q/89r3U2H7sSsE2t6Kca0lfwTK8JdoNGS/yzM/4iH5I= github.com/bradleyjkemp/cupaloy/v2 v2.6.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= @@ -43,6 +45,7 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/evanphx/json-patch/v5 v5.1.0 h1:B0aXl1o/1cP8NbviYiBMkcHBtUjIJ1/Ccg6b+SwCLQg= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -50,8 +53,10 @@ github.com/getsentry/sentry-go v0.20.0 h1:bwXW98iMRIWxn+4FgPW7vMrjmbym6HblXALmhj github.com/getsentry/sentry-go v0.20.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= +github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= +github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -73,6 +78,7 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= @@ -92,6 +98,7 @@ github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVc github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg= github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -109,12 +116,15 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pressly/goose/v3 v3.10.0 h1:Gn5E9CkPqTtWvfaDVqtJqMjYtsrZ9K5mU/8wzTsvg04= +github.com/pressly/goose/v3 v3.10.0/go.mod h1:c5D3a7j66cT0fhRPj7KsXolfduVrhLlxKZjmCVSey5w= github.com/qri-io/jsonpointer v0.1.1 h1:prVZBZLL6TW5vsSB9fFHFAMBLI4b0ri5vribQlTJiBA= github.com/qri-io/jsonpointer v0.1.1/go.mod h1:DnJPaYgiKu56EuDp8TU5wFLdZIcAnb/uH9v37ZaMV64= github.com/qri-io/jsonschema v0.2.1 h1:NNFoKms+kut6ABPf6xiKNM5214jzxAhDBrPHCJ97Wg0= github.com/qri-io/jsonschema v0.2.1/go.mod h1:g7DPkiOsK1xv6T/Ao5scXRkd+yTFygcANPBaaqW+VrI= github.com/rabbitmq/amqp091-go v1.5.0 h1:VouyHPBu1CrKyJVfteGknGOGCzmOz0zcv/tONLkb7rg= github.com/rabbitmq/amqp091-go v1.5.0/go.mod h1:JsV0ofX5f1nwOGafb8L5rBItt9GyhfQfcJj+oyz0dGg= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/rogpeppe/fastuuid v1.1.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE= github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= @@ -193,13 +203,14 @@ golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= +golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -244,8 +255,8 @@ golang.org/x/tools v0.0.0-20200815165600-90abf76919f3/go.mod h1:njjCfa9FT2d7l9Bc golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= +golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -258,6 +269,7 @@ gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -270,3 +282,13 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= diff --git a/graph/converter.go b/graph/converter.go new file mode 100644 index 0000000..4af8538 --- /dev/null +++ b/graph/converter.go @@ -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 +} diff --git a/graph/generated/generated.go b/graph/generated/generated.go index 56bb665..91d1534 100644 --- a/graph/generated/generated.go +++ b/graph/generated/generated.go @@ -42,17 +42,36 @@ type ResolverRoot interface { } type DirectiveRoot struct { - HasApiKey func(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) + Auth func(ctx context.Context, obj interface{}, next graphql.Resolver, user *bool, organization *bool) (res interface{}, err error) } type ComplexityRoot struct { + APIKey struct { + ID func(childComplexity int) int + Key func(childComplexity int) int + Name func(childComplexity int) int + Organization func(childComplexity int) int + Publish func(childComplexity int) int + Read func(childComplexity int) int + Refs func(childComplexity int) int + } + Mutation struct { - UpdateSubGraph func(childComplexity int, input model.InputSubGraph) int + AddAPIKey func(childComplexity int, input *model.InputAPIKey) int + AddOrganization func(childComplexity int, name string) int + UpdateSubGraph func(childComplexity int, input model.InputSubGraph) int + } + + Organization struct { + APIKeys func(childComplexity int) int + ID func(childComplexity int) int + Name func(childComplexity int) int + Users func(childComplexity int) int } Query struct { - SubGraphs func(childComplexity int, ref string) int - Supergraph func(childComplexity int, ref string, isAfter *string) int + Organizations func(childComplexity int) int + Supergraph func(childComplexity int, ref string, isAfter *string) int } SubGraph struct { @@ -75,13 +94,19 @@ type ComplexityRoot struct { ID func(childComplexity int) int MinDelaySeconds func(childComplexity int) int } + + User struct { + ID func(childComplexity int) int + } } type MutationResolver interface { + AddOrganization(ctx context.Context, name string) (*model.Organization, error) + AddAPIKey(ctx context.Context, input *model.InputAPIKey) (*model.APIKey, error) UpdateSubGraph(ctx context.Context, input model.InputSubGraph) (*model.SubGraph, error) } type QueryResolver interface { - SubGraphs(ctx context.Context, ref string) ([]*model.SubGraph, error) + Organizations(ctx context.Context) ([]*model.Organization, error) Supergraph(ctx context.Context, ref string, isAfter *string) (model.Supergraph, error) } @@ -100,6 +125,79 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in _ = ec switch typeName + "." + field { + case "APIKey.id": + if e.complexity.APIKey.ID == nil { + break + } + + return e.complexity.APIKey.ID(childComplexity), true + + case "APIKey.key": + if e.complexity.APIKey.Key == nil { + break + } + + return e.complexity.APIKey.Key(childComplexity), true + + case "APIKey.name": + if e.complexity.APIKey.Name == nil { + break + } + + return e.complexity.APIKey.Name(childComplexity), true + + case "APIKey.organization": + if e.complexity.APIKey.Organization == nil { + break + } + + return e.complexity.APIKey.Organization(childComplexity), true + + case "APIKey.publish": + if e.complexity.APIKey.Publish == nil { + break + } + + return e.complexity.APIKey.Publish(childComplexity), true + + case "APIKey.read": + if e.complexity.APIKey.Read == nil { + break + } + + return e.complexity.APIKey.Read(childComplexity), true + + case "APIKey.refs": + if e.complexity.APIKey.Refs == nil { + break + } + + return e.complexity.APIKey.Refs(childComplexity), true + + case "Mutation.addAPIKey": + if e.complexity.Mutation.AddAPIKey == nil { + break + } + + args, err := ec.field_Mutation_addAPIKey_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.AddAPIKey(childComplexity, args["input"].(*model.InputAPIKey)), true + + case "Mutation.addOrganization": + if e.complexity.Mutation.AddOrganization == nil { + break + } + + args, err := ec.field_Mutation_addOrganization_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Mutation.AddOrganization(childComplexity, args["name"].(string)), true + case "Mutation.updateSubGraph": if e.complexity.Mutation.UpdateSubGraph == nil { break @@ -112,17 +210,40 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Mutation.UpdateSubGraph(childComplexity, args["input"].(model.InputSubGraph)), true - case "Query.subGraphs": - if e.complexity.Query.SubGraphs == nil { + case "Organization.apiKeys": + if e.complexity.Organization.APIKeys == nil { break } - args, err := ec.field_Query_subGraphs_args(context.TODO(), rawArgs) - if err != nil { - return 0, false + return e.complexity.Organization.APIKeys(childComplexity), true + + case "Organization.id": + if e.complexity.Organization.ID == nil { + break } - return e.complexity.Query.SubGraphs(childComplexity, args["ref"].(string)), true + return e.complexity.Organization.ID(childComplexity), true + + case "Organization.name": + if e.complexity.Organization.Name == nil { + break + } + + return e.complexity.Organization.Name(childComplexity), true + + case "Organization.users": + if e.complexity.Organization.Users == nil { + break + } + + return e.complexity.Organization.Users(childComplexity), true + + case "Query.organizations": + if e.complexity.Query.Organizations == nil { + break + } + + return e.complexity.Query.Organizations(childComplexity), true case "Query.supergraph": if e.complexity.Query.Supergraph == nil { @@ -220,6 +341,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Unchanged.MinDelaySeconds(childComplexity), true + case "User.id": + if e.complexity.User.ID == nil { + break + } + + return e.complexity.User.ID(childComplexity), true + } return 0, false } @@ -228,6 +356,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { rc := graphql.GetOperationContext(ctx) ec := executionContext{rc, e} inputUnmarshalMap := graphql.BuildUnmarshalerMap( + ec.unmarshalInputInputAPIKey, ec.unmarshalInputInputSubGraph, ) first := true @@ -290,12 +419,35 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er var sources = []*ast.Source{ {Name: "../schema.graphqls", Input: `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 @@ -321,6 +473,14 @@ type SubGraph { changedAt: Time! } +input InputAPIKey { + name: String! + organizationId: ID! + refs: [String!]! + read: Boolean! + publish: Boolean! +} + input InputSubGraph { ref: String! service: String! @@ -331,7 +491,7 @@ input InputSubGraph { scalar Time -directive @hasApiKey on FIELD_DEFINITION +directive @auth(user: Boolean, organization: Boolean) on FIELD_DEFINITION `, BuiltIn: false}, } var parsedSchema = gqlparser.MustLoadSchema(sources...) @@ -340,6 +500,60 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...) // region ***************************** args.gotpl ***************************** +func (ec *executionContext) dir_auth_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *bool + if tmp, ok := rawArgs["user"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("user")) + arg0, err = ec.unmarshalOBoolean2ᚖbool(ctx, tmp) + if err != nil { + return nil, err + } + } + args["user"] = arg0 + var arg1 *bool + if tmp, ok := rawArgs["organization"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("organization")) + arg1, err = ec.unmarshalOBoolean2ᚖbool(ctx, tmp) + if err != nil { + return nil, err + } + } + args["organization"] = arg1 + return args, nil +} + +func (ec *executionContext) field_Mutation_addAPIKey_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *model.InputAPIKey + if tmp, ok := rawArgs["input"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("input")) + arg0, err = ec.unmarshalOInputAPIKey2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐInputAPIKey(ctx, tmp) + if err != nil { + return nil, err + } + } + args["input"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Mutation_addOrganization_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 string + if tmp, ok := rawArgs["name"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) + arg0, err = ec.unmarshalNString2string(ctx, tmp) + if err != nil { + return nil, err + } + } + args["name"] = arg0 + return args, nil +} + func (ec *executionContext) field_Mutation_updateSubGraph_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -370,21 +584,6 @@ func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs return args, nil } -func (ec *executionContext) field_Query_subGraphs_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { - var err error - args := map[string]interface{}{} - var arg0 string - if tmp, ok := rawArgs["ref"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("ref")) - arg0, err = ec.unmarshalNString2string(ctx, tmp) - if err != nil { - return nil, err - } - } - args["ref"] = arg0 - return args, nil -} - func (ec *executionContext) field_Query_supergraph_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} @@ -447,6 +646,505 @@ func (ec *executionContext) field___Type_fields_args(ctx context.Context, rawArg // region **************************** field.gotpl ***************************** +func (ec *executionContext) _APIKey_id(ctx context.Context, field graphql.CollectedField, obj *model.APIKey) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_APIKey_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, 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.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_APIKey_id(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "APIKey", + 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) _APIKey_name(ctx context.Context, field graphql.CollectedField, obj *model.APIKey) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_APIKey_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, 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_APIKey_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "APIKey", + 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) _APIKey_key(ctx context.Context, field graphql.CollectedField, obj *model.APIKey) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_APIKey_key(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Key, nil + }) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if resTmp == nil { + return graphql.Null + } + res := resTmp.(*string) + fc.Result = res + return ec.marshalOString2ᚖstring(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_APIKey_key(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "APIKey", + 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) _APIKey_organization(ctx context.Context, field graphql.CollectedField, obj *model.APIKey) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_APIKey_organization(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Organization, 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.(*model.Organization) + fc.Result = res + return ec.marshalNOrganization2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_APIKey_organization(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "APIKey", + 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_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) _APIKey_refs(ctx context.Context, field graphql.CollectedField, obj *model.APIKey) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_APIKey_refs(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Refs, 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.marshalNString2ᚕstringᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_APIKey_refs(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "APIKey", + 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) _APIKey_read(ctx context.Context, field graphql.CollectedField, obj *model.APIKey) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_APIKey_read(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Read, 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.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_APIKey_read(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "APIKey", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _APIKey_publish(ctx context.Context, field graphql.CollectedField, obj *model.APIKey) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_APIKey_publish(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Publish, 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.(bool) + fc.Result = res + return ec.marshalNBoolean2bool(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_APIKey_publish(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "APIKey", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Boolean does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _Mutation_addOrganization(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_addOrganization(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().AddOrganization(rctx, fc.Args["name"].(string)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true) + if err != nil { + return nil, err + } + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") + } + return ec.directives.Auth(ctx, nil, directive0, user, nil) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*model.Organization); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *gitlab.com/unboundsoftware/schemas/graph/model.Organization`, tmp) + }) + 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.(*model.Organization) + fc.Result = res + return ec.marshalNOrganization2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_addOrganization(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_addOrganization_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return + } + return fc, nil +} + +func (ec *executionContext) _Mutation_addAPIKey(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Mutation_addAPIKey(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Mutation().AddAPIKey(rctx, fc.Args["input"].(*model.InputAPIKey)) + } + directive1 := func(ctx context.Context) (interface{}, error) { + user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true) + if err != nil { + return nil, err + } + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") + } + return ec.directives.Auth(ctx, nil, directive0, user, nil) + } + + tmp, err := directive1(rctx) + if err != nil { + return nil, graphql.ErrorOnPath(ctx, err) + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.(*model.APIKey); ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be *gitlab.com/unboundsoftware/schemas/graph/model.APIKey`, tmp) + }) + 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.(*model.APIKey) + fc.Result = res + return ec.marshalNAPIKey2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Mutation_addAPIKey(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_APIKey_id(ctx, field) + case "name": + return ec.fieldContext_APIKey_name(ctx, field) + case "key": + return ec.fieldContext_APIKey_key(ctx, field) + case "organization": + return ec.fieldContext_APIKey_organization(ctx, field) + case "refs": + return ec.fieldContext_APIKey_refs(ctx, field) + case "read": + return ec.fieldContext_APIKey_read(ctx, field) + case "publish": + return ec.fieldContext_APIKey_publish(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type APIKey", 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_addAPIKey_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return + } + return fc, nil +} + func (ec *executionContext) _Mutation_updateSubGraph(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { fc, err := ec.fieldContext_Mutation_updateSubGraph(ctx, field) if err != nil { @@ -465,10 +1163,14 @@ func (ec *executionContext) _Mutation_updateSubGraph(ctx context.Context, field return ec.resolvers.Mutation().UpdateSubGraph(rctx, fc.Args["input"].(model.InputSubGraph)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.HasApiKey == nil { - return nil, errors.New("directive hasApiKey is not implemented") + organization, err := ec.unmarshalOBoolean2ᚖbool(ctx, true) + if err != nil { + return nil, err } - return ec.directives.HasApiKey(ctx, nil, directive0) + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") + } + return ec.directives.Auth(ctx, nil, directive0, nil, organization) } tmp, err := directive1(rctx) @@ -538,8 +1240,204 @@ func (ec *executionContext) fieldContext_Mutation_updateSubGraph(ctx context.Con return fc, nil } -func (ec *executionContext) _Query_subGraphs(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { - fc, err := ec.fieldContext_Query_subGraphs(ctx, field) +func (ec *executionContext) _Organization_id(ctx context.Context, field graphql.CollectedField, obj *model.Organization) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Organization_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, 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.(string) + fc.Result = res + return ec.marshalNID2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Organization_id(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Organization", + 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) _Organization_name(ctx context.Context, field graphql.CollectedField, obj *model.Organization) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Organization_name(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, 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_Organization_name(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Organization", + 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) _Organization_users(ctx context.Context, field graphql.CollectedField, obj *model.Organization) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Organization_users(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.Users, 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.([]*model.User) + fc.Result = res + return ec.marshalNUser2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUserᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Organization_users(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Organization", + 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_User_id(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type User", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Organization_apiKeys(ctx context.Context, field graphql.CollectedField, obj *model.Organization) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Organization_apiKeys(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return obj.APIKeys, 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.([]*model.APIKey) + fc.Result = res + return ec.marshalNAPIKey2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKeyᚄ(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_Organization_apiKeys(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "Organization", + 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_APIKey_id(ctx, field) + case "name": + return ec.fieldContext_APIKey_name(ctx, field) + case "key": + return ec.fieldContext_APIKey_key(ctx, field) + case "organization": + return ec.fieldContext_APIKey_organization(ctx, field) + case "refs": + return ec.fieldContext_APIKey_refs(ctx, field) + case "read": + return ec.fieldContext_APIKey_read(ctx, field) + case "publish": + return ec.fieldContext_APIKey_publish(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type APIKey", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _Query_organizations(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_Query_organizations(ctx, field) if err != nil { return graphql.Null } @@ -553,13 +1451,17 @@ func (ec *executionContext) _Query_subGraphs(ctx context.Context, field graphql. resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { directive0 := func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().SubGraphs(rctx, fc.Args["ref"].(string)) + return ec.resolvers.Query().Organizations(rctx) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.HasApiKey == nil { - return nil, errors.New("directive hasApiKey is not implemented") + user, err := ec.unmarshalOBoolean2ᚖbool(ctx, true) + if err != nil { + return nil, err } - return ec.directives.HasApiKey(ctx, nil, directive0) + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") + } + return ec.directives.Auth(ctx, nil, directive0, user, nil) } tmp, err := directive1(rctx) @@ -569,10 +1471,10 @@ func (ec *executionContext) _Query_subGraphs(ctx context.Context, field graphql. if tmp == nil { return nil, nil } - if data, ok := tmp.([]*model.SubGraph); ok { + if data, ok := tmp.([]*model.Organization); ok { return data, nil } - return nil, fmt.Errorf(`unexpected type %T from directive, should be []*gitlab.com/unboundsoftware/schemas/graph/model.SubGraph`, tmp) + return nil, fmt.Errorf(`unexpected type %T from directive, should be []*gitlab.com/unboundsoftware/schemas/graph/model.Organization`, tmp) }) if err != nil { ec.Error(ctx, err) @@ -584,12 +1486,12 @@ func (ec *executionContext) _Query_subGraphs(ctx context.Context, field graphql. } return graphql.Null } - res := resTmp.([]*model.SubGraph) + res := resTmp.([]*model.Organization) fc.Result = res - return ec.marshalNSubGraph2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraphᚄ(ctx, field.Selections, res) + return ec.marshalNOrganization2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganizationᚄ(ctx, field.Selections, res) } -func (ec *executionContext) fieldContext_Query_subGraphs(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { +func (ec *executionContext) fieldContext_Query_organizations(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { fc = &graphql.FieldContext{ Object: "Query", Field: field, @@ -598,34 +1500,17 @@ func (ec *executionContext) fieldContext_Query_subGraphs(ctx context.Context, fi Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { switch field.Name { case "id": - return ec.fieldContext_SubGraph_id(ctx, field) - case "service": - return ec.fieldContext_SubGraph_service(ctx, field) - case "url": - return ec.fieldContext_SubGraph_url(ctx, field) - case "wsUrl": - return ec.fieldContext_SubGraph_wsUrl(ctx, field) - case "sdl": - return ec.fieldContext_SubGraph_sdl(ctx, field) - case "changedBy": - return ec.fieldContext_SubGraph_changedBy(ctx, field) - case "changedAt": - return ec.fieldContext_SubGraph_changedAt(ctx, field) + 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 SubGraph", field.Name) + 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_Query_subGraphs_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { - ec.Error(ctx, err) - return - } return fc, nil } @@ -647,10 +1532,14 @@ func (ec *executionContext) _Query_supergraph(ctx context.Context, field graphql return ec.resolvers.Query().Supergraph(rctx, fc.Args["ref"].(string), fc.Args["isAfter"].(*string)) } directive1 := func(ctx context.Context) (interface{}, error) { - if ec.directives.HasApiKey == nil { - return nil, errors.New("directive hasApiKey is not implemented") + organization, err := ec.unmarshalOBoolean2ᚖbool(ctx, true) + if err != nil { + return nil, err } - return ec.directives.HasApiKey(ctx, nil, directive0) + if ec.directives.Auth == nil { + return nil, errors.New("directive auth is not implemented") + } + return ec.directives.Auth(ctx, nil, directive0, nil, organization) } tmp, err := directive1(rctx) @@ -1371,6 +2260,50 @@ func (ec *executionContext) fieldContext_Unchanged_minDelaySeconds(ctx context.C return fc, nil } +func (ec *executionContext) _User_id(ctx context.Context, field graphql.CollectedField, obj *model.User) (ret graphql.Marshaler) { + fc, err := ec.fieldContext_User_id(ctx, field) + if err != nil { + return graphql.Null + } + ctx = graphql.WithFieldContext(ctx, fc) + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, 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.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + +func (ec *executionContext) fieldContext_User_id(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "User", + 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) ___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 { @@ -3144,6 +4077,66 @@ func (ec *executionContext) fieldContext___Type_specifiedByURL(ctx context.Conte // region **************************** input.gotpl ***************************** +func (ec *executionContext) unmarshalInputInputAPIKey(ctx context.Context, obj interface{}) (model.InputAPIKey, error) { + var it model.InputAPIKey + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"name", "organizationId", "refs", "read", "publish"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "name": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name")) + it.Name, err = ec.unmarshalNString2string(ctx, v) + if err != nil { + return it, err + } + case "organizationId": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("organizationId")) + it.OrganizationID, err = ec.unmarshalNID2string(ctx, v) + if err != nil { + return it, err + } + case "refs": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("refs")) + it.Refs, err = ec.unmarshalNString2ᚕstringᚄ(ctx, v) + if err != nil { + return it, err + } + case "read": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("read")) + it.Read, err = ec.unmarshalNBoolean2bool(ctx, v) + if err != nil { + return it, err + } + case "publish": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("publish")) + it.Publish, err = ec.unmarshalNBoolean2bool(ctx, v) + if err != nil { + return it, err + } + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputInputSubGraph(ctx context.Context, obj interface{}) (model.InputSubGraph, error) { var it model.InputSubGraph asMap := map[string]interface{}{} @@ -3235,6 +4228,73 @@ func (ec *executionContext) _Supergraph(ctx context.Context, sel ast.SelectionSe // region **************************** object.gotpl **************************** +var aPIKeyImplementors = []string{"APIKey"} + +func (ec *executionContext) _APIKey(ctx context.Context, sel ast.SelectionSet, obj *model.APIKey) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, aPIKeyImplementors) + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("APIKey") + case "id": + + out.Values[i] = ec._APIKey_id(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "name": + + out.Values[i] = ec._APIKey_name(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "key": + + out.Values[i] = ec._APIKey_key(ctx, field, obj) + + case "organization": + + out.Values[i] = ec._APIKey_organization(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "refs": + + out.Values[i] = ec._APIKey_refs(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "read": + + out.Values[i] = ec._APIKey_read(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "publish": + + out.Values[i] = ec._APIKey_publish(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var mutationImplementors = []string{"Mutation"} func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { @@ -3254,6 +4314,24 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("Mutation") + case "addOrganization": + + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_addOrganization(ctx, field) + }) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "addAPIKey": + + out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { + return ec._Mutation_addAPIKey(ctx, field) + }) + + if out.Values[i] == graphql.Null { + invalids++ + } case "updateSubGraph": out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) { @@ -3274,6 +4352,55 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) return out } +var organizationImplementors = []string{"Organization"} + +func (ec *executionContext) _Organization(ctx context.Context, sel ast.SelectionSet, obj *model.Organization) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, organizationImplementors) + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("Organization") + case "id": + + out.Values[i] = ec._Organization_id(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "name": + + out.Values[i] = ec._Organization_name(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "users": + + out.Values[i] = ec._Organization_users(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + case "apiKeys": + + out.Values[i] = ec._Organization_apiKeys(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var queryImplementors = []string{"Query"} func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler { @@ -3293,7 +4420,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr switch field.Name { case "__typename": out.Values[i] = graphql.MarshalString("Query") - case "subGraphs": + case "organizations": field := field innerFunc := func(ctx context.Context) (res graphql.Marshaler) { @@ -3302,7 +4429,7 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr ec.Error(ctx, ec.Recover(ctx, r)) } }() - res = ec._Query_subGraphs(ctx, field) + res = ec._Query_organizations(ctx, field) if res == graphql.Null { atomic.AddUint32(&invalids, 1) } @@ -3503,6 +4630,34 @@ func (ec *executionContext) _Unchanged(ctx context.Context, sel ast.SelectionSet return out } +var userImplementors = []string{"User"} + +func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj *model.User) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, userImplementors) + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("User") + case "id": + + out.Values[i] = ec._User_id(ctx, field, obj) + + if out.Values[i] == graphql.Null { + invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch() + if invalids > 0 { + return graphql.Null + } + return out +} + var __DirectiveImplementors = []string{"__Directive"} func (ec *executionContext) ___Directive(ctx context.Context, sel ast.SelectionSet, obj *introspection.Directive) graphql.Marshaler { @@ -3821,6 +4976,64 @@ func (ec *executionContext) ___Type(ctx context.Context, sel ast.SelectionSet, o // region ***************************** type.gotpl ***************************** +func (ec *executionContext) marshalNAPIKey2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey(ctx context.Context, sel ast.SelectionSet, v model.APIKey) graphql.Marshaler { + return ec._APIKey(ctx, sel, &v) +} + +func (ec *executionContext) marshalNAPIKey2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKeyᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.APIKey) 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) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNAPIKey2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey(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) marshalNAPIKey2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐAPIKey(ctx context.Context, sel ast.SelectionSet, v *model.APIKey) 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._APIKey(ctx, sel, v) +} + func (ec *executionContext) unmarshalNBoolean2bool(ctx context.Context, v interface{}) (bool, error) { res, err := graphql.UnmarshalBoolean(v) return res, graphql.ErrorOnPath(ctx, err) @@ -3871,6 +5084,64 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti return res } +func (ec *executionContext) marshalNOrganization2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization(ctx context.Context, sel ast.SelectionSet, v model.Organization) graphql.Marshaler { + return ec._Organization(ctx, sel, &v) +} + +func (ec *executionContext) marshalNOrganization2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganizationᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.Organization) 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) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNOrganization2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization(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) marshalNOrganization2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐOrganization(ctx context.Context, sel ast.SelectionSet, v *model.Organization) 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._Organization(ctx, sel, v) +} + func (ec *executionContext) unmarshalNString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) @@ -3886,6 +5157,38 @@ func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.S return res } +func (ec *executionContext) unmarshalNString2ᚕstringᚄ(ctx context.Context, v interface{}) ([]string, error) { + var vSlice []interface{} + if v != nil { + vSlice = graphql.CoerceList(v) + } + var err error + res := make([]string, len(vSlice)) + for i := range vSlice { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithIndex(i)) + res[i], err = ec.unmarshalNString2string(ctx, vSlice[i]) + if err != nil { + return nil, err + } + } + return res, nil +} + +func (ec *executionContext) marshalNString2ᚕstringᚄ(ctx context.Context, sel ast.SelectionSet, v []string) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + for i := range v { + ret[i] = ec.marshalNString2string(ctx, sel, v[i]) + } + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + func (ec *executionContext) marshalNSubGraph2gitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐSubGraph(ctx context.Context, sel ast.SelectionSet, v model.SubGraph) graphql.Marshaler { return ec._SubGraph(ctx, sel, &v) } @@ -3969,6 +5272,60 @@ func (ec *executionContext) marshalNTime2timeᚐTime(ctx context.Context, sel as return res } +func (ec *executionContext) marshalNUser2ᚕᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUserᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.User) 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) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNUser2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUser(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) marshalNUser2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐUser(ctx context.Context, sel ast.SelectionSet, v *model.User) 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._User(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) } @@ -4248,6 +5605,14 @@ func (ec *executionContext) marshalOBoolean2ᚖbool(ctx context.Context, sel ast return res } +func (ec *executionContext) unmarshalOInputAPIKey2ᚖgitlabᚗcomᚋunboundsoftwareᚋschemasᚋgraphᚋmodelᚐInputAPIKey(ctx context.Context, v interface{}) (*model.InputAPIKey, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputInputAPIKey(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOString2ᚖstring(ctx context.Context, v interface{}) (*string, error) { if v == nil { return nil, nil diff --git a/graph/model/models_gen.go b/graph/model/models_gen.go index 9a178d6..55718c1 100644 --- a/graph/model/models_gen.go +++ b/graph/model/models_gen.go @@ -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"` +} diff --git a/graph/resolver.go b/graph/resolver.go index 56de381..5c6c003 100644 --- a/graph/resolver.go +++ b/graph/resolver.go @@ -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) +} diff --git a/graph/schema.graphqls b/graph/schema.graphqls index a980dda..b58b244 100644 --- a/graph/schema.graphqls +++ b/graph/schema.graphqls @@ -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 diff --git a/graph/schema.resolvers.go b/graph/schema.resolvers.go index cce9d5d..fa6fca9 100644 --- a/graph/schema.resolvers.go +++ b/graph/schema.resolvers.go @@ -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, diff --git a/hash/hash.go b/hash/hash.go new file mode 100644 index 0000000..ad5ae73 --- /dev/null +++ b/hash/hash.go @@ -0,0 +1,11 @@ +package hash + +import ( + "crypto/sha256" + "encoding/base64" +) + +func String(s string) string { + encoded := sha256.New().Sum([]byte(s)) + return base64.StdEncoding.EncodeToString(encoded) +} diff --git a/middleware/apikey.go b/middleware/apikey.go index 5d5a810..567a821 100644 --- a/middleware/apikey.go +++ b/middleware/apikey.go @@ -4,26 +4,17 @@ import ( "context" "fmt" "net/http" - - "github.com/99designs/gqlgen/graphql" - "github.com/apex/log" ) -type ContextKey string - const ( ApiKey = ContextKey("apikey") ) -func NewApiKey(apiKey string, logger log.Interface) *ApiKeyMiddleware { - return &ApiKeyMiddleware{ - apiKey: apiKey, - } +func NewApiKey() *ApiKeyMiddleware { + return &ApiKeyMiddleware{} } -type ApiKeyMiddleware struct { - apiKey string -} +type ApiKeyMiddleware struct{} func (m *ApiKeyMiddleware) Handler(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -37,23 +28,12 @@ func (m *ApiKeyMiddleware) Handler(next http.Handler) http.Handler { }) } -func (m *ApiKeyMiddleware) Directive(ctx context.Context, obj interface{}, next graphql.Resolver) (res interface{}, err error) { - key, err := m.fromContext(ctx) - if err != nil { - return nil, err - } - if key != m.apiKey { - return nil, fmt.Errorf("invalid API-key") - } - return next(ctx) -} - -func (m *ApiKeyMiddleware) fromContext(ctx context.Context) (string, error) { +func ApiKeyFromContext(ctx context.Context) (string, error) { if value := ctx.Value(ApiKey); value != nil { if u, ok := value.(string); ok { return u, nil } return "", fmt.Errorf("current API-key is in wrong format") } - return "", fmt.Errorf("no API-key found") + return "", nil } diff --git a/middleware/auth.go b/middleware/auth.go new file mode 100644 index 0000000..cc2ebe3 --- /dev/null +++ b/middleware/auth.go @@ -0,0 +1,90 @@ +package middleware + +import ( + "context" + "fmt" + "net/http" + + "github.com/99designs/gqlgen/graphql" + "github.com/golang-jwt/jwt/v4" + + "gitlab.com/unboundsoftware/schemas/domain" + "gitlab.com/unboundsoftware/schemas/hash" +) + +const ( + UserKey = ContextKey("user") + OrganizationKey = ContextKey("organization") +) + +type Cache interface { + OrganizationByAPIKey(apiKey string) *domain.Organization +} + +func NewAuth(cache Cache) *AuthMiddleware { + return &AuthMiddleware{ + cache: cache, + } +} + +type AuthMiddleware struct { + cache Cache +} + +func (m *AuthMiddleware) Handler(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + token, err := TokenFromContext(r.Context()) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte("Invalid JWT token format")) + return + } + if token != nil { + ctx = context.WithValue(ctx, UserKey, token.Claims.(jwt.MapClaims)["sub"]) + } + apiKey, err := ApiKeyFromContext(r.Context()) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + _, _ = w.Write([]byte("Invalid API Key format")) + return + } + if organization := m.cache.OrganizationByAPIKey(hash.String(apiKey)); organization != nil { + ctx = context.WithValue(ctx, OrganizationKey, *organization) + } + + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} + +func UserFromContext(ctx context.Context) string { + if value := ctx.Value(UserKey); value != nil { + if u, ok := value.(string); ok { + return u + } + } + return "" +} + +func OrganizationFromContext(ctx context.Context) string { + if value := ctx.Value(OrganizationKey); value != nil { + if u, ok := value.(domain.Organization); ok { + return u.ID.String() + } + } + return "" +} + +func (m *AuthMiddleware) Directive(ctx context.Context, _ interface{}, next graphql.Resolver, user *bool, organization *bool) (res interface{}, err error) { + if user != nil && *user { + if u := UserFromContext(ctx); u == "" { + return nil, fmt.Errorf("no user available in request") + } + } + if organization != nil && *organization { + if orgId := OrganizationFromContext(ctx); orgId == "" { + return nil, fmt.Errorf("no organization available in request") + } + } + return next(ctx) +} diff --git a/middleware/auth0.go b/middleware/auth0.go new file mode 100644 index 0000000..e56ad94 --- /dev/null +++ b/middleware/auth0.go @@ -0,0 +1,189 @@ +package middleware + +import ( + "context" + "crypto/tls" + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" + + mw "github.com/auth0/go-jwt-middleware/v2" + "github.com/golang-jwt/jwt/v4" + "github.com/pkg/errors" +) + +type Auth0 struct { + domain string + audience string + client *http.Client + cache JwksCache +} + +func NewAuth0(audience, domain string, strictSsl bool) *Auth0 { + customTransport := http.DefaultTransport.(*http.Transport).Clone() + customTransport.TLSClientConfig = &tls.Config{InsecureSkipVerify: !strictSsl} + client := &http.Client{Transport: customTransport} + + return &Auth0{ + domain: domain, + audience: audience, + client: client, + cache: JwksCache{ + RWMutex: &sync.RWMutex{}, + cache: make(map[string]cacheItem), + }, + } +} + +type Response struct { + Message string `json:"message"` +} + +type Jwks struct { + Keys []JSONWebKeys `json:"keys"` +} + +type JSONWebKeys struct { + Kty string `json:"kty"` + Kid string `json:"kid"` + Use string `json:"use"` + N string `json:"n"` + E string `json:"e"` + X5c []string `json:"x5c"` +} + +func (a *Auth0) ValidationKeyGetter() func(token *jwt.Token) (interface{}, error) { + issuer := fmt.Sprintf("https://%s/", a.domain) + return func(token *jwt.Token) (interface{}, error) { + // Verify 'aud' claim + aud := a.audience + checkAud := token.Claims.(jwt.MapClaims).VerifyAudience(aud, false) + if !checkAud { + return token, errors.New("Invalid audience.") + } + // Verify 'iss' claim + iss := issuer + checkIss := token.Claims.(jwt.MapClaims).VerifyIssuer(iss, false) + if !checkIss { + return token, errors.New("Invalid issuer.") + } + + cert, err := a.getPemCert(token) + if err != nil { + panic(err.Error()) + } + + result, _ := jwt.ParseRSAPublicKeyFromPEM([]byte(cert)) + return result, nil + } +} + +func (a *Auth0) Middleware() *mw.JWTMiddleware { + jwtMiddleware := mw.New(func(ctx context.Context, token string) (interface{}, error) { + jwtToken, err := jwt.Parse(token, a.ValidationKeyGetter()) + if err != nil { + return nil, err + } + if _, ok := jwtToken.Method.(*jwt.SigningMethodRSA); !ok { + return nil, fmt.Errorf("unexpected signing method: %v", jwtToken.Header["alg"]) + } + err = jwtToken.Claims.Valid() + if err != nil { + return nil, err + } + return jwtToken, nil + }, + mw.WithTokenExtractor(func(r *http.Request) (string, error) { + token := r.Header.Get("Authorization") + if strings.HasPrefix(token, "Bearer ") { + return token[7:], nil + } + return "", nil + }), + mw.WithCredentialsOptional(true), + ) + + return jwtMiddleware +} + +func TokenFromContext(ctx context.Context) (*jwt.Token, error) { + if value := ctx.Value(mw.ContextKey{}); value != nil { + if u, ok := value.(*jwt.Token); ok { + return u, nil + } + return nil, fmt.Errorf("token is in wrong format") + } + return nil, nil +} + +func (a *Auth0) cacheGetWellknown(url string) (*Jwks, error) { + if value := a.cache.get(url); value != nil { + return value, nil + } + jwks := &Jwks{} + resp, err := a.client.Get(url) + if err != nil { + return jwks, err + } + defer func() { + _ = resp.Body.Close() + }() + err = json.NewDecoder(resp.Body).Decode(jwks) + if err == nil && jwks != nil { + a.cache.put(url, jwks) + } + return jwks, err +} + +func (a *Auth0) getPemCert(token *jwt.Token) (string, error) { + jwks, err := a.cacheGetWellknown(fmt.Sprintf("https://%s/.well-known/jwks.json", a.domain)) + if err != nil { + return "", err + } + var cert string + for k := range jwks.Keys { + if token.Header["kid"] == jwks.Keys[k].Kid { + cert = "-----BEGIN CERTIFICATE-----\n" + jwks.Keys[k].X5c[0] + "\n-----END CERTIFICATE-----" + } + } + + if cert == "" { + err := errors.New("Unable to find appropriate key.") + return cert, err + } + + return cert, nil +} + +type JwksCache struct { + *sync.RWMutex + cache map[string]cacheItem +} +type cacheItem struct { + data *Jwks + expiration time.Time +} + +func (c *JwksCache) get(url string) *Jwks { + c.RLock() + defer c.RUnlock() + if value, ok := c.cache[url]; ok { + if time.Now().After(value.expiration) { + return nil + } + return value.data + } + return nil +} + +func (c *JwksCache) put(url string, jwks *Jwks) { + c.Lock() + defer c.Unlock() + c.cache[url] = cacheItem{ + data: jwks, + expiration: time.Now().Add(time.Minute * 60), + } +} diff --git a/middleware/context.go b/middleware/context.go new file mode 100644 index 0000000..58e4e76 --- /dev/null +++ b/middleware/context.go @@ -0,0 +1,3 @@ +package middleware + +type ContextKey string diff --git a/rand/string.go b/rand/string.go new file mode 100644 index 0000000..7f4acd2 --- /dev/null +++ b/rand/string.go @@ -0,0 +1,24 @@ +package rand + +import ( + "math/rand" + "time" +) + +const charset = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + +var seededRand *rand.Rand = rand.New( + rand.NewSource(time.Now().UnixNano())) + +func StringWithCharset(length int, charset string) string { + b := make([]byte, length) + for i := range b { + b[i] = charset[seededRand.Intn(len(charset))] + } + return string(b) +} + +func String(length int) string { + return StringWithCharset(length, charset) +} diff --git a/store/event_store_migrations/20230426145300_add_organization.sql b/store/event_store_migrations/20230426145300_add_organization.sql new file mode 100644 index 0000000..05bc0a9 --- /dev/null +++ b/store/event_store_migrations/20230426145300_add_organization.sql @@ -0,0 +1,52 @@ +-- +goose Up +-- Add Unbound Software Development organization +insert into aggregates (id, name) +values ('d46ffcb0-19e8-4769-8697-590326ef7b51', 'domain.Organization'); + +insert into events (name, aggregate_id, sequence_no, payload, tstamp, aggregate_name) +values ('domain.OrganizationAdded', 'd46ffcb0-19e8-4769-8697-590326ef7b51', 1, '{"id":"d46ffcb0-19e8-4769-8697-590326ef7b51","time":"2023-04-26T14:46:04.43462+02:00","name":"Unbound Software Development","initiator":"google-oauth2|101953650269257914934"}', '2023-04-26T14:46:04.43462+02:00', 'domain.Organization'); + +-- Add API keys +insert into events (name, aggregate_id, sequence_no, payload, tstamp, aggregate_name) +values ('domain.APIKeyAdded', 'd46ffcb0-19e8-4769-8697-590326ef7b51', 2, + '{"id":"d46ffcb0-19e8-4769-8697-590326ef7b51","time":"2023-04-26T15:46:54.181929+02:00","organizationId":"","name":"CI","key":"dXNfYWtfeUl2R3RRQUJQTmJzVEFrUeOwxEKY/BwUmvv0yJlvuSQnrkHkZJuTTKSVmRt4UrhV","refs":["Shiny@staging","Shiny@prod"],"read":false,"publish":true,"initiator":"google-oauth2|101953650269257914934"}', + '2023-04-26 15:46:54.181929 +02:00', 'domain.Organization'); + +insert into events (name, aggregate_id, sequence_no, payload, tstamp, aggregate_name) +values ('domain.APIKeyAdded', 'd46ffcb0-19e8-4769-8697-590326ef7b51', 3, + '{"id":"d46ffcb0-19e8-4769-8697-590326ef7b51","time":"2023-04-26T15:52:55.955203+02:00","organizationId":"","name":"Gateway","key":"dXNfYWtfdnkzSkRseDNlSDNjcnZzOeOwxEKY/BwUmvv0yJlvuSQnrkHkZJuTTKSVmRt4UrhV","refs":["Shiny@staging","Shiny@prod"],"read":true,"publish":false,"initiator":"google-oauth2|101953650269257914934"}', + '2023-04-26 15:52:55.955203 +02:00', 'domain.Organization'); + +insert into events (name, aggregate_id, sequence_no, payload, tstamp, aggregate_name) +values ('domain.APIKeyAdded', 'd46ffcb0-19e8-4769-8697-590326ef7b51', 4, + '{"id":"d46ffcb0-19e8-4769-8697-590326ef7b51","time":"2023-04-26T16:30:00.0011+02:00","organizationId":"","name":"Local dev","key":"dXNfYWtfM0kzaGZndmVaQllyQzdjVOOwxEKY/BwUmvv0yJlvuSQnrkHkZJuTTKSVmRt4UrhV","refs":["Shiny@dev"],"read":true,"publish":true,"initiator":"google-oauth2|101953650269257914934"}', + '2023-04-26 16:30:00.001100 +02:00', 'domain.Organization'); + +insert into events (name, aggregate_id, sequence_no, payload, tstamp, aggregate_name) +values ('domain.APIKeyAdded', 'd46ffcb0-19e8-4769-8697-590326ef7b51', 5, + '{"id":"d46ffcb0-19e8-4769-8697-590326ef7b51","time":"2023-04-27T07:43:26.599544+02:00","organizationId":"","name":"Acctest","key":"dXNfYWtfdlVqMzdBMXVraklmaGtKSOOwxEKY/BwUmvv0yJlvuSQnrkHkZJuTTKSVmRt4UrhV","refs":["Shiny@test"],"read":true,"publish":true,"initiator":"google-oauth2|101953650269257914934"}', + '2023-04-27 07:43:26.599544 +02:00', 'domain.Organization'); + +-- Update events since json-tags were added +UPDATE events e +SET payload = jsonb_build_object( + 'id', payload::jsonb ->> 'id', + 'time', payload::jsonb ->> 'time', + 'ref', payload::jsonb ->> 'Ref', + 'sdl', payload::jsonb ->> 'Sdl', + 'url', payload::jsonb ->> 'Url', + 'wsUrl', payload::jsonb ->> 'WSUrl', + 'service', payload::jsonb ->> 'Service', + 'initiator', 'CI' + ) +WHERE e.name = 'domain.SubGraphUpdated'; + +-- Add organization id to all existing subgraphs +update events e +set payload = jsonb_set(payload::jsonb, '{organizationId}', '"d46ffcb0-19e8-4769-8697-590326ef7b51"', true) +where e.name = 'domain.SubGraphUpdated'; + +DELETE +from snapshots; + +-- +goose Down diff --git a/store/store.go b/store/store.go index 637b1f5..4f3d54f 100644 --- a/store/store.go +++ b/store/store.go @@ -1,7 +1,10 @@ package store import ( + "embed" + "github.com/jmoiron/sqlx" + "github.com/pressly/goose/v3" ) func SetupDB(driverName, url string) (*sqlx.DB, error) { @@ -25,3 +28,13 @@ func SetupDB(driverName, url string) (*sqlx.DB, error) { // // return goose.Up(db.DB, "migrations") //} + +//go:embed event_store_migrations/*.sql +var embedEventStoreMigrations embed.FS + +func RunEventStoreMigrations(db *sqlx.DB) error { + goose.SetTableName("goose_db_version_event") + goose.SetBaseFS(embedEventStoreMigrations) + + return goose.Up(db.DB, "event_store_migrations") +}