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" }}