graphql: expose allIdentities, identities and userIdentity in the repo

Michael Muré created

Change summary

Gopkg.lock                                              |   6 
graphql/connections/connection_template.go              |  13 
graphql/connections/connections.go                      |   9 
graphql/connections/gen_bug.go                          |  10 
graphql/connections/gen_comment.go                      |  10 
graphql/connections/gen_identity.go                     | 110 +
graphql/connections/gen_operation.go                    |  10 
graphql/connections/gen_timeline.go                     |  10 
graphql/connections/lazy_identity.go                    |  12 
graphql/graph/gen_graph.go                              | 826 +++++++++-
graphql/models/edges.go                                 |   5 
graphql/models/gen_models.go                            |  13 
graphql/resolvers/bug.go                                |   9 
graphql/resolvers/identity.go                           |   7 
graphql/resolvers/mutation.go                           |   3 
graphql/resolvers/query.go                              |   3 
graphql/resolvers/repo.go                               |  81 +
graphql/resolvers/root.go                               |   2 
graphql/schema/bug.graphql                              |  17 
graphql/schema/identity.graphql                         |  14 
graphql/schema/repository.graphql                       |  35 
graphql/schema/root.graphql                             |  22 
graphql/schema/types.graphql                            |  21 
vendor/github.com/99designs/gqlgen/graphql/version.go   |   2 
vendor/github.com/99designs/gqlgen/handler/graphql.go   |  28 
vendor/github.com/99designs/gqlgen/handler/websocket.go |  42 
26 files changed, 1,145 insertions(+), 175 deletions(-)

Detailed changes

Gopkg.lock 🔗

@@ -2,7 +2,7 @@
 
 
 [[projects]]
-  digest = "1:cc0bbbf01f9f639555f9a9d4d5ff9c72da4c4c4bfe71f02fd318d37498270d80"
+  digest = "1:e8f6639eaa399c8595b9a2dee175514a9f3842888dc080e2776360dc604150dc"
   name = "github.com/99designs/gqlgen"
   packages = [
     "cmd",
@@ -16,8 +16,8 @@
     "internal/imports",
   ]
   pruneopts = "UT"
-  revision = "3a7f37c7e22a8fedce430c4d340ad5c1351198f4"
-  version = "v0.7.1"
+  revision = "da1e07f5876c0fb79cbad19006f7135be08590d6"
+  version = "v0.7.2"
 
 [[projects]]
   branch = "master"

graphql/connections/connection_template.go 🔗

@@ -7,6 +7,9 @@ import (
 	"github.com/cheekybits/genny/generic"
 )
 
+// Name define the name of the connection
+type Name generic.Type
+
 // NodeType define the node type handled by this relay connection
 type NodeType generic.Type
 
@@ -18,17 +21,17 @@ type ConnectionType generic.Type
 
 // NodeTypeEdgeMaker define a function that take a NodeType and an offset and
 // create an Edge.
-type NodeTypeEdgeMaker func(value NodeType, offset int) Edge
+type NameEdgeMaker func(value NodeType, offset int) Edge
 
-// NodeTypeConMaker define a function that create a ConnectionType
-type NodeTypeConMaker func(
+// NameConMaker define a function that create a ConnectionType
+type NameConMaker func(
 	edges []EdgeType,
 	nodes []NodeType,
 	info models.PageInfo,
 	totalCount int) (ConnectionType, error)
 
-// NodeTypeCon will paginate a source according to the input of a relay connection
-func NodeTypeCon(source []NodeType, edgeMaker NodeTypeEdgeMaker, conMaker NodeTypeConMaker, input models.ConnectionInput) (ConnectionType, error) {
+// NameCon will paginate a source according to the input of a relay connection
+func NameCon(source []NodeType, edgeMaker NameEdgeMaker, conMaker NameConMaker, input models.ConnectionInput) (ConnectionType, error) {
 	var nodes []NodeType
 	var edges []EdgeType
 	var cursors []string

graphql/connections/connections.go 🔗

@@ -1,7 +1,8 @@
-//go:generate genny -in=connection_template.go -out=gen_bug.go gen "NodeType=string EdgeType=LazyBugEdge ConnectionType=models.BugConnection"
-//go:generate genny -in=connection_template.go -out=gen_operation.go gen "NodeType=bug.Operation EdgeType=models.OperationEdge ConnectionType=models.OperationConnection"
-//go:generate genny -in=connection_template.go -out=gen_comment.go gen "NodeType=bug.Comment EdgeType=models.CommentEdge ConnectionType=models.CommentConnection"
-//go:generate genny -in=connection_template.go -out=gen_timeline.go gen "NodeType=bug.TimelineItem EdgeType=models.TimelineItemEdge ConnectionType=models.TimelineItemConnection"
+//go:generate genny -in=connection_template.go -out=gen_bug.go gen "Name=LazyBug NodeType=string EdgeType=LazyBugEdge ConnectionType=models.BugConnection"
+//go:generate genny -in=connection_template.go -out=gen_identity.go gen "Name=LazyIdentity NodeType=string EdgeType=LazyIdentityEdge ConnectionType=models.IdentityConnection"
+//go:generate genny -in=connection_template.go -out=gen_operation.go gen "Name=Operation NodeType=bug.Operation EdgeType=models.OperationEdge ConnectionType=models.OperationConnection"
+//go:generate genny -in=connection_template.go -out=gen_comment.go gen "Name=Comment NodeType=bug.Comment EdgeType=models.CommentEdge ConnectionType=models.CommentConnection"
+//go:generate genny -in=connection_template.go -out=gen_timeline.go gen "Name=TimelineItem NodeType=bug.TimelineItem EdgeType=models.TimelineItemEdge ConnectionType=models.TimelineItemConnection"
 
 // Package connections implement a generic GraphQL relay connection
 package connections

graphql/connections/gen_bug.go 🔗

@@ -12,17 +12,17 @@ import (
 
 // StringEdgeMaker define a function that take a string and an offset and
 // create an Edge.
-type StringEdgeMaker func(value string, offset int) Edge
+type LazyBugEdgeMaker func(value string, offset int) Edge
 
-// StringConMaker define a function that create a models.BugConnection
-type StringConMaker func(
+// LazyBugConMaker define a function that create a models.BugConnection
+type LazyBugConMaker func(
 	edges []LazyBugEdge,
 	nodes []string,
 	info models.PageInfo,
 	totalCount int) (models.BugConnection, error)
 
-// StringCon will paginate a source according to the input of a relay connection
-func StringCon(source []string, edgeMaker StringEdgeMaker, conMaker StringConMaker, input models.ConnectionInput) (models.BugConnection, error) {
+// LazyBugCon will paginate a source according to the input of a relay connection
+func LazyBugCon(source []string, edgeMaker LazyBugEdgeMaker, conMaker LazyBugConMaker, input models.ConnectionInput) (models.BugConnection, error) {
 	var nodes []string
 	var edges []LazyBugEdge
 	var cursors []string

graphql/connections/gen_comment.go 🔗

@@ -13,17 +13,17 @@ import (
 
 // BugCommentEdgeMaker define a function that take a bug.Comment and an offset and
 // create an Edge.
-type BugCommentEdgeMaker func(value bug.Comment, offset int) Edge
+type CommentEdgeMaker func(value bug.Comment, offset int) Edge
 
-// BugCommentConMaker define a function that create a models.CommentConnection
-type BugCommentConMaker func(
+// CommentConMaker define a function that create a models.CommentConnection
+type CommentConMaker func(
 	edges []models.CommentEdge,
 	nodes []bug.Comment,
 	info models.PageInfo,
 	totalCount int) (models.CommentConnection, error)
 
-// BugCommentCon will paginate a source according to the input of a relay connection
-func BugCommentCon(source []bug.Comment, edgeMaker BugCommentEdgeMaker, conMaker BugCommentConMaker, input models.ConnectionInput) (models.CommentConnection, error) {
+// CommentCon will paginate a source according to the input of a relay connection
+func CommentCon(source []bug.Comment, edgeMaker CommentEdgeMaker, conMaker CommentConMaker, input models.ConnectionInput) (models.CommentConnection, error) {
 	var nodes []bug.Comment
 	var edges []models.CommentEdge
 	var cursors []string

graphql/connections/gen_identity.go 🔗

@@ -0,0 +1,110 @@
+// This file was automatically generated by genny.
+// Any changes will be lost if this file is regenerated.
+// see https://github.com/cheekybits/genny
+
+package connections
+
+import (
+	"fmt"
+
+	"github.com/MichaelMure/git-bug/graphql/models"
+)
+
+// StringEdgeMaker define a function that take a string and an offset and
+// create an Edge.
+type LazyIdentityEdgeMaker func(value string, offset int) Edge
+
+// LazyIdentityConMaker define a function that create a models.IdentityConnection
+type LazyIdentityConMaker func(
+	edges []LazyIdentityEdge,
+	nodes []string,
+	info models.PageInfo,
+	totalCount int) (models.IdentityConnection, error)
+
+// LazyIdentityCon will paginate a source according to the input of a relay connection
+func LazyIdentityCon(source []string, edgeMaker LazyIdentityEdgeMaker, conMaker LazyIdentityConMaker, input models.ConnectionInput) (models.IdentityConnection, error) {
+	var nodes []string
+	var edges []LazyIdentityEdge
+	var cursors []string
+	var pageInfo models.PageInfo
+	var totalCount = len(source)
+
+	emptyCon, _ := conMaker(edges, nodes, pageInfo, 0)
+
+	offset := 0
+
+	if input.After != nil {
+		for i, value := range source {
+			edge := edgeMaker(value, i)
+			if edge.GetCursor() == *input.After {
+				// remove all previous element including the "after" one
+				source = source[i+1:]
+				offset = i + 1
+				pageInfo.HasPreviousPage = true
+				break
+			}
+		}
+	}
+
+	if input.Before != nil {
+		for i, value := range source {
+			edge := edgeMaker(value, i+offset)
+
+			if edge.GetCursor() == *input.Before {
+				// remove all after element including the "before" one
+				pageInfo.HasNextPage = true
+				break
+			}
+
+			edges = append(edges, edge.(LazyIdentityEdge))
+			cursors = append(cursors, edge.GetCursor())
+			nodes = append(nodes, value)
+		}
+	} else {
+		edges = make([]LazyIdentityEdge, len(source))
+		cursors = make([]string, len(source))
+		nodes = source
+
+		for i, value := range source {
+			edge := edgeMaker(value, i+offset)
+			edges[i] = edge.(LazyIdentityEdge)
+			cursors[i] = edge.GetCursor()
+		}
+	}
+
+	if input.First != nil {
+		if *input.First < 0 {
+			return emptyCon, fmt.Errorf("first less than zero")
+		}
+
+		if len(edges) > *input.First {
+			// Slice result to be of length first by removing edges from the end
+			edges = edges[:*input.First]
+			cursors = cursors[:*input.First]
+			nodes = nodes[:*input.First]
+			pageInfo.HasNextPage = true
+		}
+	}
+
+	if input.Last != nil {
+		if *input.Last < 0 {
+			return emptyCon, fmt.Errorf("last less than zero")
+		}
+
+		if len(edges) > *input.Last {
+			// Slice result to be of length last by removing edges from the start
+			edges = edges[len(edges)-*input.Last:]
+			cursors = cursors[len(cursors)-*input.Last:]
+			nodes = nodes[len(nodes)-*input.Last:]
+			pageInfo.HasPreviousPage = true
+		}
+	}
+
+	// Fill up pageInfo cursors
+	if len(cursors) > 0 {
+		pageInfo.StartCursor = cursors[0]
+		pageInfo.EndCursor = cursors[len(cursors)-1]
+	}
+
+	return conMaker(edges, nodes, pageInfo, totalCount)
+}

graphql/connections/gen_operation.go 🔗

@@ -13,17 +13,17 @@ import (
 
 // BugOperationEdgeMaker define a function that take a bug.Operation and an offset and
 // create an Edge.
-type BugOperationEdgeMaker func(value bug.Operation, offset int) Edge
+type OperationEdgeMaker func(value bug.Operation, offset int) Edge
 
-// BugOperationConMaker define a function that create a models.OperationConnection
-type BugOperationConMaker func(
+// OperationConMaker define a function that create a models.OperationConnection
+type OperationConMaker func(
 	edges []models.OperationEdge,
 	nodes []bug.Operation,
 	info models.PageInfo,
 	totalCount int) (models.OperationConnection, error)
 
-// BugOperationCon will paginate a source according to the input of a relay connection
-func BugOperationCon(source []bug.Operation, edgeMaker BugOperationEdgeMaker, conMaker BugOperationConMaker, input models.ConnectionInput) (models.OperationConnection, error) {
+// OperationCon will paginate a source according to the input of a relay connection
+func OperationCon(source []bug.Operation, edgeMaker OperationEdgeMaker, conMaker OperationConMaker, input models.ConnectionInput) (models.OperationConnection, error) {
 	var nodes []bug.Operation
 	var edges []models.OperationEdge
 	var cursors []string

graphql/connections/gen_timeline.go 🔗

@@ -13,17 +13,17 @@ import (
 
 // BugTimelineItemEdgeMaker define a function that take a bug.TimelineItem and an offset and
 // create an Edge.
-type BugTimelineItemEdgeMaker func(value bug.TimelineItem, offset int) Edge
+type TimelineItemEdgeMaker func(value bug.TimelineItem, offset int) Edge
 
-// BugTimelineItemConMaker define a function that create a models.TimelineItemConnection
-type BugTimelineItemConMaker func(
+// TimelineItemConMaker define a function that create a models.TimelineItemConnection
+type TimelineItemConMaker func(
 	edges []models.TimelineItemEdge,
 	nodes []bug.TimelineItem,
 	info models.PageInfo,
 	totalCount int) (models.TimelineItemConnection, error)
 
-// BugTimelineItemCon will paginate a source according to the input of a relay connection
-func BugTimelineItemCon(source []bug.TimelineItem, edgeMaker BugTimelineItemEdgeMaker, conMaker BugTimelineItemConMaker, input models.ConnectionInput) (models.TimelineItemConnection, error) {
+// TimelineItemCon will paginate a source according to the input of a relay connection
+func TimelineItemCon(source []bug.TimelineItem, edgeMaker TimelineItemEdgeMaker, conMaker TimelineItemConMaker, input models.ConnectionInput) (models.TimelineItemConnection, error) {
 	var nodes []bug.TimelineItem
 	var edges []models.TimelineItemEdge
 	var cursors []string

graphql/connections/lazy_identity.go 🔗

@@ -0,0 +1,12 @@
+package connections
+
+// LazyIdentityEdge is a special relay edge used to implement a lazy loading connection
+type LazyIdentityEdge struct {
+	Id     string
+	Cursor string
+}
+
+// GetCursor return the cursor of a LazyIdentityEdge
+func (lbe LazyIdentityEdge) GetCursor() string {
+	return lbe.Cursor
+}

graphql/graph/gen_graph.go 🔗

@@ -161,6 +161,7 @@ type ComplexityRoot struct {
 
 	Identity struct {
 		Id          func(childComplexity int) int
+		HumanId     func(childComplexity int) int
 		Name        func(childComplexity int) int
 		Email       func(childComplexity int) int
 		Login       func(childComplexity int) int
@@ -169,6 +170,18 @@ type ComplexityRoot struct {
 		IsProtected func(childComplexity int) int
 	}
 
+	IdentityConnection struct {
+		Edges      func(childComplexity int) int
+		Nodes      func(childComplexity int) int
+		PageInfo   func(childComplexity int) int
+		TotalCount func(childComplexity int) int
+	}
+
+	IdentityEdge struct {
+		Cursor func(childComplexity int) int
+		Node   func(childComplexity int) int
+	}
+
 	LabelChangeOperation struct {
 		Hash    func(childComplexity int) int
 		Author  func(childComplexity int) int
@@ -220,8 +233,11 @@ type ComplexityRoot struct {
 	}
 
 	Repository struct {
-		AllBugs func(childComplexity int, after *string, before *string, first *int, last *int, query *string) int
-		Bug     func(childComplexity int, prefix string) int
+		AllBugs       func(childComplexity int, after *string, before *string, first *int, last *int, query *string) int
+		Bug           func(childComplexity int, prefix string) int
+		AllIdentities func(childComplexity int, after *string, before *string, first *int, last *int) int
+		Identity      func(childComplexity int, prefix string) int
+		UserIdentity  func(childComplexity int) int
 	}
 
 	SetStatusOperation struct {
@@ -297,6 +313,7 @@ type EditCommentOperationResolver interface {
 }
 type IdentityResolver interface {
 	ID(ctx context.Context, obj *identity.Interface) (string, error)
+	HumanID(ctx context.Context, obj *identity.Interface) (string, error)
 	Name(ctx context.Context, obj *identity.Interface) (*string, error)
 	Email(ctx context.Context, obj *identity.Interface) (*string, error)
 	Login(ctx context.Context, obj *identity.Interface) (*string, error)
@@ -326,6 +343,9 @@ type QueryResolver interface {
 type RepositoryResolver interface {
 	AllBugs(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, query *string) (models.BugConnection, error)
 	Bug(ctx context.Context, obj *models.Repository, prefix string) (*bug.Snapshot, error)
+	AllIdentities(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (models.IdentityConnection, error)
+	Identity(ctx context.Context, obj *models.Repository, prefix string) (*identity.Interface, error)
+	UserIdentity(ctx context.Context, obj *models.Repository) (*identity.Interface, error)
 }
 type SetStatusOperationResolver interface {
 	Date(ctx context.Context, obj *bug.SetStatusOperation) (time.Time, error)
@@ -959,6 +979,83 @@ func field_Repository_bug_args(rawArgs map[string]interface{}) (map[string]inter
 
 }
 
+func field_Repository_allIdentities_args(rawArgs map[string]interface{}) (map[string]interface{}, error) {
+	args := map[string]interface{}{}
+	var arg0 *string
+	if tmp, ok := rawArgs["after"]; ok {
+		var err error
+		var ptr1 string
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalString(tmp)
+			arg0 = &ptr1
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["after"] = arg0
+	var arg1 *string
+	if tmp, ok := rawArgs["before"]; ok {
+		var err error
+		var ptr1 string
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalString(tmp)
+			arg1 = &ptr1
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["before"] = arg1
+	var arg2 *int
+	if tmp, ok := rawArgs["first"]; ok {
+		var err error
+		var ptr1 int
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalInt(tmp)
+			arg2 = &ptr1
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["first"] = arg2
+	var arg3 *int
+	if tmp, ok := rawArgs["last"]; ok {
+		var err error
+		var ptr1 int
+		if tmp != nil {
+			ptr1, err = graphql.UnmarshalInt(tmp)
+			arg3 = &ptr1
+		}
+
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["last"] = arg3
+	return args, nil
+
+}
+
+func field_Repository_identity_args(rawArgs map[string]interface{}) (map[string]interface{}, error) {
+	args := map[string]interface{}{}
+	var arg0 string
+	if tmp, ok := rawArgs["prefix"]; ok {
+		var err error
+		arg0, err = graphql.UnmarshalString(tmp)
+		if err != nil {
+			return nil, err
+		}
+	}
+	args["prefix"] = arg0
+	return args, nil
+
+}
+
 func field___Type_fields_args(rawArgs map[string]interface{}) (map[string]interface{}, error) {
 	args := map[string]interface{}{}
 	var arg0 bool
@@ -1465,6 +1562,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.Identity.Id(childComplexity), true
 
+	case "Identity.humanId":
+		if e.complexity.Identity.HumanId == nil {
+			break
+		}
+
+		return e.complexity.Identity.HumanId(childComplexity), true
+
 	case "Identity.name":
 		if e.complexity.Identity.Name == nil {
 			break
@@ -1507,6 +1611,48 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.Identity.IsProtected(childComplexity), true
 
+	case "IdentityConnection.edges":
+		if e.complexity.IdentityConnection.Edges == nil {
+			break
+		}
+
+		return e.complexity.IdentityConnection.Edges(childComplexity), true
+
+	case "IdentityConnection.nodes":
+		if e.complexity.IdentityConnection.Nodes == nil {
+			break
+		}
+
+		return e.complexity.IdentityConnection.Nodes(childComplexity), true
+
+	case "IdentityConnection.pageInfo":
+		if e.complexity.IdentityConnection.PageInfo == nil {
+			break
+		}
+
+		return e.complexity.IdentityConnection.PageInfo(childComplexity), true
+
+	case "IdentityConnection.totalCount":
+		if e.complexity.IdentityConnection.TotalCount == nil {
+			break
+		}
+
+		return e.complexity.IdentityConnection.TotalCount(childComplexity), true
+
+	case "IdentityEdge.cursor":
+		if e.complexity.IdentityEdge.Cursor == nil {
+			break
+		}
+
+		return e.complexity.IdentityEdge.Cursor(childComplexity), true
+
+	case "IdentityEdge.node":
+		if e.complexity.IdentityEdge.Node == nil {
+			break
+		}
+
+		return e.complexity.IdentityEdge.Node(childComplexity), true
+
 	case "LabelChangeOperation.hash":
 		if e.complexity.LabelChangeOperation.Hash == nil {
 			break
@@ -1774,6 +1920,37 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
 
 		return e.complexity.Repository.Bug(childComplexity, args["prefix"].(string)), true
 
+	case "Repository.allIdentities":
+		if e.complexity.Repository.AllIdentities == nil {
+			break
+		}
+
+		args, err := field_Repository_allIdentities_args(rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Repository.AllIdentities(childComplexity, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int)), true
+
+	case "Repository.identity":
+		if e.complexity.Repository.Identity == nil {
+			break
+		}
+
+		args, err := field_Repository_identity_args(rawArgs)
+		if err != nil {
+			return 0, false
+		}
+
+		return e.complexity.Repository.Identity(childComplexity, args["prefix"].(string)), true
+
+	case "Repository.userIdentity":
+		if e.complexity.Repository.UserIdentity == nil {
+			break
+		}
+
+		return e.complexity.Repository.UserIdentity(childComplexity), true
+
 	case "SetStatusOperation.hash":
 		if e.complexity.SetStatusOperation.Hash == nil {
 			break
@@ -4680,6 +4857,15 @@ func (ec *executionContext) _Identity(ctx context.Context, sel ast.SelectionSet,
 				}
 				wg.Done()
 			}(i, field)
+		case "humanId":
+			wg.Add(1)
+			go func(i int, field graphql.CollectedField) {
+				out.Values[i] = ec._Identity_humanId(ctx, field, obj)
+				if out.Values[i] == graphql.Null {
+					invalid = true
+				}
+				wg.Done()
+			}(i, field)
 		case "name":
 			wg.Add(1)
 			go func(i int, field graphql.CollectedField) {
@@ -4760,6 +4946,33 @@ func (ec *executionContext) _Identity_id(ctx context.Context, field graphql.Coll
 	return graphql.MarshalString(res)
 }
 
+// nolint: vetshadow
+func (ec *executionContext) _Identity_humanId(ctx context.Context, field graphql.CollectedField, obj *identity.Interface) graphql.Marshaler {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rctx := &graphql.ResolverContext{
+		Object: "Identity",
+		Args:   nil,
+		Field:  field,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Identity().HumanID(rctx, obj)
+	})
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return graphql.MarshalString(res)
+}
+
 // nolint: vetshadow
 func (ec *executionContext) _Identity_name(ctx context.Context, field graphql.CollectedField, obj *identity.Interface) graphql.Marshaler {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
@@ -4926,13 +5139,12 @@ func (ec *executionContext) _Identity_isProtected(ctx context.Context, field gra
 	return graphql.MarshalBoolean(res)
 }
 
-var labelChangeOperationImplementors = []string{"LabelChangeOperation", "Operation", "Authored"}
+var identityConnectionImplementors = []string{"IdentityConnection"}
 
 // nolint: gocyclo, errcheck, gas, goconst
-func (ec *executionContext) _LabelChangeOperation(ctx context.Context, sel ast.SelectionSet, obj *bug.LabelChangeOperation) graphql.Marshaler {
-	fields := graphql.CollectFields(ctx, sel, labelChangeOperationImplementors)
+func (ec *executionContext) _IdentityConnection(ctx context.Context, sel ast.SelectionSet, obj *models.IdentityConnection) graphql.Marshaler {
+	fields := graphql.CollectFields(ctx, sel, identityConnectionImplementors)
 
-	var wg sync.WaitGroup
 	out := graphql.NewOrderedMap(len(fields))
 	invalid := false
 	for i, field := range fields {
@@ -4940,33 +5152,24 @@ func (ec *executionContext) _LabelChangeOperation(ctx context.Context, sel ast.S
 
 		switch field.Name {
 		case "__typename":
-			out.Values[i] = graphql.MarshalString("LabelChangeOperation")
-		case "hash":
-			out.Values[i] = ec._LabelChangeOperation_hash(ctx, field, obj)
+			out.Values[i] = graphql.MarshalString("IdentityConnection")
+		case "edges":
+			out.Values[i] = ec._IdentityConnection_edges(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
 				invalid = true
 			}
-		case "author":
-			out.Values[i] = ec._LabelChangeOperation_author(ctx, field, obj)
+		case "nodes":
+			out.Values[i] = ec._IdentityConnection_nodes(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
 				invalid = true
 			}
-		case "date":
-			wg.Add(1)
-			go func(i int, field graphql.CollectedField) {
-				out.Values[i] = ec._LabelChangeOperation_date(ctx, field, obj)
-				if out.Values[i] == graphql.Null {
-					invalid = true
-				}
-				wg.Done()
-			}(i, field)
-		case "added":
-			out.Values[i] = ec._LabelChangeOperation_added(ctx, field, obj)
+		case "pageInfo":
+			out.Values[i] = ec._IdentityConnection_pageInfo(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
 				invalid = true
 			}
-		case "removed":
-			out.Values[i] = ec._LabelChangeOperation_removed(ctx, field, obj)
+		case "totalCount":
+			out.Values[i] = ec._IdentityConnection_totalCount(ctx, field, obj)
 			if out.Values[i] == graphql.Null {
 				invalid = true
 			}
@@ -4974,7 +5177,7 @@ func (ec *executionContext) _LabelChangeOperation(ctx context.Context, sel ast.S
 			panic("unknown field " + strconv.Quote(field.Name))
 		}
 	}
-	wg.Wait()
+
 	if invalid {
 		return graphql.Null
 	}
@@ -4982,11 +5185,11 @@ func (ec *executionContext) _LabelChangeOperation(ctx context.Context, sel ast.S
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _LabelChangeOperation_hash(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) graphql.Marshaler {
+func (ec *executionContext) _IdentityConnection_edges(ctx context.Context, field graphql.CollectedField, obj *models.IdentityConnection) graphql.Marshaler {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
 	rctx := &graphql.ResolverContext{
-		Object: "LabelChangeOperation",
+		Object: "IdentityConnection",
 		Args:   nil,
 		Field:  field,
 	}
@@ -4994,7 +5197,7 @@ func (ec *executionContext) _LabelChangeOperation_hash(ctx context.Context, fiel
 	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
 	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return obj.Hash()
+		return obj.Edges, nil
 	})
 	if resTmp == nil {
 		if !ec.HasError(rctx) {
@@ -5002,18 +5205,51 @@ func (ec *executionContext) _LabelChangeOperation_hash(ctx context.Context, fiel
 		}
 		return graphql.Null
 	}
-	res := resTmp.(git.Hash)
+	res := resTmp.([]models.IdentityEdge)
 	rctx.Result = res
 	ctx = ec.Tracer.StartFieldChildExecution(ctx)
-	return res
+
+	arr1 := make(graphql.Array, len(res))
+	var wg sync.WaitGroup
+
+	isLen1 := len(res) == 1
+	if !isLen1 {
+		wg.Add(len(res))
+	}
+
+	for idx1 := range res {
+		idx1 := idx1
+		rctx := &graphql.ResolverContext{
+			Index:  &idx1,
+			Result: &res[idx1],
+		}
+		ctx := graphql.WithResolverContext(ctx, rctx)
+		f := func(idx1 int) {
+			if !isLen1 {
+				defer wg.Done()
+			}
+			arr1[idx1] = func() graphql.Marshaler {
+
+				return ec._IdentityEdge(ctx, field.Selections, &res[idx1])
+			}()
+		}
+		if isLen1 {
+			f(idx1)
+		} else {
+			go f(idx1)
+		}
+
+	}
+	wg.Wait()
+	return arr1
 }
 
 // nolint: vetshadow
-func (ec *executionContext) _LabelChangeOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) graphql.Marshaler {
+func (ec *executionContext) _IdentityConnection_nodes(ctx context.Context, field graphql.CollectedField, obj *models.IdentityConnection) graphql.Marshaler {
 	ctx = ec.Tracer.StartFieldExecution(ctx, field)
 	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
 	rctx := &graphql.ResolverContext{
-		Object: "LabelChangeOperation",
+		Object: "IdentityConnection",
 		Args:   nil,
 		Field:  field,
 	}
@@ -5021,7 +5257,7 @@ func (ec *executionContext) _LabelChangeOperation_author(ctx context.Context, fi
 	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
 	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
-		return obj.Author, nil
+		return obj.Nodes, nil
 	})
 	if resTmp == nil {
 		if !ec.HasError(rctx) {
@@ -5029,25 +5265,312 @@ func (ec *executionContext) _LabelChangeOperation_author(ctx context.Context, fi
 		}
 		return graphql.Null
 	}
-	res := resTmp.(identity.Interface)
+	res := resTmp.([]identity.Interface)
 	rctx.Result = res
 	ctx = ec.Tracer.StartFieldChildExecution(ctx)
 
-	return ec._Identity(ctx, field.Selections, &res)
-}
+	arr1 := make(graphql.Array, len(res))
+	var wg sync.WaitGroup
 
-// nolint: vetshadow
-func (ec *executionContext) _LabelChangeOperation_date(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) graphql.Marshaler {
-	ctx = ec.Tracer.StartFieldExecution(ctx, field)
-	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
-	rctx := &graphql.ResolverContext{
-		Object: "LabelChangeOperation",
-		Args:   nil,
-		Field:  field,
+	isLen1 := len(res) == 1
+	if !isLen1 {
+		wg.Add(len(res))
 	}
-	ctx = graphql.WithResolverContext(ctx, rctx)
-	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
-	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+
+	for idx1 := range res {
+		idx1 := idx1
+		rctx := &graphql.ResolverContext{
+			Index:  &idx1,
+			Result: &res[idx1],
+		}
+		ctx := graphql.WithResolverContext(ctx, rctx)
+		f := func(idx1 int) {
+			if !isLen1 {
+				defer wg.Done()
+			}
+			arr1[idx1] = func() graphql.Marshaler {
+
+				return ec._Identity(ctx, field.Selections, &res[idx1])
+			}()
+		}
+		if isLen1 {
+			f(idx1)
+		} else {
+			go f(idx1)
+		}
+
+	}
+	wg.Wait()
+	return arr1
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _IdentityConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *models.IdentityConnection) graphql.Marshaler {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rctx := &graphql.ResolverContext{
+		Object: "IdentityConnection",
+		Args:   nil,
+		Field:  field,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.PageInfo, nil
+	})
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(models.PageInfo)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+
+	return ec._PageInfo(ctx, field.Selections, &res)
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _IdentityConnection_totalCount(ctx context.Context, field graphql.CollectedField, obj *models.IdentityConnection) graphql.Marshaler {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rctx := &graphql.ResolverContext{
+		Object: "IdentityConnection",
+		Args:   nil,
+		Field:  field,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.TotalCount, nil
+	})
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(int)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return graphql.MarshalInt(res)
+}
+
+var identityEdgeImplementors = []string{"IdentityEdge"}
+
+// nolint: gocyclo, errcheck, gas, goconst
+func (ec *executionContext) _IdentityEdge(ctx context.Context, sel ast.SelectionSet, obj *models.IdentityEdge) graphql.Marshaler {
+	fields := graphql.CollectFields(ctx, sel, identityEdgeImplementors)
+
+	out := graphql.NewOrderedMap(len(fields))
+	invalid := false
+	for i, field := range fields {
+		out.Keys[i] = field.Alias
+
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("IdentityEdge")
+		case "cursor":
+			out.Values[i] = ec._IdentityEdge_cursor(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalid = true
+			}
+		case "node":
+			out.Values[i] = ec._IdentityEdge_node(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalid = true
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+
+	if invalid {
+		return graphql.Null
+	}
+	return out
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _IdentityEdge_cursor(ctx context.Context, field graphql.CollectedField, obj *models.IdentityEdge) graphql.Marshaler {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rctx := &graphql.ResolverContext{
+		Object: "IdentityEdge",
+		Args:   nil,
+		Field:  field,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Cursor, nil
+	})
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(string)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return graphql.MarshalString(res)
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _IdentityEdge_node(ctx context.Context, field graphql.CollectedField, obj *models.IdentityEdge) graphql.Marshaler {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rctx := &graphql.ResolverContext{
+		Object: "IdentityEdge",
+		Args:   nil,
+		Field:  field,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Node, nil
+	})
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(identity.Interface)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+
+	return ec._Identity(ctx, field.Selections, &res)
+}
+
+var labelChangeOperationImplementors = []string{"LabelChangeOperation", "Operation", "Authored"}
+
+// nolint: gocyclo, errcheck, gas, goconst
+func (ec *executionContext) _LabelChangeOperation(ctx context.Context, sel ast.SelectionSet, obj *bug.LabelChangeOperation) graphql.Marshaler {
+	fields := graphql.CollectFields(ctx, sel, labelChangeOperationImplementors)
+
+	var wg sync.WaitGroup
+	out := graphql.NewOrderedMap(len(fields))
+	invalid := false
+	for i, field := range fields {
+		out.Keys[i] = field.Alias
+
+		switch field.Name {
+		case "__typename":
+			out.Values[i] = graphql.MarshalString("LabelChangeOperation")
+		case "hash":
+			out.Values[i] = ec._LabelChangeOperation_hash(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalid = true
+			}
+		case "author":
+			out.Values[i] = ec._LabelChangeOperation_author(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalid = true
+			}
+		case "date":
+			wg.Add(1)
+			go func(i int, field graphql.CollectedField) {
+				out.Values[i] = ec._LabelChangeOperation_date(ctx, field, obj)
+				if out.Values[i] == graphql.Null {
+					invalid = true
+				}
+				wg.Done()
+			}(i, field)
+		case "added":
+			out.Values[i] = ec._LabelChangeOperation_added(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalid = true
+			}
+		case "removed":
+			out.Values[i] = ec._LabelChangeOperation_removed(ctx, field, obj)
+			if out.Values[i] == graphql.Null {
+				invalid = true
+			}
+		default:
+			panic("unknown field " + strconv.Quote(field.Name))
+		}
+	}
+	wg.Wait()
+	if invalid {
+		return graphql.Null
+	}
+	return out
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _LabelChangeOperation_hash(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) graphql.Marshaler {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rctx := &graphql.ResolverContext{
+		Object: "LabelChangeOperation",
+		Args:   nil,
+		Field:  field,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Hash()
+	})
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(git.Hash)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+	return res
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _LabelChangeOperation_author(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) graphql.Marshaler {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rctx := &graphql.ResolverContext{
+		Object: "LabelChangeOperation",
+		Args:   nil,
+		Field:  field,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return obj.Author, nil
+	})
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(identity.Interface)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+
+	return ec._Identity(ctx, field.Selections, &res)
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _LabelChangeOperation_date(ctx context.Context, field graphql.CollectedField, obj *bug.LabelChangeOperation) graphql.Marshaler {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rctx := &graphql.ResolverContext{
+		Object: "LabelChangeOperation",
+		Args:   nil,
+		Field:  field,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
 		ctx = rctx // use context from middleware stack in children
 		return ec.resolvers.LabelChangeOperation().Date(rctx, obj)
 	})
@@ -6313,6 +6836,27 @@ func (ec *executionContext) _Repository(ctx context.Context, sel ast.SelectionSe
 				out.Values[i] = ec._Repository_bug(ctx, field, obj)
 				wg.Done()
 			}(i, field)
+		case "allIdentities":
+			wg.Add(1)
+			go func(i int, field graphql.CollectedField) {
+				out.Values[i] = ec._Repository_allIdentities(ctx, field, obj)
+				if out.Values[i] == graphql.Null {
+					invalid = true
+				}
+				wg.Done()
+			}(i, field)
+		case "identity":
+			wg.Add(1)
+			go func(i int, field graphql.CollectedField) {
+				out.Values[i] = ec._Repository_identity(ctx, field, obj)
+				wg.Done()
+			}(i, field)
+		case "userIdentity":
+			wg.Add(1)
+			go func(i int, field graphql.CollectedField) {
+				out.Values[i] = ec._Repository_userIdentity(ctx, field, obj)
+				wg.Done()
+			}(i, field)
 		default:
 			panic("unknown field " + strconv.Quote(field.Name))
 		}
@@ -6393,6 +6937,104 @@ func (ec *executionContext) _Repository_bug(ctx context.Context, field graphql.C
 	return ec._Bug(ctx, field.Selections, res)
 }
 
+// nolint: vetshadow
+func (ec *executionContext) _Repository_allIdentities(ctx context.Context, field graphql.CollectedField, obj *models.Repository) graphql.Marshaler {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rawArgs := field.ArgumentMap(ec.Variables)
+	args, err := field_Repository_allIdentities_args(rawArgs)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	rctx := &graphql.ResolverContext{
+		Object: "Repository",
+		Args:   args,
+		Field:  field,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Repository().AllIdentities(rctx, obj, args["after"].(*string), args["before"].(*string), args["first"].(*int), args["last"].(*int))
+	})
+	if resTmp == nil {
+		if !ec.HasError(rctx) {
+			ec.Errorf(ctx, "must not be null")
+		}
+		return graphql.Null
+	}
+	res := resTmp.(models.IdentityConnection)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+
+	return ec._IdentityConnection(ctx, field.Selections, &res)
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _Repository_identity(ctx context.Context, field graphql.CollectedField, obj *models.Repository) graphql.Marshaler {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rawArgs := field.ArgumentMap(ec.Variables)
+	args, err := field_Repository_identity_args(rawArgs)
+	if err != nil {
+		ec.Error(ctx, err)
+		return graphql.Null
+	}
+	rctx := &graphql.ResolverContext{
+		Object: "Repository",
+		Args:   args,
+		Field:  field,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Repository().Identity(rctx, obj, args["prefix"].(string))
+	})
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*identity.Interface)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+
+	if res == nil {
+		return graphql.Null
+	}
+
+	return ec._Identity(ctx, field.Selections, res)
+}
+
+// nolint: vetshadow
+func (ec *executionContext) _Repository_userIdentity(ctx context.Context, field graphql.CollectedField, obj *models.Repository) graphql.Marshaler {
+	ctx = ec.Tracer.StartFieldExecution(ctx, field)
+	defer func() { ec.Tracer.EndFieldExecution(ctx) }()
+	rctx := &graphql.ResolverContext{
+		Object: "Repository",
+		Args:   nil,
+		Field:  field,
+	}
+	ctx = graphql.WithResolverContext(ctx, rctx)
+	ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx)
+	resTmp := ec.FieldMiddleware(ctx, obj, func(rctx context.Context) (interface{}, error) {
+		ctx = rctx // use context from middleware stack in children
+		return ec.resolvers.Repository().UserIdentity(rctx, obj)
+	})
+	if resTmp == nil {
+		return graphql.Null
+	}
+	res := resTmp.(*identity.Interface)
+	rctx.Result = res
+	ctx = ec.Tracer.StartFieldChildExecution(ctx)
+
+	if res == nil {
+		return graphql.Null
+	}
+
+	return ec._Identity(ctx, field.Selections, res)
+}
+
 var setStatusOperationImplementors = []string{"SetStatusOperation", "Operation", "Authored"}
 
 // nolint: gocyclo, errcheck, gas, goconst
@@ -8987,7 +9629,9 @@ enum Status {
 }
 
 type Bug {
+  """The identifier for this bug"""
   id: String!
+  """The human version (truncated) identifier for this bug"""
   humanId: String!
   status: Status!
   title: String!
@@ -9049,26 +9693,13 @@ type BugEdge {
   node: Bug!
 }
 
-type Repository {
-  allBugs(
-    """Returns the elements in the list that come after the specified cursor."""
-    after: String
-    """Returns the elements in the list that come before the specified cursor."""
-    before: String
-    """Returns the first _n_ elements from the list."""
-    first: Int
-    """Returns the last _n_ elements from the list."""
-    last: Int
-    """A query to select and order bugs"""
-    query: String
-  ): BugConnection!
-  bug(prefix: String!): Bug
-}
 `},
 	&ast.Source{Name: "schema/identity.graphql", Input: `"""Represents an identity"""
 type Identity {
     """The identifier for this identity"""
     id: String!
+    """The human version (truncated) identifier for this identity"""
+    humanId: String!
     """The name of the person, if known."""
     name: String
     """The email of the person, if known."""
@@ -9082,6 +9713,18 @@ type Identity {
     """isProtected is true if the chain of git commits started to be signed.
     If that's the case, only signed commit with a valid key for this identity can be added."""
     isProtected: Boolean!
+}
+
+type IdentityConnection {
+    edges: [IdentityEdge!]!
+    nodes: [Identity!]!
+    pageInfo: PageInfo!
+    totalCount: Int!
+}
+
+type IdentityEdge {
+    cursor: String!
+    node: Identity!
 }`},
 	&ast.Source{Name: "schema/operations.graphql", Input: `"""An operation applied to a bug."""
 interface Operation {
@@ -9184,29 +9827,42 @@ type LabelChangeOperation implements Operation & Authored {
     removed: [Label!]!
 }
 `},
-	&ast.Source{Name: "schema/root.graphql", Input: `scalar Time
-scalar Label
-scalar Hash
-
-"""Information about pagination in a connection."""
-type PageInfo {
-    """When paginating forwards, are there more items?"""
-    hasNextPage: Boolean!
-    """When paginating backwards, are there more items?"""
-    hasPreviousPage: Boolean!
-    """When paginating backwards, the cursor to continue."""
-    startCursor: String!
-    """When paginating forwards, the cursor to continue."""
-    endCursor: String!
-}
-
-"""An object that has an author."""
-interface Authored {
-    """The author of this object."""
-    author: Identity!
-}
-
-type Query {
+	&ast.Source{Name: "schema/repository.graphql", Input: `
+type Repository {
+    """All the bugs"""
+    allBugs(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the elements in the list that come before the specified cursor."""
+        before: String
+        """Returns the first _n_ elements from the list."""
+        first: Int
+        """Returns the last _n_ elements from the list."""
+        last: Int
+        """A query to select and order bugs"""
+        query: String
+    ): BugConnection!
+
+    bug(prefix: String!): Bug
+
+    """All the identities"""
+    allIdentities(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the elements in the list that come before the specified cursor."""
+        before: String
+        """Returns the first _n_ elements from the list."""
+        first: Int
+        """Returns the last _n_ elements from the list."""
+        last: Int
+    ): IdentityConnection!
+
+    identity(prefix: String!):Identity
+
+    """The identity created or selected by the user as its own"""
+    userIdentity:Identity
+}`},
+	&ast.Source{Name: "schema/root.graphql", Input: `type Query {
     defaultRepository: Repository
     repository(id: String!): Repository
 }

graphql/models/edges.go 🔗

@@ -19,3 +19,8 @@ func (e CommentEdge) GetCursor() string {
 func (e TimelineItemEdge) GetCursor() string {
 	return e.Cursor
 }
+
+// GetCursor return the cursor entry of an edge
+func (e IdentityEdge) GetCursor() string {
+	return e.Cursor
+}

graphql/models/gen_models.go 🔗

@@ -8,6 +8,7 @@ import (
 	"strconv"
 
 	"github.com/MichaelMure/git-bug/bug"
+	"github.com/MichaelMure/git-bug/identity"
 )
 
 // An object that has an author.
@@ -41,6 +42,18 @@ type CommentEdge struct {
 	Node   bug.Comment `json:"node"`
 }
 
+type IdentityConnection struct {
+	Edges      []IdentityEdge       `json:"edges"`
+	Nodes      []identity.Interface `json:"nodes"`
+	PageInfo   PageInfo             `json:"pageInfo"`
+	TotalCount int                  `json:"totalCount"`
+}
+
+type IdentityEdge struct {
+	Cursor string             `json:"cursor"`
+	Node   identity.Interface `json:"node"`
+}
+
 // The connection type for an Operation
 type OperationConnection struct {
 	Edges      []OperationEdge `json:"edges"`

graphql/resolvers/bug.go 🔗

@@ -6,9 +6,12 @@ import (
 
 	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/graphql/connections"
+	"github.com/MichaelMure/git-bug/graphql/graph"
 	"github.com/MichaelMure/git-bug/graphql/models"
 )
 
+var _ graph.BugResolver = &bugResolver{}
+
 type bugResolver struct{}
 
 func (bugResolver) Status(ctx context.Context, obj *bug.Snapshot) (models.Status, error) {
@@ -39,7 +42,7 @@ func (bugResolver) Comments(ctx context.Context, obj *bug.Snapshot, after *strin
 		}, nil
 	}
 
-	return connections.BugCommentCon(obj.Comments, edger, conMaker, input)
+	return connections.CommentCon(obj.Comments, edger, conMaker, input)
 }
 
 func (bugResolver) Operations(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.OperationConnection, error) {
@@ -66,7 +69,7 @@ func (bugResolver) Operations(ctx context.Context, obj *bug.Snapshot, after *str
 		}, nil
 	}
 
-	return connections.BugOperationCon(obj.Operations, edger, conMaker, input)
+	return connections.OperationCon(obj.Operations, edger, conMaker, input)
 }
 
 func (bugResolver) Timeline(ctx context.Context, obj *bug.Snapshot, after *string, before *string, first *int, last *int) (models.TimelineItemConnection, error) {
@@ -93,7 +96,7 @@ func (bugResolver) Timeline(ctx context.Context, obj *bug.Snapshot, after *strin
 		}, nil
 	}
 
-	return connections.BugTimelineItemCon(obj.Timeline, edger, conMaker, input)
+	return connections.TimelineItemCon(obj.Timeline, edger, conMaker, input)
 }
 
 func (bugResolver) LastEdit(ctx context.Context, obj *bug.Snapshot) (time.Time, error) {

graphql/resolvers/identity.go 🔗

@@ -3,15 +3,22 @@ package resolvers
 import (
 	"context"
 
+	"github.com/MichaelMure/git-bug/graphql/graph"
 	"github.com/MichaelMure/git-bug/identity"
 )
 
+var _ graph.IdentityResolver = &identityResolver{}
+
 type identityResolver struct{}
 
 func (identityResolver) ID(ctx context.Context, obj *identity.Interface) (string, error) {
 	return (*obj).Id(), nil
 }
 
+func (identityResolver) HumanID(ctx context.Context, obj *identity.Interface) (string, error) {
+	return (*obj).HumanId(), nil
+}
+
 func (identityResolver) Name(ctx context.Context, obj *identity.Interface) (*string, error) {
 	return nilIfEmpty((*obj).Name())
 }

graphql/resolvers/mutation.go 🔗

@@ -5,9 +5,12 @@ import (
 
 	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/cache"
+	"github.com/MichaelMure/git-bug/graphql/graph"
 	"github.com/MichaelMure/git-bug/util/git"
 )
 
+var _ graph.MutationResolver = &mutationResolver{}
+
 type mutationResolver struct {
 	cache *cache.MultiRepoCache
 }

graphql/resolvers/query.go 🔗

@@ -4,9 +4,12 @@ import (
 	"context"
 
 	"github.com/MichaelMure/git-bug/cache"
+	"github.com/MichaelMure/git-bug/graphql/graph"
 	"github.com/MichaelMure/git-bug/graphql/models"
 )
 
+var _ graph.QueryResolver = &rootQueryResolver{}
+
 type rootQueryResolver struct {
 	cache *cache.MultiRepoCache
 }

graphql/resolvers/repo.go 🔗

@@ -6,9 +6,13 @@ import (
 	"github.com/MichaelMure/git-bug/bug"
 	"github.com/MichaelMure/git-bug/cache"
 	"github.com/MichaelMure/git-bug/graphql/connections"
+	"github.com/MichaelMure/git-bug/graphql/graph"
 	"github.com/MichaelMure/git-bug/graphql/models"
+	"github.com/MichaelMure/git-bug/identity"
 )
 
+var _ graph.RepositoryResolver = &repoResolver{}
+
 type repoResolver struct{}
 
 func (repoResolver) AllBugs(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, queryStr *string) (models.BugConnection, error) {
@@ -70,7 +74,7 @@ func (repoResolver) AllBugs(ctx context.Context, obj *models.Repository, after *
 		}, nil
 	}
 
-	return connections.StringCon(source, edger, conMaker, input)
+	return connections.LazyBugCon(source, edger, conMaker, input)
 }
 
 func (repoResolver) Bug(ctx context.Context, obj *models.Repository, prefix string) (*bug.Snapshot, error) {
@@ -82,3 +86,78 @@ func (repoResolver) Bug(ctx context.Context, obj *models.Repository, prefix stri
 
 	return b.Snapshot(), nil
 }
+
+func (repoResolver) AllIdentities(ctx context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (models.IdentityConnection, error) {
+	input := models.ConnectionInput{
+		Before: before,
+		After:  after,
+		First:  first,
+		Last:   last,
+	}
+
+	// Simply pass a []string with the ids to the pagination algorithm
+	source := obj.Repo.AllIdentityIds()
+
+	// The edger create a custom edge holding just the id
+	edger := func(id string, offset int) connections.Edge {
+		return connections.LazyIdentityEdge{
+			Id:     id,
+			Cursor: connections.OffsetToCursor(offset),
+		}
+	}
+
+	// The conMaker will finally load and compile identities from git to replace the selected edges
+	conMaker := func(lazyIdentityEdges []connections.LazyIdentityEdge, lazyNode []string, info models.PageInfo, totalCount int) (models.IdentityConnection, error) {
+		edges := make([]models.IdentityEdge, len(lazyIdentityEdges))
+		nodes := make([]identity.Interface, len(lazyIdentityEdges))
+
+		for k, lazyIdentityEdge := range lazyIdentityEdges {
+			i, err := obj.Repo.ResolveIdentity(lazyIdentityEdge.Id)
+
+			if err != nil {
+				return models.IdentityConnection{}, err
+			}
+
+			ii := identity.Interface(i.Identity)
+
+			edges[k] = models.IdentityEdge{
+				Cursor: lazyIdentityEdge.Cursor,
+				Node:   ii,
+			}
+			nodes[k] = ii
+		}
+
+		return models.IdentityConnection{
+			Edges:      edges,
+			Nodes:      nodes,
+			PageInfo:   info,
+			TotalCount: totalCount,
+		}, nil
+	}
+
+	return connections.LazyIdentityCon(source, edger, conMaker, input)
+}
+
+func (repoResolver) Identity(ctx context.Context, obj *models.Repository, prefix string) (*identity.Interface, error) {
+	i, err := obj.Repo.ResolveIdentityPrefix(prefix)
+
+	if err != nil {
+		return nil, err
+	}
+
+	ii := identity.Interface(i.Identity)
+
+	return &ii, nil
+}
+
+func (repoResolver) UserIdentity(ctx context.Context, obj *models.Repository) (*identity.Interface, error) {
+	i, err := obj.Repo.GetUserIdentity()
+
+	if err != nil {
+		return nil, err
+	}
+
+	ii := identity.Interface(i.Identity)
+
+	return &ii, nil
+}

graphql/resolvers/root.go 🔗

@@ -6,6 +6,8 @@ import (
 	"github.com/MichaelMure/git-bug/graphql/graph"
 )
 
+var _ graph.ResolverRoot = &RootResolver{}
+
 type RootResolver struct {
 	cache.MultiRepoCache
 }

graphql/schema/bug.graphql 🔗

@@ -28,7 +28,9 @@ enum Status {
 }
 
 type Bug {
+  """The identifier for this bug"""
   id: String!
+  """The human version (truncated) identifier for this bug"""
   humanId: String!
   status: Status!
   title: String!
@@ -90,18 +92,3 @@ type BugEdge {
   node: Bug!
 }
 
-type Repository {
-  allBugs(
-    """Returns the elements in the list that come after the specified cursor."""
-    after: String
-    """Returns the elements in the list that come before the specified cursor."""
-    before: String
-    """Returns the first _n_ elements from the list."""
-    first: Int
-    """Returns the last _n_ elements from the list."""
-    last: Int
-    """A query to select and order bugs"""
-    query: String
-  ): BugConnection!
-  bug(prefix: String!): Bug
-}

graphql/schema/identity.graphql 🔗

@@ -2,6 +2,8 @@
 type Identity {
     """The identifier for this identity"""
     id: String!
+    """The human version (truncated) identifier for this identity"""
+    humanId: String!
     """The name of the person, if known."""
     name: String
     """The email of the person, if known."""
@@ -16,3 +18,15 @@ type Identity {
     If that's the case, only signed commit with a valid key for this identity can be added."""
     isProtected: Boolean!
 }
+
+type IdentityConnection {
+    edges: [IdentityEdge!]!
+    nodes: [Identity!]!
+    pageInfo: PageInfo!
+    totalCount: Int!
+}
+
+type IdentityEdge {
+    cursor: String!
+    node: Identity!
+}

graphql/schema/repository.graphql 🔗

@@ -0,0 +1,35 @@
+
+type Repository {
+    """All the bugs"""
+    allBugs(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the elements in the list that come before the specified cursor."""
+        before: String
+        """Returns the first _n_ elements from the list."""
+        first: Int
+        """Returns the last _n_ elements from the list."""
+        last: Int
+        """A query to select and order bugs"""
+        query: String
+    ): BugConnection!
+
+    bug(prefix: String!): Bug
+
+    """All the identities"""
+    allIdentities(
+        """Returns the elements in the list that come after the specified cursor."""
+        after: String
+        """Returns the elements in the list that come before the specified cursor."""
+        before: String
+        """Returns the first _n_ elements from the list."""
+        first: Int
+        """Returns the last _n_ elements from the list."""
+        last: Int
+    ): IdentityConnection!
+
+    identity(prefix: String!):Identity
+
+    """The identity created or selected by the user as its own"""
+    userIdentity:Identity
+}

graphql/schema/root.graphql 🔗

@@ -1,25 +1,3 @@
-scalar Time
-scalar Label
-scalar Hash
-
-"""Information about pagination in a connection."""
-type PageInfo {
-    """When paginating forwards, are there more items?"""
-    hasNextPage: Boolean!
-    """When paginating backwards, are there more items?"""
-    hasPreviousPage: Boolean!
-    """When paginating backwards, the cursor to continue."""
-    startCursor: String!
-    """When paginating forwards, the cursor to continue."""
-    endCursor: String!
-}
-
-"""An object that has an author."""
-interface Authored {
-    """The author of this object."""
-    author: Identity!
-}
-
 type Query {
     defaultRepository: Repository
     repository(id: String!): Repository

graphql/schema/types.graphql 🔗

@@ -0,0 +1,21 @@
+scalar Time
+scalar Label
+scalar Hash
+
+"""Information about pagination in a connection."""
+type PageInfo {
+    """When paginating forwards, are there more items?"""
+    hasNextPage: Boolean!
+    """When paginating backwards, are there more items?"""
+    hasPreviousPage: Boolean!
+    """When paginating backwards, the cursor to continue."""
+    startCursor: String!
+    """When paginating forwards, the cursor to continue."""
+    endCursor: String!
+}
+
+"""An object that has an author."""
+interface Authored {
+    """The author of this object."""
+    author: Identity!
+}

vendor/github.com/99designs/gqlgen/handler/graphql.go 🔗

@@ -7,6 +7,7 @@ import (
 	"io"
 	"net/http"
 	"strings"
+	"time"
 
 	"github.com/99designs/gqlgen/complexity"
 	"github.com/99designs/gqlgen/graphql"
@@ -25,15 +26,16 @@ type params struct {
 }
 
 type Config struct {
-	cacheSize            int
-	upgrader             websocket.Upgrader
-	recover              graphql.RecoverFunc
-	errorPresenter       graphql.ErrorPresenterFunc
-	resolverHook         graphql.FieldMiddleware
-	requestHook          graphql.RequestMiddleware
-	tracer               graphql.Tracer
-	complexityLimit      int
-	disableIntrospection bool
+	cacheSize                       int
+	upgrader                        websocket.Upgrader
+	recover                         graphql.RecoverFunc
+	errorPresenter                  graphql.ErrorPresenterFunc
+	resolverHook                    graphql.FieldMiddleware
+	requestHook                     graphql.RequestMiddleware
+	tracer                          graphql.Tracer
+	complexityLimit                 int
+	disableIntrospection            bool
+	connectionKeepAlivePingInterval time.Duration
 }
 
 func (c *Config) newRequestContext(es graphql.ExecutableSchema, doc *ast.QueryDocument, op *ast.OperationDefinition, query string, variables map[string]interface{}) *graphql.RequestContext {
@@ -243,6 +245,14 @@ func CacheSize(size int) Option {
 
 const DefaultCacheSize = 1000
 
+// WebsocketKeepAliveDuration allows you to reconfigure the keepAlive behavior.
+// By default, keep-alive is disabled.
+func WebsocketKeepAliveDuration(duration time.Duration) Option {
+	return func(cfg *Config) {
+		cfg.connectionKeepAlivePingInterval = duration
+	}
+}
+
 func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc {
 	cfg := &Config{
 		cacheSize: DefaultCacheSize,

vendor/github.com/99designs/gqlgen/handler/websocket.go 🔗

@@ -8,6 +8,7 @@ import (
 	"log"
 	"net/http"
 	"sync"
+	"time"
 
 	"github.com/99designs/gqlgen/graphql"
 	"github.com/gorilla/websocket"
@@ -27,7 +28,7 @@ const (
 	dataMsg                = "data"                 // Server -> Client
 	errorMsg               = "error"                // Server -> Client
 	completeMsg            = "complete"             // Server -> Client
-	//connectionKeepAliveMsg = "ka"                 // Server -> Client  TODO: keepalives
+	connectionKeepAliveMsg = "ka"                   // Server -> Client
 )
 
 type operationMessage struct {
@@ -37,12 +38,13 @@ type operationMessage struct {
 }
 
 type wsConnection struct {
-	ctx    context.Context
-	conn   *websocket.Conn
-	exec   graphql.ExecutableSchema
-	active map[string]context.CancelFunc
-	mu     sync.Mutex
-	cfg    *Config
+	ctx             context.Context
+	conn            *websocket.Conn
+	exec            graphql.ExecutableSchema
+	active          map[string]context.CancelFunc
+	mu              sync.Mutex
+	cfg             *Config
+	keepAliveTicker *time.Ticker
 
 	initPayload InitPayload
 }
@@ -109,6 +111,20 @@ func (c *wsConnection) write(msg *operationMessage) {
 }
 
 func (c *wsConnection) run() {
+	// We create a cancellation that will shutdown the keep-alive when we leave
+	// this function.
+	ctx, cancel := context.WithCancel(c.ctx)
+	defer cancel()
+
+	// Create a timer that will fire every interval to keep the connection alive.
+	if c.cfg.connectionKeepAlivePingInterval != 0 {
+		c.mu.Lock()
+		c.keepAliveTicker = time.NewTicker(c.cfg.connectionKeepAlivePingInterval)
+		c.mu.Unlock()
+
+		go c.keepAlive(ctx)
+	}
+
 	for {
 		message := c.readOp()
 		if message == nil {
@@ -141,6 +157,18 @@ func (c *wsConnection) run() {
 	}
 }
 
+func (c *wsConnection) keepAlive(ctx context.Context) {
+	for {
+		select {
+		case <-ctx.Done():
+			c.keepAliveTicker.Stop()
+			return
+		case <-c.keepAliveTicker.C:
+			c.write(&operationMessage{Type: connectionKeepAliveMsg})
+		}
+	}
+}
+
 func (c *wsConnection) subscribe(message *operationMessage) bool {
 	var reqParams params
 	if err := jsonDecode(bytes.NewReader(message.Payload), &reqParams); err != nil {