package handlers import ( "context" "encoding/json" "html/template" "log/slog" "net/http" "go.uber.org/fx" "sneak.berlin/go/webhooker/internal/database" "sneak.berlin/go/webhooker/internal/globals" "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 type HandlersParams struct { fx.In Logger *logger.Logger Globals *globals.Globals Database *database.Database Healthcheck *healthcheck.Healthcheck Session *session.Session } type Handlers struct { 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) { s := new(Handlers) s.params = ¶ms s.log = params.Logger.Get() 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 { return nil }, }) return s, nil } //nolint:unparam // r parameter will be used in the future for request context func (s *Handlers) respondJSON(w http.ResponseWriter, r *http.Request, data interface{}, status int) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) if data != nil { err := json.NewEncoder(w).Encode(data) if err != nil { s.log.Error("json encode error", "error", err) } } } //nolint:unparam,unused // will be used for handling JSON requests func (s *Handlers) decodeJSON(w http.ResponseWriter, r *http.Request, v interface{}) error { return json.NewDecoder(r.Body).Decode(v) } // TemplateData represents the common data passed to templates type TemplateData struct { User *UserInfo Version string UserCount int64 Uptime string } // UserInfo represents user information for templates type UserInfo struct { ID string Username string } // 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 } // Get user from session if available var userInfo *UserInfo sess, err := s.session.Get(r) if err == nil && s.session.IsAuthenticated(sess) { if username, ok := s.session.GetUsername(sess); ok { if userID, ok := s.session.GetUserID(sess); ok { userInfo = &UserInfo{ ID: userID, Username: username, } } } } // 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 Data interface{} } wrapper := templateDataWrapper{ User: userInfo, Data: data, } 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) } }