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  }