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