WIP render the /admin with the index.gohtml and the date from the strings.yml
- commit
- accf0b9b4aef26fc58bea137b00c45769ff9cffd
- author
- bnema · · 2023-09-13 18:39 UTC
- parent
- 48af454
- signature
- unsigned
6 files · +123 -17
R internal/webui/strings.yml → internal/gotemplate/models/strings.yml +0 -0 renamed, 100% similar
renamed, 100% similar
A internal/gotemplate/render/html.go +51 -0
| @@ -0,0 +1,51 @@ | |||
| 1 | + | package render | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "bytes" | |
| 5 | + | "fmt" | |
| 6 | + | "html/template" | |
| 7 | + | "io/fs" | |
| 8 | + | ||
| 9 | + | "github.com/bnema/gordon/internal/app" | |
| 10 | + | ) | |
| 11 | + | ||
| 12 | + | type Renderer struct { | |
| 13 | + | Template *template.Template | |
| 14 | + | ParseError error | |
| 15 | + | } | |
| 16 | + | ||
| 17 | + | // Render function renders the template with the given data | |
| 18 | + | func (r *Renderer) Render(data interface{}, a *app.App) (string, error) { | |
| 19 | + | if r.ParseError != nil { | |
| 20 | + | return "", fmt.Errorf("failed to parse template: %w", r.ParseError) | |
| 21 | + | ||
| 22 | + | } | |
| 23 | + | if r.Template == nil { | |
| 24 | + | return "", fmt.Errorf("template is nil") | |
| 25 | + | } | |
| 26 | + | ||
| 27 | + | buf := new(bytes.Buffer) | |
| 28 | + | err := r.Template.Execute(buf, data) | |
| 29 | + | if err != nil { | |
| 30 | + | return "", fmt.Errorf("failed to execute template: %w", err) | |
| 31 | + | } | |
| 32 | + | ||
| 33 | + | return buf.String(), nil | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | // GetRenderer function returns a new Renderer instance | |
| 37 | + | func GetHTMLRenderer(filename string, fs fs.FS, a *app.App) (*Renderer, error) { | |
| 38 | + | // Check if the file exists in the provided fs.FS using fs.Open | |
| 39 | + | file, err := fs.Open(filename) | |
| 40 | + | if err != nil { | |
| 41 | + | return nil, fmt.Errorf("failed to open %s: %w", filename, err) | |
| 42 | + | } | |
| 43 | + | file.Close() // Close the file after checking | |
| 44 | + | tmpl, err := template.New(filename).ParseFS(fs, filename) | |
| 45 | + | if err != nil { | |
| 46 | + | return nil, fmt.Errorf("failed to parse %s: %w", filename, err) | |
| 47 | + | } | |
| 48 | + | return &Renderer{ | |
| 49 | + | Template: tmpl, | |
| 50 | + | }, nil | |
| 51 | + | } | |
| @@ -0,0 +1,51 @@ | |||||
| 1 | + | package render | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "bytes" | |||
| 5 | + | "fmt" | |||
| 6 | + | "html/template" | |||
| 7 | + | "io/fs" | |||
| 8 | + | ||||
| 9 | + | "github.com/bnema/gordon/internal/app" | |||
| 10 | + | ) | |||
| 11 | + | ||||
| 12 | + | type Renderer struct { | |||
| 13 | + | Template *template.Template | |||
| 14 | + | ParseError error | |||
| 15 | + | } | |||
| 16 | + | ||||
| 17 | + | // Render function renders the template with the given data | |||
| 18 | + | func (r *Renderer) Render(data interface{}, a *app.App) (string, error) { | |||
| 19 | + | if r.ParseError != nil { | |||
| 20 | + | return "", fmt.Errorf("failed to parse template: %w", r.ParseError) | |||
| 21 | + | ||||
| 22 | + | } | |||
| 23 | + | if r.Template == nil { | |||
| 24 | + | return "", fmt.Errorf("template is nil") | |||
| 25 | + | } | |||
| 26 | + | ||||
| 27 | + | buf := new(bytes.Buffer) | |||
| 28 | + | err := r.Template.Execute(buf, data) | |||
| 29 | + | if err != nil { | |||
| 30 | + | return "", fmt.Errorf("failed to execute template: %w", err) | |||
| 31 | + | } | |||
| 32 | + | ||||
| 33 | + | return buf.String(), nil | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | // GetRenderer function returns a new Renderer instance | |||
| 37 | + | func GetHTMLRenderer(filename string, fs fs.FS, a *app.App) (*Renderer, error) { | |||
| 38 | + | // Check if the file exists in the provided fs.FS using fs.Open | |||
| 39 | + | file, err := fs.Open(filename) | |||
| 40 | + | if err != nil { | |||
| 41 | + | return nil, fmt.Errorf("failed to open %s: %w", filename, err) | |||
| 42 | + | } | |||
| 43 | + | file.Close() // Close the file after checking | |||
| 44 | + | tmpl, err := template.New(filename).ParseFS(fs, filename) | |||
| 45 | + | if err != nil { | |||
| 46 | + | return nil, fmt.Errorf("failed to parse %s: %w", filename, err) | |||
| 47 | + | } | |||
| 48 | + | return &Renderer{ | |||
| 49 | + | Template: tmpl, | |||
| 50 | + | }, nil | |||
| 51 | + | } | |||
M internal/httpserve/handler/adminroutes.go +30 -3
| @@ -1,10 +1,37 @@ | |||
| 1 | 1 | package handler | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "fmt" | |
| 5 | + | ||
| 6 | + | "github.com/bnema/gordon/internal/app" | |
| 7 | + | "github.com/bnema/gordon/internal/gotemplate/render" | |
| 8 | + | "github.com/bnema/gordon/internal/httpserve/middleware" | |
| 9 | + | "github.com/bnema/gordon/internal/webui" | |
| 4 | 10 | "github.com/labstack/echo/v4" | |
| 5 | 11 | ) | |
| 6 | 12 | ||
| 7 | - | // Handle the admin route to display index.gohtml from the templateFS with the data from strings.yaml | |
| 8 | - | func AdminRoute(c echo.Context) error { | |
| 9 | - | return c.Render(200, "index.gohtml", c.Get("data")) | |
| 13 | + | // Handle the admin route to display index.gohtml from the templateFS with the data from strings.yml | |
| 14 | + | func AdminRoute(c echo.Context, a *app.App) error { | |
| 15 | + | lang := c.Get(middleware.LangKey).(string) | |
| 16 | + | fmt.Println(lang) | |
| 17 | + | data := webui.StringsYamlData{} | |
| 18 | + | err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &data) | |
| 19 | + | if err != nil { | |
| 20 | + | return fmt.Errorf("failed to read strings data from YAML: %w", err) | |
| 21 | + | } | |
| 22 | + | ||
| 23 | + | // Navigate inside the fs.FS to get the template | |
| 24 | + | ||
| 25 | + | rendererData, err := render.GetHTMLRenderer("html/admin/index.gohtml", a.TemplateFS, a) | |
| 26 | + | if err != nil { | |
| 27 | + | return fmt.Errorf("failed to get renderer: %w", err) | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | renderedHTML, err := rendererData.Render(data, a) | |
| 31 | + | fmt.Println(renderedHTML) | |
| 32 | + | if err != nil { | |
| 33 | + | return fmt.Errorf("failed to render template: %w", err) | |
| 34 | + | } | |
| 35 | + | ||
| 36 | + | return c.HTML(200, renderedHTML) | |
| 10 | 37 | } | |
| @@ -1,10 +1,37 @@ | |||||
| 1 | package handler | 1 | package handler | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "fmt" | |||
| 5 | + | ||||
| 6 | + | "github.com/bnema/gordon/internal/app" | |||
| 7 | + | "github.com/bnema/gordon/internal/gotemplate/render" | |||
| 8 | + | "github.com/bnema/gordon/internal/httpserve/middleware" | |||
| 9 | + | "github.com/bnema/gordon/internal/webui" | |||
| 4 | "github.com/labstack/echo/v4" | 10 | "github.com/labstack/echo/v4" | ||
| 5 | ) | 11 | ) | ||
| 6 | 12 | ||||
| 7 | - | // Handle the admin route to display index.gohtml from the templateFS with the data from strings.yaml | 13 | + | // Handle the admin route to display index.gohtml from the templateFS with the data from strings.yml |
| 8 | - | func AdminRoute(c echo.Context) error { | 14 | + | func AdminRoute(c echo.Context, a *app.App) error { |
| 9 | - | return c.Render(200, "index.gohtml", c.Get("data")) | 15 | + | lang := c.Get(middleware.LangKey).(string) |
| 16 | + | fmt.Println(lang) | |||
| 17 | + | data := webui.StringsYamlData{} | |||
| 18 | + | err := webui.ReadStringsDataFromYAML(lang, a.TemplateFS, "strings.yml", &data) | |||
| 19 | + | if err != nil { | |||
| 20 | + | return fmt.Errorf("failed to read strings data from YAML: %w", err) | |||
| 21 | + | } | |||
| 22 | + | ||||
| 23 | + | // Navigate inside the fs.FS to get the template | |||
| 24 | + | ||||
| 25 | + | rendererData, err := render.GetHTMLRenderer("html/admin/index.gohtml", a.TemplateFS, a) | |||
| 26 | + | if err != nil { | |||
| 27 | + | return fmt.Errorf("failed to get renderer: %w", err) | |||
| 28 | + | } | |||
| 29 | + | ||||
| 30 | + | renderedHTML, err := rendererData.Render(data, a) | |||
| 31 | + | fmt.Println(renderedHTML) | |||
| 32 | + | if err != nil { | |||
| 33 | + | return fmt.Errorf("failed to render template: %w", err) | |||
| 34 | + | } | |||
| 35 | + | ||||
| 36 | + | return c.HTML(200, renderedHTML) | |||
| 10 | } | 37 | } | ||
A internal/httpserve/middleware/error.go +22 -0
| @@ -0,0 +1,22 @@ | |||
| 1 | + | package middleware | |
| 2 | + | ||
| 3 | + | import ( | |
| 4 | + | "log" | |
| 5 | + | "net/http" | |
| 6 | + | ||
| 7 | + | "github.com/labstack/echo/v4" | |
| 8 | + | ) | |
| 9 | + | ||
| 10 | + | func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc { | |
| 11 | + | return func(c echo.Context) error { | |
| 12 | + | err := next(c) | |
| 13 | + | if err != nil { | |
| 14 | + | log.Println("Error encountered:", err) | |
| 15 | + | if he, ok := err.(*echo.HTTPError); ok { | |
| 16 | + | return c.String(he.Code, he.Message.(string)) | |
| 17 | + | } | |
| 18 | + | return c.String(http.StatusInternalServerError, "Internal Server Error") | |
| 19 | + | } | |
| 20 | + | return nil | |
| 21 | + | } | |
| 22 | + | } | |
| @@ -0,0 +1,22 @@ | |||||
| 1 | + | package middleware | |||
| 2 | + | ||||
| 3 | + | import ( | |||
| 4 | + | "log" | |||
| 5 | + | "net/http" | |||
| 6 | + | ||||
| 7 | + | "github.com/labstack/echo/v4" | |||
| 8 | + | ) | |||
| 9 | + | ||||
| 10 | + | func ErrorHandler(next echo.HandlerFunc) echo.HandlerFunc { | |||
| 11 | + | return func(c echo.Context) error { | |||
| 12 | + | err := next(c) | |||
| 13 | + | if err != nil { | |||
| 14 | + | log.Println("Error encountered:", err) | |||
| 15 | + | if he, ok := err.(*echo.HTTPError); ok { | |||
| 16 | + | return c.String(he.Code, he.Message.(string)) | |||
| 17 | + | } | |||
| 18 | + | return c.String(http.StatusInternalServerError, "Internal Server Error") | |||
| 19 | + | } | |||
| 20 | + | return nil | |||
| 21 | + | } | |||
| 22 | + | } | |||
M internal/httpserve/router.go +9 -3
| @@ -1,8 +1,6 @@ | |||
| 1 | 1 | package httpserve | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | - | "fmt" | |
| 5 | - | ||
| 6 | 4 | "github.com/bnema/gordon/internal/app" | |
| 7 | 5 | "github.com/bnema/gordon/internal/httpserve/handler" | |
| 8 | 6 | "github.com/bnema/gordon/internal/httpserve/middleware" | |
| @@ -12,7 +10,7 @@ import ( | |||
| 12 | 10 | // RegisterRoutes registers all routes for the application | |
| 13 | 11 | func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo { | |
| 14 | 12 | AdminPath := a.AdminPath | |
| 15 | - | fmt.Println(AdminPath) | |
| 13 | + | e.Use(middleware.ErrorHandler) | |
| 16 | 14 | // Use middlewares | |
| 17 | 15 | // e.Use(middleware.SecureRoutes()) | |
| 18 | 16 | e.Use(middleware.ColorSchemeDetection) | |
| @@ -20,6 +18,8 @@ func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo { | |||
| 20 | 18 | ||
| 21 | 19 | // Register routes | |
| 22 | 20 | ||
| 21 | + | // Serve admin routes | |
| 22 | + | bindAdminRoute(e, a, AdminPath) | |
| 23 | 23 | // Serve static files | |
| 24 | 24 | bindStaticRoute(e, a, "/*") | |
| 25 | 25 | // Protect the root path with a 403 | |
| @@ -35,3 +35,9 @@ func bindStaticRoute(e *echo.Echo, a *app.App, path string) { | |||
| 35 | 35 | return handler.StaticRoute(c, a) | |
| 36 | 36 | }) | |
| 37 | 37 | } | |
| 38 | + | ||
| 39 | + | func bindAdminRoute(e *echo.Echo, a *app.App, path string) { | |
| 40 | + | e.GET(path, func(c echo.Context) error { | |
| 41 | + | return handler.AdminRoute(c, a) | |
| 42 | + | }) | |
| 43 | + | } | |
| @@ -1,8 +1,6 @@ | |||||
| 1 | package httpserve | 1 | package httpserve | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | - | "fmt" | |||
| 5 | - | ||||
| 6 | "github.com/bnema/gordon/internal/app" | 4 | "github.com/bnema/gordon/internal/app" | ||
| 7 | "github.com/bnema/gordon/internal/httpserve/handler" | 5 | "github.com/bnema/gordon/internal/httpserve/handler" | ||
| 8 | "github.com/bnema/gordon/internal/httpserve/middleware" | 6 | "github.com/bnema/gordon/internal/httpserve/middleware" | ||
| @@ -12,7 +10,7 @@ import ( | |||||
| 12 | // RegisterRoutes registers all routes for the application | 10 | // RegisterRoutes registers all routes for the application | ||
| 13 | func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo { | 11 | func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo { | ||
| 14 | AdminPath := a.AdminPath | 12 | AdminPath := a.AdminPath | ||
| 15 | - | fmt.Println(AdminPath) | 13 | + | e.Use(middleware.ErrorHandler) |
| 16 | // Use middlewares | 14 | // Use middlewares | ||
| 17 | // e.Use(middleware.SecureRoutes()) | 15 | // e.Use(middleware.SecureRoutes()) | ||
| 18 | e.Use(middleware.ColorSchemeDetection) | 16 | e.Use(middleware.ColorSchemeDetection) | ||
| @@ -20,6 +18,8 @@ func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo { | |||||
| 20 | 18 | ||||
| 21 | // Register routes | 19 | // Register routes | ||
| 22 | 20 | ||||
| 21 | + | // Serve admin routes | |||
| 22 | + | bindAdminRoute(e, a, AdminPath) | |||
| 23 | // Serve static files | 23 | // Serve static files | ||
| 24 | bindStaticRoute(e, a, "/*") | 24 | bindStaticRoute(e, a, "/*") | ||
| 25 | // Protect the root path with a 403 | 25 | // Protect the root path with a 403 | ||
| @@ -35,3 +35,9 @@ func bindStaticRoute(e *echo.Echo, a *app.App, path string) { | |||||
| 35 | return handler.StaticRoute(c, a) | 35 | return handler.StaticRoute(c, a) | ||
| 36 | }) | 36 | }) | ||
| 37 | } | 37 | } | ||
| 38 | + | ||||
| 39 | + | func bindAdminRoute(e *echo.Echo, a *app.App, path string) { | |||
| 40 | + | e.GET(path, func(c echo.Context) error { | |||
| 41 | + | return handler.AdminRoute(c, a) | |||
| 42 | + | }) | |||
| 43 | + | } | |||
M pkg/utils/htmx/htmlfragments.go +11 -11
| @@ -2,41 +2,41 @@ package htmx | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "bytes" | |
| 5 | + | "fmt" | |
| 5 | 6 | ||
| 6 | 7 | "github.com/PuerkitoBio/goquery" | |
| 7 | - | ||
| 8 | + | "github.com/bnema/gordon/internal/app" | |
| 9 | + | "github.com/bnema/gordon/internal/gotemplate/render" | |
| 8 | 10 | ) | |
| 9 | 11 | ||
| 10 | 12 | // GetHTMLFragmentByID returns the HTML fragment with the specified id | |
| 11 | - | func GetHTMLFragmentByID(id string, data interface{}, a *app.App, ac *app.Config) (string, error) { | |
| 13 | + | func GetHTMLFragmentByID(id string, data interface{}, a *app.App) (string, error) { | |
| 12 | 14 | // 1. Render the template | |
| 13 | - | renderer, err := templating.GetHTMLRenderer("components.gohtml", ac.GetTemplateFS(), a) | |
| 15 | + | renderer, err := render.GetHTMLRenderer("components.gohtml", a.TemplateFS, a) | |
| 14 | 16 | if err != nil { | |
| 15 | - | return "", err | |
| 17 | + | return "", fmt.Errorf("failed to get renderer: %w", err) | |
| 16 | 18 | } | |
| 17 | 19 | ||
| 18 | 20 | renderedHTML, err := renderer.Render(data, a) | |
| 19 | 21 | if err != nil { | |
| 20 | - | return "", err | |
| 22 | + | return "", fmt.Errorf("failed to render template: %w", err) | |
| 21 | 23 | } | |
| 22 | 24 | ||
| 23 | 25 | // 2. Parse the rendered HTML with goquery | |
| 24 | 26 | doc, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(renderedHTML))) | |
| 25 | 27 | if err != nil { | |
| 26 | - | return "", err | |
| 28 | + | return "", fmt.Errorf("failed to parse rendered HTML: %w", err) | |
| 27 | 29 | } | |
| 28 | 30 | ||
| 29 | 31 | // 3. Find the div with the specified id | |
| 30 | 32 | fragmentContent, err := doc.Find("#" + id).Html() | |
| 31 | 33 | if err != nil { | |
| 32 | - | return "", err | |
| 34 | + | return "", fmt.Errorf("failed to find fragment with id %s: %w", id, err) | |
| 33 | 35 | } | |
| 34 | 36 | ||
| 35 | - | if fragmentcontent == "" { | |
| 36 | - | return "", err | |
| 37 | + | if fragmentContent == "" { | |
| 38 | + | return "", fmt.Errorf("failed to find fragment with id %s: %w", id, err) | |
| 37 | 39 | } | |
| 38 | 40 | ||
| 39 | 41 | return fragmentContent, nil | |
| 40 | 42 | } | |
| 41 | - | ||
| 42 | - | ||
| @@ -2,41 +2,41 @@ package htmx | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "bytes" | 4 | "bytes" | ||
| 5 | + | "fmt" | |||
| 5 | 6 | ||||
| 6 | "github.com/PuerkitoBio/goquery" | 7 | "github.com/PuerkitoBio/goquery" | ||
| 7 | - | 8 | + | "github.com/bnema/gordon/internal/app" | |
| 9 | + | "github.com/bnema/gordon/internal/gotemplate/render" | |||
| 8 | ) | 10 | ) | ||
| 9 | 11 | ||||
| 10 | // GetHTMLFragmentByID returns the HTML fragment with the specified id | 12 | // GetHTMLFragmentByID returns the HTML fragment with the specified id | ||
| 11 | - | func GetHTMLFragmentByID(id string, data interface{}, a *app.App, ac *app.Config) (string, error) { | 13 | + | func GetHTMLFragmentByID(id string, data interface{}, a *app.App) (string, error) { |
| 12 | // 1. Render the template | 14 | // 1. Render the template | ||
| 13 | - | renderer, err := templating.GetHTMLRenderer("components.gohtml", ac.GetTemplateFS(), a) | 15 | + | renderer, err := render.GetHTMLRenderer("components.gohtml", a.TemplateFS, a) |
| 14 | if err != nil { | 16 | if err != nil { | ||
| 15 | - | return "", err | 17 | + | return "", fmt.Errorf("failed to get renderer: %w", err) |
| 16 | } | 18 | } | ||
| 17 | 19 | ||||
| 18 | renderedHTML, err := renderer.Render(data, a) | 20 | renderedHTML, err := renderer.Render(data, a) | ||
| 19 | if err != nil { | 21 | if err != nil { | ||
| 20 | - | return "", err | 22 | + | return "", fmt.Errorf("failed to render template: %w", err) |
| 21 | } | 23 | } | ||
| 22 | 24 | ||||
| 23 | // 2. Parse the rendered HTML with goquery | 25 | // 2. Parse the rendered HTML with goquery | ||
| 24 | doc, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(renderedHTML))) | 26 | doc, err := goquery.NewDocumentFromReader(bytes.NewReader([]byte(renderedHTML))) | ||
| 25 | if err != nil { | 27 | if err != nil { | ||
| 26 | - | return "", err | 28 | + | return "", fmt.Errorf("failed to parse rendered HTML: %w", err) |
| 27 | } | 29 | } | ||
| 28 | 30 | ||||
| 29 | // 3. Find the div with the specified id | 31 | // 3. Find the div with the specified id | ||
| 30 | fragmentContent, err := doc.Find("#" + id).Html() | 32 | fragmentContent, err := doc.Find("#" + id).Html() | ||
| 31 | if err != nil { | 33 | if err != nil { | ||
| 32 | - | return "", err | 34 | + | return "", fmt.Errorf("failed to find fragment with id %s: %w", id, err) |
| 33 | } | 35 | } | ||
| 34 | 36 | ||||
| 35 | - | if fragmentcontent == "" { | 37 | + | if fragmentContent == "" { |
| 36 | - | return "", err | 38 | + | return "", fmt.Errorf("failed to find fragment with id %s: %w", id, err) |
| 37 | } | 39 | } | ||
| 38 | 40 | ||||
| 39 | return fragmentContent, nil | 41 | return fragmentContent, nil | ||
| 40 | } | 42 | } | ||
| 41 | - | ||||
| 42 | - | ||||