detached 22ef2df

WIP

commit
22ef2df1e4cd84e6977a6c24055f49b89f0a64ea
author
bnema · · 2023-09-05 16:43 UTC
parent
fcbb900
signature
unsigned
21 files · +256 -89
D .env +0 -0

no textual change.

A .env.example +7 -0
@@ -0,0 +1,7 @@
1+ENV=EITHER PRODUCTION OR DEVELOPMENT
2+LOG_LEVEL= DEBUG, INFO, WARN, ERROR
3+PORT= DEFAULT 1323
4+ABSOLUTE_PATH= ABSOLUTE INSTALL PATH OF THE APP
5+TOP_DOMAIN= EXAMPLE.COM
6+GORDON_DOMAIN= GORDON.EXAMPLE.COM
7+ADMIN_EMAIL= EMAIL ADDRESS OF THE ADMIN⏎̸
@@ -0,0 +1,7 @@
1+ENV=EITHER PRODUCTION OR DEVELOPMENT
2+LOG_LEVEL= DEBUG, INFO, WARN, ERROR
3+PORT= DEFAULT 1323
4+ABSOLUTE_PATH= ABSOLUTE INSTALL PATH OF THE APP
5+TOP_DOMAIN= EXAMPLE.COM
6+GORDON_DOMAIN= GORDON.EXAMPLE.COM
7+ADMIN_EMAIL= EMAIL ADDRESS OF THE ADMIN⏎̸
M cmd/main.go +8 -1
@@ -1,9 +1,11 @@
11 package main
22
33 import (
4+ "embed"
45 "os"
56
67 "github.com/labstack/echo/v4"
8+ root "gogs.bnema.dev/gordon-echo"
79 "gogs.bnema.dev/gordon-echo/internal/app"
810 "gogs.bnema.dev/gordon-echo/internal/http/routes"
911 "gogs.bnema.dev/gordon-echo/pkg/utils"
@@ -16,8 +18,13 @@ func main() {
1618 port = "1323"
1719 }
1820
21+ config := app.Config{
22+ TemplateFS: root.TemplateFS.(embed.FS),
23+ PublicFS: root.PublicFS.(embed.FS),
24+ ModelFS: root.ModelFS,
25+ }
1926 // Initialize Gordon
20- gordon, err := app.NewApp()
27+ gordon, err := app.NewApp(&config)
2128 if err != nil {
2229 // Handle initialization error and exit
2330 gordon.AppLogger.Error().Err(err).Msg("Failed to initialize app")
@@ -1,9 +1,11 @@
1 package main1 package main
2 2
3 import (3 import (
4+ "embed"
4 "os"5 "os"
5 6
6 "github.com/labstack/echo/v4"7 "github.com/labstack/echo/v4"
8+ root "gogs.bnema.dev/gordon-echo"
7 "gogs.bnema.dev/gordon-echo/internal/app"9 "gogs.bnema.dev/gordon-echo/internal/app"
8 "gogs.bnema.dev/gordon-echo/internal/http/routes"10 "gogs.bnema.dev/gordon-echo/internal/http/routes"
9 "gogs.bnema.dev/gordon-echo/pkg/utils"11 "gogs.bnema.dev/gordon-echo/pkg/utils"
@@ -16,8 +18,13 @@ func main() {
16 port = "1323"18 port = "1323"
17 }19 }
18 20
21+ config := app.Config{
22+ TemplateFS: root.TemplateFS.(embed.FS),
23+ PublicFS: root.PublicFS.(embed.FS),
24+ ModelFS: root.ModelFS,
25+ }
19 // Initialize Gordon26 // Initialize Gordon
20- gordon, err := app.NewApp()27+ gordon, err := app.NewApp(&config)
21 if err != nil {28 if err != nil {
22 // Handle initialization error and exit29 // Handle initialization error and exit
23 gordon.AppLogger.Error().Err(err).Msg("Failed to initialize app")30 gordon.AppLogger.Error().Err(err).Msg("Failed to initialize app")
A config/config-provider.go +9 -0
@@ -0,0 +1,9 @@
1+package config
2+
3+import "embed"
4+
5+type Provider interface {
6+ GetTemplateFS() embed.FS
7+ GetPublicFS() embed.FS
8+ GetModelFS() embed.FS
9+}
@@ -0,0 +1,9 @@
1+package config
2+
3+import "embed"
4+
5+type Provider interface {
6+ GetTemplateFS() embed.FS
7+ GetPublicFS() embed.FS
8+ GetModelFS() embed.FS
9+}
A embed.go +26 -0
@@ -0,0 +1,26 @@
1+package embed
2+
3+import (
4+ "embed"
5+
6+ "github.com/labstack/echo/v4"
7+)
8+
9+// Embedding the public and templates directories
10+
11+//go:embed internal/ui/public/*
12+var public embed.FS
13+
14+//go:embed internal/ui/templates/*
15+var templates embed.FS
16+
17+// Embedding the YAML models
18+//
19+//go:embed pkg/templating/models/*
20+var modelsEmbed embed.FS
21+
22+var PublicFS = echo.MustSubFS(public, "internal/ui/public")
23+var TemplateFS = echo.MustSubFS(templates, "internal/ui/templates")
24+
25+// Dont need Echo for models so we are just using embed.FS
26+var ModelFS embed.FS = modelsEmbed
@@ -0,0 +1,26 @@
1+package embed
2+
3+import (
4+ "embed"
5+
6+ "github.com/labstack/echo/v4"
7+)
8+
9+// Embedding the public and templates directories
10+
11+//go:embed internal/ui/public/*
12+var public embed.FS
13+
14+//go:embed internal/ui/templates/*
15+var templates embed.FS
16+
17+// Embedding the YAML models
18+//
19+//go:embed pkg/templating/models/*
20+var modelsEmbed embed.FS
21+
22+var PublicFS = echo.MustSubFS(public, "internal/ui/public")
23+var TemplateFS = echo.MustSubFS(templates, "internal/ui/templates")
24+
25+// Dont need Echo for models so we are just using embed.FS
26+var ModelFS embed.FS = modelsEmbed
M internal/app/app.go +35 -21
@@ -1,17 +1,16 @@
11 package app
22
33 import (
4- "html/template"
4+ "embed"
55 "io"
66 "os"
77 "path/filepath"
88
99 "github.com/rs/zerolog"
10+ "gogs.bnema.dev/gordon-echo/config"
1011 "gogs.bnema.dev/gordon-echo/pkg/utils"
1112 )
1213
13-var cfg = loadConfig()
14-
1514 const (
1615 DefaultBuildDir = "tmp"
1716 DefaultLogDir = "logs"
@@ -24,14 +23,30 @@ const (
2423 type App struct {
2524 AppLogger *utils.Logger
2625 HttpLogger *utils.Logger
27- Template *template.Template
2826 }
2927 type Config struct {
30- BuildDir string
31- LogDir string
28+ BuildDir string
29+ LogDir string
30+ TemplateFS embed.FS
31+ PublicFS embed.FS
32+ ModelFS embed.FS
33+}
34+
35+func (c *Config) GetTemplateFS() embed.FS {
36+ return c.TemplateFS
37+}
38+
39+func (c *Config) GetPublicFS() embed.FS {
40+ return c.PublicFS
41+}
42+
43+func (c *Config) GetModelFS() embed.FS {
44+ return c.ModelFS
3245 }
3346
34-func loadConfig() Config {
47+var _ config.Provider = &Config{}
48+
49+func loadConfig(templateFS, publicFS, modelFS embed.FS) Config {
3550 // Get the environment variable
3651 env := os.Getenv(EnvVarName)
3752 // If the environment variable is empty, considere it is dev -> (/tmp)
@@ -56,25 +71,24 @@ func loadConfig() Config {
5671 }
5772
5873 return Config{
59- BuildDir: buildDir,
60- LogDir: logDir,
74+ BuildDir: buildDir,
75+ LogDir: logDir,
76+ TemplateFS: templateFS,
77+ PublicFS: publicFS,
78+ ModelFS: modelFS,
6179 }
6280 }
63-func NewApp() (*App, error) {
64- SetupLogging()
65- app := &App{}
66-
67- // Initialize the general application logger
68- app.AppLogger = utils.NewLogger().SetOutput(initializeLoggerOutput("app.log"))
69- // Initialize the HTTP logger
70- app.HttpLogger = utils.NewLogger().SetOutput(initializeLoggerOutput("http.log"))
71-
81+func NewApp(config *Config) (*App, error) {
82+ SetupLogging(config)
83+ app := &App{
84+ AppLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "app.log")),
85+ HttpLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "http.log")),
86+ }
7287 return app, nil
7388 }
7489
75-func initializeLoggerOutput(logFile string) io.Writer {
90+func initializeLoggerOutput(cfg *Config, logFile string) io.Writer {
7691 logPath := filepath.Join(cfg.LogDir, logFile)
77-
7892 dir := filepath.Dir(logPath)
7993 if _, err := os.Stat(dir); os.IsNotExist(err) {
8094 if mkdirErr := os.MkdirAll(dir, 0755); mkdirErr != nil {
@@ -90,7 +104,7 @@ func initializeLoggerOutput(logFile string) io.Writer {
90104 return zerolog.MultiLevelWriter(file, consoleWriter)
91105 }
92106
93-func SetupLogging() {
107+func SetupLogging(cfg *Config) {
94108 err := utils.CreateLogsDir(cfg.LogDir)
95109 if err != nil {
96110 panic(err)
@@ -1,17 +1,16 @@
1 package app1 package app
2 2
3 import (3 import (
4- "html/template"4+ "embed"
5 "io"5 "io"
6 "os"6 "os"
7 "path/filepath"7 "path/filepath"
8 8
9 "github.com/rs/zerolog"9 "github.com/rs/zerolog"
10+ "gogs.bnema.dev/gordon-echo/config"
10 "gogs.bnema.dev/gordon-echo/pkg/utils"11 "gogs.bnema.dev/gordon-echo/pkg/utils"
11 )12 )
12 13
13-var cfg = loadConfig()
14-
15 const (14 const (
16 DefaultBuildDir = "tmp"15 DefaultBuildDir = "tmp"
17 DefaultLogDir = "logs"16 DefaultLogDir = "logs"
@@ -24,14 +23,30 @@ const (
24 type App struct {23 type App struct {
25 AppLogger *utils.Logger24 AppLogger *utils.Logger
26 HttpLogger *utils.Logger25 HttpLogger *utils.Logger
27- Template *template.Template
28 }26 }
29 type Config struct {27 type Config struct {
30- BuildDir string28+ BuildDir string
31- LogDir string29+ LogDir string
30+ TemplateFS embed.FS
31+ PublicFS embed.FS
32+ ModelFS embed.FS
33+}
34+
35+func (c *Config) GetTemplateFS() embed.FS {
36+ return c.TemplateFS
37+}
38+
39+func (c *Config) GetPublicFS() embed.FS {
40+ return c.PublicFS
41+}
42+
43+func (c *Config) GetModelFS() embed.FS {
44+ return c.ModelFS
32 }45 }
33 46
34-func loadConfig() Config {47+var _ config.Provider = &Config{}
48+
49+func loadConfig(templateFS, publicFS, modelFS embed.FS) Config {
35 // Get the environment variable50 // Get the environment variable
36 env := os.Getenv(EnvVarName)51 env := os.Getenv(EnvVarName)
37 // If the environment variable is empty, considere it is dev -> (/tmp)52 // If the environment variable is empty, considere it is dev -> (/tmp)
@@ -56,25 +71,24 @@ func loadConfig() Config {
56 }71 }
57 72
58 return Config{73 return Config{
59- BuildDir: buildDir,74+ BuildDir: buildDir,
60- LogDir: logDir,75+ LogDir: logDir,
76+ TemplateFS: templateFS,
77+ PublicFS: publicFS,
78+ ModelFS: modelFS,
61 }79 }
62 }80 }
63-func NewApp() (*App, error) {81+func NewApp(config *Config) (*App, error) {
64- SetupLogging()82+ SetupLogging(config)
65- app := &App{}83+ app := &App{
66-84+ AppLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "app.log")),
67- // Initialize the general application logger85+ HttpLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "http.log")),
68- app.AppLogger = utils.NewLogger().SetOutput(initializeLoggerOutput("app.log"))86+ }
69- // Initialize the HTTP logger
70- app.HttpLogger = utils.NewLogger().SetOutput(initializeLoggerOutput("http.log"))
71-
72 return app, nil87 return app, nil
73 }88 }
74 89
75-func initializeLoggerOutput(logFile string) io.Writer {90+func initializeLoggerOutput(cfg *Config, logFile string) io.Writer {
76 logPath := filepath.Join(cfg.LogDir, logFile)91 logPath := filepath.Join(cfg.LogDir, logFile)
77-
78 dir := filepath.Dir(logPath)92 dir := filepath.Dir(logPath)
79 if _, err := os.Stat(dir); os.IsNotExist(err) {93 if _, err := os.Stat(dir); os.IsNotExist(err) {
80 if mkdirErr := os.MkdirAll(dir, 0755); mkdirErr != nil {94 if mkdirErr := os.MkdirAll(dir, 0755); mkdirErr != nil {
@@ -90,7 +104,7 @@ func initializeLoggerOutput(logFile string) io.Writer {
90 return zerolog.MultiLevelWriter(file, consoleWriter)104 return zerolog.MultiLevelWriter(file, consoleWriter)
91 }105 }
92 106
93-func SetupLogging() {107+func SetupLogging(cfg *Config) {
94 err := utils.CreateLogsDir(cfg.LogDir)108 err := utils.CreateLogsDir(cfg.LogDir)
95 if err != nil {109 if err != nil {
96 panic(err)110 panic(err)
M internal/http/handlers/errors.go +5 -7
@@ -5,20 +5,18 @@ import (
55 "net/http"
66
77 "github.com/labstack/echo/v4"
8- "gogs.bnema.dev/gordon-echo/internal/ui"
8+ "gogs.bnema.dev/gordon-echo/internal/app"
99 "gogs.bnema.dev/gordon-echo/pkg/utils"
1010 )
1111
12-type ErrorHandler struct {
13- Err error
14- C echo.Context
15- ErrorNumber int
12+type AppConfig struct {
13+ *app.Config
1614 }
1715
18-func Error404Handler(err error, c echo.Context) {
16+func (a *AppConfig) Error404Handler(err error, c echo.Context) {
1917 if he, ok := err.(*echo.HTTPError); ok {
2018 if he.Code == http.StatusNotFound {
21- renderer, err := utils.GetRenderer("404.gohtml", ui.PublicFS, utils.NewLogger())
19+ renderer, err := utils.GetRenderer("404.gohtml", a.Config.PublicFS, utils.NewLogger())
2220 if err != nil {
2321 c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
2422 }
@@ -5,20 +5,18 @@ import (
5 "net/http"5 "net/http"
6 6
7 "github.com/labstack/echo/v4"7 "github.com/labstack/echo/v4"
8- "gogs.bnema.dev/gordon-echo/internal/ui"8+ "gogs.bnema.dev/gordon-echo/internal/app"
9 "gogs.bnema.dev/gordon-echo/pkg/utils"9 "gogs.bnema.dev/gordon-echo/pkg/utils"
10 )10 )
11 11
12-type ErrorHandler struct {12+type AppConfig struct {
13- Err error13+ *app.Config
14- C echo.Context
15- ErrorNumber int
16 }14 }
17 15
18-func Error404Handler(err error, c echo.Context) {16+func (a *AppConfig) Error404Handler(err error, c echo.Context) {
19 if he, ok := err.(*echo.HTTPError); ok {17 if he, ok := err.(*echo.HTTPError); ok {
20 if he.Code == http.StatusNotFound {18 if he.Code == http.StatusNotFound {
21- renderer, err := utils.GetRenderer("404.gohtml", ui.PublicFS, utils.NewLogger())19+ renderer, err := utils.GetRenderer("404.gohtml", a.Config.PublicFS, utils.NewLogger())
22 if err != nil {20 if err != nil {
23 c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))21 c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
24 }22 }
M internal/http/handlers/htmx-installer.go +18 -6
@@ -4,14 +4,19 @@ import (
44 "fmt"
55
66 "github.com/labstack/echo/v4"
7+ "gogs.bnema.dev/gordon-echo/pkg/scripts"
78 "gogs.bnema.dev/gordon-echo/pkg/utils"
89 )
910
10-// Create a RejectPOSTPolicy function to reject POST requests if they are not allowed
11-
12-func traefikInstallerHandler(c echo.Context, logger *utils.Logger) error {
11+// TraefikInstallerHandler handles the installation of Traefik
12+func TraefikInstallerHandler(c echo.Context) error {
13+ // Retrieve the logger from the context
14+ logger := c.Get("logger").(*utils.Logger)
1315 // Reject POST requests if they are not allowed with the RejectPOSTPolicy function
14- RejectPOSTPolicy(c)
16+ if err := RejectPOSTPolicy(c); err != nil {
17+ logger.Error().Err(err).Msg("POST request rejected")
18+ return err
19+ }
1520
1621 // Get data from the form install-traefik-form (input fields : topdomain, adminemail) and sanitize it
1722 topDomain, err := SanitizeUserInput(c.FormValue("topdomain"))
@@ -25,9 +30,16 @@ func traefikInstallerHandler(c echo.Context, logger *utils.Logger) error {
2530 return err
2631 }
2732
28- fmt.Println(topDomain, adminEmail)
33+ fmt.Println("POST request PAYLOAD=", topDomain, adminEmail)
2934
30- // stop here for now
35+ // Call to create the YAML files
36+ err = scripts.CreateYAMLFiles(topDomain, adminEmail, logger)
37+ if err != nil {
38+ logger.Error().Err(err).Msg("Failed to create YAML files")
39+ return err
40+ }
3141 return nil
3242
3343 }
44+
45+// use the handler at the route /admin/install/traefik
@@ -4,14 +4,19 @@ import (
4 "fmt"4 "fmt"
5 5
6 "github.com/labstack/echo/v4"6 "github.com/labstack/echo/v4"
7+ "gogs.bnema.dev/gordon-echo/pkg/scripts"
7 "gogs.bnema.dev/gordon-echo/pkg/utils"8 "gogs.bnema.dev/gordon-echo/pkg/utils"
8 )9 )
9 10
10-// Create a RejectPOSTPolicy function to reject POST requests if they are not allowed11+// TraefikInstallerHandler handles the installation of Traefik
11-12+func TraefikInstallerHandler(c echo.Context) error {
12-func traefikInstallerHandler(c echo.Context, logger *utils.Logger) error {13+ // Retrieve the logger from the context
14+ logger := c.Get("logger").(*utils.Logger)
13 // Reject POST requests if they are not allowed with the RejectPOSTPolicy function15 // Reject POST requests if they are not allowed with the RejectPOSTPolicy function
14- RejectPOSTPolicy(c)16+ if err := RejectPOSTPolicy(c); err != nil {
17+ logger.Error().Err(err).Msg("POST request rejected")
18+ return err
19+ }
15 20
16 // Get data from the form install-traefik-form (input fields : topdomain, adminemail) and sanitize it21 // Get data from the form install-traefik-form (input fields : topdomain, adminemail) and sanitize it
17 topDomain, err := SanitizeUserInput(c.FormValue("topdomain"))22 topDomain, err := SanitizeUserInput(c.FormValue("topdomain"))
@@ -25,9 +30,16 @@ func traefikInstallerHandler(c echo.Context, logger *utils.Logger) error {
25 return err30 return err
26 }31 }
27 32
28- fmt.Println(topDomain, adminEmail)33+ fmt.Println("POST request PAYLOAD=", topDomain, adminEmail)
29 34
30- // stop here for now35+ // Call to create the YAML files
36+ err = scripts.CreateYAMLFiles(topDomain, adminEmail, logger)
37+ if err != nil {
38+ logger.Error().Err(err).Msg("Failed to create YAML files")
39+ return err
40+ }
31 return nil41 return nil
32 42
33 }43 }
44+
45+// use the handler at the route /admin/install/traefik
M internal/http/handlers/post.go +0 -5
@@ -25,11 +25,6 @@ func RejectPOSTPolicy(c echo.Context) error {
2525 return fmt.Errorf("missing user agent")
2626 }
2727
28- // Check for suspicious content types, e.g., "application/x-www-form-urlencoded"
29- if c.Request().Header.Get("Content-Type") != "application/json" {
30- return fmt.Errorf("invalid content type")
31- }
32-
3328 // If all checks pass
3429 return nil
3530 }
@@ -25,11 +25,6 @@ func RejectPOSTPolicy(c echo.Context) error {
25 return fmt.Errorf("missing user agent")25 return fmt.Errorf("missing user agent")
26 }26 }
27 27
28- // Check for suspicious content types, e.g., "application/x-www-form-urlencoded"
29- if c.Request().Header.Get("Content-Type") != "application/json" {
30- return fmt.Errorf("invalid content type")
31- }
32-
33 // If all checks pass28 // If all checks pass
34 return nil29 return nil
35 }30 }
M internal/http/middlewares/logger.go +18 -2
@@ -1,16 +1,23 @@
11 package middlewares
22
33 import (
4+ "strings"
5+
46 "github.com/labstack/echo/v4"
57 "github.com/labstack/echo/v4/middleware"
6- "github.com/rs/zerolog"
8+ "gogs.bnema.dev/gordon-echo/pkg/utils"
79 )
810
9-func NewRequestLogger(logger zerolog.Logger) echo.MiddlewareFunc {
11+// HTTPAccessLogger creates a new middleware to log HTTP requests using the provided HttpLogger.
12+func HTTPAccessLogger(logger *utils.Logger) echo.MiddlewareFunc {
1013 return middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
1114 LogURI: true,
1215 LogStatus: true,
1316 LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
17+ // Filter out any requests that contain the substring "sse" in the URI
18+ if strings.Contains(v.URI, "sse") {
19+ return nil
20+ }
1421 logger.Info().
1522 Str("type", "http").
1623 Str("remote_ip", c.RealIP()).
@@ -22,3 +29,12 @@ func NewRequestLogger(logger zerolog.Logger) echo.MiddlewareFunc {
2229 },
2330 })
2431 }
32+
33+func SetLogger(logger *utils.Logger) echo.MiddlewareFunc {
34+ return func(next echo.HandlerFunc) echo.HandlerFunc {
35+ return func(c echo.Context) error {
36+ c.Set("logger", logger)
37+ return next(c)
38+ }
39+ }
40+}
@@ -1,16 +1,23 @@
1 package middlewares1 package middlewares
2 2
3 import (3 import (
4+ "strings"
5+
4 "github.com/labstack/echo/v4"6 "github.com/labstack/echo/v4"
5 "github.com/labstack/echo/v4/middleware"7 "github.com/labstack/echo/v4/middleware"
6- "github.com/rs/zerolog"8+ "gogs.bnema.dev/gordon-echo/pkg/utils"
7 )9 )
8 10
9-func NewRequestLogger(logger zerolog.Logger) echo.MiddlewareFunc {11+// HTTPAccessLogger creates a new middleware to log HTTP requests using the provided HttpLogger.
12+func HTTPAccessLogger(logger *utils.Logger) echo.MiddlewareFunc {
10 return middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{13 return middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{
11 LogURI: true,14 LogURI: true,
12 LogStatus: true,15 LogStatus: true,
13 LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {16 LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error {
17+ // Filter out any requests that contain the substring "sse" in the URI
18+ if strings.Contains(v.URI, "sse") {
19+ return nil
20+ }
14 logger.Info().21 logger.Info().
15 Str("type", "http").22 Str("type", "http").
16 Str("remote_ip", c.RealIP()).23 Str("remote_ip", c.RealIP()).
@@ -22,3 +29,12 @@ func NewRequestLogger(logger zerolog.Logger) echo.MiddlewareFunc {
22 },29 },
23 })30 })
24 }31 }
32+
33+func SetLogger(logger *utils.Logger) echo.MiddlewareFunc {
34+ return func(next echo.HandlerFunc) echo.HandlerFunc {
35+ return func(c echo.Context) error {
36+ c.Set("logger", logger)
37+ return next(c)
38+ }
39+ }
40+}
M internal/http/routes/admin-routes.go +11 -7
@@ -5,12 +5,16 @@ import (
55 "net/http"
66
77 "github.com/labstack/echo/v4"
8+ "gogs.bnema.dev/gordon-echo/config"
89 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
9- "gogs.bnema.dev/gordon-echo/internal/ui"
1010 "gogs.bnema.dev/gordon-echo/pkg/utils"
1111 )
1212
13-func AdminRoute(c echo.Context) error {
13+type AppConfig struct {
14+ Config config.Provider
15+}
16+
17+func (a *AppConfig) AdminRoute(c echo.Context) error {
1418 // Check if it's the first launch of the app
1519 if utils.IsConfigFilePresent() {
1620 fmt.Println("Fresh installation detected, redirecting to /admin/install")
@@ -20,12 +24,12 @@ func AdminRoute(c echo.Context) error {
2024 // Retrieve the current language from the context
2125 lang := c.Get(middlewares.LangKey).(string)
2226
23- staticData, err := utils.PopulateDataFromYAML(lang)
27+ staticData, err := a.PopulateDataFromYAML(lang)
2428 if err != nil {
2529 return err
2630 }
2731
28- renderer, err := utils.GetRenderer("index.gohtml", ui.PublicFS, utils.NewLogger())
32+ renderer, err := utils.GetRenderer("index.gohtml", a.Config.GetPublicFS(), utils.NewLogger())
2933 if err != nil {
3034 return err
3135 }
@@ -40,7 +44,7 @@ func AdminRoute(c echo.Context) error {
4044 return c.HTML(http.StatusOK, html)
4145 }
4246
43-func InstallRoute(c echo.Context) error {
47+func (a *AppConfig) InstallRoute(c echo.Context) error {
4448 // Check if it's the first launch of the app
4549 if !utils.IsConfigFilePresent() {
4650 fmt.Println("Config file already present, redirecting to /admin")
@@ -50,12 +54,12 @@ func InstallRoute(c echo.Context) error {
5054 // Retrieve the current language from the context
5155 lang := c.Get(middlewares.LangKey).(string)
5256
53- staticData, err := utils.PopulateDataFromYAML(lang)
57+ staticData, err := a.PopulateDataFromYAML(lang)
5458 if err != nil {
5559 return err
5660 }
5761
58- renderer, err := utils.GetRenderer("install.gohtml", ui.TemplateFS, utils.NewLogger())
62+ renderer, err := utils.GetRenderer("install.gohtml", a.Config.GetTemplateFS(), utils.NewLogger())
5963 if err != nil {
6064 return err
6165 }
@@ -5,12 +5,16 @@ import (
5 "net/http"5 "net/http"
6 6
7 "github.com/labstack/echo/v4"7 "github.com/labstack/echo/v4"
8+ "gogs.bnema.dev/gordon-echo/config"
8 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"9 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
9- "gogs.bnema.dev/gordon-echo/internal/ui"
10 "gogs.bnema.dev/gordon-echo/pkg/utils"10 "gogs.bnema.dev/gordon-echo/pkg/utils"
11 )11 )
12 12
13-func AdminRoute(c echo.Context) error {13+type AppConfig struct {
14+ Config config.Provider
15+}
16+
17+func (a *AppConfig) AdminRoute(c echo.Context) error {
14 // Check if it's the first launch of the app18 // Check if it's the first launch of the app
15 if utils.IsConfigFilePresent() {19 if utils.IsConfigFilePresent() {
16 fmt.Println("Fresh installation detected, redirecting to /admin/install")20 fmt.Println("Fresh installation detected, redirecting to /admin/install")
@@ -20,12 +24,12 @@ func AdminRoute(c echo.Context) error {
20 // Retrieve the current language from the context24 // Retrieve the current language from the context
21 lang := c.Get(middlewares.LangKey).(string)25 lang := c.Get(middlewares.LangKey).(string)
22 26
23- staticData, err := utils.PopulateDataFromYAML(lang)27+ staticData, err := a.PopulateDataFromYAML(lang)
24 if err != nil {28 if err != nil {
25 return err29 return err
26 }30 }
27 31
28- renderer, err := utils.GetRenderer("index.gohtml", ui.PublicFS, utils.NewLogger())32+ renderer, err := utils.GetRenderer("index.gohtml", a.Config.GetPublicFS(), utils.NewLogger())
29 if err != nil {33 if err != nil {
30 return err34 return err
31 }35 }
@@ -40,7 +44,7 @@ func AdminRoute(c echo.Context) error {
40 return c.HTML(http.StatusOK, html)44 return c.HTML(http.StatusOK, html)
41 }45 }
42 46
43-func InstallRoute(c echo.Context) error {47+func (a *AppConfig) InstallRoute(c echo.Context) error {
44 // Check if it's the first launch of the app48 // Check if it's the first launch of the app
45 if !utils.IsConfigFilePresent() {49 if !utils.IsConfigFilePresent() {
46 fmt.Println("Config file already present, redirecting to /admin")50 fmt.Println("Config file already present, redirecting to /admin")
@@ -50,12 +54,12 @@ func InstallRoute(c echo.Context) error {
50 // Retrieve the current language from the context54 // Retrieve the current language from the context
51 lang := c.Get(middlewares.LangKey).(string)55 lang := c.Get(middlewares.LangKey).(string)
52 56
53- staticData, err := utils.PopulateDataFromYAML(lang)57+ staticData, err := a.PopulateDataFromYAML(lang)
54 if err != nil {58 if err != nil {
55 return err59 return err
56 }60 }
57 61
58- renderer, err := utils.GetRenderer("install.gohtml", ui.TemplateFS, utils.NewLogger())62+ renderer, err := utils.GetRenderer("install.gohtml", a.Config.GetTemplateFS(), utils.NewLogger())
59 if err != nil {63 if err != nil {
60 return err64 return err
61 }65 }
M internal/http/routes/router.go +23 -9
@@ -8,23 +8,37 @@ import (
88 )
99
1010 func ConfigureRouter(e *echo.Echo, app *app.App) *echo.Echo {
11- // The logger is already set in main.go, so we don't need to set it again here.
12-
13- // Register middlewares
14- e.Use(middlewares.NewRequestLogger(app.HttpLogger.Logger))
11+ // SetLogger is a middleware that sets the logger in the context
12+ e.Use(middlewares.SetLogger(app.HttpLogger))
13+ // HTTPAccessLogger is a middleware that logs HTTP requests
14+ e.Use(middlewares.HTTPAccessLogger(app.HttpLogger))
1515 e.Use(middlewares.LanguageDetection)
1616 e.Use(middlewares.ColorSchemeDetection)
17- //e.Use(middlewares.SecureMiddleware()) // Uncomment this line to enable the secure middleware
18- // Register routes
19- e = bindStaticAdminUI(e)
17+ //e.Use(middlewares.SecureMiddleware()) // Need testing
2018
19+ // Register routes
20+ e = bindAdminRoutes(e)
21+ e = bindHtmxRoutes(e)
22+ e = bindStaticRoutes(e)
23+ e.HTTPErrorHandler = handlers.ErrorNumberHandler
2124 return e
2225 }
23-func bindStaticAdminUI(e *echo.Echo) *echo.Echo {
26+func bindAdminRoutes(e *echo.Echo) *echo.Echo {
2427 e.GET("/admin", AdminRoute)
2528 e.GET("/admin/install", InstallRoute)
29+ e.POST("/admin/install/traefik", handlers.TraefikInstallerHandler)
30+ return e
31+}
32+
33+func bindHtmxRoutes(e *echo.Echo) *echo.Echo {
2634 e.GET("/htmx", handlers.HTMXHandler)
35+ e.GET("/htmx/fragment", handlers.HTMXHandler)
36+ e.POST("/htmx", handlers.HTMXHandler)
37+ e.POST("/htmx/fragment", handlers.HTMXHandler)
38+ return e
39+}
40+
41+func bindStaticRoutes(e *echo.Echo) *echo.Echo {
2742 e.GET("/*", StaticRoute)
28- e.HTTPErrorHandler = handlers.ErrorNumberHandler
2943 return e
3044 }
@@ -8,23 +8,37 @@ import (
8 )8 )
9 9
10 func ConfigureRouter(e *echo.Echo, app *app.App) *echo.Echo {10 func ConfigureRouter(e *echo.Echo, app *app.App) *echo.Echo {
11- // The logger is already set in main.go, so we don't need to set it again here.11+ // SetLogger is a middleware that sets the logger in the context
12-12+ e.Use(middlewares.SetLogger(app.HttpLogger))
13- // Register middlewares13+ // HTTPAccessLogger is a middleware that logs HTTP requests
14- e.Use(middlewares.NewRequestLogger(app.HttpLogger.Logger))14+ e.Use(middlewares.HTTPAccessLogger(app.HttpLogger))
15 e.Use(middlewares.LanguageDetection)15 e.Use(middlewares.LanguageDetection)
16 e.Use(middlewares.ColorSchemeDetection)16 e.Use(middlewares.ColorSchemeDetection)
17- //e.Use(middlewares.SecureMiddleware()) // Uncomment this line to enable the secure middleware17+ //e.Use(middlewares.SecureMiddleware()) // Need testing
18- // Register routes
19- e = bindStaticAdminUI(e)
20 18
19+ // Register routes
20+ e = bindAdminRoutes(e)
21+ e = bindHtmxRoutes(e)
22+ e = bindStaticRoutes(e)
23+ e.HTTPErrorHandler = handlers.ErrorNumberHandler
21 return e24 return e
22 }25 }
23-func bindStaticAdminUI(e *echo.Echo) *echo.Echo {26+func bindAdminRoutes(e *echo.Echo) *echo.Echo {
24 e.GET("/admin", AdminRoute)27 e.GET("/admin", AdminRoute)
25 e.GET("/admin/install", InstallRoute)28 e.GET("/admin/install", InstallRoute)
29+ e.POST("/admin/install/traefik", handlers.TraefikInstallerHandler)
30+ return e
31+}
32+
33+func bindHtmxRoutes(e *echo.Echo) *echo.Echo {
26 e.GET("/htmx", handlers.HTMXHandler)34 e.GET("/htmx", handlers.HTMXHandler)
35+ e.GET("/htmx/fragment", handlers.HTMXHandler)
36+ e.POST("/htmx", handlers.HTMXHandler)
37+ e.POST("/htmx/fragment", handlers.HTMXHandler)
38+ return e
39+}
40+
41+func bindStaticRoutes(e *echo.Echo) *echo.Echo {
27 e.GET("/*", StaticRoute)42 e.GET("/*", StaticRoute)
28- e.HTTPErrorHandler = handlers.ErrorNumberHandler
29 return e43 return e
30 }44 }
M internal/http/routes/static-routes.go +2 -3
@@ -4,10 +4,9 @@ import (
44 "os"
55
66 "github.com/labstack/echo/v4"
7- "gogs.bnema.dev/gordon-echo/internal/ui"
87 )
98
10-func StaticRoute(c echo.Context) error {
9+func (a *AppConfig) StaticRoute(c echo.Context) error {
1110
1211 // Set the cache-control header to cache the static files for 1 day if PROD env is set to true
1312
@@ -16,5 +15,5 @@ func StaticRoute(c echo.Context) error {
1615 } else {
1716 c.Response().Header().Set("Cache-Control", "no-cache")
1817 }
19- return echo.StaticDirectoryHandler(ui.PublicFS, false)(c)
18+ return echo.StaticDirectoryHandler(a.Config.GetPublicFS(), false)(c)
2019 }
@@ -4,10 +4,9 @@ import (
4 "os"4 "os"
5 5
6 "github.com/labstack/echo/v4"6 "github.com/labstack/echo/v4"
7- "gogs.bnema.dev/gordon-echo/internal/ui"
8 )7 )
9 8
10-func StaticRoute(c echo.Context) error {9+func (a *AppConfig) StaticRoute(c echo.Context) error {
11 10
12 // Set the cache-control header to cache the static files for 1 day if PROD env is set to true11 // Set the cache-control header to cache the static files for 1 day if PROD env is set to true
13 12
@@ -16,5 +15,5 @@ func StaticRoute(c echo.Context) error {
16 } else {15 } else {
17 c.Response().Header().Set("Cache-Control", "no-cache")16 c.Response().Header().Set("Cache-Control", "no-cache")
18 }17 }
19- return echo.StaticDirectoryHandler(ui.PublicFS, false)(c)18+ return echo.StaticDirectoryHandler(a.Config.GetPublicFS(), false)(c)
20 }19 }
D internal/ui/embed.go +0 -17
@@ -1,17 +0,0 @@
1-package ui
2-
3-import (
4- "embed"
5-
6- "github.com/labstack/echo/v4"
7-)
8-
9-//go:embed public/*
10-var public embed.FS
11-
12-//go:embed templates/*
13-var templates embed.FS
14-
15-var PublicFS = echo.MustSubFS(public, "public")
16-
17-var TemplateFS = echo.MustSubFS(templates, "templates")
@@ -1,17 +0,0 @@
1-package ui
2-
3-import (
4- "embed"
5-
6- "github.com/labstack/echo/v4"
7-)
8-
9-//go:embed public/*
10-var public embed.FS
11-
12-//go:embed templates/*
13-var templates embed.FS
14-
15-var PublicFS = echo.MustSubFS(public, "public")
16-
17-var TemplateFS = echo.MustSubFS(templates, "templates")
M internal/ui/templates/install.gohtml +3 -2
@@ -59,9 +59,10 @@
5959 <div class="w-full md:w-1/2 p-4">
6060 <form
6161 id="install-traefik-form"
62- action="/install/traefik"
62+ action="/admin/install/traefik"
6363 method="POST"
64- hx-post="/install/traefik"
64+ hx-post="/admin/install/traefik"
65+ hx-swap="outerHTML"
6566 >
6667 <h2 class="">1.1 - Set your top domain name</h2>
6768 <input
@@ -59,9 +59,10 @@
59 <div class="w-full md:w-1/2 p-4">59 <div class="w-full md:w-1/2 p-4">
60 <form60 <form
61 id="install-traefik-form"61 id="install-traefik-form"
62- action="/install/traefik"62+ action="/admin/install/traefik"
63 method="POST"63 method="POST"
64- hx-post="/install/traefik"64+ hx-post="/admin/install/traefik"
65+ hx-swap="outerHTML"
65 >66 >
66 <h2 class="">1.1 - Set your top domain name</h2>67 <h2 class="">1.1 - Set your top domain name</h2>
67 <input68 <input
M pkg/docker/registry.go +13 -0
@@ -5,12 +5,14 @@ import (
55 "fmt"
66 "io"
77 "os"
8+ "os/exec"
89
910 "github.com/docker/docker/api/types"
1011 "github.com/docker/docker/api/types/container"
1112 "github.com/docker/docker/client"
1213 "github.com/docker/docker/pkg/archive"
1314 "github.com/docker/docker/pkg/stdcopy"
15+ "gogs.bnema.dev/gordon-echo/pkg/utils"
1416 )
1517
1618 func DockertPullImageTest() {
@@ -125,3 +127,14 @@ func CreateImageFromDockerfile() {
125127 panic(err)
126128 }
127129 }
130+
131+func RunCommand(command string, arg string, logger *utils.Logger) error {
132+ cmd := exec.Command(command, arg)
133+ cmd.Stdout = logger
134+ cmd.Stderr = logger
135+ err := cmd.Run()
136+ if err != nil {
137+ return err
138+ }
139+ return nil
140+}
@@ -5,12 +5,14 @@ import (
5 "fmt"5 "fmt"
6 "io"6 "io"
7 "os"7 "os"
8+ "os/exec"
8 9
9 "github.com/docker/docker/api/types"10 "github.com/docker/docker/api/types"
10 "github.com/docker/docker/api/types/container"11 "github.com/docker/docker/api/types/container"
11 "github.com/docker/docker/client"12 "github.com/docker/docker/client"
12 "github.com/docker/docker/pkg/archive"13 "github.com/docker/docker/pkg/archive"
13 "github.com/docker/docker/pkg/stdcopy"14 "github.com/docker/docker/pkg/stdcopy"
15+ "gogs.bnema.dev/gordon-echo/pkg/utils"
14 )16 )
15 17
16 func DockertPullImageTest() {18 func DockertPullImageTest() {
@@ -125,3 +127,14 @@ func CreateImageFromDockerfile() {
125 panic(err)127 panic(err)
126 }128 }
127 }129 }
130+
131+func RunCommand(command string, arg string, logger *utils.Logger) error {
132+ cmd := exec.Command(command, arg)
133+ cmd.Stdout = logger
134+ cmd.Stderr = logger
135+ err := cmd.Run()
136+ if err != nil {
137+ return err
138+ }
139+ return nil
140+}
M pkg/scripts/install-traefik.go +51 -0
@@ -1,1 +1,52 @@
11 package scripts
2+
3+import (
4+ "os"
5+
6+ "gogs.bnema.dev/gordon-echo/pkg/templating"
7+ "gogs.bnema.dev/gordon-echo/pkg/utils"
8+)
9+
10+func CreateYAMLFiles(topDomain, adminEmail string, logger *utils.Logger) error {
11+ // Setup the TXT renderer for traefik-compose.goyml
12+ rendererCompose, err := templating.GetTXTRenderer("traefik-compose.goyml", nil, logger)
13+ if err != nil {
14+ return err
15+ }
16+
17+ // Render the traefik-compose.goyml with payload data
18+ composeContent, err := rendererCompose.TXTRender(map[string]string{
19+ "topdomain": topDomain,
20+ })
21+ if err != nil {
22+ return err
23+ }
24+
25+ // Save the rendered content to a file
26+ err = os.WriteFile("traefik-compose.yml", []byte(composeContent), 0644)
27+ if err != nil {
28+ return err
29+ }
30+
31+ // Setup the TXT renderer for traefik.goyml
32+ rendererTraefik, err := templating.GetTXTRenderer("traefik.goyml", nil, logger)
33+ if err != nil {
34+ return err
35+ }
36+
37+ // Render the traefik.goyml with payload data
38+ traefikContent, err := rendererTraefik.TXTRender(map[string]string{
39+ "admin.email": adminEmail,
40+ })
41+ if err != nil {
42+ return err
43+ }
44+
45+ // Save the rendered content to a file
46+ err = os.WriteFile("traefik/traefik.yml", []byte(traefikContent), 0644)
47+ if err != nil {
48+ return err
49+ }
50+
51+ return nil
52+}
@@ -1,1 +1,52 @@
1 package scripts1 package scripts
2+
3+import (
4+ "os"
5+
6+ "gogs.bnema.dev/gordon-echo/pkg/templating"
7+ "gogs.bnema.dev/gordon-echo/pkg/utils"
8+)
9+
10+func CreateYAMLFiles(topDomain, adminEmail string, logger *utils.Logger) error {
11+ // Setup the TXT renderer for traefik-compose.goyml
12+ rendererCompose, err := templating.GetTXTRenderer("traefik-compose.goyml", nil, logger)
13+ if err != nil {
14+ return err
15+ }
16+
17+ // Render the traefik-compose.goyml with payload data
18+ composeContent, err := rendererCompose.TXTRender(map[string]string{
19+ "topdomain": topDomain,
20+ })
21+ if err != nil {
22+ return err
23+ }
24+
25+ // Save the rendered content to a file
26+ err = os.WriteFile("traefik-compose.yml", []byte(composeContent), 0644)
27+ if err != nil {
28+ return err
29+ }
30+
31+ // Setup the TXT renderer for traefik.goyml
32+ rendererTraefik, err := templating.GetTXTRenderer("traefik.goyml", nil, logger)
33+ if err != nil {
34+ return err
35+ }
36+
37+ // Render the traefik.goyml with payload data
38+ traefikContent, err := rendererTraefik.TXTRender(map[string]string{
39+ "admin.email": adminEmail,
40+ })
41+ if err != nil {
42+ return err
43+ }
44+
45+ // Save the rendered content to a file
46+ err = os.WriteFile("traefik/traefik.yml", []byte(traefikContent), 0644)
47+ if err != nil {
48+ return err
49+ }
50+
51+ return nil
52+}
M pkg/utils/html-fragments.go +7 -3
@@ -5,13 +5,17 @@ import (
55 "fmt"
66
77 "github.com/PuerkitoBio/goquery"
8- "gogs.bnema.dev/gordon-echo/internal/ui"
8+ "gogs.bnema.dev/gordon-echo/config"
99 )
1010
11+type AppConfig struct {
12+ Config config.Provider
13+}
14+
1115 // GetHTMLFragmentByID returns the HTML fragment with the specified id
12-func GetHTMLFragmentByID(id string, data interface{}) (string, error) {
16+func (a *AppConfig) GetHTMLFragmentByID(id string, data interface{}) (string, error) {
1317 // 1. Render the template
14- renderer, err := GetRenderer("components.gohtml", ui.TemplateFS, NewLogger())
18+ renderer, err := GetRenderer("components.gohtml", a.Config.GetTemplateFS(), NewLogger())
1519 if err != nil {
1620 return "", fmt.Errorf("failed to get renderer: %w", err)
1721 }
@@ -5,13 +5,17 @@ import (
5 "fmt"5 "fmt"
6 6
7 "github.com/PuerkitoBio/goquery"7 "github.com/PuerkitoBio/goquery"
8- "gogs.bnema.dev/gordon-echo/internal/ui"8+ "gogs.bnema.dev/gordon-echo/config"
9 )9 )
10 10
11+type AppConfig struct {
12+ Config config.Provider
13+}
14+
11 // GetHTMLFragmentByID returns the HTML fragment with the specified id15 // GetHTMLFragmentByID returns the HTML fragment with the specified id
12-func GetHTMLFragmentByID(id string, data interface{}) (string, error) {16+func (a *AppConfig) GetHTMLFragmentByID(id string, data interface{}) (string, error) {
13 // 1. Render the template17 // 1. Render the template
14- renderer, err := GetRenderer("components.gohtml", ui.TemplateFS, NewLogger())18+ renderer, err := GetRenderer("components.gohtml", a.Config.GetTemplateFS(), NewLogger())
15 if err != nil {19 if err != nil {
16 return "", fmt.Errorf("failed to get renderer: %w", err)20 return "", fmt.Errorf("failed to get renderer: %w", err)
17 }21 }
M pkg/utils/html-render.go +7 -0
@@ -39,6 +39,13 @@ func (r *Renderer) Render(data interface{}) (string, error) {
3939
4040 // GetRenderer function returns a new Renderer instance
4141 func GetRenderer(filename string, fs fs.FS, logger *Logger) (*Renderer, error) {
42+ // Check if the file exists in the provided fs.FS using fs.Open
43+ file, err := fs.Open(filename)
44+ if err != nil {
45+ logger.Error().Err(err).Msg("Template or model '%s' not found" + filename)
46+ return nil, err
47+ }
48+ file.Close() // Close the file after checking
4249 tmpl, err := template.New(filename).ParseFS(fs, filename)
4350 if err != nil {
4451 logger.Error().Err(err).Msg("Failed to parse template")
@@ -39,6 +39,13 @@ func (r *Renderer) Render(data interface{}) (string, error) {
39 39
40 // GetRenderer function returns a new Renderer instance40 // GetRenderer function returns a new Renderer instance
41 func GetRenderer(filename string, fs fs.FS, logger *Logger) (*Renderer, error) {41 func GetRenderer(filename string, fs fs.FS, logger *Logger) (*Renderer, error) {
42+ // Check if the file exists in the provided fs.FS using fs.Open
43+ file, err := fs.Open(filename)
44+ if err != nil {
45+ logger.Error().Err(err).Msg("Template or model '%s' not found" + filename)
46+ return nil, err
47+ }
48+ file.Close() // Close the file after checking
42 tmpl, err := template.New(filename).ParseFS(fs, filename)49 tmpl, err := template.New(filename).ParseFS(fs, filename)
43 if err != nil {50 if err != nil {
44 logger.Error().Err(err).Msg("Failed to parse template")51 logger.Error().Err(err).Msg("Failed to parse template")
M pkg/utils/parser.go +4 -5
@@ -6,7 +6,6 @@ import (
66 "os"
77
88 "github.com/tidwall/gjson"
9- "gogs.bnema.dev/gordon-echo/internal/ui"
109 "gopkg.in/yaml.v3"
1110 )
1211
@@ -19,11 +18,11 @@ type YamlData struct {
1918 }
2019
2120 // ReadDataFromYAML loads the data from the given YAML file
22-func ReadDataFromYAML() (YamlData, error) {
21+func (a *AppConfig) ReadDataFromYAML() (YamlData, error) {
2322 var data YamlData
2423
2524 // Open the YAML file inside the ui/components directory
26- file, err := ui.TemplateFS.Open("strings.yaml")
25+ file, err := a.Config.GetTemplateFS().Open("strings.yaml")
2726 if err != nil {
2827 return data, fmt.Errorf("failed to open strings.yaml: %w", err)
2928 }
@@ -44,8 +43,8 @@ func ReadDataFromYAML() (YamlData, error) {
4443 }
4544
4645 // PopulateDataFromYAML retrieves the data from the YAML file and sets the appropriate language data
47-func PopulateDataFromYAML(currentLang string) (YamlData, error) {
48- data, err := ReadDataFromYAML()
46+func (a *AppConfig) PopulateDataFromYAML(currentLang string) (YamlData, error) {
47+ data, err := a.ReadDataFromYAML()
4948 if err != nil {
5049 return data, err
5150 }
@@ -6,7 +6,6 @@ import (
6 "os"6 "os"
7 7
8 "github.com/tidwall/gjson"8 "github.com/tidwall/gjson"
9- "gogs.bnema.dev/gordon-echo/internal/ui"
10 "gopkg.in/yaml.v3"9 "gopkg.in/yaml.v3"
11 )10 )
12 11
@@ -19,11 +18,11 @@ type YamlData struct {
19 }18 }
20 19
21 // ReadDataFromYAML loads the data from the given YAML file20 // ReadDataFromYAML loads the data from the given YAML file
22-func ReadDataFromYAML() (YamlData, error) {21+func (a *AppConfig) ReadDataFromYAML() (YamlData, error) {
23 var data YamlData22 var data YamlData
24 23
25 // Open the YAML file inside the ui/components directory24 // Open the YAML file inside the ui/components directory
26- file, err := ui.TemplateFS.Open("strings.yaml")25+ file, err := a.Config.GetTemplateFS().Open("strings.yaml")
27 if err != nil {26 if err != nil {
28 return data, fmt.Errorf("failed to open strings.yaml: %w", err)27 return data, fmt.Errorf("failed to open strings.yaml: %w", err)
29 }28 }
@@ -44,8 +43,8 @@ func ReadDataFromYAML() (YamlData, error) {
44 }43 }
45 44
46 // PopulateDataFromYAML retrieves the data from the YAML file and sets the appropriate language data45 // PopulateDataFromYAML retrieves the data from the YAML file and sets the appropriate language data
47-func PopulateDataFromYAML(currentLang string) (YamlData, error) {46+func (a *AppConfig) PopulateDataFromYAML(currentLang string) (YamlData, error) {
48- data, err := ReadDataFromYAML()47+ data, err := a.ReadDataFromYAML()
49 if err != nil {48 if err != nil {
50 return data, err49 return data, err
51 }50 }
M pkg/utils/txt-render.go +9 -1
@@ -38,8 +38,16 @@ func (r *TXTRenderer) TXTRender(data interface{}) (string, error) {
3838 return buf.String(), nil
3939 }
4040
41-// GetRenderer function returns a new Renderer instance
41+// GetTXTRenderer function returns a new Renderer instance
4242 func GetTXTRenderer(filename string, fs fs.FS, logger *Logger) (*TXTRenderer, error) {
43+ // Check if the file exists in the provided fs.FS using fs.Open
44+ file, err := fs.Open(filename)
45+ if err != nil {
46+ logger.Error().Err(err).Msg("Template or model '%s' not found" + filename)
47+ return nil, err
48+ }
49+ file.Close() // Close the file after checking
50+
4351 mdls, err := template.New(filename).ParseFS(fs, filename)
4452 if err != nil {
4553 logger.Error().Err(err).Msg("Failed to parse template")
@@ -38,8 +38,16 @@ func (r *TXTRenderer) TXTRender(data interface{}) (string, error) {
38 return buf.String(), nil38 return buf.String(), nil
39 }39 }
40 40
41-// GetRenderer function returns a new Renderer instance41+// GetTXTRenderer function returns a new Renderer instance
42 func GetTXTRenderer(filename string, fs fs.FS, logger *Logger) (*TXTRenderer, error) {42 func GetTXTRenderer(filename string, fs fs.FS, logger *Logger) (*TXTRenderer, error) {
43+ // Check if the file exists in the provided fs.FS using fs.Open
44+ file, err := fs.Open(filename)
45+ if err != nil {
46+ logger.Error().Err(err).Msg("Template or model '%s' not found" + filename)
47+ return nil, err
48+ }
49+ file.Close() // Close the file after checking
50+
43 mdls, err := template.New(filename).ParseFS(fs, filename)51 mdls, err := template.New(filename).ParseFS(fs, filename)
44 if err != nil {52 if err != nil {
45 logger.Error().Err(err).Msg("Failed to parse template")53 logger.Error().Err(err).Msg("Failed to parse template")

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close