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/delivery" "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 WebhookDBMgr *database.WebhookDBManager Healthcheck *healthcheck.Healthcheck Session *session.Session Notifier delivery.Notifier } type Handlers struct { params *HandlersParams log *slog.Logger hc *healthcheck.Healthcheck db *database.Database dbMgr *database.WebhookDBManager session *session.Session notifier delivery.Notifier 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. // The page file must be listed first so that its root action ({{template "base" .}}) // becomes the template set's entry point. If a shared partial (e.g. htmlheader.html) // is listed first, its {{define}} block becomes the root — which is empty — and // Execute() produces no output. func parsePageTemplate(pageFile string) *template.Template { return template.Must( template.ParseFS(templates.Templates, pageFile, "base.html", "htmlheader.html", "navbar.html"), ) } 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.dbMgr = params.WebhookDBMgr s.session = params.Session s.notifier = params.Notifier // 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"), "sources_list.html": parsePageTemplate("sources_list.html"), "sources_new.html": parsePageTemplate("sources_new.html"), "source_detail.html": parsePageTemplate("source_detail.html"), "source_edit.html": parsePageTemplate("source_edit.html"), "source_logs.html": parsePageTemplate("source_logs.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) } // 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) } }