package web import ( "embed" "errors" "fmt" "html/template" "io" "io/fs" "git.janky.solutions/finn/go-project-template/config" "git.janky.solutions/finn/go-project-template/db" "github.com/gorilla/sessions" pgx "github.com/jackc/pgx/v5" echo "github.com/labstack/echo/v4" "github.com/sirupsen/logrus" ) var ( //go:embed templates templatesFS embed.FS templatesSubFS fs.FS allTemplates *template.Template funcs = template.FuncMap{ "version": func() string { return fmt.Sprintf("%s %s", config.BuildInfo.Main.Path, config.Version) }, } //go:embed static static embed.FS Static fs.FS ) func init() { t := template.New("").Funcs(funcs) var err error templatesSubFS, err = fs.Sub(templatesFS, "templates") if err != nil { panic(err) } allTemplates, err = t.ParseFS(templatesSubFS, "*") if err != nil { panic(err) } Static, err = fs.Sub(static, "static") if err != nil { panic(err) } } type Template struct { } type templateData struct { Authenticated bool User db.User Version string Data any } func (t *templateData) dataFromContext(c echo.Context) error { sessionInterface := c.Get(contextKeySession) if sessionInterface == nil { return nil // no session, no data to populate } session := sessionInterface.(*sessions.Session) userIDInterface, ok := session.Values[sessionValueAuthUser] if !ok { return nil } userID, ok := userIDInterface.(int32) if !ok { logrus.WithField("userID", userIDInterface).Warn("unexpected session error: user ID is not an int32") return errors.New("user ID is not an in32") } ctx := c.Request().Context() queries, conn, err := db.Get(ctx) if err != nil { return err } defer conn.Close(ctx) user, err := queries.GetUser(ctx, userID) if err != nil { if errors.Is(err, pgx.ErrNoRows) { return nil } return err } t.Authenticated = true t.User = user t.Version = fmt.Sprintf("%s %s", config.BuildInfo.Main.Path, config.Version) return nil } func (t *Template) Render(w io.Writer, name string, data any, c echo.Context) error { td := templateData{Data: data} td.dataFromContext(c) // Why does it work like this? because go's templating system doesn't handle multiple templates extending from a common base well // just doing it normally causes every template to render the same (the import form, at time of writing, probably the last template alphabetically) // https://stackoverflow.com/a/69244593/21894038 tmpl := template.Must(allTemplates.Clone()) tmpl = template.Must(tmpl.ParseFS(templatesSubFS, name)) return tmpl.ExecuteTemplate(w, name, td) }