refactor: use go:embed for templates
Templates are now embedded using //go:embed and parsed once at startup with template.Must(template.ParseFS(...)). This avoids re-parsing template files from disk on every request and removes the dependency on template files being present at runtime. closes #7
This commit is contained in:
@@ -13,6 +13,7 @@ import (
|
||||
"sneak.berlin/go/webhooker/internal/healthcheck"
|
||||
"sneak.berlin/go/webhooker/internal/logger"
|
||||
"sneak.berlin/go/webhooker/internal/session"
|
||||
"sneak.berlin/go/webhooker/templates"
|
||||
)
|
||||
|
||||
// nolint:revive // HandlersParams is a standard fx naming convention
|
||||
@@ -26,11 +27,20 @@ type HandlersParams struct {
|
||||
}
|
||||
|
||||
type Handlers struct {
|
||||
params *HandlersParams
|
||||
log *slog.Logger
|
||||
hc *healthcheck.Healthcheck
|
||||
db *database.Database
|
||||
session *session.Session
|
||||
params *HandlersParams
|
||||
log *slog.Logger
|
||||
hc *healthcheck.Healthcheck
|
||||
db *database.Database
|
||||
session *session.Session
|
||||
templates map[string]*template.Template
|
||||
}
|
||||
|
||||
// parsePageTemplate parses a page-specific template set from the embedded FS.
|
||||
// Each page template is combined with the shared base, htmlheader, and navbar templates.
|
||||
func parsePageTemplate(pageFile string) *template.Template {
|
||||
return template.Must(
|
||||
template.ParseFS(templates.Templates, "htmlheader.html", "navbar.html", "base.html", pageFile),
|
||||
)
|
||||
}
|
||||
|
||||
func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) {
|
||||
@@ -40,9 +50,16 @@ func New(lc fx.Lifecycle, params HandlersParams) (*Handlers, error) {
|
||||
s.hc = params.Healthcheck
|
||||
s.db = params.Database
|
||||
s.session = params.Session
|
||||
|
||||
// Parse all page templates once at startup
|
||||
s.templates = map[string]*template.Template{
|
||||
"index.html": parsePageTemplate("index.html"),
|
||||
"login.html": parsePageTemplate("login.html"),
|
||||
"profile.html": parsePageTemplate("profile.html"),
|
||||
}
|
||||
|
||||
lc.Append(fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
// FIXME compile some templates here or something
|
||||
return nil
|
||||
},
|
||||
})
|
||||
@@ -80,16 +97,11 @@ type UserInfo struct {
|
||||
Username string
|
||||
}
|
||||
|
||||
// renderTemplate renders a template with common data
|
||||
func (s *Handlers) renderTemplate(w http.ResponseWriter, r *http.Request, templateFiles []string, data interface{}) {
|
||||
// Always include the common templates
|
||||
allTemplates := []string{"templates/htmlheader.html", "templates/navbar.html"}
|
||||
allTemplates = append(allTemplates, templateFiles...)
|
||||
|
||||
// Parse templates
|
||||
tmpl, err := template.ParseFiles(allTemplates...)
|
||||
if err != nil {
|
||||
s.log.Error("failed to parse template", "error", err)
|
||||
// renderTemplate renders a pre-parsed template with common data
|
||||
func (s *Handlers) renderTemplate(w http.ResponseWriter, r *http.Request, pageTemplate string, data interface{}) {
|
||||
tmpl, ok := s.templates[pageTemplate]
|
||||
if !ok {
|
||||
s.log.Error("template not found", "template", pageTemplate)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
@@ -108,6 +120,16 @@ func (s *Handlers) renderTemplate(w http.ResponseWriter, r *http.Request, templa
|
||||
}
|
||||
}
|
||||
|
||||
// If data is a map, merge user info into it
|
||||
if m, ok := data.(map[string]interface{}); ok {
|
||||
m["User"] = userInfo
|
||||
if err := tmpl.Execute(w, m); err != nil {
|
||||
s.log.Error("failed to execute template", "error", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Wrap data with base template data
|
||||
type templateDataWrapper struct {
|
||||
User *UserInfo
|
||||
@@ -119,17 +141,6 @@ func (s *Handlers) renderTemplate(w http.ResponseWriter, r *http.Request, templa
|
||||
Data: data,
|
||||
}
|
||||
|
||||
// If data is a map, merge user info into it
|
||||
if m, ok := data.(map[string]interface{}); ok {
|
||||
m["User"] = userInfo
|
||||
if err := tmpl.Execute(w, m); err != nil {
|
||||
s.log.Error("failed to execute template", "error", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise use wrapper
|
||||
if err := tmpl.Execute(w, wrapper); err != nil {
|
||||
s.log.Error("failed to execute template", "error", err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
|
||||
Reference in New Issue
Block a user