aboutsummaryrefslogtreecommitdiffstats
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
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
-rw-r--r--README.md2
-rw-r--r--api/auth/context.go28
-rw-r--r--api/auth/errors.go (renamed from graphql/graphqlidentity/errors.go)2
-rw-r--r--api/auth/middleware.go16
-rw-r--r--api/graphql/connections/connection_template.go (renamed from graphql/connections/connection_template.go)2
-rw-r--r--api/graphql/connections/connections.go (renamed from graphql/connections/connections.go)0
-rw-r--r--api/graphql/connections/edges.go (renamed from graphql/connections/edges.go)0
-rw-r--r--api/graphql/connections/gen_comment.go (renamed from graphql/connections/gen_comment.go)2
-rw-r--r--api/graphql/connections/gen_identity.go (renamed from graphql/connections/gen_identity.go)2
-rw-r--r--api/graphql/connections/gen_label.go (renamed from graphql/connections/gen_label.go)2
-rw-r--r--api/graphql/connections/gen_lazy_bug.go (renamed from graphql/connections/gen_lazy_bug.go)2
-rw-r--r--api/graphql/connections/gen_lazy_identity.go (renamed from graphql/connections/gen_lazy_identity.go)2
-rw-r--r--api/graphql/connections/gen_operation.go (renamed from graphql/connections/gen_operation.go)2
-rw-r--r--api/graphql/connections/gen_timeline.go (renamed from graphql/connections/gen_timeline.go)2
-rw-r--r--api/graphql/gen_graphql.go (renamed from graphql/gen_graphql.go)0
-rw-r--r--api/graphql/gqlgen.yml (renamed from graphql/gqlgen.yml)0
-rw-r--r--api/graphql/graph/gen_graph.go (renamed from graphql/graph/gen_graph.go)7
-rw-r--r--api/graphql/graphql_test.go (renamed from graphql/graphql_test.go)17
-rw-r--r--api/graphql/handler.go32
-rw-r--r--api/graphql/models/edges.go (renamed from graphql/models/edges.go)0
-rw-r--r--api/graphql/models/gen_models.go (renamed from graphql/models/gen_models.go)0
-rw-r--r--api/graphql/models/lazy_bug.go (renamed from graphql/models/lazy_bug.go)0
-rw-r--r--api/graphql/models/lazy_identity.go (renamed from graphql/models/lazy_identity.go)0
-rw-r--r--api/graphql/models/models.go (renamed from graphql/models/models.go)0
-rw-r--r--api/graphql/resolvers/bug.go (renamed from graphql/resolvers/bug.go)6
-rw-r--r--api/graphql/resolvers/color.go (renamed from graphql/resolvers/color.go)2
-rw-r--r--api/graphql/resolvers/comment.go (renamed from graphql/resolvers/comment.go)4
-rw-r--r--api/graphql/resolvers/identity.go (renamed from graphql/resolvers/identity.go)4
-rw-r--r--api/graphql/resolvers/label.go (renamed from graphql/resolvers/label.go)4
-rw-r--r--api/graphql/resolvers/mutation.go (renamed from graphql/resolvers/mutation.go)34
-rw-r--r--api/graphql/resolvers/operations.go (renamed from graphql/resolvers/operations.go)4
-rw-r--r--api/graphql/resolvers/query.go (renamed from graphql/resolvers/query.go)4
-rw-r--r--api/graphql/resolvers/repo.go (renamed from graphql/resolvers/repo.go)12
-rw-r--r--api/graphql/resolvers/root.go (renamed from graphql/resolvers/root.go)12
-rw-r--r--api/graphql/resolvers/timeline.go (renamed from graphql/resolvers/timeline.go)4
-rw-r--r--api/graphql/schema/bug.graphql (renamed from graphql/schema/bug.graphql)0
-rw-r--r--api/graphql/schema/identity.graphql (renamed from graphql/schema/identity.graphql)0
-rw-r--r--api/graphql/schema/label.graphql (renamed from graphql/schema/label.graphql)0
-rw-r--r--api/graphql/schema/mutations.graphql (renamed from graphql/schema/mutations.graphql)0
-rw-r--r--api/graphql/schema/operations.graphql (renamed from graphql/schema/operations.graphql)0
-rw-r--r--api/graphql/schema/repository.graphql (renamed from graphql/schema/repository.graphql)0
-rw-r--r--api/graphql/schema/root.graphql (renamed from graphql/schema/root.graphql)0
-rw-r--r--api/graphql/schema/timeline.graphql (renamed from graphql/schema/timeline.graphql)0
-rw-r--r--api/graphql/schema/types.graphql (renamed from graphql/schema/types.graphql)0
-rw-r--r--api/http/git_file_handler.go61
-rw-r--r--api/http/git_file_handlers_test.go91
-rw-r--r--api/http/git_file_upload_handler.go104
-rw-r--r--cache/multi_repo_cache.go16
-rw-r--r--cache/repo_cache.go10
-rw-r--r--commands/webui.go176
-rw-r--r--graphql/graphqlidentity/graphqlidentity.go41
-rw-r--r--graphql/handler.go39
-rw-r--r--webui/codegen.yaml2
-rw-r--r--webui/handler.go31
54 files changed, 472 insertions, 309 deletions
diff --git a/README.md b/README.md
index bb2750a2..52a734bf 100644
--- a/README.md
+++ b/README.md
@@ -174,7 +174,7 @@ You can launch a rich Web UI with `git bug webui`.
This web UI is entirely packed inside the same go binary and serve static content through a localhost http server.
-The web UI interact with the backend through a GraphQL API. The schema is available [here](graphql/).
+The web UI interact with the backend through a GraphQL API. The schema is available [here](api/graphql/schema).
## Bridges
diff --git a/api/auth/context.go b/api/auth/context.go
new file mode 100644
index 00000000..17171261
--- /dev/null
+++ b/api/auth/context.go
@@ -0,0 +1,28 @@
+// Package auth contains helpers for managing identities within the GraphQL API.
+package auth
+
+import (
+ "context"
+
+ "github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/entity"
+)
+
+// identityCtxKey is a unique context key, accessible only in this package.
+var identityCtxKey = &struct{}{}
+
+// CtxWithUser attaches an Identity to a context.
+func CtxWithUser(ctx context.Context, userId entity.Id) context.Context {
+ return context.WithValue(ctx, identityCtxKey, userId)
+}
+
+// UserFromCtx retrieves an IdentityCache from the context.
+// If there is no identity in the context, ErrNotAuthenticated is returned.
+// If an error occurs while resolving the identity (e.g. I/O error), then it will be returned.
+func UserFromCtx(ctx context.Context, r *cache.RepoCache) (*cache.IdentityCache, error) {
+ id, ok := ctx.Value(identityCtxKey).(entity.Id)
+ if !ok {
+ return nil, ErrNotAuthenticated
+ }
+ return r.ResolveIdentity(id)
+}
diff --git a/graphql/graphqlidentity/errors.go b/api/auth/errors.go
index 5ec58b32..9675afbf 100644
--- a/graphql/graphqlidentity/errors.go
+++ b/api/auth/errors.go
@@ -1,4 +1,4 @@
-package graphqlidentity
+package auth
import "errors"
diff --git a/api/auth/middleware.go b/api/auth/middleware.go
new file mode 100644
index 00000000..d1d654ce
--- /dev/null
+++ b/api/auth/middleware.go
@@ -0,0 +1,16 @@
+package auth
+
+import (
+ "net/http"
+
+ "github.com/MichaelMure/git-bug/entity"
+)
+
+func Middleware(fixedUserId entity.Id) func(http.Handler) http.Handler {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ ctx := CtxWithUser(r.Context(), fixedUserId)
+ next.ServeHTTP(w, r.WithContext(ctx))
+ })
+ }
+}
diff --git a/graphql/connections/connection_template.go b/api/graphql/connections/connection_template.go
index f276b2d0..935a0a77 100644
--- a/graphql/connections/connection_template.go
+++ b/api/graphql/connections/connection_template.go
@@ -5,7 +5,7 @@ import (
"github.com/cheekybits/genny/generic"
- "github.com/MichaelMure/git-bug/graphql/models"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
)
// Name define the name of the connection
diff --git a/graphql/connections/connections.go b/api/graphql/connections/connections.go
index 0083f8b2..0083f8b2 100644
--- a/graphql/connections/connections.go
+++ b/api/graphql/connections/connections.go
diff --git a/graphql/connections/edges.go b/api/graphql/connections/edges.go
index 4e37fcd9..4e37fcd9 100644
--- a/graphql/connections/edges.go
+++ b/api/graphql/connections/edges.go
diff --git a/graphql/connections/gen_comment.go b/api/graphql/connections/gen_comment.go
index 9f96f2bb..bae74030 100644
--- a/graphql/connections/gen_comment.go
+++ b/api/graphql/connections/gen_comment.go
@@ -7,8 +7,8 @@ package connections
import (
"fmt"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
"github.com/MichaelMure/git-bug/bug"
- "github.com/MichaelMure/git-bug/graphql/models"
)
// BugCommentEdgeMaker define a function that take a bug.Comment and an offset and
diff --git a/graphql/connections/gen_identity.go b/api/graphql/connections/gen_identity.go
index 061e8936..2138b17e 100644
--- a/graphql/connections/gen_identity.go
+++ b/api/graphql/connections/gen_identity.go
@@ -7,7 +7,7 @@ package connections
import (
"fmt"
- "github.com/MichaelMure/git-bug/graphql/models"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
)
// ModelsIdentityWrapperEdgeMaker define a function that take a models.IdentityWrapper and an offset and
diff --git a/graphql/connections/gen_label.go b/api/graphql/connections/gen_label.go
index 7f1b2fc9..39b1c536 100644
--- a/graphql/connections/gen_label.go
+++ b/api/graphql/connections/gen_label.go
@@ -7,8 +7,8 @@ package connections
import (
"fmt"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
"github.com/MichaelMure/git-bug/bug"
- "github.com/MichaelMure/git-bug/graphql/models"
)
// BugLabelEdgeMaker define a function that take a bug.Label and an offset and
diff --git a/graphql/connections/gen_lazy_bug.go b/api/graphql/connections/gen_lazy_bug.go
index 9638e86b..1dc4692e 100644
--- a/graphql/connections/gen_lazy_bug.go
+++ b/api/graphql/connections/gen_lazy_bug.go
@@ -7,8 +7,8 @@ package connections
import (
"fmt"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
"github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/graphql/models"
)
// EntityIdEdgeMaker define a function that take a entity.Id and an offset and
diff --git a/graphql/connections/gen_lazy_identity.go b/api/graphql/connections/gen_lazy_identity.go
index 932d802c..4996e219 100644
--- a/graphql/connections/gen_lazy_identity.go
+++ b/api/graphql/connections/gen_lazy_identity.go
@@ -7,8 +7,8 @@ package connections
import (
"fmt"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
"github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/graphql/models"
)
// EntityIdEdgeMaker define a function that take a entity.Id and an offset and
diff --git a/graphql/connections/gen_operation.go b/api/graphql/connections/gen_operation.go
index 0f40e2c4..4bd84895 100644
--- a/graphql/connections/gen_operation.go
+++ b/api/graphql/connections/gen_operation.go
@@ -7,8 +7,8 @@ package connections
import (
"fmt"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
"github.com/MichaelMure/git-bug/bug"
- "github.com/MichaelMure/git-bug/graphql/models"
)
// BugOperationEdgeMaker define a function that take a bug.Operation and an offset and
diff --git a/graphql/connections/gen_timeline.go b/api/graphql/connections/gen_timeline.go
index 01dac96b..952d095c 100644
--- a/graphql/connections/gen_timeline.go
+++ b/api/graphql/connections/gen_timeline.go
@@ -7,8 +7,8 @@ package connections
import (
"fmt"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
"github.com/MichaelMure/git-bug/bug"
- "github.com/MichaelMure/git-bug/graphql/models"
)
// BugTimelineItemEdgeMaker define a function that take a bug.TimelineItem and an offset and
diff --git a/graphql/gen_graphql.go b/api/graphql/gen_graphql.go
index 47f2c458..47f2c458 100644
--- a/graphql/gen_graphql.go
+++ b/api/graphql/gen_graphql.go
diff --git a/graphql/gqlgen.yml b/api/graphql/gqlgen.yml
index fe33b8c7..fe33b8c7 100644
--- a/graphql/gqlgen.yml
+++ b/api/graphql/gqlgen.yml
diff --git a/graphql/graph/gen_graph.go b/api/graphql/graph/gen_graph.go
index 277cb7cb..be0e92ee 100644
--- a/graphql/graph/gen_graph.go
+++ b/api/graphql/graph/gen_graph.go
@@ -15,11 +15,12 @@ import (
"github.com/99designs/gqlgen/graphql"
"github.com/99designs/gqlgen/graphql/introspection"
- "github.com/MichaelMure/git-bug/bug"
- "github.com/MichaelMure/git-bug/graphql/models"
- "github.com/MichaelMure/git-bug/util/git"
"github.com/vektah/gqlparser"
"github.com/vektah/gqlparser/ast"
+
+ "github.com/MichaelMure/git-bug/api/graphql/models"
+ "github.com/MichaelMure/git-bug/bug"
+ "github.com/MichaelMure/git-bug/util/git"
)
// region ************************** generated!.gotpl **************************
diff --git a/graphql/graphql_test.go b/api/graphql/graphql_test.go
index 0ff2c3fb..45e88e9a 100644
--- a/graphql/graphql_test.go
+++ b/api/graphql/graphql_test.go
@@ -4,8 +4,11 @@ import (
"testing"
"github.com/99designs/gqlgen/client"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
- "github.com/MichaelMure/git-bug/graphql/models"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
+ "github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/misc/random_bugs"
"github.com/MichaelMure/git-bug/repository"
)
@@ -16,10 +19,11 @@ func TestQueries(t *testing.T) {
random_bugs.FillRepoWithSeed(repo, 10, 42)
- handler, err := NewHandler(repo)
- if err != nil {
- t.Fatal(err)
- }
+ mrc := cache.NewMultiRepoCache()
+ _, err := mrc.RegisterDefaultRepository(repo)
+ require.NoError(t, err)
+
+ handler := NewHandler(mrc)
c := client.New(handler)
@@ -211,5 +215,6 @@ func TestQueries(t *testing.T) {
}
}
- c.MustPost(query, &resp)
+ err = c.Post(query, &resp)
+ assert.NoError(t, err)
}
diff --git a/api/graphql/handler.go b/api/graphql/handler.go
new file mode 100644
index 00000000..03dc32e9
--- /dev/null
+++ b/api/graphql/handler.go
@@ -0,0 +1,32 @@
+//go:generate go run gen_graphql.go
+
+// Package graphql contains the root GraphQL http handler
+package graphql
+
+import (
+ "io"
+ "net/http"
+
+ "github.com/99designs/gqlgen/graphql/handler"
+
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+ "github.com/MichaelMure/git-bug/api/graphql/resolvers"
+ "github.com/MichaelMure/git-bug/cache"
+)
+
+// Handler is the root GraphQL http handler
+type Handler struct {
+ http.Handler
+ io.Closer
+}
+
+func NewHandler(mrc *cache.MultiRepoCache) Handler {
+ rootResolver := resolvers.NewRootResolver(mrc)
+ config := graph.Config{Resolvers: rootResolver}
+ h := handler.NewDefaultServer(graph.NewExecutableSchema(config))
+
+ return Handler{
+ Handler: h,
+ Closer: rootResolver,
+ }
+}
diff --git a/graphql/models/edges.go b/api/graphql/models/edges.go
index 6a331e3e..6a331e3e 100644
--- a/graphql/models/edges.go
+++ b/api/graphql/models/edges.go
diff --git a/graphql/models/gen_models.go b/api/graphql/models/gen_models.go
index cbece6fe..cbece6fe 100644
--- a/graphql/models/gen_models.go
+++ b/api/graphql/models/gen_models.go
diff --git a/graphql/models/lazy_bug.go b/api/graphql/models/lazy_bug.go
index a7840df2..a7840df2 100644
--- a/graphql/models/lazy_bug.go
+++ b/api/graphql/models/lazy_bug.go
diff --git a/graphql/models/lazy_identity.go b/api/graphql/models/lazy_identity.go
index 344bb5f0..344bb5f0 100644
--- a/graphql/models/lazy_identity.go
+++ b/api/graphql/models/lazy_identity.go
diff --git a/graphql/models/models.go b/api/graphql/models/models.go
index 816a04a8..816a04a8 100644
--- a/graphql/models/models.go
+++ b/api/graphql/models/models.go
diff --git a/graphql/resolvers/bug.go b/api/graphql/resolvers/bug.go
index fd8f4b6e..815cba8d 100644
--- a/graphql/resolvers/bug.go
+++ b/api/graphql/resolvers/bug.go
@@ -3,10 +3,10 @@ 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"
- "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{}
diff --git a/graphql/resolvers/color.go b/api/graphql/resolvers/color.go
index 8dc13095..cfa411f8 100644
--- a/graphql/resolvers/color.go
+++ b/api/graphql/resolvers/color.go
@@ -4,7 +4,7 @@ import (
"context"
"image/color"
- "github.com/MichaelMure/git-bug/graphql/graph"
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
)
var _ graph.ColorResolver = &colorResolver{}
diff --git a/graphql/resolvers/comment.go b/api/graphql/resolvers/comment.go
index b142712a..5206e8a7 100644
--- a/graphql/resolvers/comment.go
+++ b/api/graphql/resolvers/comment.go
@@ -3,9 +3,9 @@ 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"
- "github.com/MichaelMure/git-bug/graphql/graph"
- "github.com/MichaelMure/git-bug/graphql/models"
)
var _ graph.CommentResolver = &commentResolver{}
diff --git a/graphql/resolvers/identity.go b/api/graphql/resolvers/identity.go
index b8aa72a7..69a32c98 100644
--- a/graphql/resolvers/identity.go
+++ b/api/graphql/resolvers/identity.go
@@ -3,8 +3,8 @@ package resolvers
import (
"context"
- "github.com/MichaelMure/git-bug/graphql/graph"
- "github.com/MichaelMure/git-bug/graphql/models"
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
+ "github.com/MichaelMure/git-bug/api/graphql/models"
)
var _ graph.IdentityResolver = &identityResolver{}
diff --git a/graphql/resolvers/label.go b/api/graphql/resolvers/label.go
index 0368a1e6..83e95029 100644
--- a/graphql/resolvers/label.go
+++ b/api/graphql/resolvers/label.go
@@ -5,9 +5,9 @@ import (
"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"
- "github.com/MichaelMure/git-bug/graphql/graph"
- "github.com/MichaelMure/git-bug/graphql/models"
)
var _ graph.LabelResolver = &labelResolver{}
diff --git a/graphql/resolvers/mutation.go b/api/graphql/resolvers/mutation.go
index 31505047..642a4fb9 100644
--- a/graphql/resolvers/mutation.go
+++ b/api/graphql/resolvers/mutation.go
@@ -4,11 +4,11 @@ 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"
- "github.com/MichaelMure/git-bug/graphql/graph"
- "github.com/MichaelMure/git-bug/graphql/graphqlidentity"
- "github.com/MichaelMure/git-bug/graphql/models"
)
var _ graph.MutationResolver = &mutationResolver{}
@@ -31,11 +31,11 @@ func (r mutationResolver) getBug(repoRef *string, bugPrefix string) (*cache.Repo
return nil, nil, err
}
- bug, err := repo.ResolveBugPrefix(bugPrefix)
+ b, err := repo.ResolveBugPrefix(bugPrefix)
if err != nil {
return nil, nil, err
}
- return repo, bug, nil
+ return repo, b, nil
}
func (r mutationResolver) NewBug(ctx context.Context, input models.NewBugInput) (*models.NewBugPayload, error) {
@@ -44,12 +44,12 @@ func (r mutationResolver) NewBug(ctx context.Context, input models.NewBugInput)
return nil, err
}
- id, err := graphqlidentity.ForContext(ctx, repo)
+ author, err := auth.UserFromCtx(ctx, repo)
if err != nil {
return nil, err
}
- b, op, err := repo.NewBugRaw(id, time.Now().Unix(), input.Title, input.Message, input.Files, nil)
+ b, op, err := repo.NewBugRaw(author, time.Now().Unix(), input.Title, input.Message, input.Files, nil)
if err != nil {
return nil, err
}
@@ -67,12 +67,12 @@ func (r mutationResolver) AddComment(ctx context.Context, input models.AddCommen
return nil, err
}
- id, err := graphqlidentity.ForContext(ctx, repo)
+ author, err := auth.UserFromCtx(ctx, repo)
if err != nil {
return nil, err
}
- op, err := b.AddCommentRaw(id, time.Now().Unix(), input.Message, input.Files, nil)
+ op, err := b.AddCommentRaw(author, time.Now().Unix(), input.Message, input.Files, nil)
if err != nil {
return nil, err
}
@@ -95,12 +95,12 @@ func (r mutationResolver) ChangeLabels(ctx context.Context, input *models.Change
return nil, err
}
- id, err := graphqlidentity.ForContext(ctx, repo)
+ author, err := auth.UserFromCtx(ctx, repo)
if err != nil {
return nil, err
}
- results, op, err := b.ChangeLabelsRaw(id, time.Now().Unix(), input.Added, input.Removed, nil)
+ results, op, err := b.ChangeLabelsRaw(author, time.Now().Unix(), input.Added, input.Removed, nil)
if err != nil {
return nil, err
}
@@ -129,12 +129,12 @@ func (r mutationResolver) OpenBug(ctx context.Context, input models.OpenBugInput
return nil, err
}
- id, err := graphqlidentity.ForContext(ctx, repo)
+ author, err := auth.UserFromCtx(ctx, repo)
if err != nil {
return nil, err
}
- op, err := b.OpenRaw(id, time.Now().Unix(), nil)
+ op, err := b.OpenRaw(author, time.Now().Unix(), nil)
if err != nil {
return nil, err
}
@@ -157,12 +157,12 @@ func (r mutationResolver) CloseBug(ctx context.Context, input models.CloseBugInp
return nil, err
}
- id, err := graphqlidentity.ForContext(ctx, repo)
+ author, err := auth.UserFromCtx(ctx, repo)
if err != nil {
return nil, err
}
- op, err := b.CloseRaw(id, time.Now().Unix(), nil)
+ op, err := b.CloseRaw(author, time.Now().Unix(), nil)
if err != nil {
return nil, err
}
@@ -185,12 +185,12 @@ func (r mutationResolver) SetTitle(ctx context.Context, input models.SetTitleInp
return nil, err
}
- id, err := graphqlidentity.ForContext(ctx, repo)
+ author, err := auth.UserFromCtx(ctx, repo)
if err != nil {
return nil, err
}
- op, err := b.SetTitleRaw(id, time.Now().Unix(), input.Title, nil)
+ op, err := b.SetTitleRaw(author, time.Now().Unix(), input.Title, nil)
if err != nil {
return nil, err
}
diff --git a/graphql/resolvers/operations.go b/api/graphql/resolvers/operations.go
index 29110cf3..8d3e5bba 100644
--- a/graphql/resolvers/operations.go
+++ b/api/graphql/resolvers/operations.go
@@ -5,9 +5,9 @@ import (
"fmt"
"time"
+ "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/graphql/graph"
- "github.com/MichaelMure/git-bug/graphql/models"
)
var _ graph.CreateOperationResolver = createOperationResolver{}
diff --git a/graphql/resolvers/query.go b/api/graphql/resolvers/query.go
index 6fb18638..4ad7ae0c 100644
--- a/graphql/resolvers/query.go
+++ b/api/graphql/resolvers/query.go
@@ -3,9 +3,9 @@ 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"
- "github.com/MichaelMure/git-bug/graphql/graph"
- "github.com/MichaelMure/git-bug/graphql/models"
)
var _ graph.QueryResolver = &rootQueryResolver{}
diff --git a/graphql/resolvers/repo.go b/api/graphql/resolvers/repo.go
index 009ccab6..5d96428e 100644
--- a/graphql/resolvers/repo.go
+++ b/api/graphql/resolvers/repo.go
@@ -3,12 +3,12 @@ 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/graphql/connections"
- "github.com/MichaelMure/git-bug/graphql/graph"
- "github.com/MichaelMure/git-bug/graphql/graphqlidentity"
- "github.com/MichaelMure/git-bug/graphql/models"
"github.com/MichaelMure/git-bug/query"
)
@@ -151,8 +151,8 @@ func (repoResolver) Identity(_ context.Context, obj *models.Repository, prefix s
}
func (repoResolver) UserIdentity(ctx context.Context, obj *models.Repository) (models.IdentityWrapper, error) {
- id, err := graphqlidentity.ForContext(ctx, obj.Repo)
- if err == graphqlidentity.ErrNotAuthenticated {
+ id, err := auth.UserFromCtx(ctx, obj.Repo)
+ if err == auth.ErrNotAuthenticated {
return nil, nil
} else if err != nil {
return nil, err
diff --git a/graphql/resolvers/root.go b/api/graphql/resolvers/root.go
index 9973ff59..bb3bf5cf 100644
--- a/graphql/resolvers/root.go
+++ b/api/graphql/resolvers/root.go
@@ -2,31 +2,31 @@
package resolvers
import (
+ "github.com/MichaelMure/git-bug/api/graphql/graph"
"github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/graphql/graph"
)
var _ graph.ResolverRoot = &RootResolver{}
type RootResolver struct {
- cache.MultiRepoCache
+ *cache.MultiRepoCache
}
-func NewRootResolver() *RootResolver {
+func NewRootResolver(mrc *cache.MultiRepoCache) *RootResolver {
return &RootResolver{
- MultiRepoCache: cache.NewMultiRepoCache(),
+ MultiRepoCache: mrc,
}
}
func (r RootResolver) Query() graph.QueryResolver {
return &rootQueryResolver{
- cache: &r.MultiRepoCache,
+ cache: r.MultiRepoCache,
}
}
func (r RootResolver) Mutation() graph.MutationResolver {
return &mutationResolver{
- cache: &r.MultiRepoCache,
+ cache: r.MultiRepoCache,
}
}
diff --git a/graphql/resolvers/timeline.go b/api/graphql/resolvers/timeline.go
index acf236f8..3223b3a0 100644
--- a/graphql/resolvers/timeline.go
+++ b/api/graphql/resolvers/timeline.go
@@ -4,9 +4,9 @@ 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"
- "github.com/MichaelMure/git-bug/graphql/graph"
- "github.com/MichaelMure/git-bug/graphql/models"
)
var _ graph.CommentHistoryStepResolver = commentHistoryStepResolver{}
diff --git a/graphql/schema/bug.graphql b/api/graphql/schema/bug.graphql
index 03aa95b8..03aa95b8 100644
--- a/graphql/schema/bug.graphql
+++ b/api/graphql/schema/bug.graphql
diff --git a/graphql/schema/identity.graphql b/api/graphql/schema/identity.graphql
index 93154a90..93154a90 100644
--- a/graphql/schema/identity.graphql
+++ b/api/graphql/schema/identity.graphql
diff --git a/graphql/schema/label.graphql b/api/graphql/schema/label.graphql
index 1205915c..1205915c 100644
--- a/graphql/schema/label.graphql
+++ b/api/graphql/schema/label.graphql
diff --git a/graphql/schema/mutations.graphql b/api/graphql/schema/mutations.graphql
index e6b70faf..e6b70faf 100644
--- a/graphql/schema/mutations.graphql
+++ b/api/graphql/schema/mutations.graphql
diff --git a/graphql/schema/operations.graphql b/api/graphql/schema/operations.graphql
index 18e0929c..18e0929c 100644
--- a/graphql/schema/operations.graphql
+++ b/api/graphql/schema/operations.graphql
diff --git a/graphql/schema/repository.graphql b/api/graphql/schema/repository.graphql
index 2b98fe37..2b98fe37 100644
--- a/graphql/schema/repository.graphql
+++ b/api/graphql/schema/repository.graphql
diff --git a/graphql/schema/root.graphql b/api/graphql/schema/root.graphql
index 94a0b530..94a0b530 100644
--- a/graphql/schema/root.graphql
+++ b/api/graphql/schema/root.graphql
diff --git a/graphql/schema/timeline.graphql b/api/graphql/schema/timeline.graphql
index 12462aa3..12462aa3 100644
--- a/graphql/schema/timeline.graphql
+++ b/api/graphql/schema/timeline.graphql
diff --git a/graphql/schema/types.graphql b/api/graphql/schema/types.graphql
index 0182885e..0182885e 100644
--- a/graphql/schema/types.graphql
+++ b/api/graphql/schema/types.graphql
diff --git a/api/http/git_file_handler.go b/api/http/git_file_handler.go
new file mode 100644
index 00000000..6bd6fa85
--- /dev/null
+++ b/api/http/git_file_handler.go
@@ -0,0 +1,61 @@
+package http
+
+import (
+ "bytes"
+ "net/http"
+ "time"
+
+ "github.com/gorilla/mux"
+
+ "github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/util/git"
+)
+
+// implement a http.Handler that will read and server git blob.
+//
+// Expected gorilla/mux parameters:
+// - "repo" : the ref of the repo or "" for the default one
+// - "hash" : the git hash of the file to retrieve
+type gitFileHandler struct {
+ mrc *cache.MultiRepoCache
+}
+
+func NewGitFileHandler(mrc *cache.MultiRepoCache) http.Handler {
+ return &gitFileHandler{mrc: mrc}
+}
+
+func (gfh *gitFileHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+ var repo *cache.RepoCache
+ var err error
+
+ repoVar := mux.Vars(r)["repo"]
+ switch repoVar {
+ case "":
+ repo, err = gfh.mrc.DefaultRepo()
+ default:
+ repo, err = gfh.mrc.ResolveRepo(repoVar)
+ }
+
+ if err != nil {
+ http.Error(rw, "invalid repo reference", http.StatusBadRequest)
+ return
+ }
+
+ hash := git.Hash(mux.Vars(r)["hash"])
+ if !hash.IsValid() {
+ http.Error(rw, "invalid git hash", http.StatusBadRequest)
+ return
+ }
+
+ // TODO: this mean that the whole file will he buffered in memory
+ // This can be a problem for big files. There might be a way around
+ // that by implementing a io.ReadSeeker that would read and discard
+ // data when a seek is called.
+ data, err := repo.ReadData(hash)
+ if err != nil {
+ http.Error(rw, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ http.ServeContent(rw, r, "", time.Now(), bytes.NewReader(data))
+}
diff --git a/api/http/git_file_handlers_test.go b/api/http/git_file_handlers_test.go
new file mode 100644
index 00000000..81d97d61
--- /dev/null
+++ b/api/http/git_file_handlers_test.go
@@ -0,0 +1,91 @@
+package http
+
+import (
+ "bytes"
+ "image"
+ "image/png"
+ "mime/multipart"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/gorilla/mux"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/MichaelMure/git-bug/api/auth"
+ "github.com/MichaelMure/git-bug/cache"
+ "github.com/MichaelMure/git-bug/repository"
+)
+
+func TestGitFileHandlers(t *testing.T) {
+ repo := repository.CreateTestRepo(false)
+ defer repository.CleanupTestRepos(repo)
+
+ mrc := cache.NewMultiRepoCache()
+ repoCache, err := mrc.RegisterDefaultRepository(repo)
+ require.NoError(t, err)
+
+ author, err := repoCache.NewIdentity("test identity", "test@test.org")
+ require.NoError(t, err)
+
+ err = repoCache.SetUserIdentity(author)
+ require.NoError(t, err)
+
+ // UPLOAD
+
+ uploadHandler := NewGitUploadFileHandler(mrc)
+
+ img := image.NewNRGBA(image.Rect(0, 0, 50, 50))
+ data := &bytes.Buffer{}
+ err = png.Encode(data, img)
+ require.NoError(t, err)
+
+ body := &bytes.Buffer{}
+ writer := multipart.NewWriter(body)
+ part, err := writer.CreateFormFile("uploadfile", "noname")
+ assert.NoError(t, err)
+
+ _, err = part.Write(data.Bytes())
+ assert.NoError(t, err)
+
+ err = writer.Close()
+ assert.NoError(t, err)
+
+ w := httptest.NewRecorder()
+ r, _ := http.NewRequest("GET", "/", body)
+ r.Header.Add("Content-Type", writer.FormDataContentType())
+
+ // Simulate auth
+ r = r.WithContext(auth.CtxWithUser(r.Context(), author.Id()))
+
+ // Handler's params
+ r = mux.SetURLVars(r, map[string]string{
+ "repo": "",
+ })
+
+ uploadHandler.ServeHTTP(w, r)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Equal(t, `{"hash":"3426a1488292d8f3f3c59ca679681336542b986f"}`, w.Body.String())
+ // DOWNLOAD
+
+ downloadHandler := NewGitFileHandler(mrc)
+
+ w = httptest.NewRecorder()
+ r, _ = http.NewRequest("GET", "/", nil)
+
+ // Simulate auth
+ r = r.WithContext(auth.CtxWithUser(r.Context(), author.Id()))
+
+ // Handler's params
+ r = mux.SetURLVars(r, map[string]string{
+ "repo": "",
+ "hash": "3426a1488292d8f3f3c59ca679681336542b986f",
+ })
+
+ downloadHandler.ServeHTTP(w, r)
+ assert.Equal(t, http.StatusOK, w.Code)
+
+ assert.Equal(t, data.Bytes(), w.Body.Bytes())
+}
diff --git a/api/http/git_file_upload_handler.go b/api/http/git_file_upload_handler.go
new file mode 100644
index 00000000..1702b8b1
--- /dev/null
+++ b/api/http/git_file_upload_handler.go
@@ -0,0 +1,104 @@
+package http
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+
+ "github.com/gorilla/mux"
+
+ "github.com/MichaelMure/git-bug/api/auth"
+ "github.com/MichaelMure/git-bug/cache"
+)
+
+// implement a http.Handler that will accept and store content into git blob.
+//
+// Expected gorilla/mux parameters:
+// - "repo" : the ref of the repo or "" for the default one
+type gitUploadFileHandler struct {
+ mrc *cache.MultiRepoCache
+}
+
+func NewGitUploadFileHandler(mrc *cache.MultiRepoCache) http.Handler {
+ return &gitUploadFileHandler{mrc: mrc}
+}
+
+func (gufh *gitUploadFileHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
+ var repo *cache.RepoCache
+ var err error
+
+ repoVar := mux.Vars(r)["repo"]
+ switch repoVar {
+ case "":
+ repo, err = gufh.mrc.DefaultRepo()
+ default:
+ repo, err = gufh.mrc.ResolveRepo(repoVar)
+ }
+
+ if err != nil {
+ http.Error(rw, "invalid repo reference", http.StatusBadRequest)
+ return
+ }
+
+ _, err = auth.UserFromCtx(r.Context(), repo)
+ if err == auth.ErrNotAuthenticated {
+ http.Error(rw, "read-only mode or not logged in", http.StatusForbidden)
+ return
+ } else if err != nil {
+ http.Error(rw, fmt.Sprintf("loading identity: %v", err), http.StatusInternalServerError)
+ return
+ }
+
+ // 100MB (github limit)
+ var maxUploadSize int64 = 100 * 1000 * 1000
+ r.Body = http.MaxBytesReader(rw, r.Body, maxUploadSize)
+ if err := r.ParseMultipartForm(maxUploadSize); err != nil {
+ http.Error(rw, "file too big (100MB max)", http.StatusBadRequest)
+ return
+ }
+
+ file, _, err := r.FormFile("uploadfile")
+ if err != nil {
+ http.Error(rw, "invalid file", http.StatusBadRequest)
+ return
+ }
+ defer file.Close()
+ fileBytes, err := ioutil.ReadAll(file)
+ if err != nil {
+ http.Error(rw, "invalid file", http.StatusBadRequest)
+ return
+ }
+
+ filetype := http.DetectContentType(fileBytes)
+ if filetype != "image/jpeg" && filetype != "image/jpg" &&
+ filetype != "image/gif" && filetype != "image/png" {
+ http.Error(rw, "invalid file type", http.StatusBadRequest)
+ return
+ }
+
+ hash, err := repo.StoreData(fileBytes)
+ if err != nil {
+ http.Error(rw, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ type response struct {
+ Hash string `json:"hash"`
+ }
+
+ resp := response{Hash: string(hash)}
+
+ js, err := json.Marshal(resp)
+ if err != nil {
+ http.Error(rw, err.Error(), http.StatusInternalServerError)
+ return
+ }
+
+ rw.Header().Set("Content-Type", "application/json")
+ _, err = rw.Write(js)
+ if err != nil {
+ http.Error(rw, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
diff --git a/cache/multi_repo_cache.go b/cache/multi_repo_cache.go
index a55bbcce..726558d9 100644
--- a/cache/multi_repo_cache.go
+++ b/cache/multi_repo_cache.go
@@ -13,32 +13,32 @@ type MultiRepoCache struct {
repos map[string]*RepoCache
}
-func NewMultiRepoCache() MultiRepoCache {
- return MultiRepoCache{
+func NewMultiRepoCache() *MultiRepoCache {
+ return &MultiRepoCache{
repos: make(map[string]*RepoCache),
}
}
// RegisterRepository register a named repository. Use this for multi-repo setup
-func (c *MultiRepoCache) RegisterRepository(ref string, repo repository.ClockedRepo) error {
+func (c *MultiRepoCache) RegisterRepository(ref string, repo repository.ClockedRepo) (*RepoCache, error) {
r, err := NewRepoCache(repo)
if err != nil {
- return err
+ return nil, err
}
c.repos[ref] = r
- return nil
+ return r, nil
}
// RegisterDefaultRepository register a unnamed repository. Use this for mono-repo setup
-func (c *MultiRepoCache) RegisterDefaultRepository(repo repository.ClockedRepo) error {
+func (c *MultiRepoCache) RegisterDefaultRepository(repo repository.ClockedRepo) (*RepoCache, error) {
r, err := NewRepoCache(repo)
if err != nil {
- return err
+ return nil, err
}
c.repos[""] = r
- return nil
+ return r, nil
}
// DefaultRepo retrieve the default repository
diff --git a/cache/repo_cache.go b/cache/repo_cache.go
index 4a6b007f..92760bbb 100644
--- a/cache/repo_cache.go
+++ b/cache/repo_cache.go
@@ -142,6 +142,16 @@ func (c *RepoCache) GetUserEmail() (string, error) {
return c.repo.GetUserEmail()
}
+// ReadData will attempt to read arbitrary data from the given hash
+func (c *RepoCache) ReadData(hash git.Hash) ([]byte, error) {
+ return c.repo.ReadData(hash)
+}
+
+// StoreData will store arbitrary data and return the corresponding hash
+func (c *RepoCache) StoreData(data []byte) (git.Hash, error) {
+ return c.repo.StoreData(data)
+}
+
func (c *RepoCache) lock() error {
lockPath := repoLockFilePath(c.repo)
diff --git a/commands/webui.go b/commands/webui.go
index c07f74fd..83480e08 100644
--- a/commands/webui.go
+++ b/commands/webui.go
@@ -1,11 +1,8 @@
package commands
import (
- "bytes"
"context"
- "encoding/json"
"fmt"
- "io/ioutil"
"log"
"net/http"
"os"
@@ -18,11 +15,12 @@ import (
"github.com/skratchdot/open-golang/open"
"github.com/spf13/cobra"
- "github.com/MichaelMure/git-bug/graphql"
- "github.com/MichaelMure/git-bug/graphql/graphqlidentity"
+ "github.com/MichaelMure/git-bug/api/auth"
+ "github.com/MichaelMure/git-bug/api/graphql"
+ httpapi "github.com/MichaelMure/git-bug/api/http"
+ "github.com/MichaelMure/git-bug/cache"
"github.com/MichaelMure/git-bug/identity"
"github.com/MichaelMure/git-bug/repository"
- "github.com/MichaelMure/git-bug/util/git"
"github.com/MichaelMure/git-bug/webui"
)
@@ -35,15 +33,6 @@ var (
const webUIOpenConfigKey = "git-bug.webui.open"
-func authMiddleware(id *identity.Identity) func(http.Handler) http.Handler {
- return func(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- ctx := graphqlidentity.AttachToContext(r.Context(), id)
- next.ServeHTTP(w, r.WithContext(ctx))
- })
- }
-}
-
func runWebUI(cmd *cobra.Command, args []string) error {
if webUIPort == 0 {
var err error
@@ -53,38 +42,36 @@ func runWebUI(cmd *cobra.Command, args []string) error {
}
}
- var id *identity.Identity
+ addr := fmt.Sprintf("127.0.0.1:%d", webUIPort)
+ webUiAddr := fmt.Sprintf("http://%s", addr)
+
+ router := mux.NewRouter()
+
+ // If the webUI is not read-only, use an authentication middleware with a
+ // fixed identity: the default user of the repo
+ // TODO: support dynamic authentication with OAuth
if !webUIReadOnly {
- // Verify that we have an identity.
- var err error
- id, err = identity.GetUserIdentity(repo)
+ author, err := identity.GetUserIdentity(repo)
if err != nil {
return err
}
+ router.Use(auth.Middleware(author.Id()))
}
- addr := fmt.Sprintf("127.0.0.1:%d", webUIPort)
- webUiAddr := fmt.Sprintf("http://%s", addr)
-
- router := mux.NewRouter()
-
- graphqlHandler, err := graphql.NewHandler(repo)
+ mrc := cache.NewMultiRepoCache()
+ _, err := mrc.RegisterDefaultRepository(repo)
if err != nil {
return err
}
- assetsHandler := &fileSystemWithDefault{
- FileSystem: webui.WebUIAssets,
- defaultFile: "index.html",
- }
+ graphqlHandler := graphql.NewHandler(mrc)
// Routes
router.Path("/playground").Handler(playground.Handler("git-bug", "/graphql"))
router.Path("/graphql").Handler(graphqlHandler)
- router.Path("/gitfile/{hash}").Handler(newGitFileHandler(repo))
- router.Path("/upload").Methods("POST").Handler(newGitUploadFileHandler(repo))
- router.PathPrefix("/").Handler(http.FileServer(assetsHandler))
- router.Use(authMiddleware(id))
+ router.Path("/gitfile/{repo}/{hash}").Handler(httpapi.NewGitFileHandler(mrc))
+ router.Path("/upload/{repo}").Methods("POST").Handler(httpapi.NewGitUploadFileHandler(mrc))
+ router.PathPrefix("/").Handler(webui.NewHandler())
srv := &http.Server{
Addr: addr,
@@ -151,128 +138,6 @@ func runWebUI(cmd *cobra.Command, args []string) error {
return nil
}
-// implement a http.FileSystem that will serve a default file when the looked up
-// file doesn't exist. Useful for Single-Page App that implement routing client
-// side, where the server has to return the root index.html file for every route.
-type fileSystemWithDefault struct {
- http.FileSystem
- defaultFile string
-}
-
-func (fswd *fileSystemWithDefault) Open(name string) (http.File, error) {
- f, err := fswd.FileSystem.Open(name)
- if os.IsNotExist(err) {
- return fswd.FileSystem.Open(fswd.defaultFile)
- }
- return f, err
-}
-
-// implement a http.Handler that will read and server git blob.
-type gitFileHandler struct {
- repo repository.Repo
-}
-
-func newGitFileHandler(repo repository.Repo) http.Handler {
- return &gitFileHandler{
- repo: repo,
- }
-}
-
-func (gfh *gitFileHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
- hash := git.Hash(mux.Vars(r)["hash"])
-
- if !hash.IsValid() {
- http.Error(rw, "invalid git hash", http.StatusBadRequest)
- return
- }
-
- // TODO: this mean that the whole file will he buffered in memory
- // This can be a problem for big files. There might be a way around
- // that by implementing a io.ReadSeeker that would read and discard
- // data when a seek is called.
- data, err := gfh.repo.ReadData(git.Hash(hash))
- if err != nil {
- http.Error(rw, err.Error(), http.StatusInternalServerError)
- return
- }
-
- http.ServeContent(rw, r, "", time.Now(), bytes.NewReader(data))
-}
-
-// implement a http.Handler that will accept and store content into git blob.
-type gitUploadFileHandler struct {
- repo repository.Repo
-}
-
-func newGitUploadFileHandler(repo repository.Repo) http.Handler {
- return &gitUploadFileHandler{
- repo: repo,
- }
-}
-
-func (gufh *gitUploadFileHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
- _, err := graphqlidentity.ForContextUncached(r.Context(), gufh.repo)
- if err == graphqlidentity.ErrNotAuthenticated {
- http.Error(rw, fmt.Sprintf("read-only mode or not logged in"), http.StatusForbidden)
- return
- } else if err != nil {
- http.Error(rw, fmt.Sprintf("loading identity: %v", err), http.StatusInternalServerError)
- return
- }
-
- // 100MB (github limit)
- var maxUploadSize int64 = 100 * 1000 * 1000
- r.Body = http.MaxBytesReader(rw, r.Body, maxUploadSize)
- if err := r.ParseMultipartForm(maxUploadSize); err != nil {
- http.Error(rw, "file too big (100MB max)", http.StatusBadRequest)
- return
- }
-
- file, _, err := r.FormFile("uploadfile")
- if err != nil {
- http.Error(rw, "invalid file", http.StatusBadRequest)
- return
- }
- defer file.Close()
- fileBytes, err := ioutil.ReadAll(file)
- if err != nil {
- http.Error(rw, "invalid file", http.StatusBadRequest)
- return
- }
-
- filetype := http.DetectContentType(fileBytes)
- if filetype != "image/jpeg" && filetype != "image/jpg" &&
- filetype != "image/gif" && filetype != "image/png" {
- http.Error(rw, "invalid file type", http.StatusBadRequest)
- return
- }
-
- hash, err := gufh.repo.StoreData(fileBytes)
- if err != nil {
- http.Error(rw, err.Error(), http.StatusInternalServerError)
- return
- }
-
- type response struct {
- Hash string `json:"hash"`
- }
-
- resp := response{Hash: string(hash)}
-
- js, err := json.Marshal(resp)
- if err != nil {
- http.Error(rw, err.Error(), http.StatusInternalServerError)
- return
- }
-
- rw.Header().Set("Content-Type", "application/json")
- _, err = rw.Write(js)
- if err != nil {
- http.Error(rw, err.Error(), http.StatusInternalServerError)
- return
- }
-}
-
var webUICmd = &cobra.Command{
Use: "webui",
Short: "Launch the web UI.",
@@ -294,5 +159,4 @@ func init() {
webUICmd.Flags().BoolVar(&webUINoOpen, "no-open", false, "Prevent the automatic opening of the web UI in the default browser")
webUICmd.Flags().IntVarP(&webUIPort, "port", "p", 0, "Port to listen to (default is random)")
webUICmd.Flags().BoolVar(&webUIReadOnly, "read-only", false, "Whether to run the web UI in read-only mode")
-
}
diff --git a/graphql/graphqlidentity/graphqlidentity.go b/graphql/graphqlidentity/graphqlidentity.go
deleted file mode 100644
index 36b496f3..00000000
--- a/graphql/graphqlidentity/graphqlidentity.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// Package graphqlidentity contains helpers for managing identities within the GraphQL API.
-package graphqlidentity
-
-import (
- "context"
-
- "github.com/MichaelMure/git-bug/cache"
- "github.com/MichaelMure/git-bug/entity"
- "github.com/MichaelMure/git-bug/identity"
- "github.com/MichaelMure/git-bug/repository"
-)
-
-// identityCtxKey is a unique context key, accessible only in this package.
-var identityCtxKey = &struct{}{}
-
-// AttachToContext attaches an Identity to a context.
-func AttachToContext(ctx context.Context, u *identity.Identity) context.Context {
- return context.WithValue(ctx, identityCtxKey, u.Id())
-}
-
-// ForContext retrieves an IdentityCache from the context.
-// If there is no identity in the context, ErrNotAuthenticated is returned.
-// If an error occurs while resolving the identity (e.g. I/O error), then it will be returned.
-func ForContext(ctx context.Context, r *cache.RepoCache) (*cache.IdentityCache, error) {
- id, ok := ctx.Value(identityCtxKey).(entity.Id)
- if !ok {
- return nil, ErrNotAuthenticated
- }
- return r.ResolveIdentity(id)
-}
-
-// ForContextUncached retrieves an Identity from the context.
-// If there is no identity in the context, ErrNotAuthenticated is returned.
-// If an error occurs while resolving the identity (e.g. I/O error), then it will be returned.
-func ForContextUncached(ctx context.Context, repo repository.Repo) (*identity.Identity, error) {
- id, ok := ctx.Value(identityCtxKey).(entity.Id)
- if !ok {
- return nil, ErrNotAuthenticated
- }
- return identity.ReadLocal(repo, id)
-}
diff --git a/graphql/handler.go b/graphql/handler.go
deleted file mode 100644
index 55ef6fc4..00000000
--- a/graphql/handler.go
+++ /dev/null
@@ -1,39 +0,0 @@
-//go:generate go run gen_graphql.go
-
-// Package graphql contains the root GraphQL http handler
-package graphql
-
-import (
- "net/http"
-
- "github.com/99designs/gqlgen/graphql/handler"
-
- "github.com/MichaelMure/git-bug/graphql/graph"
- "github.com/MichaelMure/git-bug/graphql/resolvers"
- "github.com/MichaelMure/git-bug/repository"
-)
-
-// Handler is the root GraphQL http handler
-type Handler struct {
- http.Handler
- *resolvers.RootResolver
-}
-
-func NewHandler(repo repository.ClockedRepo) (Handler, error) {
- h := Handler{
- RootResolver: resolvers.NewRootResolver(),
- }
-
- err := h.RootResolver.RegisterDefaultRepository(repo)
- if err != nil {
- return Handler{}, err
- }
-
- config := graph.Config{
- Resolvers: h.RootResolver,
- }
-
- h.Handler = handler.NewDefaultServer(graph.NewExecutableSchema(config))
-
- return h, nil
-}
diff --git a/webui/codegen.yaml b/webui/codegen.yaml
index 3cdb0517..1c2a91a8 100644
--- a/webui/codegen.yaml
+++ b/webui/codegen.yaml
@@ -1,4 +1,4 @@
-schema: '../graphql/schema/*.graphql'
+schema: '../api/graphql/schema/*.graphql'
overwrite: true
documents: src/**/*.graphql
generates:
diff --git a/webui/handler.go b/webui/handler.go
new file mode 100644
index 00000000..476a46cf
--- /dev/null
+++ b/webui/handler.go
@@ -0,0 +1,31 @@
+package webui
+
+import (
+ "net/http"
+ "os"
+)
+
+// implement a http.FileSystem that will serve a default file when the looked up
+// file doesn't exist. Useful for Single-Page App that implement routing client
+// side, where the server has to return the root index.html file for every route.
+type fileSystemWithDefault struct {
+ http.FileSystem
+ defaultFile string
+}
+
+func (fswd *fileSystemWithDefault) Open(name string) (http.File, error) {
+ f, err := fswd.FileSystem.Open(name)
+ if os.IsNotExist(err) {
+ return fswd.FileSystem.Open(fswd.defaultFile)
+ }
+ return f, err
+}
+
+func NewHandler() http.Handler {
+ assetsHandler := &fileSystemWithDefault{
+ FileSystem: WebUIAssets,
+ defaultFile: "index.html",
+ }
+
+ return http.FileServer(assetsHandler)
+}