1package commands
  2
  3import (
  4	"bytes"
  5	"encoding/json"
  6	"fmt"
  7	"io/ioutil"
  8	"log"
  9	"net/http"
 10	"time"
 11
 12	"github.com/MichaelMure/git-bug/graphql"
 13	"github.com/MichaelMure/git-bug/repository"
 14	"github.com/MichaelMure/git-bug/util"
 15	"github.com/MichaelMure/git-bug/webui"
 16	"github.com/gorilla/mux"
 17	"github.com/phayes/freeport"
 18	"github.com/skratchdot/open-golang/open"
 19	"github.com/spf13/cobra"
 20	"github.com/vektah/gqlgen/handler"
 21)
 22
 23var port int
 24
 25func runWebUI(cmd *cobra.Command, args []string) error {
 26	if port == 0 {
 27		var err error
 28		port, err = freeport.GetFreePort()
 29		if err != nil {
 30			return err
 31		}
 32	}
 33
 34	addr := fmt.Sprintf("127.0.0.1:%d", port)
 35	webUiAddr := fmt.Sprintf("http://%s", addr)
 36
 37	fmt.Printf("Web UI: %s\n", webUiAddr)
 38	fmt.Printf("Graphql API: http://%s/graphql\n", addr)
 39	fmt.Printf("Graphql Playground: http://%s/playground\n", addr)
 40
 41	router := mux.NewRouter()
 42
 43	// Routes
 44	router.Path("/playground").Handler(handler.Playground("git-bug", "/graphql"))
 45	router.Path("/graphql").Handler(graphql.NewHandler(repo))
 46	router.Path("/gitfile/{hash}").Handler(newGitFileHandler(repo))
 47	router.Path("/upload").Methods("POST").Handler(newGitUploadFileHandler(repo))
 48	router.PathPrefix("/").Handler(http.FileServer(webui.WebUIAssets))
 49
 50	open.Run(webUiAddr)
 51
 52	log.Fatal(http.ListenAndServe(addr, router))
 53
 54	return nil
 55}
 56
 57type gitFileHandler struct {
 58	repo repository.Repo
 59}
 60
 61func newGitFileHandler(repo repository.Repo) http.Handler {
 62	return &gitFileHandler{
 63		repo: repo,
 64	}
 65}
 66
 67func (gfh *gitFileHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
 68	hash := util.Hash(mux.Vars(r)["hash"])
 69
 70	if !hash.IsValid() {
 71		http.Error(rw, "invalid git hash", http.StatusBadRequest)
 72		return
 73	}
 74
 75	// TODO: this mean that the whole file will he buffered in memory
 76	// This can be a problem for big files. There might be a way around
 77	// that by implementing a io.ReadSeeker that would read and discard
 78	// data when a seek is called.
 79	data, err := gfh.repo.ReadData(util.Hash(hash))
 80	if err != nil {
 81		http.Error(rw, err.Error(), http.StatusInternalServerError)
 82		return
 83	}
 84
 85	http.ServeContent(rw, r, "", time.Now(), bytes.NewReader(data))
 86}
 87
 88type gitUploadFileHandler struct {
 89	repo repository.Repo
 90}
 91
 92func newGitUploadFileHandler(repo repository.Repo) http.Handler {
 93	return &gitUploadFileHandler{
 94		repo: repo,
 95	}
 96}
 97
 98func (gufh *gitUploadFileHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
 99	// 100MB (github limit)
100	var maxUploadSize int64 = 100 * 1000 * 1000
101	r.Body = http.MaxBytesReader(rw, r.Body, maxUploadSize)
102	if err := r.ParseMultipartForm(maxUploadSize); err != nil {
103		http.Error(rw, "file too big (100MB max)", http.StatusBadRequest)
104		return
105	}
106
107	file, _, err := r.FormFile("uploadfile")
108	if err != nil {
109		http.Error(rw, "invalid file", http.StatusBadRequest)
110		return
111	}
112	defer file.Close()
113	fileBytes, err := ioutil.ReadAll(file)
114	if err != nil {
115		http.Error(rw, "invalid file", http.StatusBadRequest)
116		return
117	}
118
119	filetype := http.DetectContentType(fileBytes)
120	if filetype != "image/jpeg" && filetype != "image/jpg" &&
121		filetype != "image/gif" && filetype != "image/png" {
122		http.Error(rw, "invalid file type", http.StatusBadRequest)
123		return
124	}
125
126	hash, err := gufh.repo.StoreData(fileBytes)
127	if err != nil {
128		http.Error(rw, err.Error(), http.StatusInternalServerError)
129		return
130	}
131
132	type response struct {
133		Hash string `json:"hash"`
134	}
135
136	resp := response{Hash: string(hash)}
137
138	js, err := json.Marshal(resp)
139	if err != nil {
140		http.Error(rw, err.Error(), http.StatusInternalServerError)
141		return
142	}
143
144	rw.Header().Set("Content-Type", "application/json")
145	rw.Write(js)
146}
147
148var webUICmd = &cobra.Command{
149	Use:   "webui",
150	Short: "Launch the web UI",
151	RunE:  runWebUI,
152}
153
154func init() {
155	RootCmd.AddCommand(webUICmd)
156	webUICmd.Flags().IntVarP(&port, "port", "p", 0, "Port to listen to")
157}