aboutsummaryrefslogtreecommitdiffstats
path: root/api/graphql/resolvers
diff options
context:
space:
mode:
authorMichael Muré <batolettre@gmail.com>2020-06-21 22:12:04 +0200
committerMichael Muré <batolettre@gmail.com>2020-06-27 23:03:05 +0200
commit2ab6381a94d55fa22b80acdbb18849d6b24951f9 (patch)
tree99942b000955623ea7466b9fa4cc7dab37645df6 /api/graphql/resolvers
parent5f72b04ef8e84b1c367ca6874519706318e351f5 (diff)
downloadgit-bug-2ab6381a94d55fa22b80acdbb18849d6b24951f9.tar.gz
Reorganize the webUI and API code
Included in the changes: - create a new /api root package to hold all API code, migrate /graphql in there - git API handlers all use the cache instead of the repo directly - git API handlers are now tested - git API handlers now require a "repo" mux parameter - lots of untangling of API/handlers/middleware - less code in commands/webui.go
Diffstat (limited to 'api/graphql/resolvers')
-rw-r--r--api/graphql/resolvers/bug.go190
-rw-r--r--api/graphql/resolvers/color.go24
-rw-r--r--api/graphql/resolvers/comment.go17
-rw-r--r--api/graphql/resolvers/identity.go21
-rw-r--r--api/graphql/resolvers/label.go45
-rw-r--r--api/graphql/resolvers/mutation.go208
-rw-r--r--api/graphql/resolvers/operations.go132
-rw-r--r--api/graphql/resolvers/query.go48
-rw-r--r--api/graphql/resolvers/repo.go188
-rw-r--r--api/graphql/resolvers/root.go107
-rw-r--r--api/graphql/resolvers/timeline.go118
11 files changed, 1098 insertions, 0 deletions
diff --git a/api/graphql/resolvers/bug.go b/api/graphql/resolvers/bug.go
new file mode 100644
index 00000000..815cba8d
--- /dev/null
+++ b/api/graphql/resolvers/bug.go
@@ -0,0 +1,190 @@
+package resolvers
+
+import (
+ "context"
+
+ "github.com/MichaelMure/git-bug/api/graphql/connections"
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
+ "github.com/MichaelMure/git-bug/bug"
+)
+
+var _ graph.BugResolver = &bugResolver{}
+
+type bugResolver struct{}
+
+func (bugResolver) ID(_ context.Context, obj models.BugWrapper) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (bugResolver) HumanID(_ context.Context, obj models.BugWrapper) (string, error) {
+ return obj.Id().Human(), nil
+}
+
+func (bugResolver) Status(_ context.Context, obj models.BugWrapper) (models.Status, error) {
+ return convertStatus(obj.Status())
+}
+
+func (bugResolver) Comments(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.CommentConnection, error) {
+ input := models.ConnectionInput{
+ Before: before,
+ After: after,
+ First: first,
+ Last: last,
+ }
+
+ edger := func(comment bug.Comment, offset int) connections.Edge {
+ return models.CommentEdge{
+ Node: &comment,
+ Cursor: connections.OffsetToCursor(offset),
+ }
+ }
+
+ conMaker := func(edges []*models.CommentEdge, nodes []bug.Comment, info *models.PageInfo, totalCount int) (*models.CommentConnection, error) {
+ var commentNodes []*bug.Comment
+ for _, c := range nodes {
+ commentNodes = append(commentNodes, &c)
+ }
+ return &models.CommentConnection{
+ Edges: edges,
+ Nodes: commentNodes,
+ PageInfo: info,
+ TotalCount: totalCount,
+ }, nil
+ }
+
+ comments, err := obj.Comments()
+ if err != nil {
+ return nil, err
+ }
+
+ return connections.CommentCon(comments, edger, conMaker, input)
+}
+
+func (bugResolver) Operations(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.OperationConnection, error) {
+ input := models.ConnectionInput{
+ Before: before,
+ After: after,
+ First: first,
+ Last: last,
+ }
+
+ edger := func(op bug.Operation, offset int) connections.Edge {
+ return models.OperationEdge{
+ Node: op,
+ Cursor: connections.OffsetToCursor(offset),
+ }
+ }
+
+ conMaker := func(edges []*models.OperationEdge, nodes []bug.Operation, info *models.PageInfo, totalCount int) (*models.OperationConnection, error) {
+ return &models.OperationConnection{
+ Edges: edges,
+ Nodes: nodes,
+ PageInfo: info,
+ TotalCount: totalCount,
+ }, nil
+ }
+
+ ops, err := obj.Operations()
+ if err != nil {
+ return nil, err
+ }
+
+ return connections.OperationCon(ops, edger, conMaker, input)
+}
+
+func (bugResolver) Timeline(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.TimelineItemConnection, error) {
+ input := models.ConnectionInput{
+ Before: before,
+ After: after,
+ First: first,
+ Last: last,
+ }
+
+ edger := func(op bug.TimelineItem, offset int) connections.Edge {
+ return models.TimelineItemEdge{
+ Node: op,
+ Cursor: connections.OffsetToCursor(offset),
+ }
+ }
+
+ conMaker := func(edges []*models.TimelineItemEdge, nodes []bug.TimelineItem, info *models.PageInfo, totalCount int) (*models.TimelineItemConnection, error) {
+ return &models.TimelineItemConnection{
+ Edges: edges,
+ Nodes: nodes,
+ PageInfo: info,
+ TotalCount: totalCount,
+ }, nil
+ }
+
+ timeline, err := obj.Timeline()
+ if err != nil {
+ return nil, err
+ }
+
+ return connections.TimelineItemCon(timeline, edger, conMaker, input)
+}
+
+func (bugResolver) Actors(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) {
+ input := models.ConnectionInput{
+ Before: before,
+ After: after,
+ First: first,
+ Last: last,
+ }
+
+ edger := func(actor models.IdentityWrapper, offset int) connections.Edge {
+ return models.IdentityEdge{
+ Node: actor,
+ Cursor: connections.OffsetToCursor(offset),
+ }
+ }
+
+ conMaker := func(edges []*models.IdentityEdge, nodes []models.IdentityWrapper, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) {
+ return &models.IdentityConnection{
+ Edges: edges,
+ Nodes: nodes,
+ PageInfo: info,
+ TotalCount: totalCount,
+ }, nil
+ }
+
+ actors, err := obj.Actors()
+ if err != nil {
+ return nil, err
+ }
+
+ return connections.IdentityCon(actors, edger, conMaker, input)
+}
+
+func (bugResolver) Participants(_ context.Context, obj models.BugWrapper, after *string, before *string, first *int, last *int) (*models.IdentityConnection, error) {
+ input := models.ConnectionInput{
+ Before: before,
+ After: after,
+ First: first,
+ Last: last,
+ }
+
+ edger := func(participant models.IdentityWrapper, offset int) connections.Edge {
+ return models.IdentityEdge{
+ Node: participant,
+ Cursor: connections.OffsetToCursor(offset),
+ }
+ }
+
+ conMaker := func(edges []*models.IdentityEdge, nodes []models.IdentityWrapper, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) {
+ return &models.IdentityConnection{
+ Edges: edges,
+ Nodes: nodes,
+ PageInfo: info,
+ TotalCount: totalCount,
+ }, nil
+ }
+
+ participants, err := obj.Participants()
+ if err != nil {
+ return nil, err
+ }
+
+ return connections.IdentityCon(participants, edger, conMaker, input)
+}
diff --git a/api/graphql/resolvers/color.go b/api/graphql/resolvers/color.go
new file mode 100644
index 00000000..cfa411f8
--- /dev/null
+++ b/api/graphql/resolvers/color.go
@@ -0,0 +1,24 @@
+package resolvers
+
+import (
+ "context"
+ "image/color"
+
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+)
+
+var _ graph.ColorResolver = &colorResolver{}
+
+type colorResolver struct{}
+
+func (colorResolver) R(_ context.Context, obj *color.RGBA) (int, error) {
+ return int(obj.R), nil
+}
+
+func (colorResolver) G(_ context.Context, obj *color.RGBA) (int, error) {
+ return int(obj.G), nil
+}
+
+func (colorResolver) B(_ context.Context, obj *color.RGBA) (int, error) {
+ return int(obj.B), nil
+}
diff --git a/api/graphql/resolvers/comment.go b/api/graphql/resolvers/comment.go
new file mode 100644
index 00000000..5206e8a7
--- /dev/null
+++ b/api/graphql/resolvers/comment.go
@@ -0,0 +1,17 @@
+package resolvers
+
+import (
+ "context"
+
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
+ "github.com/MichaelMure/git-bug/bug"
+)
+
+var _ graph.CommentResolver = &commentResolver{}
+
+type commentResolver struct{}
+
+func (c commentResolver) Author(_ context.Context, obj *bug.Comment) (models.IdentityWrapper, error) {
+ return models.NewLoadedIdentity(obj.Author), nil
+}
diff --git a/api/graphql/resolvers/identity.go b/api/graphql/resolvers/identity.go
new file mode 100644
index 00000000..69a32c98
--- /dev/null
+++ b/api/graphql/resolvers/identity.go
@@ -0,0 +1,21 @@
+package resolvers
+
+import (
+ "context"
+
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
+)
+
+var _ graph.IdentityResolver = &identityResolver{}
+
+type identityResolver struct{}
+
+func (identityResolver) ID(ctx context.Context, obj models.IdentityWrapper) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (r identityResolver) HumanID(ctx context.Context, obj models.IdentityWrapper) (string, error) {
+ return obj.Id().Human(), nil
+
+}
diff --git a/api/graphql/resolvers/label.go b/api/graphql/resolvers/label.go
new file mode 100644
index 00000000..83e95029
--- /dev/null
+++ b/api/graphql/resolvers/label.go
@@ -0,0 +1,45 @@
+package resolvers
+
+import (
+ "context"
+ "fmt"
+ "image/color"
+
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
+ "github.com/MichaelMure/git-bug/bug"
+)
+
+var _ graph.LabelResolver = &labelResolver{}
+
+type labelResolver struct{}
+
+func (labelResolver) Name(ctx context.Context, obj *bug.Label) (string, error) {
+ return obj.String(), nil
+}
+
+func (labelResolver) Color(ctx context.Context, obj *bug.Label) (*color.RGBA, error) {
+ rgba := obj.Color().RGBA()
+ return &rgba, nil
+}
+
+var _ graph.LabelChangeResultResolver = &labelChangeResultResolver{}
+
+type labelChangeResultResolver struct{}
+
+func (labelChangeResultResolver) Status(ctx context.Context, obj *bug.LabelChangeResult) (models.LabelChangeStatus, error) {
+ switch obj.Status {
+ case bug.LabelChangeAdded:
+ return models.LabelChangeStatusAdded, nil
+ case bug.LabelChangeRemoved:
+ return models.LabelChangeStatusRemoved, nil
+ case bug.LabelChangeDuplicateInOp:
+ return models.LabelChangeStatusDuplicateInOp, nil
+ case bug.LabelChangeAlreadySet:
+ return models.LabelChangeStatusAlreadyExist, nil
+ case bug.LabelChangeDoesntExist:
+ return models.LabelChangeStatusDoesntExist, nil
+ }
+
+ return "", fmt.Errorf("unknown status")
+}
diff --git a/api/graphql/resolvers/mutation.go b/api/graphql/resolvers/mutation.go
new file mode 100644
index 00000000..642a4fb9
--- /dev/null
+++ b/api/graphql/resolvers/mutation.go
@@ -0,0 +1,208 @@
+package resolvers
+
+import (
+ "context"
+ "time"
+
+ "github.com/MichaelMure/git-bug/api/auth"
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
+ "github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/cache"
+)
+
+var _ graph.MutationResolver = &mutationResolver{}
+
+type mutationResolver struct {
+ cache *cache.MultiRepoCache
+}
+
+func (r mutationResolver) getRepo(ref *string) (*cache.RepoCache, error) {
+ if ref != nil {
+ return r.cache.ResolveRepo(*ref)
+ }
+
+ return r.cache.DefaultRepo()
+}
+
+func (r mutationResolver) getBug(repoRef *string, bugPrefix string) (*cache.RepoCache, *cache.BugCache, error) {
+ repo, err := r.getRepo(repoRef)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ b, err := repo.ResolveBugPrefix(bugPrefix)
+ if err != nil {
+ return nil, nil, err
+ }
+ return repo, b, nil
+}
+
+func (r mutationResolver) NewBug(ctx context.Context, input models.NewBugInput) (*models.NewBugPayload, error) {
+ repo, err := r.getRepo(input.RepoRef)
+ if err != nil {
+ return nil, err
+ }
+
+ author, err := auth.UserFromCtx(ctx, repo)
+ if err != nil {
+ return nil, err
+ }
+
+ b, op, err := repo.NewBugRaw(author, time.Now().Unix(), input.Title, input.Message, input.Files, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ return &models.NewBugPayload{
+ ClientMutationID: input.ClientMutationID,
+ Bug: models.NewLoadedBug(b.Snapshot()),
+ Operation: op,
+ }, nil
+}
+
+func (r mutationResolver) AddComment(ctx context.Context, input models.AddCommentInput) (*models.AddCommentPayload, error) {
+ repo, b, err := r.getBug(input.RepoRef, input.Prefix)
+ if err != nil {
+ return nil, err
+ }
+
+ author, err := auth.UserFromCtx(ctx, repo)
+ if err != nil {
+ return nil, err
+ }
+
+ op, err := b.AddCommentRaw(author, time.Now().Unix(), input.Message, input.Files, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ err = b.Commit()
+ if err != nil {
+ return nil, err
+ }
+
+ return &models.AddCommentPayload{
+ ClientMutationID: input.ClientMutationID,
+ Bug: models.NewLoadedBug(b.Snapshot()),
+ Operation: op,
+ }, nil
+}
+
+func (r mutationResolver) ChangeLabels(ctx context.Context, input *models.ChangeLabelInput) (*models.ChangeLabelPayload, error) {
+ repo, b, err := r.getBug(input.RepoRef, input.Prefix)
+ if err != nil {
+ return nil, err
+ }
+
+ author, err := auth.UserFromCtx(ctx, repo)
+ if err != nil {
+ return nil, err
+ }
+
+ results, op, err := b.ChangeLabelsRaw(author, time.Now().Unix(), input.Added, input.Removed, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ err = b.Commit()
+ if err != nil {
+ return nil, err
+ }
+
+ resultsPtr := make([]*bug.LabelChangeResult, len(results))
+ for i, result := range results {
+ resultsPtr[i] = &result
+ }
+
+ return &models.ChangeLabelPayload{
+ ClientMutationID: input.ClientMutationID,
+ Bug: models.NewLoadedBug(b.Snapshot()),
+ Operation: op,
+ Results: resultsPtr,
+ }, nil
+}
+
+func (r mutationResolver) OpenBug(ctx context.Context, input models.OpenBugInput) (*models.OpenBugPayload, error) {
+ repo, b, err := r.getBug(input.RepoRef, input.Prefix)
+ if err != nil {
+ return nil, err
+ }
+
+ author, err := auth.UserFromCtx(ctx, repo)
+ if err != nil {
+ return nil, err
+ }
+
+ op, err := b.OpenRaw(author, time.Now().Unix(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ err = b.Commit()
+ if err != nil {
+ return nil, err
+ }
+
+ return &models.OpenBugPayload{
+ ClientMutationID: input.ClientMutationID,
+ Bug: models.NewLoadedBug(b.Snapshot()),
+ Operation: op,
+ }, nil
+}
+
+func (r mutationResolver) CloseBug(ctx context.Context, input models.CloseBugInput) (*models.CloseBugPayload, error) {
+ repo, b, err := r.getBug(input.RepoRef, input.Prefix)
+ if err != nil {
+ return nil, err
+ }
+
+ author, err := auth.UserFromCtx(ctx, repo)
+ if err != nil {
+ return nil, err
+ }
+
+ op, err := b.CloseRaw(author, time.Now().Unix(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ err = b.Commit()
+ if err != nil {
+ return nil, err
+ }
+
+ return &models.CloseBugPayload{
+ ClientMutationID: input.ClientMutationID,
+ Bug: models.NewLoadedBug(b.Snapshot()),
+ Operation: op,
+ }, nil
+}
+
+func (r mutationResolver) SetTitle(ctx context.Context, input models.SetTitleInput) (*models.SetTitlePayload, error) {
+ repo, b, err := r.getBug(input.RepoRef, input.Prefix)
+ if err != nil {
+ return nil, err
+ }
+
+ author, err := auth.UserFromCtx(ctx, repo)
+ if err != nil {
+ return nil, err
+ }
+
+ op, err := b.SetTitleRaw(author, time.Now().Unix(), input.Title, nil)
+ if err != nil {
+ return nil, err
+ }
+
+ err = b.Commit()
+ if err != nil {
+ return nil, err
+ }
+
+ return &models.SetTitlePayload{
+ ClientMutationID: input.ClientMutationID,
+ Bug: models.NewLoadedBug(b.Snapshot()),
+ Operation: op,
+ }, nil
+}
diff --git a/api/graphql/resolvers/operations.go b/api/graphql/resolvers/operations.go
new file mode 100644
index 00000000..8d3e5bba
--- /dev/null
+++ b/api/graphql/resolvers/operations.go
@@ -0,0 +1,132 @@
+package resolvers
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
+ "github.com/MichaelMure/git-bug/bug"
+)
+
+var _ graph.CreateOperationResolver = createOperationResolver{}
+
+type createOperationResolver struct{}
+
+func (createOperationResolver) ID(_ context.Context, obj *bug.CreateOperation) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (createOperationResolver) Author(_ context.Context, obj *bug.CreateOperation) (models.IdentityWrapper, error) {
+ return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (createOperationResolver) Date(_ context.Context, obj *bug.CreateOperation) (*time.Time, error) {
+ t := obj.Time()
+ return &t, nil
+}
+
+var _ graph.AddCommentOperationResolver = addCommentOperationResolver{}
+
+type addCommentOperationResolver struct{}
+
+func (addCommentOperationResolver) ID(_ context.Context, obj *bug.AddCommentOperation) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (addCommentOperationResolver) Author(_ context.Context, obj *bug.AddCommentOperation) (models.IdentityWrapper, error) {
+ return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (addCommentOperationResolver) Date(_ context.Context, obj *bug.AddCommentOperation) (*time.Time, error) {
+ t := obj.Time()
+ return &t, nil
+}
+
+var _ graph.EditCommentOperationResolver = editCommentOperationResolver{}
+
+type editCommentOperationResolver struct{}
+
+func (editCommentOperationResolver) ID(_ context.Context, obj *bug.EditCommentOperation) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (editCommentOperationResolver) Target(_ context.Context, obj *bug.EditCommentOperation) (string, error) {
+ return obj.Target.String(), nil
+}
+
+func (editCommentOperationResolver) Author(_ context.Context, obj *bug.EditCommentOperation) (models.IdentityWrapper, error) {
+ return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (editCommentOperationResolver) Date(_ context.Context, obj *bug.EditCommentOperation) (*time.Time, error) {
+ t := obj.Time()
+ return &t, nil
+}
+
+var _ graph.LabelChangeOperationResolver = labelChangeOperationResolver{}
+
+type labelChangeOperationResolver struct{}
+
+func (labelChangeOperationResolver) ID(_ context.Context, obj *bug.LabelChangeOperation) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (labelChangeOperationResolver) Author(_ context.Context, obj *bug.LabelChangeOperation) (models.IdentityWrapper, error) {
+ return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (labelChangeOperationResolver) Date(_ context.Context, obj *bug.LabelChangeOperation) (*time.Time, error) {
+ t := obj.Time()
+ return &t, nil
+}
+
+var _ graph.SetStatusOperationResolver = setStatusOperationResolver{}
+
+type setStatusOperationResolver struct{}
+
+func (setStatusOperationResolver) ID(_ context.Context, obj *bug.SetStatusOperation) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (setStatusOperationResolver) Author(_ context.Context, obj *bug.SetStatusOperation) (models.IdentityWrapper, error) {
+ return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (setStatusOperationResolver) Date(_ context.Context, obj *bug.SetStatusOperation) (*time.Time, error) {
+ t := obj.Time()
+ return &t, nil
+}
+
+func (setStatusOperationResolver) Status(_ context.Context, obj *bug.SetStatusOperation) (models.Status, error) {
+ return convertStatus(obj.Status)
+}
+
+var _ graph.SetTitleOperationResolver = setTitleOperationResolver{}
+
+type setTitleOperationResolver struct{}
+
+func (setTitleOperationResolver) ID(_ context.Context, obj *bug.SetTitleOperation) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (setTitleOperationResolver) Author(_ context.Context, obj *bug.SetTitleOperation) (models.IdentityWrapper, error) {
+ return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (setTitleOperationResolver) Date(_ context.Context, obj *bug.SetTitleOperation) (*time.Time, error) {
+ t := obj.Time()
+ return &t, nil
+}
+
+func convertStatus(status bug.Status) (models.Status, error) {
+ switch status {
+ case bug.OpenStatus:
+ return models.StatusOpen, nil
+ case bug.ClosedStatus:
+ return models.StatusClosed, nil
+ }
+
+ return "", fmt.Errorf("unknown status")
+}
diff --git a/api/graphql/resolvers/query.go b/api/graphql/resolvers/query.go
new file mode 100644
index 00000000..4ad7ae0c
--- /dev/null
+++ b/api/graphql/resolvers/query.go
@@ -0,0 +1,48 @@
+package resolvers
+
+import (
+ "context"
+
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
+ "github.com/MichaelMure/git-bug/cache"
+)
+
+var _ graph.QueryResolver = &rootQueryResolver{}
+
+type rootQueryResolver struct {
+ cache *cache.MultiRepoCache
+}
+
+func (r rootQueryResolver) DefaultRepository(_ context.Context) (*models.Repository, error) {
+ repo, err := r.cache.DefaultRepo()
+
+ if err != nil {
+ return nil, err
+ }
+
+ return &models.Repository{
+ Cache: r.cache,
+ Repo: repo,
+ }, nil
+}
+
+func (r rootQueryResolver) Repository(_ context.Context, ref *string) (*models.Repository, error) {
+ var repo *cache.RepoCache
+ var err error
+
+ if ref == nil {
+ repo, err = r.cache.DefaultRepo()
+ } else {
+ repo, err = r.cache.ResolveRepo(*ref)
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ return &models.Repository{
+ Cache: r.cache,
+ Repo: repo,
+ }, nil
+}
diff --git a/api/graphql/resolvers/repo.go b/api/graphql/resolvers/repo.go
new file mode 100644
index 00000000..5d96428e
--- /dev/null
+++ b/api/graphql/resolvers/repo.go
@@ -0,0 +1,188 @@
+package resolvers
+
+import (
+ "context"
+
+ "github.com/MichaelMure/git-bug/api/auth"
+ "github.com/MichaelMure/git-bug/api/graphql/connections"
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
+ "github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/entity"
+ "github.com/MichaelMure/git-bug/query"
+)
+
+var _ graph.RepositoryResolver = &repoResolver{}
+
+type repoResolver struct{}
+
+func (repoResolver) Name(_ context.Context, obj *models.Repository) (*string, error) {
+ name := obj.Repo.Name()
+ return &name, nil
+}
+
+func (repoResolver) AllBugs(_ context.Context, obj *models.Repository, after *string, before *string, first *int, last *int, queryStr *string) (*models.BugConnection, error) {
+ input := models.ConnectionInput{
+ Before: before,
+ After: after,
+ First: first,
+ Last: last,
+ }
+
+ var q *query.Query
+ if queryStr != nil {
+ query2, err := query.Parse(*queryStr)
+ if err != nil {
+ return nil, err
+ }
+ q = query2
+ } else {
+ q = query.NewQuery()
+ }
+
+ // Simply pass a []string with the ids to the pagination algorithm
+ source := obj.Repo.QueryBugs(q)
+
+ // The edger create a custom edge holding just the id
+ edger := func(id entity.Id, offset int) connections.Edge {
+ return connections.LazyBugEdge{
+ Id: id,
+ Cursor: connections.OffsetToCursor(offset),
+ }
+ }
+
+ // The conMaker will finally load and compile bugs from git to replace the selected edges
+ conMaker := func(lazyBugEdges []*connections.LazyBugEdge, lazyNode []entity.Id, info *models.PageInfo, totalCount int) (*models.BugConnection, error) {
+ edges := make([]*models.BugEdge, len(lazyBugEdges))
+ nodes := make([]models.BugWrapper, len(lazyBugEdges))
+
+ for i, lazyBugEdge := range lazyBugEdges {
+ excerpt, err := obj.Repo.ResolveBugExcerpt(lazyBugEdge.Id)
+ if err != nil {
+ return nil, err
+ }
+
+ b := models.NewLazyBug(obj.Repo, excerpt)
+
+ edges[i] = &models.BugEdge{
+ Cursor: lazyBugEdge.Cursor,
+ Node: b,
+ }
+ nodes[i] = b
+ }
+
+ return &models.BugConnection{
+ Edges: edges,
+ Nodes: nodes,
+ PageInfo: info,
+ TotalCount: totalCount,
+ }, nil
+ }
+
+ return connections.LazyBugCon(source, edger, conMaker, input)
+}
+
+func (repoResolver) Bug(_ context.Context, obj *models.Repository, prefix string) (models.BugWrapper, error) {
+ excerpt, err := obj.Repo.ResolveBugExcerptPrefix(prefix)
+ if err != nil {
+ return nil, err
+ }
+
+ return models.NewLazyBug(obj.Repo, excerpt), nil
+}
+
+func (repoResolver) AllIdentities(_ 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 entity.Id, 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 []entity.Id, info *models.PageInfo, totalCount int) (*models.IdentityConnection, error) {
+ edges := make([]*models.IdentityEdge, len(lazyIdentityEdges))
+ nodes := make([]models.IdentityWrapper, len(lazyIdentityEdges))
+
+ for k, lazyIdentityEdge := range lazyIdentityEdges {
+ excerpt, err := obj.Repo.ResolveIdentityExcerpt(lazyIdentityEdge.Id)
+ if err != nil {
+ return nil, err
+ }
+
+ i := models.NewLazyIdentity(obj.Repo, excerpt)
+
+ edges[k] = &models.IdentityEdge{
+ Cursor: lazyIdentityEdge.Cursor,
+ Node: i,
+ }
+ nodes[k] = i
+ }
+
+ return &models.IdentityConnection{
+ Edges: edges,
+ Nodes: nodes,
+ PageInfo: info,
+ TotalCount: totalCount,
+ }, nil
+ }
+
+ return connections.LazyIdentityCon(source, edger, conMaker, input)
+}
+
+func (repoResolver) Identity(_ context.Context, obj *models.Repository, prefix string) (models.IdentityWrapper, error) {
+ excerpt, err := obj.Repo.ResolveIdentityExcerptPrefix(prefix)
+ if err != nil {
+ return nil, err
+ }
+
+ return models.NewLazyIdentity(obj.Repo, excerpt), nil
+}
+
+func (repoResolver) UserIdentity(ctx context.Context, obj *models.Repository) (models.IdentityWrapper, error) {
+ id, err := auth.UserFromCtx(ctx, obj.Repo)
+ if err == auth.ErrNotAuthenticated {
+ return nil, nil
+ } else if err != nil {
+ return nil, err
+ }
+ return models.NewLoadedIdentity(id.Identity), nil
+}
+
+func (repoResolver) ValidLabels(_ context.Context, obj *models.Repository, after *string, before *string, first *int, last *int) (*models.LabelConnection, error) {
+ input := models.ConnectionInput{
+ Before: before,
+ After: after,
+ First: first,
+ Last: last,
+ }
+
+ edger := func(label bug.Label, offset int) connections.Edge {
+ return models.LabelEdge{
+ Node: label,
+ Cursor: connections.OffsetToCursor(offset),
+ }
+ }
+
+ conMaker := func(edges []*models.LabelEdge, nodes []bug.Label, info *models.PageInfo, totalCount int) (*models.LabelConnection, error) {
+ return &models.LabelConnection{
+ Edges: edges,
+ Nodes: nodes,
+ PageInfo: info,
+ TotalCount: totalCount,
+ }, nil
+ }
+
+ return connections.LabelCon(obj.Repo.ValidLabels(), edger, conMaker, input)
+}
diff --git a/api/graphql/resolvers/root.go b/api/graphql/resolvers/root.go
new file mode 100644
index 00000000..bb3bf5cf
--- /dev/null
+++ b/api/graphql/resolvers/root.go
@@ -0,0 +1,107 @@
+// Package resolvers contains the various GraphQL resolvers
+package resolvers
+
+import (
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+ "github.com/MichaelMure/git-bug/cache"
+)
+
+var _ graph.ResolverRoot = &RootResolver{}
+
+type RootResolver struct {
+ *cache.MultiRepoCache
+}
+
+func NewRootResolver(mrc *cache.MultiRepoCache) *RootResolver {
+ return &RootResolver{
+ MultiRepoCache: mrc,
+ }
+}
+
+func (r RootResolver) Query() graph.QueryResolver {
+ return &rootQueryResolver{
+ cache: r.MultiRepoCache,
+ }
+}
+
+func (r RootResolver) Mutation() graph.MutationResolver {
+ return &mutationResolver{
+ cache: r.MultiRepoCache,
+ }
+}
+
+func (RootResolver) Repository() graph.RepositoryResolver {
+ return &repoResolver{}
+}
+
+func (RootResolver) Bug() graph.BugResolver {
+ return &bugResolver{}
+}
+
+func (RootResolver) Color() graph.ColorResolver {
+ return &colorResolver{}
+}
+
+func (r RootResolver) Comment() graph.CommentResolver {
+ return &commentResolver{}
+}
+
+func (RootResolver) Label() graph.LabelResolver {
+ return &labelResolver{}
+}
+
+func (r RootResolver) Identity() graph.IdentityResolver {
+ return &identityResolver{}
+}
+
+func (RootResolver) CommentHistoryStep() graph.CommentHistoryStepResolver {
+ return &commentHistoryStepResolver{}
+}
+
+func (RootResolver) AddCommentTimelineItem() graph.AddCommentTimelineItemResolver {
+ return &addCommentTimelineItemResolver{}
+}
+
+func (RootResolver) CreateTimelineItem() graph.CreateTimelineItemResolver {
+ return &createTimelineItemResolver{}
+}
+
+func (r RootResolver) LabelChangeTimelineItem() graph.LabelChangeTimelineItemResolver {
+ return &labelChangeTimelineItem{}
+}
+
+func (r RootResolver) SetStatusTimelineItem() graph.SetStatusTimelineItemResolver {
+ return &setStatusTimelineItem{}
+}
+
+func (r RootResolver) SetTitleTimelineItem() graph.SetTitleTimelineItemResolver {
+ return &setTitleTimelineItem{}
+}
+
+func (RootResolver) CreateOperation() graph.CreateOperationResolver {
+ return &createOperationResolver{}
+}
+
+func (RootResolver) AddCommentOperation() graph.AddCommentOperationResolver {
+ return &addCommentOperationResolver{}
+}
+
+func (r RootResolver) EditCommentOperation() graph.EditCommentOperationResolver {
+ return &editCommentOperationResolver{}
+}
+
+func (RootResolver) LabelChangeOperation() graph.LabelChangeOperationResolver {
+ return &labelChangeOperationResolver{}
+}
+
+func (RootResolver) SetStatusOperation() graph.SetStatusOperationResolver {
+ return &setStatusOperationResolver{}
+}
+
+func (RootResolver) SetTitleOperation() graph.SetTitleOperationResolver {
+ return &setTitleOperationResolver{}
+}
+
+func (r RootResolver) LabelChangeResult() graph.LabelChangeResultResolver {
+ return &labelChangeResultResolver{}
+}
diff --git a/api/graphql/resolvers/timeline.go b/api/graphql/resolvers/timeline.go
new file mode 100644
index 00000000..3223b3a0
--- /dev/null
+++ b/api/graphql/resolvers/timeline.go
@@ -0,0 +1,118 @@
+package resolvers
+
+import (
+ "context"
+ "time"
+
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
+ "github.com/MichaelMure/git-bug/bug"
+)
+
+var _ graph.CommentHistoryStepResolver = commentHistoryStepResolver{}
+
+type commentHistoryStepResolver struct{}
+
+func (commentHistoryStepResolver) Date(_ context.Context, obj *bug.CommentHistoryStep) (*time.Time, error) {
+ t := obj.UnixTime.Time()
+ return &t, nil
+}
+
+var _ graph.AddCommentTimelineItemResolver = addCommentTimelineItemResolver{}
+
+type addCommentTimelineItemResolver struct{}
+
+func (addCommentTimelineItemResolver) ID(_ context.Context, obj *bug.AddCommentTimelineItem) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (addCommentTimelineItemResolver) Author(_ context.Context, obj *bug.AddCommentTimelineItem) (models.IdentityWrapper, error) {
+ return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (addCommentTimelineItemResolver) CreatedAt(_ context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error) {
+ t := obj.CreatedAt.Time()
+ return &t, nil
+}
+
+func (addCommentTimelineItemResolver) LastEdit(_ context.Context, obj *bug.AddCommentTimelineItem) (*time.Time, error) {
+ t := obj.LastEdit.Time()
+ return &t, nil
+}
+
+var _ graph.CreateTimelineItemResolver = createTimelineItemResolver{}
+
+type createTimelineItemResolver struct{}
+
+func (createTimelineItemResolver) ID(_ context.Context, obj *bug.CreateTimelineItem) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (r createTimelineItemResolver) Author(_ context.Context, obj *bug.CreateTimelineItem) (models.IdentityWrapper, error) {
+ return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (createTimelineItemResolver) CreatedAt(_ context.Context, obj *bug.CreateTimelineItem) (*time.Time, error) {
+ t := obj.CreatedAt.Time()
+ return &t, nil
+}
+
+func (createTimelineItemResolver) LastEdit(_ context.Context, obj *bug.CreateTimelineItem) (*time.Time, error) {
+ t := obj.LastEdit.Time()
+ return &t, nil
+}
+
+var _ graph.LabelChangeTimelineItemResolver = labelChangeTimelineItem{}
+
+type labelChangeTimelineItem struct{}
+
+func (labelChangeTimelineItem) ID(_ context.Context, obj *bug.LabelChangeTimelineItem) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (i labelChangeTimelineItem) Author(_ context.Context, obj *bug.LabelChangeTimelineItem) (models.IdentityWrapper, error) {
+ return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (labelChangeTimelineItem) Date(_ context.Context, obj *bug.LabelChangeTimelineItem) (*time.Time, error) {
+ t := obj.UnixTime.Time()
+ return &t, nil
+}
+
+var _ graph.SetStatusTimelineItemResolver = setStatusTimelineItem{}
+
+type setStatusTimelineItem struct{}
+
+func (setStatusTimelineItem) ID(_ context.Context, obj *bug.SetStatusTimelineItem) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (i setStatusTimelineItem) Author(_ context.Context, obj *bug.SetStatusTimelineItem) (models.IdentityWrapper, error) {
+ return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (setStatusTimelineItem) Date(_ context.Context, obj *bug.SetStatusTimelineItem) (*time.Time, error) {
+ t := obj.UnixTime.Time()
+ return &t, nil
+}
+
+func (setStatusTimelineItem) Status(_ context.Context, obj *bug.SetStatusTimelineItem) (models.Status, error) {
+ return convertStatus(obj.Status)
+}
+
+var _ graph.SetTitleTimelineItemResolver = setTitleTimelineItem{}
+
+type setTitleTimelineItem struct{}
+
+func (setTitleTimelineItem) ID(_ context.Context, obj *bug.SetTitleTimelineItem) (string, error) {
+ return obj.Id().String(), nil
+}
+
+func (i setTitleTimelineItem) Author(_ context.Context, obj *bug.SetTitleTimelineItem) (models.IdentityWrapper, error) {
+ return models.NewLoadedIdentity(obj.Author), nil
+}
+
+func (setTitleTimelineItem) Date(_ context.Context, obj *bug.SetTitleTimelineItem) (*time.Time, error) {
+ t := obj.UnixTime.Time()
+ return &t, nil
+}