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 /commands | |
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 'commands')
-rw-r--r-- | commands/webui.go | 176 |
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") - } |