go-project-template/web/templates.go

116 lines
2.6 KiB
Go

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)
}