webui: add the http handlers for read/write git blob for media in comments

Michael Muré created

Change summary

commands/webui.go | 111 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 111 insertions(+)

Detailed changes

commands/webui.go 🔗

@@ -1,16 +1,22 @@
 package commands
 
 import (
+	"bytes"
+	"encoding/json"
 	"fmt"
 	"github.com/MichaelMure/git-bug/graphql"
+	"github.com/MichaelMure/git-bug/repository"
+	"github.com/MichaelMure/git-bug/util"
 	"github.com/MichaelMure/git-bug/webui"
 	"github.com/gorilla/mux"
 	"github.com/phayes/freeport"
 	"github.com/skratchdot/open-golang/open"
 	"github.com/spf13/cobra"
 	"github.com/vektah/gqlgen/handler"
+	"io/ioutil"
 	"log"
 	"net/http"
+	"time"
 )
 
 var port int
@@ -36,6 +42,8 @@ func runWebUI(cmd *cobra.Command, args []string) error {
 	// Routes
 	router.Path("/playground").Handler(handler.Playground("git-bug", "/graphql"))
 	router.Path("/graphql").Handler(graphql.NewHandler(repo))
+	router.Path("/gitfile/{hash}").Handler(newGitFileHandler(repo))
+	router.Path("/upload").Methods("POST").Handler(newGitUploadFileHandler(repo))
 	router.PathPrefix("/").Handler(http.FileServer(webui.WebUIAssets))
 
 	open.Run(webUiAddr)
@@ -45,6 +53,109 @@ func runWebUI(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
+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 := mux.Vars(r)["hash"]
+
+	if !isGitHash(hash) {
+		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(util.Hash(hash))
+	if err != nil {
+		http.Error(rw, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	http.ServeContent(rw, r, "", time.Now(), bytes.NewReader(data))
+}
+
+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) {
+	// 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")
+	rw.Write(js)
+}
+
+func isGitHash(s string) bool {
+	if len(s) != 40 {
+		return false
+	}
+	for _, r := range s {
+		if (r < 'a' || r > 'z') && (r < '0' || r > '9') {
+			return false
+		}
+	}
+	return true
+}
+
 var webUICmd = &cobra.Command{
 	Use:   "webui",
 	Short: "Launch the web UI",