Luke Granger-Brown
e21db7a061
We add the hash of the file to the static assets, so they can be cached indefinitely. This also, however, means that we need some way of referring to them.
101 lines
2.5 KiB
Go
101 lines
2.5 KiB
Go
// SPDX-FileCopyrightText: 2021 Luke Granger-Brown <depot@lukegb.com>
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package fuphttp
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/fs"
|
|
"net/http"
|
|
|
|
"github.com/google/safehtml"
|
|
"github.com/google/safehtml/template"
|
|
"github.com/google/safehtml/template/uncheckedconversions"
|
|
shuncheckedconversions "github.com/google/safehtml/uncheckedconversions"
|
|
"github.com/gorilla/mux"
|
|
"hg.lukegb.com/lukegb/depot/web/fup/hashfs"
|
|
)
|
|
|
|
type Config struct {
|
|
Templates fs.FS
|
|
Static fs.FS
|
|
StaticRoot safehtml.TrustedResourceURL
|
|
}
|
|
|
|
type Application struct {
|
|
indexTmpl *template.Template
|
|
notFoundTmpl *template.Template
|
|
}
|
|
|
|
func (a *Application) Handler() http.Handler {
|
|
r := mux.NewRouter()
|
|
|
|
r.NotFoundHandler = http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
|
rw.WriteHeader(http.StatusNotFound)
|
|
a.notFoundTmpl.Execute(rw, nil)
|
|
})
|
|
|
|
return r
|
|
}
|
|
|
|
func parseTemplate(t *template.Template, fsys fs.FS, name string) (*template.Template, error) {
|
|
bs, err := fs.ReadFile(fsys, name)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading template %q: %w", name, err)
|
|
}
|
|
|
|
return t.ParseFromTrustedTemplate(
|
|
uncheckedconversions.TrustedTemplateFromStringKnownToSatisfyTypeContract(string(bs)),
|
|
)
|
|
}
|
|
|
|
func loadTemplate(fsys fs.FS, name string, funcs template.FuncMap) (*template.Template, error) {
|
|
t := template.New(name).Funcs(funcs)
|
|
var err error
|
|
if t, err = parseTemplate(t, fsys, "base.html"); err != nil {
|
|
return nil, fmt.Errorf("loading base template: %w", err)
|
|
}
|
|
if t, err = parseTemplate(t, fsys, fmt.Sprintf("%s.html", name)); err != nil {
|
|
return nil, fmt.Errorf("loading leaf template: %w", err)
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
func New(ctx context.Context, cfg *Config) (*Application, error) {
|
|
a := new(Application)
|
|
|
|
tmpls := []struct {
|
|
t **template.Template
|
|
name string
|
|
}{
|
|
{&a.indexTmpl, "index"},
|
|
{&a.notFoundTmpl, "404"},
|
|
}
|
|
|
|
funcMap := template.FuncMap{
|
|
"static": func(s string) safehtml.TrustedResourceURL {
|
|
staticPath := s
|
|
if fs, ok := cfg.Static.(*hashfs.FS); ok {
|
|
sp, ok := fs.LookupHashedName(staticPath)
|
|
if ok {
|
|
staticPath = sp
|
|
} else {
|
|
log.Printf("warning: couldn't find static file %v", staticPath)
|
|
}
|
|
}
|
|
return shuncheckedconversions.TrustedResourceURLFromStringKnownToSatisfyTypeContract(cfg.StaticRoot.String() + staticPath)
|
|
},
|
|
}
|
|
|
|
for _, tmpl := range tmpls {
|
|
t, err := loadTemplate(cfg.Templates, tmpl.name, funcMap)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("loading template %q: %w", tmpl.name, err)
|
|
}
|
|
*tmpl.t = t
|
|
}
|
|
|
|
return a, nil
|
|
}
|