feat: webhooker 1.0 MVP — entity rename, core engine, delivery, management UI #16
@@ -21,7 +21,7 @@ func (h *Handlers) HandleLoginPage() http.HandlerFunc {
|
|||||||
"Error": "",
|
"Error": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
h.renderTemplate(w, r, []string{"templates/base.html", "templates/login.html"}, data)
|
h.renderTemplate(w, r, "login.html", data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ func (h *Handlers) HandleLoginSubmit() http.HandlerFunc {
|
|||||||
"Error": "Username and password are required",
|
"Error": "Username and password are required",
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
h.renderTemplate(w, r, []string{"templates/base.html", "templates/login.html"}, data)
|
h.renderTemplate(w, r, "login.html", data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ func (h *Handlers) HandleLoginSubmit() http.HandlerFunc {
|
|||||||
"Error": "Invalid username or password",
|
"Error": "Invalid username or password",
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
h.renderTemplate(w, r, []string{"templates/base.html", "templates/login.html"}, data)
|
h.renderTemplate(w, r, "login.html", data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ func (h *Handlers) HandleLoginSubmit() http.HandlerFunc {
|
|||||||
"Error": "Invalid username or password",
|
"Error": "Invalid username or password",
|
||||||
}
|
}
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
h.renderTemplate(w, r, []string{"templates/base.html", "templates/login.html"}, data)
|
h.renderTemplate(w, r, "login.html", data)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"sneak.berlin/go/webhooker/internal/healthcheck"
|
"sneak.berlin/go/webhooker/internal/healthcheck"
|
||||||
"sneak.berlin/go/webhooker/internal/logger"
|
"sneak.berlin/go/webhooker/internal/logger"
|
||||||
"sneak.berlin/go/webhooker/internal/session"
|
"sneak.berlin/go/webhooker/internal/session"
|
||||||
|
"sneak.berlin/go/webhooker/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
// nolint:revive // HandlersParams is a standard fx naming convention
|
// nolint:revive // HandlersParams is a standard fx naming convention
|
||||||
@@ -26,11 +27,20 @@ type HandlersParams struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Handlers struct {
|
type Handlers struct {
|
||||||
params *HandlersParams
|
params *HandlersParams
|
||||||
log *slog.Logger
|
log *slog.Logger
|
||||||
hc *healthcheck.Healthcheck
|
hc *healthcheck.Healthcheck
|
||||||
db *database.Database
|
db *database.Database
|
||||||
session *session.Session
|
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) {
|
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.hc = params.Healthcheck
|
||||||
s.db = params.Database
|
s.db = params.Database
|
||||||
s.session = params.Session
|
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{
|
lc.Append(fx.Hook{
|
||||||
OnStart: func(ctx context.Context) error {
|
OnStart: func(ctx context.Context) error {
|
||||||
// FIXME compile some templates here or something
|
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -80,16 +97,11 @@ type UserInfo struct {
|
|||||||
Username string
|
Username string
|
||||||
}
|
}
|
||||||
|
|
||||||
// renderTemplate renders a template with common data
|
// renderTemplate renders a pre-parsed template with common data
|
||||||
func (s *Handlers) renderTemplate(w http.ResponseWriter, r *http.Request, templateFiles []string, data interface{}) {
|
func (s *Handlers) renderTemplate(w http.ResponseWriter, r *http.Request, pageTemplate string, data interface{}) {
|
||||||
// Always include the common templates
|
tmpl, ok := s.templates[pageTemplate]
|
||||||
allTemplates := []string{"templates/htmlheader.html", "templates/navbar.html"}
|
if !ok {
|
||||||
allTemplates = append(allTemplates, templateFiles...)
|
s.log.Error("template not found", "template", pageTemplate)
|
||||||
|
|
||||||
// Parse templates
|
|
||||||
tmpl, err := template.ParseFiles(allTemplates...)
|
|
||||||
if err != nil {
|
|
||||||
s.log.Error("failed to parse template", "error", err)
|
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
return
|
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
|
// Wrap data with base template data
|
||||||
type templateDataWrapper struct {
|
type templateDataWrapper struct {
|
||||||
User *UserInfo
|
User *UserInfo
|
||||||
@@ -119,17 +141,6 @@ func (s *Handlers) renderTemplate(w http.ResponseWriter, r *http.Request, templa
|
|||||||
Data: data,
|
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 {
|
if err := tmpl.Execute(w, wrapper); err != nil {
|
||||||
s.log.Error("failed to execute template", "error", err)
|
s.log.Error("failed to execute template", "error", err)
|
||||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||||
|
|||||||
@@ -87,10 +87,11 @@ func TestRenderTemplate(t *testing.T) {
|
|||||||
"Version": "1.0.0",
|
"Version": "1.0.0",
|
||||||
}
|
}
|
||||||
|
|
||||||
// When templates don't exist, renderTemplate should return an error
|
// When a non-existent template name is requested, renderTemplate
|
||||||
h.renderTemplate(w, req, []string{"nonexistent.html"}, data)
|
// should return an internal server error
|
||||||
|
h.renderTemplate(w, req, "nonexistent.html", data)
|
||||||
|
|
||||||
// Should return internal server error when template parsing fails
|
// Should return internal server error when template is not found
|
||||||
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
assert.Equal(t, http.StatusInternalServerError, w.Code)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func (s *Handlers) HandleIndex() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render the template
|
// Render the template
|
||||||
s.renderTemplate(w, req, []string{"templates/base.html", "templates/index.html"}, data)
|
s.renderTemplate(w, req, "index.html", data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,6 @@ func (h *Handlers) HandleProfile() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Render the profile page
|
// Render the profile page
|
||||||
h.renderTemplate(w, r, []string{"templates/base.html", "templates/profile.html"}, data)
|
h.renderTemplate(w, r, "profile.html", data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
templates/templates.go
Normal file
8
templates/templates.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package templates
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed *.html
|
||||||
|
var Templates embed.FS
|
||||||
Reference in New Issue
Block a user