aboutsummaryrefslogtreecommitdiffstats
path: root/commands/webui.go
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 /commands/webui.go
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 'commands/webui.go')
-rw-r--r--commands/webui.go176
1 files changed, 20 insertions, 156 deletions
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")
-
}