feat: organizations and API keys
This commit is contained in:
@@ -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
|
||||
}
|
||||
+1439
-74
File diff suppressed because it is too large
Load Diff
@@ -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"`
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package graph
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/apex/log"
|
||||
"gitlab.com/unboundsoftware/eventsourced/eventsourced"
|
||||
@@ -29,3 +30,7 @@ type Resolver struct {
|
||||
func (r *Resolver) handler(ctx context.Context, aggregate eventsourced.Aggregate) (eventsourced.CommandHandler, error) {
|
||||
return eventsourced.NewHandler(ctx, aggregate, r.EventStore, eventsourced.WithEventPublisher(r.Publisher))
|
||||
}
|
||||
|
||||
func apiKeyId(orgId, name string) string {
|
||||
return fmt.Sprintf("%s-%s", orgId, name)
|
||||
}
|
||||
|
||||
+35
-4
@@ -1,10 +1,33 @@
|
||||
type Query {
|
||||
subGraphs(ref: String!): [SubGraph!]! @hasApiKey @deprecated(reason: "Use supergraph instead")
|
||||
supergraph(ref: String!, isAfter: String): Supergraph! @hasApiKey
|
||||
organizations: [Organization!]! @auth(user: true)
|
||||
supergraph(ref: String!, isAfter: String): Supergraph! @auth(organization: true)
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
updateSubGraph(input: InputSubGraph!): SubGraph! @hasApiKey
|
||||
addOrganization(name: String!): Organization! @auth(user: true)
|
||||
addAPIKey(input: InputAPIKey): APIKey! @auth(user: true)
|
||||
updateSubGraph(input: InputSubGraph!): SubGraph! @auth(organization: true)
|
||||
}
|
||||
|
||||
type Organization {
|
||||
id: ID!
|
||||
name: String!
|
||||
users: [User!]!
|
||||
apiKeys: [APIKey!]!
|
||||
}
|
||||
|
||||
type User {
|
||||
id: String!
|
||||
}
|
||||
|
||||
type APIKey {
|
||||
id: ID!
|
||||
name: String!
|
||||
key: String
|
||||
organization: Organization!
|
||||
refs: [String!]!
|
||||
read: Boolean!
|
||||
publish: Boolean!
|
||||
}
|
||||
|
||||
union Supergraph = Unchanged | SubGraphs
|
||||
@@ -30,6 +53,14 @@ type SubGraph {
|
||||
changedAt: Time!
|
||||
}
|
||||
|
||||
input InputAPIKey {
|
||||
name: String!
|
||||
organizationId: ID!
|
||||
refs: [String!]!
|
||||
read: Boolean!
|
||||
publish: Boolean!
|
||||
}
|
||||
|
||||
input InputSubGraph {
|
||||
ref: String!
|
||||
service: String!
|
||||
@@ -40,4 +71,4 @@ input InputSubGraph {
|
||||
|
||||
scalar Time
|
||||
|
||||
directive @hasApiKey on FIELD_DEFINITION
|
||||
directive @auth(user: Boolean, organization: Boolean) on FIELD_DEFINITION
|
||||
|
||||
+76
-19
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user