From 2f1f1ef7d66523abf1f9f89e8a19d135431f52e3 Mon Sep 17 00:00:00 2001 From: Vladimir Zagainov Date: Thu, 4 Jun 2026 06:51:11 +0300 Subject: [PATCH] fix: embed HTML templates into binary via go:embed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switched from runtime file-based template loading (os.Stat + ParseGlob) to compile-time embedding (embed.FS + ParseFS). Templates are now bundled into the binary — no need to COPY them into Docker image. This fixes the fallback response issue where h.loaded was always false because templates weren't present in the container filesystem. --- internal/templates/templates.go | 77 +++++++++------------------------ 1 file changed, 21 insertions(+), 56 deletions(-) diff --git a/internal/templates/templates.go b/internal/templates/templates.go index 60c2a8f..041efcf 100644 --- a/internal/templates/templates.go +++ b/internal/templates/templates.go @@ -2,16 +2,18 @@ package templates import ( + "embed" "html/template" "log" "net/http" - "os" - "path/filepath" "gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/config" "gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/database" ) +//go:embed html/*.html +var templateFS embed.FS + // pageData is passed to all templates. type pageData struct { Title string @@ -21,13 +23,12 @@ type pageData struct { // Handler serves template-rendered pages. type Handler struct { - db *database.DB - cfg *config.Config - tmpl *template.Template - loaded bool + db *database.DB + cfg *config.Config + tmpl *template.Template } -// NewHandler creates a new templates handler and parses on-disk templates. +// NewHandler creates a new templates handler and parses embedded templates. func NewHandler(db *database.DB, cfg *config.Config) *Handler { h := &Handler{db: db, cfg: cfg} h.parseTemplates() @@ -49,79 +50,43 @@ func (h *Handler) index(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - if !h.loaded { - fallback(w, "MrixsCraft") - return - } h.render(w, "index.html", pageData{Title: "Главная"}) } func (h *Handler) loginPage(w http.ResponseWriter, r *http.Request) { - if !h.loaded { - fallback(w, "Login") - return - } h.render(w, "login.html", pageData{Title: "Вход"}) } func (h *Handler) registerPage(w http.ResponseWriter, r *http.Request) { - if !h.loaded { - fallback(w, "Register") - return - } h.render(w, "register.html", pageData{Title: "Регистрация"}) } func (h *Handler) profilePage(w http.ResponseWriter, r *http.Request) { - if !h.loaded { - fallback(w, "Profile") - return - } h.render(w, "profile.html", pageData{Title: "Профиль"}) } -// render executes the base layout template with the given content page. -// The base.html layout calls {{template "content" .}} which is defined in each page file. +// render executes the base layout template which calls {{template "content" .}} +// to inject the named page content. func (h *Handler) render(w http.ResponseWriter, page string, data pageData) { w.Header().Set("Content-Type", "text/html; charset=utf-8") + if h.tmpl == nil { + http.Error(w, "Templates not loaded", http.StatusInternalServerError) + return + } if err := h.tmpl.ExecuteTemplate(w, "base.html", data); err != nil { log.Printf("Template error (base.html → %s): %v", page, err) - // Fallback: render the page without layout - if err2 := h.tmpl.ExecuteTemplate(w, page, data); err2 != nil { - http.Error(w, "Internal Server Error", http.StatusInternalServerError) - } + http.Error(w, "Internal Server Error", http.StatusInternalServerError) } } -// fallback writes a minimal placeholder when templates are missing. -func fallback(w http.ResponseWriter, title string) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("

" + title + "

")) -} - // ── Template parsing ─────────────────────────────────────────── func (h *Handler) parseTemplates() { - dirs := []string{ - filepath.Join("internal", "templates", "html"), - "templates", + tmpl, err := template.New("").Funcs(template.FuncMap{}).ParseFS(templateFS, "html/*.html") + if err != nil { + log.Printf("Template parse error: %v", err) + return } - for _, dir := range dirs { - if _, err := os.Stat(dir); err != nil { - continue - } - pattern := filepath.Join(dir, "*.html") - tmpl, err := template.New("").Funcs(template.FuncMap{}).ParseGlob(pattern) - if err != nil { - log.Printf("Template parse error in %s: %v", dir, err) - continue - } - if tmpl != nil { - h.tmpl = tmpl - h.loaded = true - log.Printf("Loaded templates from %s", dir) - return - } - } - log.Println("No HTML templates found; using placeholder responses") + h.tmpl = tmpl + log.Println("Loaded embedded HTML templates") }