github.com/machinebox/remoto@v0.1.2-0.20191024144331-eff21a7d321f/console/www/main.go (about) 1 package www 2 3 import ( 4 "bytes" 5 "crypto/md5" 6 "encoding/json" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net/http" 11 "strings" 12 "sync" 13 14 "github.com/gobuffalo/plush" 15 "github.com/machinebox/remoto/generator" 16 "github.com/oxtoacart/bpool" 17 "google.golang.org/appengine" 18 "google.golang.org/appengine/datastore" 19 "google.golang.org/appengine/log" 20 "google.golang.org/appengine/urlfetch" 21 ) 22 23 func init() { 24 s := &server{ 25 buffers: bpool.NewBufferPool(32), 26 } 27 http.HandleFunc("/definition", s.handleDefinitionSave()) 28 http.HandleFunc("/definition/", s.handleDefinitionView()) 29 http.HandleFunc("/", s.handleIndex()) 30 } 31 32 type server struct { 33 buffers *bpool.BufferPool 34 } 35 36 func (s *server) handleIndex() http.HandlerFunc { 37 var init sync.Once 38 var err error 39 var tpl *plush.Template 40 return func(w http.ResponseWriter, r *http.Request) { 41 if r.URL.Path != "/" { 42 http.NotFound(w, r) 43 return 44 } 45 ctx := appengine.NewContext(r) 46 init.Do(func() { 47 var b string 48 b, err = s.readFiles( 49 "templates/index.plush.html", 50 "templates/_layout.plush.html", 51 ) 52 if err != nil { 53 return 54 } 55 tpl, err = plush.NewTemplate(b) 56 if err != nil { 57 return 58 } 59 }) 60 if err != nil { 61 http.Error(w, err.Error(), http.StatusInternalServerError) 62 return 63 } 64 out, err := tpl.Exec(plush.NewContext()) 65 if err != nil { 66 http.Error(w, err.Error(), http.StatusInternalServerError) 67 return 68 } 69 if _, err := io.WriteString(w, out); err != nil { 70 log.Errorf(ctx, "%s", err) 71 } 72 } 73 } 74 75 // handleDefinitionView loads the definition with the ID from the path, 76 // and renders a page with generators. 77 func (s *server) handleDefinitionView() http.HandlerFunc { 78 var init sync.Once 79 var err error 80 var tpl *plush.Template 81 type template struct { 82 Name string `json:"name"` 83 Experimental bool `json:"x"` 84 Dirs []string `json:"dirs"` 85 Label string `json:"label"` 86 } 87 var templates struct { 88 Templates []template `json:"templates"` 89 } 90 return func(w http.ResponseWriter, r *http.Request) { 91 ctx := appengine.NewContext(r) 92 init.Do(func() { 93 var b string 94 b, err = s.readFiles( 95 "templates/definition.plush.html", 96 "templates/_layout.plush.html", 97 ) 98 if err != nil { 99 return 100 } 101 tpl, err = plush.NewTemplate(b) 102 if err != nil { 103 return 104 } 105 client := urlfetch.Client(ctx) 106 var resp *http.Response 107 var apihost string 108 apihost, err := appengine.ModuleHostname(ctx, "api", "", "") 109 if err != nil { 110 return 111 } 112 resp, err = client.Get("http://" + apihost + "/api/templates") 113 if err != nil { 114 return 115 } 116 defer resp.Body.Close() 117 err = json.NewDecoder(resp.Body).Decode(&templates) 118 if err != nil { 119 return 120 } 121 log.Debugf(ctx, "%v", templates) 122 }) 123 if err != nil { 124 http.Error(w, err.Error(), http.StatusInternalServerError) 125 return 126 } 127 sourceHash := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:] 128 log.Debugf(ctx, "sourceHash: %v", sourceHash) 129 entityKey := datastore.NewKey(ctx, kindDefinition, sourceHash, 0, nil) 130 var entity entityDefinition 131 err := datastore.Get(ctx, entityKey, &entity) 132 if err == datastore.ErrNoSuchEntity { 133 http.NotFound(w, r) 134 return 135 } 136 def, err := generator.Parse(bytes.NewReader(entity.Source)) 137 if err != nil { 138 http.Error(w, err.Error(), http.StatusInternalServerError) 139 return 140 } 141 plushCtx := plush.NewContextWithContext(ctx) 142 generator.AddTemplateHelpers(plushCtx) 143 plushCtx.Set("source", string(entity.Source)) 144 plushCtx.Set("def", def) 145 plushCtx.Set("templates", templates.Templates) 146 out, err := tpl.Exec(plushCtx) 147 if err != nil { 148 http.Error(w, err.Error(), http.StatusInternalServerError) 149 return 150 } 151 if _, err := io.WriteString(w, out); err != nil { 152 log.Errorf(ctx, "%s", err) 153 } 154 } 155 } 156 157 // handleDefinitionSave saves an incoming definition, and redirects to the 158 // view page for that definition. 159 func (s *server) handleDefinitionSave() http.HandlerFunc { 160 return func(w http.ResponseWriter, r *http.Request) { 161 ctx := appengine.NewContext(r) 162 source := r.FormValue("definition") 163 if source == "" { 164 http.Error(w, "definition missing", http.StatusInternalServerError) 165 return 166 } 167 _, err := generator.Parse(strings.NewReader(source)) 168 if err != nil { 169 http.Error(w, err.Error(), http.StatusBadRequest) 170 return 171 } 172 source = strings.TrimSpace(source) 173 entity := entityDefinition{ 174 Source: []byte(source), 175 } 176 sourceHash := fmt.Sprintf("%x", md5.Sum([]byte(source))) 177 entityKey := datastore.NewKey(ctx, kindDefinition, sourceHash, 0, nil) 178 if _, err := datastore.Put(ctx, entityKey, &entity); err != nil { 179 http.Error(w, err.Error(), http.StatusInternalServerError) 180 return 181 } 182 http.Redirect(w, r, "/definition/"+sourceHash, http.StatusFound) 183 } 184 } 185 186 // readFiles loads one or more files into a string. 187 func (s *server) readFiles(filenames ...string) (string, error) { 188 buf := s.buffers.Get() 189 defer s.buffers.Put(buf) 190 for _, file := range filenames { 191 b, err := ioutil.ReadFile(file) 192 if err != nil { 193 return "", err 194 } 195 if _, err := buf.Write(b); err != nil { 196 return "", err 197 } 198 if _, err := buf.WriteString("\n"); err != nil { 199 return "", err 200 } 201 } 202 return buf.String(), nil 203 } 204 205 type entityDefinition struct { 206 Key *datastore.Key `datastore:"-"` 207 Source []byte `datastore:",noindex"` 208 } 209 210 var kindDefinition = "Definition"