fix: embed HTML templates into binary via go:embed
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.
This commit is contained in:
@@ -2,16 +2,18 @@
|
|||||||
package templates
|
package templates
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"embed"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/config"
|
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/config"
|
||||||
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/database"
|
"gitea.mrixs.me/Mrixs/MrixsCraft-server/internal/database"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//go:embed html/*.html
|
||||||
|
var templateFS embed.FS
|
||||||
|
|
||||||
// pageData is passed to all templates.
|
// pageData is passed to all templates.
|
||||||
type pageData struct {
|
type pageData struct {
|
||||||
Title string
|
Title string
|
||||||
@@ -24,10 +26,9 @@ type Handler struct {
|
|||||||
db *database.DB
|
db *database.DB
|
||||||
cfg *config.Config
|
cfg *config.Config
|
||||||
tmpl *template.Template
|
tmpl *template.Template
|
||||||
loaded bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
func NewHandler(db *database.DB, cfg *config.Config) *Handler {
|
||||||
h := &Handler{db: db, cfg: cfg}
|
h := &Handler{db: db, cfg: cfg}
|
||||||
h.parseTemplates()
|
h.parseTemplates()
|
||||||
@@ -49,79 +50,43 @@ func (h *Handler) index(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !h.loaded {
|
|
||||||
fallback(w, "MrixsCraft")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.render(w, "index.html", pageData{Title: "Главная"})
|
h.render(w, "index.html", pageData{Title: "Главная"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) loginPage(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) loginPage(w http.ResponseWriter, r *http.Request) {
|
||||||
if !h.loaded {
|
|
||||||
fallback(w, "Login")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.render(w, "login.html", pageData{Title: "Вход"})
|
h.render(w, "login.html", pageData{Title: "Вход"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) registerPage(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) registerPage(w http.ResponseWriter, r *http.Request) {
|
||||||
if !h.loaded {
|
|
||||||
fallback(w, "Register")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.render(w, "register.html", pageData{Title: "Регистрация"})
|
h.render(w, "register.html", pageData{Title: "Регистрация"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) profilePage(w http.ResponseWriter, r *http.Request) {
|
func (h *Handler) profilePage(w http.ResponseWriter, r *http.Request) {
|
||||||
if !h.loaded {
|
|
||||||
fallback(w, "Profile")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
h.render(w, "profile.html", pageData{Title: "Профиль"})
|
h.render(w, "profile.html", pageData{Title: "Профиль"})
|
||||||
}
|
}
|
||||||
|
|
||||||
// render executes the base layout template with the given content page.
|
// render executes the base layout template which calls {{template "content" .}}
|
||||||
// The base.html layout calls {{template "content" .}} which is defined in each page file.
|
// to inject the named page content.
|
||||||
func (h *Handler) render(w http.ResponseWriter, page string, data pageData) {
|
func (h *Handler) render(w http.ResponseWriter, page string, data pageData) {
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
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 {
|
if err := h.tmpl.ExecuteTemplate(w, "base.html", data); err != nil {
|
||||||
log.Printf("Template error (base.html → %s): %v", page, err)
|
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("<h1>" + title + "</h1>"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Template parsing ───────────────────────────────────────────
|
// ── Template parsing ───────────────────────────────────────────
|
||||||
|
|
||||||
func (h *Handler) parseTemplates() {
|
func (h *Handler) parseTemplates() {
|
||||||
dirs := []string{
|
tmpl, err := template.New("").Funcs(template.FuncMap{}).ParseFS(templateFS, "html/*.html")
|
||||||
filepath.Join("internal", "templates", "html"),
|
|
||||||
"templates",
|
|
||||||
}
|
|
||||||
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 {
|
if err != nil {
|
||||||
log.Printf("Template parse error in %s: %v", dir, err)
|
log.Printf("Template parse error: %v", err)
|
||||||
continue
|
|
||||||
}
|
|
||||||
if tmpl != nil {
|
|
||||||
h.tmpl = tmpl
|
|
||||||
h.loaded = true
|
|
||||||
log.Printf("Loaded templates from %s", dir)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
h.tmpl = tmpl
|
||||||
log.Println("No HTML templates found; using placeholder responses")
|
log.Println("Loaded embedded HTML templates")
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user