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}