diff options
author | Michael Muré <batolettre@gmail.com> | 2020-06-21 22:12:04 +0200 |
---|---|---|
committer | Michael Muré <batolettre@gmail.com> | 2020-06-27 23:03:05 +0200 |
commit | 2ab6381a94d55fa22b80acdbb18849d6b24951f9 (patch) | |
tree | 99942b000955623ea7466b9fa4cc7dab37645df6 /api/graphql/resolvers | |
parent | 5f72b04ef8e84b1c367ca6874519706318e351f5 (diff) | |
download | git-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.go | 190 | ||||
-rw-r--r-- | api/graphql/resolvers/color.go | 24 | ||||
-rw-r--r-- | api/graphql/resolvers/comment.go | 17 | ||||
-rw-r--r-- | api/graphql/resolvers/identity.go | 21 | ||||
-rw-r--r-- | api/graphql/resolvers/label.go | 45 | ||||
-rw-r--r-- | api/graphql/resolvers/mutation.go | 208 | ||||
-rw-r--r-- | api/graphql/resolvers/operations.go | 132 | ||||
-rw-r--r-- | api/graphql/resolvers/query.go | 48 | ||||
-rw-r--r-- | api/graphql/resolvers/repo.go | 188 | ||||
-rw-r--r-- | api/graphql/resolvers/root.go | 107 | ||||
-rw-r--r-- | api/graphql/resolvers/timeline.go | 118 |
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 +} |