From 94dcb5bd75faa4adaef9f56df197a42a5e978718 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Thu, 29 Mar 2018 21:26:12 +0200 Subject: [PATCH] Use static assets for HTML templates - implement custom http.Filesystem boardvoting.AssetFS - replace "footer" and "header" with "footer.html" and "header.html" - change renderTemplate to use Assets - use boardvoting.GetAssetFS() with http.Fileserver --- boardvoting.go | 36 +++-- boardvoting/main.go | 140 ++++++++++++++++++ boardvoting/templates/create_motion_form.html | 4 +- boardvoting/templates/denied.html | 6 +- boardvoting/templates/direct_vote_form.html | 4 +- boardvoting/templates/edit_motion_form.html | 4 +- boardvoting/templates/footer.html | 2 +- boardvoting/templates/header.html | 2 +- boardvoting/templates/motion.html | 4 +- boardvoting/templates/motion_fragments.html | 4 +- boardvoting/templates/motions.html | 4 +- boardvoting/templates/proxy_vote_form.html | 4 +- .../templates/withdraw_motion_form.html | 4 +- 13 files changed, 184 insertions(+), 34 deletions(-) diff --git a/boardvoting.go b/boardvoting.go index c1fa28f..a0d6359 100644 --- a/boardvoting.go +++ b/boardvoting.go @@ -10,7 +10,7 @@ import ( "encoding/pem" "flag" "fmt" - + "git.cacert.org/cacert-boardvoting/boardvoting" "github.com/Masterminds/sprig" "github.com/gorilla/sessions" _ "github.com/mattn/go-sqlite3" @@ -35,21 +35,31 @@ var log *logging.Logger const sessionCookieName = "votesession" -func getTemplateFilenames(templates []string) (result []string) { - result = make([]string, len(templates)) - for i := range templates { - result[i] = fmt.Sprintf("templates/%s", templates[i]) - } - return result -} - func renderTemplate(w http.ResponseWriter, templates []string, context interface{}) { funcMaps := sprig.FuncMap() funcMaps["nl2br"] = func(text string) template.HTML { return template.HTML(strings.Replace(template.HTMLEscapeString(text), "\n", "
", -1)) } - t := template.Must(template.New(templates[0]).Funcs(funcMaps).ParseFiles(getTemplateFilenames(templates)...)) - if err := t.Execute(w, context); err != nil { + + var baseTemplate *template.Template + + for count, t := range templates { + if assetBytes, err := boardvoting.Asset(fmt.Sprintf("templates/%s", t)); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } else { + if count == 0 { + if baseTemplate, err = template.New(t).Funcs(funcMaps).Parse(string(assetBytes)); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } else { + if _, err := baseTemplate.New(t).Funcs(funcMaps).Parse(string(assetBytes)); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + } + } + } + + if err := baseTemplate.Execute(w, context); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } } @@ -733,7 +743,7 @@ func readConfig() { } func setupDbConfig(ctx context.Context) { - database, err := sql.Open("sqlite3", config.DatabaseFile) + database, err := sql.Open("sqlite3", config.DatabaseFile) if err != nil { log.Panicf("Opening database failed: %v", err) } @@ -777,7 +787,7 @@ func setupHandlers() { http.Handle("/newmotion/", motionsHandler{}) http.Handle("/proxy/", &decisionVoteHandler{}) http.Handle("/vote/", &decisionVoteHandler{}) - http.Handle("/static/", http.FileServer(http.Dir("."))) + http.Handle("/static/", http.FileServer(boardvoting.GetAssetFS())) http.Handle("/", http.RedirectHandler("/motions/", http.StatusMovedPermanently)) } diff --git a/boardvoting/main.go b/boardvoting/main.go index 5c8e6eb..7cc6005 100644 --- a/boardvoting/main.go +++ b/boardvoting/main.go @@ -1,3 +1,143 @@ package boardvoting //go:generate go-bindata -pkg $GOPACKAGE -o assets.go ./migrations/... ./static/... ./templates/... + +import ( + "bytes" + "errors" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + "time" +) + +var defaultFileTimestamp = time.Now() + +type AssetFS struct { + Asset func(path string) ([]byte, error) + AssetDir func(path string) ([]string, error) + AssetInfo func(path string) (os.FileInfo, error) +} + +type FakeFile struct { + Path string + Dir bool + Len int64 + Timestamp time.Time +} + +func (f *FakeFile) Name() string { + _, name := filepath.Split(f.Path) + return name +} + +func (f *FakeFile) Size() int64 { return f.Len } + +func (f *FakeFile) Mode() os.FileMode { + mode := os.FileMode(0644) + if f.Dir { + return mode | os.ModeDir + } + return mode +} + +func (f *FakeFile) ModTime() time.Time { return f.Timestamp } +func (f *FakeFile) IsDir() bool { return f.Dir } +func (f *FakeFile) Sys() interface{} { return nil } + +type AssetFile struct { + *bytes.Reader + io.Closer + FakeFile +} + +func NewAssetFile(name string, content []byte, timestamp time.Time) *AssetFile { + if timestamp.IsZero() { + timestamp = defaultFileTimestamp + } + return &AssetFile{ + bytes.NewReader(content), + ioutil.NopCloser(nil), + FakeFile{name, false, int64(len(content)), timestamp}, + } +} + +func (f *AssetFile) Readdir(count int) ([]os.FileInfo, error) { + return nil, errors.New("not a directory") +} + +func (f *AssetFile) Size() int64 { + return f.FakeFile.Size() +} + +func (f *AssetFile) Stat() (os.FileInfo, error) { + return f, nil +} + +type AssetDirectory struct { + AssetFile + ChildrenRead int + Children []os.FileInfo +} + +func (f *AssetDirectory) Readdir(count int) ([]os.FileInfo, error) { + if count <= 0 { + return f.Children, nil + } + if f.ChildrenRead+count > len(f.Children) { + count = len(f.Children) - f.ChildrenRead + } + rv := f.Children[f.ChildrenRead : f.ChildrenRead+count] + f.ChildrenRead += count + return rv, nil +} + +func (f *AssetDirectory) Stat() (os.FileInfo, error) { + return f, nil +} + +func NewAssetDirectory(name string, children []string, fs *AssetFS) *AssetDirectory { + fileInfos := make([]os.FileInfo, 0, len(children)) + for _, child := range children { + _, err := fs.AssetDir(filepath.Join(name, child)) + fileInfos = append(fileInfos, &FakeFile{child, err == nil, 0, time.Time{}}) + } + return &AssetDirectory{ + AssetFile{ + bytes.NewReader(nil), + ioutil.NopCloser(nil), + FakeFile{name, true, 0, time.Time{}}, + }, + 0, + fileInfos} +} + +func (f *AssetFS) Open(name string) (http.File, error) { + if len(name) > 0 && name[0] == '/' { + name = name[1:] + } + if b, err := f.Asset(name); err == nil { + timestamp := defaultFileTimestamp + if f.AssetInfo != nil { + if info, err := f.AssetInfo(name); err == nil { + timestamp = info.ModTime() + } + } + return NewAssetFile(name, b, timestamp), nil + } + if children, err := f.AssetDir(name); err == nil { + return NewAssetDirectory(name, children, f), nil + } else { + if strings.Contains(err.Error(), "not found") { + return nil, os.ErrNotExist + } + return nil, err + } +} + +func GetAssetFS() *AssetFS { + return &AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo} +} diff --git a/boardvoting/templates/create_motion_form.html b/boardvoting/templates/create_motion_form.html index b3cbadb..5958aa7 100644 --- a/boardvoting/templates/create_motion_form.html +++ b/boardvoting/templates/create_motion_form.html @@ -1,4 +1,4 @@ -{{ template "header" . }} +{{ template "header.html" . }}
-{{ template "footer" . }} \ No newline at end of file +{{ template "footer.html" . }} \ No newline at end of file diff --git a/boardvoting/templates/denied.html b/boardvoting/templates/denied.html index 398a36f..53bb364 100644 --- a/boardvoting/templates/denied.html +++ b/boardvoting/templates/denied.html @@ -1,7 +1,7 @@ -{{ template "header" . }} +{{ template "header.html" . }}
-
You are not authorized to act here!
+
You are not authorized to act here!

If you think this is in error, please contact the administrator.

If you don't know who that is, it is definitely not an error ;)

{{ if .Emails }} @@ -14,4 +14,4 @@ {{ end }}
-{{ template "footer" . }} \ No newline at end of file +{{ template "footer.html" . }} \ No newline at end of file diff --git a/boardvoting/templates/direct_vote_form.html b/boardvoting/templates/direct_vote_form.html index 861c68a..649c059 100644 --- a/boardvoting/templates/direct_vote_form.html +++ b/boardvoting/templates/direct_vote_form.html @@ -1,4 +1,4 @@ -{{ template "header" . }} +{{ template "header.html" . }}
-{{ template "footer" . }} \ No newline at end of file +{{ template "footer.html" . }} \ No newline at end of file diff --git a/boardvoting/templates/edit_motion_form.html b/boardvoting/templates/edit_motion_form.html index f686183..845442d 100644 --- a/boardvoting/templates/edit_motion_form.html +++ b/boardvoting/templates/edit_motion_form.html @@ -1,4 +1,4 @@ -{{ template "header" . }} +{{ template "header.html" . }} -{{ template "footer" . }} \ No newline at end of file +{{ template "footer.html" . }} \ No newline at end of file diff --git a/boardvoting/templates/footer.html b/boardvoting/templates/footer.html index f2bc089..fa873e5 100644 --- a/boardvoting/templates/footer.html +++ b/boardvoting/templates/footer.html @@ -1,4 +1,4 @@ -{{ define "footer" }} +{{ define "footer.html" }}