github.com/machinebox/remoto@v0.1.2-0.20191024144331-eff21a7d321f/console/api/main.go (about) 1 package api 2 3 import ( 4 "archive/zip" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "path" 11 "path/filepath" 12 "strings" 13 "sync" 14 15 "github.com/machinebox/remoto/generator" 16 "github.com/machinebox/remoto/generator/definition" 17 "google.golang.org/appengine" 18 "google.golang.org/appengine/log" 19 ) 20 21 func init() { 22 http.HandleFunc("/api/templates", handleTemplates()) 23 http.HandleFunc("/api/all.zip", handleRenderAllTemplates()) 24 http.HandleFunc("/api/templates/", handleRenderTemplate()) 25 http.HandleFunc("/api/define", handleDefinitionDefine()) 26 } 27 28 // handleTemplates gets a list of available templates. 29 // GET /api/templates 30 func handleTemplates() http.HandlerFunc { 31 var init sync.Once 32 var err error 33 type template struct { 34 Name string `json:"name"` 35 Experimental bool `json:"x"` 36 Dirs []string `json:"dirs"` 37 Label string `json:"label"` 38 } 39 var response struct { 40 Templates []template `json:"templates"` 41 } 42 return func(w http.ResponseWriter, r *http.Request) { 43 init.Do(func() { 44 err = filepath.Walk("templates", func(path string, info os.FileInfo, err error) error { 45 if err != nil { 46 return err 47 } 48 if info.IsDir() { 49 return nil // skip directories 50 } 51 path, err = filepath.Rel("templates", path) 52 if err != nil { 53 return err 54 } 55 if !strings.Contains(path, "/") { 56 return nil // skip root files 57 } 58 if filepath.Ext(path) == ".plush" { 59 path = path[0 : len(path)-len(".plush")] 60 } 61 response.Templates = append(response.Templates, template{ 62 Name: path, 63 Label: filepath.Base(path), 64 Dirs: strings.Split(filepath.Dir(path), string(filepath.Separator)), 65 Experimental: strings.Contains(path, "x/"), 66 }) 67 return nil 68 }) 69 }) 70 if err != nil { 71 http.Error(w, err.Error(), http.StatusInternalServerError) 72 return 73 } 74 if r.Method != http.MethodGet { 75 http.NotFound(w, r) 76 return 77 } 78 w.Header().Set("Content-Type", "application/json") 79 if err := json.NewEncoder(w).Encode(response); err != nil { 80 http.Error(w, err.Error(), http.StatusInternalServerError) 81 return 82 } 83 } 84 } 85 86 func handleRenderTemplate() http.HandlerFunc { 87 return func(w http.ResponseWriter, r *http.Request) { 88 if r.Method != http.MethodPost { 89 http.NotFound(w, r) 90 return 91 } 92 templatePath := r.URL.Path[len("/api/"):] + ".plush" 93 tplBytes, err := ioutil.ReadFile(templatePath) 94 if err != nil { 95 http.Error(w, err.Error(), http.StatusBadRequest) 96 return 97 } 98 def, err := generator.Parse(strings.NewReader(r.FormValue("definition"))) 99 if err != nil { 100 http.Error(w, err.Error(), http.StatusInternalServerError) 101 return 102 } 103 filename := filepath.Base(templatePath) 104 filename = def.PackageName + "." + filename[0:len(filename)-len(filepath.Ext(templatePath))] 105 if r.URL.Query().Get("dl") == "1" { 106 w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename=%q`, filename)) 107 } 108 if err := generator.Render(w, templatePath, string(tplBytes), def); err != nil { 109 http.Error(w, err.Error(), http.StatusInternalServerError) 110 return 111 } 112 } 113 } 114 115 func handleRenderAllTemplates() http.HandlerFunc { 116 return func(w http.ResponseWriter, r *http.Request) { 117 ctx := appengine.NewContext(r) 118 if r.Method != http.MethodPost { 119 http.NotFound(w, r) 120 return 121 } 122 def, err := generator.Parse(strings.NewReader(r.FormValue("definition"))) 123 if err != nil { 124 http.Error(w, err.Error(), http.StatusBadRequest) 125 return 126 } 127 w.Header().Set("Content-Type", "application/zip, application/octet-stream") 128 w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename=%s.remoto.zip`, def.PackageName)) 129 ww := zip.NewWriter(w) 130 defer ww.Close() 131 err = filepath.Walk("templates", func(p string, info os.FileInfo, err error) error { 132 if err != nil { 133 return err 134 } 135 if info.IsDir() { // skip dirs 136 return nil 137 } 138 if filepath.Ext(p) != ".plush" { 139 return nil // skip non .plush files 140 } 141 templatePath := p 142 p = p[0 : len(p)-len(".plush")] // trim .plush off 143 zipPath, err := filepath.Rel("templates", p) 144 if err != nil { 145 return nil 146 } 147 zipPath = path.Join(def.PackageName, zipPath) 148 f, err := ww.Create(zipPath) 149 if err != nil { 150 return err 151 } 152 tplBytes, err := ioutil.ReadFile(templatePath) 153 if err != nil { 154 log.Warningf(ctx, "skipping %q %v", templatePath, err) 155 return nil 156 } 157 err = generator.Render(f, templatePath, string(tplBytes), def) 158 if err != nil { 159 return err 160 } 161 return nil 162 }) 163 if err != nil { 164 log.Errorf(ctx, "%v", err) 165 http.Error(w, err.Error(), http.StatusInternalServerError) 166 return 167 } 168 } 169 } 170 171 // handleDefinitionDefine checks the definition file, returning a JSON object 172 // with ok and error fields. 173 func handleDefinitionDefine() http.HandlerFunc { 174 type response struct { 175 OK bool `json:"ok"` 176 Error string `json:"error,omitempty"` 177 Definition *definition.Definition `json:"definition,omitempty"` 178 } 179 return func(w http.ResponseWriter, r *http.Request) { 180 source := r.FormValue("definition") 181 if source == "" { 182 http.Error(w, "definition missing", http.StatusInternalServerError) 183 return 184 } 185 var response response 186 response.OK = true 187 def, err := generator.Parse(strings.NewReader(source)) 188 if err != nil { 189 response.OK = false 190 response.Error = err.Error() 191 } else { 192 response.Definition = &def 193 if err := response.Definition.Valid(); err != nil { 194 response.OK = false 195 response.Error = err.Error() 196 } 197 } 198 w.Header().Set("Content-Type", "application/json") 199 if err := json.NewEncoder(w).Encode(response); err != nil { 200 http.Error(w, err.Error(), http.StatusInternalServerError) 201 return 202 } 203 } 204 }