~edwargix/git.sr.ht

60f438dfc6b22dd90746296a196e11451176e61e — Drew DeVault 5 years ago 1611a3c
API: initial groundwork for cursors
M api/database/ql.go => api/database/ql.go +27 -16
@@ 10,13 10,34 @@ import (
	"git.sr.ht/~sircmpwn/gqlgen/graphql"
)

func ColumnsFor(ctx context.Context, alias string,
	colMap map[string]string) []string {

func collectFields(ctx context.Context) []graphql.CollectedField {
	var fields []graphql.CollectedField
	if graphql.GetFieldContext(ctx) != nil {
		fields = graphql.CollectFieldsCtx(ctx, nil)
	} else {

		octx := graphql.GetOperationContext(ctx)
		for _, col := range fields {
			if col.Name == "results" {
				// This endpoint is using the cursor pattern; the columns we
				// actually need to filter with are nested into the results
				// field.
				fields = graphql.CollectFields(octx, col.SelectionSet, nil)
				break
			}
		}

		sort.Slice(fields, func(a, b int) bool {
			return fields[a].Name < fields[b].Name
		})
	}
	return fields
}

func ColumnsFor(ctx context.Context, alias string,
	colMap map[string]string) []string {

	fields := collectFields(ctx)
	if len(fields) == 0 {
		// Collect all fields if we are not in an active graphql context
		for qlCol, _ := range colMap {
			fields = append(fields, graphql.CollectedField{


@@ 25,10 46,6 @@ func ColumnsFor(ctx context.Context, alias string,
		}
	}

	sort.Slice(fields, func(a, b int) bool {
		return fields[a].Name < fields[b].Name
	})

	var columns []string
	for _, qlCol := range fields {
		if sqlCol, ok := colMap[qlCol.Name]; ok {


@@ 47,10 64,8 @@ func ColumnsFor(ctx context.Context, alias string,
func FieldsFor(ctx context.Context,
	colMap map[string]interface{}) []interface{} {

	var qlFields []graphql.CollectedField
	if graphql.GetFieldContext(ctx) != nil {
		qlFields = graphql.CollectFieldsCtx(ctx, nil)
	} else {
	qlFields := collectFields(ctx)
	if len(qlFields) == 0 {
		// Collect all fields if we are not in an active graphql context
		for qlCol, _ := range colMap {
			qlFields = append(qlFields, graphql.CollectedField{


@@ 59,10 74,6 @@ func FieldsFor(ctx context.Context,
		}
	}

	sort.Slice(qlFields, func(a, b int) bool {
		return qlFields[a].Name < qlFields[b].Name
	})

	var fields []interface{}
	for _, qlField := range qlFields {
		if field, ok := colMap[qlField.Name]; ok {

M api/go.sum => api/go.sum +1 -0
@@ 2,6 2,7 @@ git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3 h1:4wDp4BKF7NQqoh7
git.sr.ht/~sircmpwn/getopt v0.0.0-20191230200459-23622cc906b3/go.mod h1:wMEGFFFNuPos7vHmWXfszqImLppbc0wEhh6JBfJIUgw=
git.sr.ht/~sircmpwn/git.sr.ht v0.0.0-20200405134845-b8fbf5bf484f h1:SW8+xV65kcga0rHmQbnZkLG36yp286BcbVOdQTVo1m8=
git.sr.ht/~sircmpwn/git.sr.ht v0.0.0-20200413150414-046cd382d7b7 h1:PYRTIcsHR5W+aPn98OCC73ly528uw5o/4Z3b5Rvc7vA=
git.sr.ht/~sircmpwn/git.sr.ht v0.0.0-20200430231646-4014870a700b h1:xHyh0xixoMog7F1kqOG55daFfK0uso98i0lQ/D4dSM8=
git.sr.ht/~sircmpwn/gqlgen v0.0.0-20200412134447-57d7234737d4 h1:J/Sb88htNHzZaN6ZEF8BnRWj3LzYoTrOL4WRhZEEiQE=
git.sr.ht/~sircmpwn/gqlgen v0.0.0-20200412134447-57d7234737d4/go.mod h1:W1cijL2EqAyL1eo1WAJ3ijNVkZM2okpYyCF5TRu1VfI=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=

M api/graph/generated/generated.go => api/graph/generated/generated.go +143 -65
@@ 121,7 121,6 @@ type ComplexityRoot struct {
	Repository struct {
		AccessControlList func(childComplexity int, cursor *model.Cursor) int
		Created           func(childComplexity int) int
		Cursor            func(childComplexity int) int
		Description       func(childComplexity int) int
		File              func(childComplexity int, revspec *string, path string) int
		Head              func(childComplexity int) int


@@ 138,6 137,11 @@ type ComplexityRoot struct {
		Visibility        func(childComplexity int) int
	}

	RepositoryCursor struct {
		Cursor  func(childComplexity int) int
		Results func(childComplexity int) int
	}

	Signature struct {
		Email func(childComplexity int) int
		Name  func(childComplexity int) int


@@ 210,7 214,7 @@ type QueryResolver interface {
	Me(ctx context.Context) (*model.User, error)
	Cursor(ctx context.Context, filter model.Filter) (*model.Cursor, error)
	User(ctx context.Context, username string) (*model.User, error)
	Repositories(ctx context.Context, cursor *model.Cursor) ([]*model.Repository, error)
	Repositories(ctx context.Context, cursor *model.Cursor) (*model.RepositoryCursor, error)
	Repository(ctx context.Context, id int) (*model.Repository, error)
	RepositoryByName(ctx context.Context, name string) (*model.Repository, error)
	RepositoryByOwner(ctx context.Context, owner string, repo string) (*model.Repository, error)


@@ 225,7 229,7 @@ type TreeResolver interface {
	Entries(ctx context.Context, obj *model.Tree, cursor *model.Cursor) ([]*model.TreeEntry, error)
}
type UserResolver interface {
	Repositories(ctx context.Context, obj *model.User, cursor *model.Cursor) ([]*model.Repository, error)
	Repositories(ctx context.Context, obj *model.User, cursor *model.Cursor) (*model.RepositoryCursor, error)
}

type executableSchema struct {


@@ 649,13 653,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in

		return e.complexity.Repository.Created(childComplexity), true

	case "Repository.cursor":
		if e.complexity.Repository.Cursor == nil {
			break
		}

		return e.complexity.Repository.Cursor(childComplexity), true

	case "Repository.description":
		if e.complexity.Repository.Description == nil {
			break


@@ 784,6 781,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in

		return e.complexity.Repository.Visibility(childComplexity), true

	case "RepositoryCursor.cursor":
		if e.complexity.RepositoryCursor.Cursor == nil {
			break
		}

		return e.complexity.RepositoryCursor.Cursor(childComplexity), true

	case "RepositoryCursor.results":
		if e.complexity.RepositoryCursor.Results == nil {
			break
		}

		return e.complexity.RepositoryCursor.Results(childComplexity), true

	case "Signature.email":
		if e.complexity.Signature.Email == nil {
			break


@@ 1158,7 1169,7 @@ interface Entity {
  canonicalName: String!

  # A list of repositories owned by this entity
  repositories(cursor: Cursor): [Repository]!
  repositories(cursor: Cursor): RepositoryCursor
}

# A registered user


@@ 1174,7 1185,7 @@ type User implements Entity {
  bio: String

  # A list of repositories owned by this user
  repositories(cursor: Cursor): [Repository]!
  repositories(cursor: Cursor): RepositoryCursor
}

# A git repository


@@ 1187,9 1198,6 @@ type Repository {
  description: String
  visibility: Visibility!

  # Pass this into the same endpoint to receive a new page of results
  cursor: Cursor

  # If this repository was cloned from another, this will be set to the
  # original clone URL
  upstreamUrl: String


@@ 1235,6 1243,16 @@ type Repository {
  revparse_single(revspec: String!): Object
}

# A cursor for enumerating a list of repositories
#
# If there are additional results available, the cursor object may be passed
# back into the same endpoint to retrieve another page. If the cursor is null,
# there are no remaining results to return.
type RepositoryCursor {
  results: [Repository]!
  cursor: Cursor
}

# Access Control List entry
type ACL {
  id: Int!


@@ 1382,7 1400,7 @@ type Query {
  # will be to return all repositories that the user either (1) has been given
  # explicit access to via ACLs or (2) has implicit access to either by
  # ownership or group membership.
  repositories(cursor: Cursor): [Repository]!
  repositories(cursor: Cursor): RepositoryCursor

  # Returns a specific repository
  repository(id: Int!): Repository


@@ 3284,14 3302,11 @@ func (ec *executionContext) _Query_repositories(ctx context.Context, field graph
		return graphql.Null
	}
	if resTmp == nil {
		if !graphql.HasFieldError(ctx, fc) {
			ec.Errorf(ctx, "must not be null")
		}
		return graphql.Null
	}
	res := resTmp.([]*model.Repository)
	res := resTmp.(*model.RepositoryCursor)
	fc.Result = res
	return ec.marshalNRepository2ᚕᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐRepository(ctx, field.Selections, res)
	return ec.marshalORepositoryCursor2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐRepositoryCursor(ctx, field.Selections, res)
}

func (ec *executionContext) _Query_repository(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {


@@ 3811,37 3826,6 @@ func (ec *executionContext) _Repository_visibility(ctx context.Context, field gr
	return ec.marshalNVisibility2gitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐVisibility(ctx, field.Selections, res)
}

func (ec *executionContext) _Repository_cursor(ctx context.Context, field graphql.CollectedField, obj *model.Repository) (ret graphql.Marshaler) {
	defer func() {
		if r := recover(); r != nil {
			ec.Error(ctx, ec.Recover(ctx, r))
			ret = graphql.Null
		}
	}()
	fc := &graphql.FieldContext{
		Object:   "Repository",
		Field:    field,
		Args:     nil,
		IsMethod: false,
	}

	ctx = graphql.WithFieldContext(ctx, fc)
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
		ctx = rctx // use context from middleware stack in children
		return obj.Cursor, nil
	})
	if err != nil {
		ec.Error(ctx, err)
		return graphql.Null
	}
	if resTmp == nil {
		return graphql.Null
	}
	res := resTmp.(*model.Cursor)
	fc.Result = res
	return ec.marshalOCursor2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐCursor(ctx, field.Selections, res)
}

func (ec *executionContext) _Repository_upstreamUrl(ctx context.Context, field graphql.CollectedField, obj *model.Repository) (ret graphql.Marshaler) {
	defer func() {
		if r := recover(); r != nil {


@@ 4182,6 4166,71 @@ func (ec *executionContext) _Repository_revparse_single(ctx context.Context, fie
	return ec.marshalOObject2gitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐObject(ctx, field.Selections, res)
}

func (ec *executionContext) _RepositoryCursor_results(ctx context.Context, field graphql.CollectedField, obj *model.RepositoryCursor) (ret graphql.Marshaler) {
	defer func() {
		if r := recover(); r != nil {
			ec.Error(ctx, ec.Recover(ctx, r))
			ret = graphql.Null
		}
	}()
	fc := &graphql.FieldContext{
		Object:   "RepositoryCursor",
		Field:    field,
		Args:     nil,
		IsMethod: false,
	}

	ctx = graphql.WithFieldContext(ctx, fc)
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
		ctx = rctx // use context from middleware stack in children
		return obj.Results, 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.Repository)
	fc.Result = res
	return ec.marshalNRepository2ᚕᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐRepository(ctx, field.Selections, res)
}

func (ec *executionContext) _RepositoryCursor_cursor(ctx context.Context, field graphql.CollectedField, obj *model.RepositoryCursor) (ret graphql.Marshaler) {
	defer func() {
		if r := recover(); r != nil {
			ec.Error(ctx, ec.Recover(ctx, r))
			ret = graphql.Null
		}
	}()
	fc := &graphql.FieldContext{
		Object:   "RepositoryCursor",
		Field:    field,
		Args:     nil,
		IsMethod: false,
	}

	ctx = graphql.WithFieldContext(ctx, fc)
	resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
		ctx = rctx // use context from middleware stack in children
		return obj.Cursor, nil
	})
	if err != nil {
		ec.Error(ctx, err)
		return graphql.Null
	}
	if resTmp == nil {
		return graphql.Null
	}
	res := resTmp.(*model.Cursor)
	fc.Result = res
	return ec.marshalOCursor2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐCursor(ctx, field.Selections, res)
}

func (ec *executionContext) _Signature_name(ctx context.Context, field graphql.CollectedField, obj *model.Signature) (ret graphql.Marshaler) {
	defer func() {
		if r := recover(); r != nil {


@@ 5266,14 5315,11 @@ func (ec *executionContext) _User_repositories(ctx context.Context, field graphq
		return graphql.Null
	}
	if resTmp == nil {
		if !graphql.HasFieldError(ctx, fc) {
			ec.Errorf(ctx, "must not be null")
		}
		return graphql.Null
	}
	res := resTmp.([]*model.Repository)
	res := resTmp.(*model.RepositoryCursor)
	fc.Result = res
	return ec.marshalNRepository2ᚕᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐRepository(ctx, field.Selections, res)
	return ec.marshalORepositoryCursor2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐRepositoryCursor(ctx, field.Selections, res)
}

func (ec *executionContext) _Version_major(ctx context.Context, field graphql.CollectedField, obj *model.Version) (ret graphql.Marshaler) {


@@ 6991,9 7037,6 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
					}
				}()
				res = ec._Query_repositories(ctx, field)
				if res == graphql.Null {
					atomic.AddUint32(&invalids, 1)
				}
				return res
			})
		case "repository":


@@ 7130,8 7173,6 @@ func (ec *executionContext) _Repository(ctx context.Context, sel ast.SelectionSe
			if out.Values[i] == graphql.Null {
				atomic.AddUint32(&invalids, 1)
			}
		case "cursor":
			out.Values[i] = ec._Repository_cursor(ctx, field, obj)
		case "upstreamUrl":
			out.Values[i] = ec._Repository_upstreamUrl(ctx, field, obj)
		case "accessControlList":


@@ 7191,6 7232,35 @@ func (ec *executionContext) _Repository(ctx context.Context, sel ast.SelectionSe
	return out
}

var repositoryCursorImplementors = []string{"RepositoryCursor"}

func (ec *executionContext) _RepositoryCursor(ctx context.Context, sel ast.SelectionSet, obj *model.RepositoryCursor) graphql.Marshaler {
	fields := graphql.CollectFields(ec.OperationContext, sel, repositoryCursorImplementors)

	out := graphql.NewFieldSet(fields)
	var invalids uint32
	for i, field := range fields {
		switch field.Name {
		case "__typename":
			out.Values[i] = graphql.MarshalString("RepositoryCursor")
		case "results":
			out.Values[i] = ec._RepositoryCursor_results(ctx, field, obj)
			if out.Values[i] == graphql.Null {
				invalids++
			}
		case "cursor":
			out.Values[i] = ec._RepositoryCursor_cursor(ctx, field, obj)
		default:
			panic("unknown field " + strconv.Quote(field.Name))
		}
	}
	out.Dispatch()
	if invalids > 0 {
		return graphql.Null
	}
	return out
}

var signatureImplementors = []string{"Signature"}

func (ec *executionContext) _Signature(ctx context.Context, sel ast.SelectionSet, obj *model.Signature) graphql.Marshaler {


@@ 7470,9 7540,6 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj
					}
				}()
				res = ec._User_repositories(ctx, field, obj)
				if res == graphql.Null {
					atomic.AddUint32(&invalids, 1)
				}
				return res
			})
		default:


@@ 8700,6 8767,17 @@ func (ec *executionContext) marshalORepository2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋg
	return ec._Repository(ctx, sel, v)
}

func (ec *executionContext) marshalORepositoryCursor2gitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐRepositoryCursor(ctx context.Context, sel ast.SelectionSet, v model.RepositoryCursor) graphql.Marshaler {
	return ec._RepositoryCursor(ctx, sel, &v)
}

func (ec *executionContext) marshalORepositoryCursor2ᚖgitᚗsrᚗhtᚋאsircmpwnᚋgitᚗsrᚗhtᚋapiᚋgraphᚋmodelᚐRepositoryCursor(ctx context.Context, sel ast.SelectionSet, v *model.RepositoryCursor) graphql.Marshaler {
	if v == nil {
		return graphql.Null
	}
	return ec._RepositoryCursor(ctx, sel, v)
}

func (ec *executionContext) unmarshalOString2string(ctx context.Context, v interface{}) (string, error) {
	return graphql.UnmarshalString(v)
}

M api/graph/model/cursor.go => api/graph/model/cursor.go +2 -0
@@ 37,5 37,7 @@ func (cur Cursor) MarshalGQL(w io.Writer) {
	if err != nil {
		panic(err)
	}
	w.Write([]byte("\""))
	w.Write(crypto.Encrypt(data))
	w.Write([]byte("\""))
}

M api/graph/model/models_gen.go => api/graph/model/models_gen.go +5 -0
@@ 43,6 43,11 @@ type RepoInput struct {
	Visibility  *Visibility `json:"visibility"`
}

type RepositoryCursor struct {
	Results []*Repository `json:"results"`
	Cursor  *Cursor       `json:"cursor"`
}

type Signature struct {
	Name  string    `json:"name"`
	Email string    `json:"email"`

M api/graph/model/repository.go => api/graph/model/repository.go +1 -1
@@ 72,7 72,7 @@ func (r *Repository) As(alias string) database.Selectable {
}

func (r *Repository) Fields(ctx context.Context) []interface{} {
	fields := FieldsFor(ctx, map[string]interface{}{
	fields := database.FieldsFor(ctx, map[string]interface{}{
		"id":           &r.ID,
		"created":      &r.Created,
		"updated":      &r.Updated,

D api/graph/model/util.go => api/graph/model/util.go +0 -71
@@ 1,71 0,0 @@
package model

import (
	"context"
	"sort"

	"git.sr.ht/~sircmpwn/gqlgen/graphql"
	"github.com/vektah/gqlparser/v2/ast"
)

func ColumnsFor(ctx context.Context,
	colMap map[string]string, tbl string) []string {

	var fields []graphql.CollectedField
	if graphql.GetFieldContext(ctx) != nil {
		fields = graphql.CollectFieldsCtx(ctx, nil)
	} else {
		// Collect all fields if we are not in an active graphql context
		for qlCol, _ := range colMap {
			fields = append(fields, graphql.CollectedField{
				&ast.Field{Name: qlCol}, nil,
			})
		}
	}

	sort.Slice(fields, func(a, b int) bool {
		return fields[a].Name < fields[b].Name
	})

	var columns []string
	for _, qlCol := range fields {
		if sqlCol, ok := colMap[qlCol.Name]; ok {
			if tbl != "" {
				columns = append(columns, tbl+"."+sqlCol)
			} else {
				columns = append(columns, sqlCol)
			}
		}
	}

	return columns
}

func FieldsFor(ctx context.Context,
	colMap map[string]interface{}) []interface{} {

	var qlFields []graphql.CollectedField
	if graphql.GetFieldContext(ctx) != nil {
		qlFields = graphql.CollectFieldsCtx(ctx, nil)
	} else {
		// Collect all fields if we are not in an active graphql context
		for qlCol, _ := range colMap {
			qlFields = append(qlFields, graphql.CollectedField{
				&ast.Field{Name: qlCol}, nil,
			})
		}
	}

	sort.Slice(qlFields, func(a, b int) bool {
		return qlFields[a].Name < qlFields[b].Name
	})

	var fields []interface{}
	for _, qlField := range qlFields {
		if field, ok := colMap[qlField.Name]; ok {
			fields = append(fields, field)
		}
	}

	return fields
}

M api/graph/schema.graphqls => api/graph/schema.graphqls +13 -6
@@ 41,7 41,7 @@ interface Entity {
  canonicalName: String!

  # A list of repositories owned by this entity
  repositories(cursor: Cursor): [Repository]!
  repositories(cursor: Cursor): RepositoryCursor
}

# A registered user


@@ 57,7 57,7 @@ type User implements Entity {
  bio: String

  # A list of repositories owned by this user
  repositories(cursor: Cursor): [Repository]!
  repositories(cursor: Cursor): RepositoryCursor
}

# A git repository


@@ 70,9 70,6 @@ type Repository {
  description: String
  visibility: Visibility!

  # Pass this into the same endpoint to receive a new page of results
  cursor: Cursor

  # If this repository was cloned from another, this will be set to the
  # original clone URL
  upstreamUrl: String


@@ 118,6 115,16 @@ type Repository {
  revparse_single(revspec: String!): Object
}

# A cursor for enumerating a list of repositories
#
# If there are additional results available, the cursor object may be passed
# back into the same endpoint to retrieve another page. If the cursor is null,
# there are no remaining results to return.
type RepositoryCursor {
  results: [Repository]!
  cursor: Cursor
}

# Access Control List entry
type ACL {
  id: Int!


@@ 265,7 272,7 @@ type Query {
  # will be to return all repositories that the user either (1) has been given
  # explicit access to via ACLs or (2) has implicit access to either by
  # ownership or group membership.
  repositories(cursor: Cursor): [Repository]!
  repositories(cursor: Cursor): RepositoryCursor

  # Returns a specific repository
  repository(id: Int!): Repository

M api/graph/schema.resolvers.go => api/graph/schema.resolvers.go +16 -5
@@ 8,6 8,7 @@ import (
	"database/sql"
	"fmt"
	"sort"
	"strconv"
	"strings"

	"git.sr.ht/~sircmpwn/git.sr.ht/api/auth"


@@ 78,7 79,7 @@ func (r *queryResolver) User(ctx context.Context, username string) (*model.User,
	return loaders.ForContext(ctx).UsersByName.Load(username)
}

func (r *queryResolver) Repositories(ctx context.Context, cursor *model.Cursor) ([]*model.Repository, error) {
func (r *queryResolver) Repositories(ctx context.Context, cursor *model.Cursor) (*model.RepositoryCursor, error) {
	var (
		err  error
		rows *sql.Rows


@@ 103,7 104,8 @@ func (r *queryResolver) Repositories(ctx context.Context, cursor *model.Cursor) 
		}
		repos = append(repos, &repo)
	}
	return repos, nil
	// TODO: Cursor
	return &model.RepositoryCursor{repos, nil}, nil
}

func (r *queryResolver) Repository(ctx context.Context, id int) (*model.Repository, error) {


@@ 157,7 159,7 @@ func (r *treeResolver) Entries(ctx context.Context, obj *model.Tree, cursor *mod
	panic(fmt.Errorf("not implemented"))
}

func (r *userResolver) Repositories(ctx context.Context, obj *model.User, cursor *model.Cursor) ([]*model.Repository, error) {
func (r *userResolver) Repositories(ctx context.Context, obj *model.User, cursor *model.Cursor) (*model.RepositoryCursor, error) {
	var (
		err  error
		rows *sql.Rows


@@ 168,7 170,7 @@ func (r *userResolver) Repositories(ctx context.Context, obj *model.User, cursor
		From(`repository repo`).
		Where(`repo.owner_id = ?`, obj.ID).
		OrderBy(`id DESC`).
		Limit(25)
		Limit(26)
	if rows, err = query.RunWith(r.DB).QueryContext(ctx); err != nil {
		panic(err)
	}


@@ 181,7 183,16 @@ func (r *userResolver) Repositories(ctx context.Context, obj *model.User, cursor
		}
		repos = append(repos, &repo)
	}
	return repos, nil
	if len(repos) > 25 {
		cursor = &model.Cursor{
			Count:   25,
			Next:    strconv.Itoa(repos[len(repos)-1].ID),
			OrderBy: `id DESC`,
			Search:  "",
		}
		repos = repos[:25]
	}
	return &model.RepositoryCursor{repos, cursor}, nil
}

// Mutation returns generated.MutationResolver implementation.