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"