detached 2cab113

- Move the embed.go at the root path of the project to be able to add

Models as embed/FS
- Full restructural change to use a config Provider to avoid circular
  dependency
- Refactor app.go to take those changes into account
- Fix logging directory setup not creating when none
commit
2cab113283ef2349e30b2468f2ea2fda8afcf68b
author
bnema · · 2023-09-05 16:22 UTC
committer
bnema · · 2023-09-05 18:24 UTC
parent
163ace9
signature
unsigned
17 files · +154 -214
M .air.toml +1 -1
@@ -7,7 +7,7 @@ tmp_dir = "tmp"
77 bin = "./tmp/main"
88 cmd = "go build -o ./tmp/main cmd/main.go"
99 delay = 0
10- exclude_dir = ["assets", "tmp", "vendor", "testdata"]
10+ exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules"]
1111 exclude_file = []
1212 exclude_regex = ["_test.go"]
1313 exclude_unchanged = false
@@ -7,7 +7,7 @@ tmp_dir = "tmp"
7 bin = "./tmp/main"7 bin = "./tmp/main"
8 cmd = "go build -o ./tmp/main cmd/main.go"8 cmd = "go build -o ./tmp/main cmd/main.go"
9 delay = 09 delay = 0
10- exclude_dir = ["assets", "tmp", "vendor", "testdata"]10+ exclude_dir = ["assets", "tmp", "vendor", "testdata", "node_modules"]
11 exclude_file = []11 exclude_file = []
12 exclude_regex = ["_test.go"]12 exclude_regex = ["_test.go"]
13 exclude_unchanged = false13 exclude_unchanged = false
M cmd/main.go +7 -8
@@ -1,7 +1,6 @@
11 package main
22
33 import (
4- "embed"
54 "os"
65
76 "github.com/labstack/echo/v4"
@@ -17,14 +16,14 @@ func main() {
1716 if port == "" {
1817 port = "1323"
1918 }
20-
21- config := app.Config{
22- TemplateFS: root.TemplateFS.(embed.FS),
23- PublicFS: root.PublicFS.(embed.FS),
24- ModelFS: root.ModelFS,
25- }
2619 // Initialize Gordon
27- gordon, err := app.NewApp(&config)
20+ gordon, err := app.NewApp(
21+ &app.Config{
22+ TemplateFS: root.TemplateFS,
23+ PublicFS: root.PublicFS,
24+ ModelFS: root.ModelFS,
25+ },
26+ )
2827 if err != nil {
2928 // Handle initialization error and exit
3029 gordon.AppLogger.Error().Err(err).Msg("Failed to initialize app")
@@ -1,7 +1,6 @@
1 package main1 package main
2 2
3 import (3 import (
4- "embed"
5 "os"4 "os"
6 5
7 "github.com/labstack/echo/v4"6 "github.com/labstack/echo/v4"
@@ -17,14 +16,14 @@ func main() {
17 if port == "" {16 if port == "" {
18 port = "1323"17 port = "1323"
19 }18 }
20-
21- config := app.Config{
22- TemplateFS: root.TemplateFS.(embed.FS),
23- PublicFS: root.PublicFS.(embed.FS),
24- ModelFS: root.ModelFS,
25- }
26 // Initialize Gordon19 // Initialize Gordon
27- gordon, err := app.NewApp(&config)20+ gordon, err := app.NewApp(
21+ &app.Config{
22+ TemplateFS: root.TemplateFS,
23+ PublicFS: root.PublicFS,
24+ ModelFS: root.ModelFS,
25+ },
26+ )
28 if err != nil {27 if err != nil {
29 // Handle initialization error and exit28 // Handle initialization error and exit
30 gordon.AppLogger.Error().Err(err).Msg("Failed to initialize app")29 gordon.AppLogger.Error().Err(err).Msg("Failed to initialize app")
M config/config-provider.go +4 -4
@@ -1,9 +1,9 @@
11 package config
22
3-import "embed"
3+import "io/fs"
44
55 type Provider interface {
6- GetTemplateFS() embed.FS
7- GetPublicFS() embed.FS
8- GetModelFS() embed.FS
6+ GetTemplateFS() fs.FS
7+ GetPublicFS() fs.FS
8+ GetModelFS() fs.FS
99 }
@@ -1,9 +1,9 @@
1 package config1 package config
2 2
3-import "embed"3+import "io/fs"
4 4
5 type Provider interface {5 type Provider interface {
6- GetTemplateFS() embed.FS6+ GetTemplateFS() fs.FS
7- GetPublicFS() embed.FS7+ GetPublicFS() fs.FS
8- GetModelFS() embed.FS8+ GetModelFS() fs.FS
9 }9 }
M embed.go +2 -6
@@ -14,13 +14,9 @@ var public embed.FS
1414 //go:embed internal/ui/templates/*
1515 var templates embed.FS
1616
17-// Embedding the YAML models
18-//
1917 //go:embed pkg/templating/models/*
20-var modelsEmbed embed.FS
18+var models embed.FS
2119
2220 var PublicFS = echo.MustSubFS(public, "internal/ui/public")
2321 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
22+var ModelFS = echo.MustSubFS(models, "pkg/templating/models")
@@ -14,13 +14,9 @@ var public embed.FS
14 //go:embed internal/ui/templates/*14 //go:embed internal/ui/templates/*
15 var templates embed.FS15 var templates embed.FS
16 16
17-// Embedding the YAML models
18-//
19 //go:embed pkg/templating/models/*17 //go:embed pkg/templating/models/*
20-var modelsEmbed embed.FS18+var models embed.FS
21 19
22 var PublicFS = echo.MustSubFS(public, "internal/ui/public")20 var PublicFS = echo.MustSubFS(public, "internal/ui/public")
23 var TemplateFS = echo.MustSubFS(templates, "internal/ui/templates")21 var TemplateFS = echo.MustSubFS(templates, "internal/ui/templates")
24-22+var ModelFS = echo.MustSubFS(models, "pkg/templating/models")
25-// Dont need Echo for models so we are just using embed.FS
26-var ModelFS embed.FS = modelsEmbed
M go.mod +1 -5
@@ -6,6 +6,7 @@ require (
66 github.com/PuerkitoBio/goquery v1.8.1
77 github.com/docker/docker v24.0.5+incompatible
88 github.com/labstack/echo/v4 v4.11.1
9+ github.com/microcosm-cc/bluemonday v1.0.25
910 github.com/tidwall/gjson v1.16.0
1011 gopkg.in/yaml.v3 v3.0.1
1112 )
@@ -25,10 +26,6 @@ require (
2526 github.com/gorilla/securecookie v1.1.1 // indirect
2627 github.com/gorilla/sessions v1.2.1 // indirect
2728 github.com/klauspost/compress v1.16.7 // indirect
28- github.com/microcosm-cc/bluemonday v1.0.25 // indirect
29- github.com/gorilla/securecookie v1.1.1 // indirect
30- github.com/gorilla/sessions v1.2.1 // indirect
31- github.com/klauspost/compress v1.16.7 // indirect
3229 github.com/moby/patternmatcher v0.6.0 // indirect
3330 github.com/moby/sys/sequential v0.5.0 // indirect
3431 github.com/moby/term v0.5.0 // indirect
@@ -47,7 +44,6 @@ require (
4744 )
4845
4946 require (
50- github.com/google/safehtml v0.1.0
5147 github.com/labstack/echo-contrib v0.15.0
5248 github.com/labstack/gommon v0.4.0
5349 github.com/mattn/go-colorable v0.1.13 // indirect
@@ -6,6 +6,7 @@ require (
6 github.com/PuerkitoBio/goquery v1.8.16 github.com/PuerkitoBio/goquery v1.8.1
7 github.com/docker/docker v24.0.5+incompatible7 github.com/docker/docker v24.0.5+incompatible
8 github.com/labstack/echo/v4 v4.11.18 github.com/labstack/echo/v4 v4.11.1
9+ github.com/microcosm-cc/bluemonday v1.0.25
9 github.com/tidwall/gjson v1.16.010 github.com/tidwall/gjson v1.16.0
10 gopkg.in/yaml.v3 v3.0.111 gopkg.in/yaml.v3 v3.0.1
11 )12 )
@@ -25,10 +26,6 @@ require (
25 github.com/gorilla/securecookie v1.1.1 // indirect26 github.com/gorilla/securecookie v1.1.1 // indirect
26 github.com/gorilla/sessions v1.2.1 // indirect27 github.com/gorilla/sessions v1.2.1 // indirect
27 github.com/klauspost/compress v1.16.7 // indirect28 github.com/klauspost/compress v1.16.7 // indirect
28- github.com/microcosm-cc/bluemonday v1.0.25 // indirect
29- github.com/gorilla/securecookie v1.1.1 // indirect
30- github.com/gorilla/sessions v1.2.1 // indirect
31- github.com/klauspost/compress v1.16.7 // indirect
32 github.com/moby/patternmatcher v0.6.0 // indirect29 github.com/moby/patternmatcher v0.6.0 // indirect
33 github.com/moby/sys/sequential v0.5.0 // indirect30 github.com/moby/sys/sequential v0.5.0 // indirect
34 github.com/moby/term v0.5.0 // indirect31 github.com/moby/term v0.5.0 // indirect
@@ -47,7 +44,6 @@ require (
47 )44 )
48 45
49 require (46 require (
50- github.com/google/safehtml v0.1.0
51 github.com/labstack/echo-contrib v0.15.047 github.com/labstack/echo-contrib v0.15.0
52 github.com/labstack/gommon v0.4.048 github.com/labstack/gommon v0.4.0
53 github.com/mattn/go-colorable v0.1.13 // indirect49 github.com/mattn/go-colorable v0.1.13 // indirect
M go.sum +0 -4
@@ -33,14 +33,10 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL
3333 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
3434 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
3535 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
36-github.com/google/safehtml v0.1.0 h1:EwLKo8qawTKfsi0orxcQAZzu07cICaBeFMegAU9eaT8=
37-github.com/google/safehtml v0.1.0/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU=
3836 github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
3937 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
4038 github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
4139 github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
42-github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
43-github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
4440 github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
4541 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
4642 github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
@@ -33,14 +33,10 @@ github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keL
33 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=33 github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
34 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=34 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
35 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=35 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
36-github.com/google/safehtml v0.1.0 h1:EwLKo8qawTKfsi0orxcQAZzu07cICaBeFMegAU9eaT8=
37-github.com/google/safehtml v0.1.0/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU=
38 github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=36 github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
39 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=37 github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
40 github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=38 github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
41 github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=39 github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
42-github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
43-github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
44 github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=40 github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
45 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=41 github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
46 github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=42 github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
M internal/app/app.go +24 -23
@@ -1,8 +1,9 @@
11 package app
22
33 import (
4- "embed"
4+ "fmt"
55 "io"
6+ "io/fs"
67 "os"
78 "path/filepath"
89
@@ -23,33 +24,33 @@ const (
2324 type App struct {
2425 AppLogger *utils.Logger
2526 HttpLogger *utils.Logger
27+ Config *Config
2628 }
2729 type Config struct {
2830 BuildDir string
2931 LogDir string
30- TemplateFS embed.FS
31- PublicFS embed.FS
32- ModelFS embed.FS
32+ TemplateFS fs.FS
33+ PublicFS fs.FS
34+ ModelFS fs.FS
3335 }
3436
35-func (c *Config) GetTemplateFS() embed.FS {
37+func (c *Config) GetTemplateFS() fs.FS {
3638 return c.TemplateFS
3739 }
38-
39-func (c *Config) GetPublicFS() embed.FS {
40+func (c *Config) GetPublicFS() fs.FS {
4041 return c.PublicFS
4142 }
4243
43-func (c *Config) GetModelFS() embed.FS {
44+func (c *Config) GetModelFS() fs.FS {
4445 return c.ModelFS
4546 }
4647
4748 var _ config.Provider = &Config{}
4849
49-func loadConfig(templateFS, publicFS, modelFS embed.FS) Config {
50+func defineEnv(config *Config) {
5051 // Get the environment variable
5152 env := os.Getenv(EnvVarName)
52- // If the environment variable is empty, considere it is dev -> (/tmp)
53+ // If the environment variable is empty, consider it is dev -> (/tmp)
5354 buildDir := os.Getenv(BuildDirEnvVarName)
5455 if buildDir == "" {
5556 buildDir = DefaultBuildDir
@@ -69,20 +70,17 @@ func loadConfig(templateFS, publicFS, modelFS embed.FS) Config {
6970 buildDir = "./" + buildDir
7071 logDir = filepath.Join(buildDir, logDir)
7172 }
72-
73- return Config{
74- BuildDir: buildDir,
75- LogDir: logDir,
76- TemplateFS: templateFS,
77- PublicFS: publicFS,
78- ModelFS: modelFS,
79- }
73+ config.BuildDir = buildDir
74+ config.LogDir = logDir
8075 }
76+
8177 func NewApp(config *Config) (*App, error) {
78+ defineEnv(config)
8279 SetupLogging(config)
8380 app := &App{
8481 AppLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "app.log")),
8582 HttpLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "http.log")),
83+ Config: config,
8684 }
8785 return app, nil
8886 }
@@ -103,10 +101,13 @@ func initializeLoggerOutput(cfg *Config, logFile string) io.Writer {
103101 consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
104102 return zerolog.MultiLevelWriter(file, consoleWriter)
105103 }
106-
107-func SetupLogging(cfg *Config) {
108- err := utils.CreateLogsDir(cfg.LogDir)
109- if err != nil {
110- panic(err)
104+func SetupLogging(config *Config) {
105+ // Check if the log directory exists
106+ if _, err := os.Stat(config.LogDir); os.IsNotExist(err) {
107+ // If not, create it
108+ err := os.MkdirAll(config.LogDir, 0755)
109+ if err != nil {
110+ panic(fmt.Sprintf("Failed to create log directory at path: %s, error: %s", config.LogDir, err))
111+ }
111112 }
112113 }
@@ -1,8 +1,9 @@
1 package app1 package app
2 2
3 import (3 import (
4- "embed"4+ "fmt"
5 "io"5 "io"
6+ "io/fs"
6 "os"7 "os"
7 "path/filepath"8 "path/filepath"
8 9
@@ -23,33 +24,33 @@ const (
23 type App struct {24 type App struct {
24 AppLogger *utils.Logger25 AppLogger *utils.Logger
25 HttpLogger *utils.Logger26 HttpLogger *utils.Logger
27+ Config *Config
26 }28 }
27 type Config struct {29 type Config struct {
28 BuildDir string30 BuildDir string
29 LogDir string31 LogDir string
30- TemplateFS embed.FS32+ TemplateFS fs.FS
31- PublicFS embed.FS33+ PublicFS fs.FS
32- ModelFS embed.FS34+ ModelFS fs.FS
33 }35 }
34 36
35-func (c *Config) GetTemplateFS() embed.FS {37+func (c *Config) GetTemplateFS() fs.FS {
36 return c.TemplateFS38 return c.TemplateFS
37 }39 }
38-40+func (c *Config) GetPublicFS() fs.FS {
39-func (c *Config) GetPublicFS() embed.FS {
40 return c.PublicFS41 return c.PublicFS
41 }42 }
42 43
43-func (c *Config) GetModelFS() embed.FS {44+func (c *Config) GetModelFS() fs.FS {
44 return c.ModelFS45 return c.ModelFS
45 }46 }
46 47
47 var _ config.Provider = &Config{}48 var _ config.Provider = &Config{}
48 49
49-func loadConfig(templateFS, publicFS, modelFS embed.FS) Config {50+func defineEnv(config *Config) {
50 // Get the environment variable51 // Get the environment variable
51 env := os.Getenv(EnvVarName)52 env := os.Getenv(EnvVarName)
52- // If the environment variable is empty, considere it is dev -> (/tmp)53+ // If the environment variable is empty, consider it is dev -> (/tmp)
53 buildDir := os.Getenv(BuildDirEnvVarName)54 buildDir := os.Getenv(BuildDirEnvVarName)
54 if buildDir == "" {55 if buildDir == "" {
55 buildDir = DefaultBuildDir56 buildDir = DefaultBuildDir
@@ -69,20 +70,17 @@ func loadConfig(templateFS, publicFS, modelFS embed.FS) Config {
69 buildDir = "./" + buildDir70 buildDir = "./" + buildDir
70 logDir = filepath.Join(buildDir, logDir)71 logDir = filepath.Join(buildDir, logDir)
71 }72 }
72-73+ config.BuildDir = buildDir
73- return Config{74+ config.LogDir = logDir
74- BuildDir: buildDir,
75- LogDir: logDir,
76- TemplateFS: templateFS,
77- PublicFS: publicFS,
78- ModelFS: modelFS,
79- }
80 }75 }
76+
81 func NewApp(config *Config) (*App, error) {77 func NewApp(config *Config) (*App, error) {
78+ defineEnv(config)
82 SetupLogging(config)79 SetupLogging(config)
83 app := &App{80 app := &App{
84 AppLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "app.log")),81 AppLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "app.log")),
85 HttpLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "http.log")),82 HttpLogger: utils.NewLogger().SetOutput(initializeLoggerOutput(config, "http.log")),
83+ Config: config,
86 }84 }
87 return app, nil85 return app, nil
88 }86 }
@@ -103,10 +101,13 @@ func initializeLoggerOutput(cfg *Config, logFile string) io.Writer {
103 consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}101 consoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
104 return zerolog.MultiLevelWriter(file, consoleWriter)102 return zerolog.MultiLevelWriter(file, consoleWriter)
105 }103 }
106-104+func SetupLogging(config *Config) {
107-func SetupLogging(cfg *Config) {105+ // Check if the log directory exists
108- err := utils.CreateLogsDir(cfg.LogDir)106+ if _, err := os.Stat(config.LogDir); os.IsNotExist(err) {
109- if err != nil {107+ // If not, create it
110- panic(err)108+ err := os.MkdirAll(config.LogDir, 0755)
109+ if err != nil {
110+ panic(fmt.Sprintf("Failed to create log directory at path: %s, error: %s", config.LogDir, err))
111+ }
111 }112 }
112 }113 }
M internal/http/handlers/errors.go +11 -41
@@ -5,50 +5,20 @@ import (
55 "net/http"
66
77 "github.com/labstack/echo/v4"
8- "gogs.bnema.dev/gordon-echo/internal/app"
9- "gogs.bnema.dev/gordon-echo/pkg/utils"
108 )
119
12-type AppConfig struct {
13- *app.Config
14-}
15-
16-func (a *AppConfig) Error404Handler(err error, c echo.Context) {
10+func ErrorNumberHandler(err error, c echo.Context) {
11+ code := http.StatusInternalServerError
1712 if he, ok := err.(*echo.HTTPError); ok {
18- if he.Code == http.StatusNotFound {
19- renderer, err := utils.GetRenderer("404.gohtml", a.Config.PublicFS, utils.NewLogger())
20- if err != nil {
21- c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
22- }
23-
24- data := map[string]interface{}{
25- "website": map[string]interface{}{
26- "title": "Page Not Found",
27- },
28- }
29-
30- html, err := renderer.Render(data)
31- if err != nil {
32- c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
33- }
34-
35- c.HTML(http.StatusNotFound, html)
36- return
37- }
13+ code = he.Code
3814 }
3915
40-}
41-
42-func Error200Handler(err error, c echo.Context) {
43- // Just print the error for now
44- fmt.Println(err)
45-}
46-
47-func Error403Handler(err error, c echo.Context) {
48- // Just print the error for now
49- fmt.Println(err)
50-}
51-
52-func ErrorNumberHandler(err error, c echo.Context) {
53- // For Each Error Code, we can have a different handler
16+ switch code {
17+ case http.StatusNotFound:
18+ c.String(code, fmt.Sprintf("An error occurred: %v", err))
19+ case http.StatusForbidden:
20+ c.String(code, fmt.Sprintf("An error occurred: %v", err))
21+ default:
22+ c.String(code, fmt.Sprintf("An error occurred: %v", err))
23+ }
5424 }
@@ -5,50 +5,20 @@ 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/app"
9- "gogs.bnema.dev/gordon-echo/pkg/utils"
10 )8 )
11 9
12-type AppConfig struct {10+func ErrorNumberHandler(err error, c echo.Context) {
13- *app.Config11+ code := http.StatusInternalServerError
14-}
15-
16-func (a *AppConfig) Error404Handler(err error, c echo.Context) {
17 if he, ok := err.(*echo.HTTPError); ok {12 if he, ok := err.(*echo.HTTPError); ok {
18- if he.Code == http.StatusNotFound {13+ code = he.Code
19- renderer, err := utils.GetRenderer("404.gohtml", a.Config.PublicFS, utils.NewLogger())
20- if err != nil {
21- c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
22- }
23-
24- data := map[string]interface{}{
25- "website": map[string]interface{}{
26- "title": "Page Not Found",
27- },
28- }
29-
30- html, err := renderer.Render(data)
31- if err != nil {
32- c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
33- }
34-
35- c.HTML(http.StatusNotFound, html)
36- return
37- }
38 }14 }
39 15
40-}16+ switch code {
41-17+ case http.StatusNotFound:
42-func Error200Handler(err error, c echo.Context) {18+ c.String(code, fmt.Sprintf("An error occurred: %v", err))
43- // Just print the error for now19+ case http.StatusForbidden:
44- fmt.Println(err)20+ c.String(code, fmt.Sprintf("An error occurred: %v", err))
45-}21+ default:
46-22+ c.String(code, fmt.Sprintf("An error occurred: %v", err))
47-func Error403Handler(err error, c echo.Context) {23+ }
48- // Just print the error for now
49- fmt.Println(err)
50-}
51-
52-func ErrorNumberHandler(err error, c echo.Context) {
53- // For Each Error Code, we can have a different handler
54 }24 }
M internal/http/handlers/htmx-fragment-header.go +14 -6
@@ -4,6 +4,7 @@ import (
44 "net/http"
55
66 "github.com/labstack/echo/v4"
7+ "gogs.bnema.dev/gordon-echo/config"
78 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
89 "gogs.bnema.dev/gordon-echo/pkg/htmx"
910 "gogs.bnema.dev/gordon-echo/pkg/utils"
@@ -12,9 +13,9 @@ import (
1213 func HTMXHandler(c echo.Context) error {
1314 logger, ok := c.Get("logger").(*utils.Logger)
1415 if !ok {
15- logger.Error().Msg("Failed to get logger from context")
1616 return c.String(http.StatusInternalServerError, "Failed to get logger from context")
1717 }
18+
1819 // Detect and extract HTMX data from the request
1920 htmxRequest, err := htmx.GetRequest(c)
2021 if err != nil {
@@ -30,14 +31,19 @@ func HTMXHandler(c echo.Context) error {
3031
3132 // If it's a GET request, handle the fragment
3233 if c.Request().Method == http.MethodGet {
33- return htmxFragmentHandler(c, logger)
34+ cfg, ok := c.Get("config").(config.Provider)
35+ if !ok {
36+ logger.Error().Msg("Failed to get config provider from context")
37+ return c.String(http.StatusInternalServerError, "Failed to get config provider from context")
38+ }
39+ return htmxFragmentHandler(c, logger, cfg)
3440 }
3541
3642 // For other HTTP methods, return an error
3743 return c.String(http.StatusBadRequest, "Invalid request method")
3844 }
3945
40-func htmxFragmentHandler(c echo.Context, logger *utils.Logger) error {
46+func htmxFragmentHandler(c echo.Context, logger *utils.Logger, fs config.Provider) error {
4147 fragment := c.Request().Header.Get("HX-Fragment")
4248 if fragment == "" {
4349 logger.Error().Msg("Missing fragment header")
@@ -45,16 +51,18 @@ func htmxFragmentHandler(c echo.Context, logger *utils.Logger) error {
4551 }
4652
4753 lang := c.Get(middlewares.LangKey).(string)
48- staticData, err := utils.PopulateDataFromYAML(lang)
54+ var data utils.StringsYamlData
55+ err := utils.PopulateDataFromYAML(lang, fs.GetTemplateFS(), "strings.yml", &data)
4956 if err != nil {
5057 logger.Error().Err(err).Msg("Failed to populate data from YAML")
5158 return err
5259 }
5360
54- content, err := utils.GetHTMLFragmentByID(fragment, staticData.CurrentLang)
55- if err != nil {
61+ content, err := utils.GetHTMLFragmentByID(fragment, data.CurrentLang, fs)
62+ if err != nil { // <-- This check was missing
5663 logger.Error().Err(err).Msg("Failed to get fragment")
5764 return c.String(http.StatusInternalServerError, err.Error())
5865 }
66+
5967 return c.HTML(http.StatusOK, content)
6068 }
@@ -4,6 +4,7 @@ import (
4 "net/http"4 "net/http"
5 5
6 "github.com/labstack/echo/v4"6 "github.com/labstack/echo/v4"
7+ "gogs.bnema.dev/gordon-echo/config"
7 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"8 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
8 "gogs.bnema.dev/gordon-echo/pkg/htmx"9 "gogs.bnema.dev/gordon-echo/pkg/htmx"
9 "gogs.bnema.dev/gordon-echo/pkg/utils"10 "gogs.bnema.dev/gordon-echo/pkg/utils"
@@ -12,9 +13,9 @@ import (
12 func HTMXHandler(c echo.Context) error {13 func HTMXHandler(c echo.Context) error {
13 logger, ok := c.Get("logger").(*utils.Logger)14 logger, ok := c.Get("logger").(*utils.Logger)
14 if !ok {15 if !ok {
15- logger.Error().Msg("Failed to get logger from context")
16 return c.String(http.StatusInternalServerError, "Failed to get logger from context")16 return c.String(http.StatusInternalServerError, "Failed to get logger from context")
17 }17 }
18+
18 // Detect and extract HTMX data from the request19 // Detect and extract HTMX data from the request
19 htmxRequest, err := htmx.GetRequest(c)20 htmxRequest, err := htmx.GetRequest(c)
20 if err != nil {21 if err != nil {
@@ -30,14 +31,19 @@ func HTMXHandler(c echo.Context) error {
30 31
31 // If it's a GET request, handle the fragment32 // If it's a GET request, handle the fragment
32 if c.Request().Method == http.MethodGet {33 if c.Request().Method == http.MethodGet {
33- return htmxFragmentHandler(c, logger)34+ cfg, ok := c.Get("config").(config.Provider)
35+ if !ok {
36+ logger.Error().Msg("Failed to get config provider from context")
37+ return c.String(http.StatusInternalServerError, "Failed to get config provider from context")
38+ }
39+ return htmxFragmentHandler(c, logger, cfg)
34 }40 }
35 41
36 // For other HTTP methods, return an error42 // For other HTTP methods, return an error
37 return c.String(http.StatusBadRequest, "Invalid request method")43 return c.String(http.StatusBadRequest, "Invalid request method")
38 }44 }
39 45
40-func htmxFragmentHandler(c echo.Context, logger *utils.Logger) error {46+func htmxFragmentHandler(c echo.Context, logger *utils.Logger, fs config.Provider) error {
41 fragment := c.Request().Header.Get("HX-Fragment")47 fragment := c.Request().Header.Get("HX-Fragment")
42 if fragment == "" {48 if fragment == "" {
43 logger.Error().Msg("Missing fragment header")49 logger.Error().Msg("Missing fragment header")
@@ -45,16 +51,18 @@ func htmxFragmentHandler(c echo.Context, logger *utils.Logger) error {
45 }51 }
46 52
47 lang := c.Get(middlewares.LangKey).(string)53 lang := c.Get(middlewares.LangKey).(string)
48- staticData, err := utils.PopulateDataFromYAML(lang)54+ var data utils.StringsYamlData
55+ err := utils.PopulateDataFromYAML(lang, fs.GetTemplateFS(), "strings.yml", &data)
49 if err != nil {56 if err != nil {
50 logger.Error().Err(err).Msg("Failed to populate data from YAML")57 logger.Error().Err(err).Msg("Failed to populate data from YAML")
51 return err58 return err
52 }59 }
53 60
54- content, err := utils.GetHTMLFragmentByID(fragment, staticData.CurrentLang)61+ content, err := utils.GetHTMLFragmentByID(fragment, data.CurrentLang, fs)
55- if err != nil {62+ if err != nil { // <-- This check was missing
56 logger.Error().Err(err).Msg("Failed to get fragment")63 logger.Error().Err(err).Msg("Failed to get fragment")
57 return c.String(http.StatusInternalServerError, err.Error())64 return c.String(http.StatusInternalServerError, err.Error())
58 }65 }
66+
59 return c.HTML(http.StatusOK, content)67 return c.HTML(http.StatusOK, content)
60 }68 }
M internal/http/routes/admin-routes.go +13 -13
@@ -10,11 +10,7 @@ import (
1010 "gogs.bnema.dev/gordon-echo/pkg/utils"
1111 )
1212
13-type AppConfig struct {
14- Config config.Provider
15-}
16-
17-func (a *AppConfig) AdminRoute(c echo.Context) error {
13+func AdminRoute(c echo.Context, provider config.Provider) error {
1814 // Check if it's the first launch of the app
1915 if utils.IsConfigFilePresent() {
2016 fmt.Println("Fresh installation detected, redirecting to /admin/install")
@@ -24,17 +20,19 @@ func (a *AppConfig) AdminRoute(c echo.Context) error {
2420 // Retrieve the current language from the context
2521 lang := c.Get(middlewares.LangKey).(string)
2622
27- staticData, err := a.PopulateDataFromYAML(lang)
23+ // Assuming the filename is "strings.yml"
24+ var data utils.StringsYamlData
25+ err := utils.PopulateDataFromYAML(lang, provider.GetTemplateFS(), "strings.yml", &data)
2826 if err != nil {
2927 return err
3028 }
3129
32- renderer, err := utils.GetRenderer("index.gohtml", a.Config.GetPublicFS(), utils.NewLogger())
30+ renderer, err := utils.GetRenderer("index.gohtml", provider.GetPublicFS(), utils.NewLogger())
3331 if err != nil {
3432 return err
3533 }
3634
37- html, err := renderer.Render(staticData.CurrentLang)
35+ html, err := renderer.Render(data.CurrentLang)
3836 if err != nil {
3937 // Return the error into NewLogger() to log it
4038 utils.NewLogger().Error()
@@ -44,7 +42,7 @@ func (a *AppConfig) AdminRoute(c echo.Context) error {
4442 return c.HTML(http.StatusOK, html)
4543 }
4644
47-func (a *AppConfig) InstallRoute(c echo.Context) error {
45+func InstallRoute(c echo.Context, provider config.Provider) error {
4846 // Check if it's the first launch of the app
4947 if !utils.IsConfigFilePresent() {
5048 fmt.Println("Config file already present, redirecting to /admin")
@@ -54,17 +52,19 @@ func (a *AppConfig) InstallRoute(c echo.Context) error {
5452 // Retrieve the current language from the context
5553 lang := c.Get(middlewares.LangKey).(string)
5654
57- staticData, err := a.PopulateDataFromYAML(lang)
55+ var data utils.StringsYamlData
56+ err := utils.PopulateDataFromYAML(lang, provider.GetTemplateFS(), "strings.yml", &data)
5857 if err != nil {
59- return err
58+ return fmt.Errorf("failed to populate data from YAML: %w", err)
6059 }
6160
62- renderer, err := utils.GetRenderer("install.gohtml", a.Config.GetTemplateFS(), utils.NewLogger())
61+ renderer, err := utils.GetRenderer("install.gohtml", provider.GetTemplateFS(), utils.NewLogger())
62+ fmt.Println(renderer)
6363 if err != nil {
6464 return err
6565 }
6666
67- html, err := renderer.Render(staticData.CurrentLang)
67+ html, err := renderer.Render(data.CurrentLang)
6868 if err != nil {
6969 // Return the error into NewLogger() to log it
7070 utils.NewLogger().Error()
@@ -10,11 +10,7 @@ import (
10 "gogs.bnema.dev/gordon-echo/pkg/utils"10 "gogs.bnema.dev/gordon-echo/pkg/utils"
11 )11 )
12 12
13-type AppConfig struct {13+func AdminRoute(c echo.Context, provider config.Provider) error {
14- Config config.Provider
15-}
16-
17-func (a *AppConfig) AdminRoute(c echo.Context) error {
18 // Check if it's the first launch of the app14 // Check if it's the first launch of the app
19 if utils.IsConfigFilePresent() {15 if utils.IsConfigFilePresent() {
20 fmt.Println("Fresh installation detected, redirecting to /admin/install")16 fmt.Println("Fresh installation detected, redirecting to /admin/install")
@@ -24,17 +20,19 @@ func (a *AppConfig) AdminRoute(c echo.Context) error {
24 // Retrieve the current language from the context20 // Retrieve the current language from the context
25 lang := c.Get(middlewares.LangKey).(string)21 lang := c.Get(middlewares.LangKey).(string)
26 22
27- staticData, err := a.PopulateDataFromYAML(lang)23+ // Assuming the filename is "strings.yml"
24+ var data utils.StringsYamlData
25+ err := utils.PopulateDataFromYAML(lang, provider.GetTemplateFS(), "strings.yml", &data)
28 if err != nil {26 if err != nil {
29 return err27 return err
30 }28 }
31 29
32- renderer, err := utils.GetRenderer("index.gohtml", a.Config.GetPublicFS(), utils.NewLogger())30+ renderer, err := utils.GetRenderer("index.gohtml", provider.GetPublicFS(), utils.NewLogger())
33 if err != nil {31 if err != nil {
34 return err32 return err
35 }33 }
36 34
37- html, err := renderer.Render(staticData.CurrentLang)35+ html, err := renderer.Render(data.CurrentLang)
38 if err != nil {36 if err != nil {
39 // Return the error into NewLogger() to log it37 // Return the error into NewLogger() to log it
40 utils.NewLogger().Error()38 utils.NewLogger().Error()
@@ -44,7 +42,7 @@ func (a *AppConfig) AdminRoute(c echo.Context) error {
44 return c.HTML(http.StatusOK, html)42 return c.HTML(http.StatusOK, html)
45 }43 }
46 44
47-func (a *AppConfig) InstallRoute(c echo.Context) error {45+func InstallRoute(c echo.Context, provider config.Provider) error {
48 // Check if it's the first launch of the app46 // Check if it's the first launch of the app
49 if !utils.IsConfigFilePresent() {47 if !utils.IsConfigFilePresent() {
50 fmt.Println("Config file already present, redirecting to /admin")48 fmt.Println("Config file already present, redirecting to /admin")
@@ -54,17 +52,19 @@ func (a *AppConfig) InstallRoute(c echo.Context) error {
54 // Retrieve the current language from the context52 // Retrieve the current language from the context
55 lang := c.Get(middlewares.LangKey).(string)53 lang := c.Get(middlewares.LangKey).(string)
56 54
57- staticData, err := a.PopulateDataFromYAML(lang)55+ var data utils.StringsYamlData
56+ err := utils.PopulateDataFromYAML(lang, provider.GetTemplateFS(), "strings.yml", &data)
58 if err != nil {57 if err != nil {
59- return err58+ return fmt.Errorf("failed to populate data from YAML: %w", err)
60 }59 }
61 60
62- renderer, err := utils.GetRenderer("install.gohtml", a.Config.GetTemplateFS(), utils.NewLogger())61+ renderer, err := utils.GetRenderer("install.gohtml", provider.GetTemplateFS(), utils.NewLogger())
62+ fmt.Println(renderer)
63 if err != nil {63 if err != nil {
64 return err64 return err
65 }65 }
66 66
67- html, err := renderer.Render(staticData.CurrentLang)67+ html, err := renderer.Render(data.CurrentLang)
68 if err != nil {68 if err != nil {
69 // Return the error into NewLogger() to log it69 // Return the error into NewLogger() to log it
70 utils.NewLogger().Error()70 utils.NewLogger().Error()
M internal/http/routes/router.go +19 -9
@@ -2,6 +2,7 @@ package routes
22
33 import (
44 "github.com/labstack/echo/v4"
5+ "gogs.bnema.dev/gordon-echo/config"
56 "gogs.bnema.dev/gordon-echo/internal/app"
67 "gogs.bnema.dev/gordon-echo/internal/http/handlers"
78 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
@@ -17,20 +18,26 @@ func ConfigureRouter(e *echo.Echo, app *app.App) *echo.Echo {
1718 //e.Use(middlewares.SecureMiddleware()) // Need testing
1819
1920 // Register routes
20- e = bindAdminRoutes(e)
21- e = bindHtmxRoutes(e)
22- e = bindStaticRoutes(e)
21+ e = bindAdminRoutes(e, app.Config)
22+ e = bindHtmxRoutes(e, app.Config)
23+ e = bindStaticRoutes(e, app.Config)
2324 e.HTTPErrorHandler = handlers.ErrorNumberHandler
2425 return e
2526 }
26-func bindAdminRoutes(e *echo.Echo) *echo.Echo {
27- e.GET("/admin", AdminRoute)
28- e.GET("/admin/install", InstallRoute)
27+func bindAdminRoutes(e *echo.Echo, config config.Provider) *echo.Echo {
28+ // We pass the embed config to the routes so we can parse the strings.yml file
29+ e.GET("/admin", func(c echo.Context) error {
30+ return AdminRoute(c, config)
31+ })
32+ e.GET("/admin/install", func(c echo.Context) error {
33+ return InstallRoute(c, config)
34+ })
35+
2936 e.POST("/admin/install/traefik", handlers.TraefikInstallerHandler)
3037 return e
3138 }
3239
33-func bindHtmxRoutes(e *echo.Echo) *echo.Echo {
40+func bindHtmxRoutes(e *echo.Echo, config config.Provider) *echo.Echo {
3441 e.GET("/htmx", handlers.HTMXHandler)
3542 e.GET("/htmx/fragment", handlers.HTMXHandler)
3643 e.POST("/htmx", handlers.HTMXHandler)
@@ -38,7 +45,10 @@ func bindHtmxRoutes(e *echo.Echo) *echo.Echo {
3845 return e
3946 }
4047
41-func bindStaticRoutes(e *echo.Echo) *echo.Echo {
42- e.GET("/*", StaticRoute)
48+func bindStaticRoutes(e *echo.Echo, config config.Provider) *echo.Echo {
49+
50+ e.GET("/*", func(c echo.Context) error {
51+ return StaticRoute(c, config)
52+ })
4353 return e
4454 }
@@ -2,6 +2,7 @@ package routes
2 2
3 import (3 import (
4 "github.com/labstack/echo/v4"4 "github.com/labstack/echo/v4"
5+ "gogs.bnema.dev/gordon-echo/config"
5 "gogs.bnema.dev/gordon-echo/internal/app"6 "gogs.bnema.dev/gordon-echo/internal/app"
6 "gogs.bnema.dev/gordon-echo/internal/http/handlers"7 "gogs.bnema.dev/gordon-echo/internal/http/handlers"
7 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"8 "gogs.bnema.dev/gordon-echo/internal/http/middlewares"
@@ -17,20 +18,26 @@ func ConfigureRouter(e *echo.Echo, app *app.App) *echo.Echo {
17 //e.Use(middlewares.SecureMiddleware()) // Need testing18 //e.Use(middlewares.SecureMiddleware()) // Need testing
18 19
19 // Register routes20 // Register routes
20- e = bindAdminRoutes(e)21+ e = bindAdminRoutes(e, app.Config)
21- e = bindHtmxRoutes(e)22+ e = bindHtmxRoutes(e, app.Config)
22- e = bindStaticRoutes(e)23+ e = bindStaticRoutes(e, app.Config)
23 e.HTTPErrorHandler = handlers.ErrorNumberHandler24 e.HTTPErrorHandler = handlers.ErrorNumberHandler
24 return e25 return e
25 }26 }
26-func bindAdminRoutes(e *echo.Echo) *echo.Echo {27+func bindAdminRoutes(e *echo.Echo, config config.Provider) *echo.Echo {
27- e.GET("/admin", AdminRoute)28+ // We pass the embed config to the routes so we can parse the strings.yml file
28- e.GET("/admin/install", InstallRoute)29+ e.GET("/admin", func(c echo.Context) error {
30+ return AdminRoute(c, config)
31+ })
32+ e.GET("/admin/install", func(c echo.Context) error {
33+ return InstallRoute(c, config)
34+ })
35+
29 e.POST("/admin/install/traefik", handlers.TraefikInstallerHandler)36 e.POST("/admin/install/traefik", handlers.TraefikInstallerHandler)
30 return e37 return e
31 }38 }
32 39
33-func bindHtmxRoutes(e *echo.Echo) *echo.Echo {40+func bindHtmxRoutes(e *echo.Echo, config config.Provider) *echo.Echo {
34 e.GET("/htmx", handlers.HTMXHandler)41 e.GET("/htmx", handlers.HTMXHandler)
35 e.GET("/htmx/fragment", handlers.HTMXHandler)42 e.GET("/htmx/fragment", handlers.HTMXHandler)
36 e.POST("/htmx", handlers.HTMXHandler)43 e.POST("/htmx", handlers.HTMXHandler)
@@ -38,7 +45,10 @@ func bindHtmxRoutes(e *echo.Echo) *echo.Echo {
38 return e45 return e
39 }46 }
40 47
41-func bindStaticRoutes(e *echo.Echo) *echo.Echo {48+func bindStaticRoutes(e *echo.Echo, config config.Provider) *echo.Echo {
42- e.GET("/*", StaticRoute)49+
50+ e.GET("/*", func(c echo.Context) error {
51+ return StaticRoute(c, config)
52+ })
43 return e53 return e
44 }54 }
M internal/http/routes/static-routes.go +10 -4
@@ -1,19 +1,25 @@
11 package routes
22
33 import (
4+ "io/fs"
5+ "net/http"
46 "os"
57
68 "github.com/labstack/echo/v4"
9+ "gogs.bnema.dev/gordon-echo/config"
710 )
811
9-func (a *AppConfig) StaticRoute(c echo.Context) error {
10-
11- // Set the cache-control header to cache the static files for 1 day if PROD env is set to true
12+func StaticRoute(c echo.Context, config config.Provider) error {
1213
14+ // Set the cache-control header based on PROD environment variable
1315 if os.Getenv("PROD") == "true" {
1416 c.Response().Header().Set("Cache-Control", "public, max-age=86400")
1517 } else {
1618 c.Response().Header().Set("Cache-Control", "no-cache")
1719 }
18- return echo.StaticDirectoryHandler(a.Config.GetPublicFS(), false)(c)
20+
21+ // Serve the file from the embedded filesystem
22+ httpFs := http.FileServer(http.FS(fs.FS(config.GetPublicFS())))
23+ httpFs.ServeHTTP(c.Response(), c.Request())
24+ return nil
1925 }
@@ -1,19 +1,25 @@
1 package routes1 package routes
2 2
3 import (3 import (
4+ "io/fs"
5+ "net/http"
4 "os"6 "os"
5 7
6 "github.com/labstack/echo/v4"8 "github.com/labstack/echo/v4"
9+ "gogs.bnema.dev/gordon-echo/config"
7 )10 )
8 11
9-func (a *AppConfig) StaticRoute(c echo.Context) error {12+func StaticRoute(c echo.Context, config config.Provider) error {
10-
11- // Set the cache-control header to cache the static files for 1 day if PROD env is set to true
12 13
14+ // Set the cache-control header based on PROD environment variable
13 if os.Getenv("PROD") == "true" {15 if os.Getenv("PROD") == "true" {
14 c.Response().Header().Set("Cache-Control", "public, max-age=86400")16 c.Response().Header().Set("Cache-Control", "public, max-age=86400")
15 } else {17 } else {
16 c.Response().Header().Set("Cache-Control", "no-cache")18 c.Response().Header().Set("Cache-Control", "no-cache")
17 }19 }
18- return echo.StaticDirectoryHandler(a.Config.GetPublicFS(), false)(c)20+
21+ // Serve the file from the embedded filesystem
22+ httpFs := http.FileServer(http.FS(fs.FS(config.GetPublicFS())))
23+ httpFs.ServeHTTP(c.Response(), c.Request())
24+ return nil
19 }25 }
R internal/ui/templates/strings.yaml → internal/ui/templates/strings.yml +0 -0 renamed, 100% similar

renamed, 100% similar

M pkg/utils/html-fragments.go +2 -6
@@ -8,14 +8,10 @@ import (
88 "gogs.bnema.dev/gordon-echo/config"
99 )
1010
11-type AppConfig struct {
12- Config config.Provider
13-}
14-
1511 // GetHTMLFragmentByID returns the HTML fragment with the specified id
16-func (a *AppConfig) GetHTMLFragmentByID(id string, data interface{}) (string, error) {
12+func GetHTMLFragmentByID(id string, data interface{}, fs config.Provider) (string, error) {
1713 // 1. Render the template
18- renderer, err := GetRenderer("components.gohtml", a.Config.GetTemplateFS(), NewLogger())
14+ renderer, err := GetRenderer("components.gohtml", fs.GetTemplateFS(), NewLogger())
1915 if err != nil {
2016 return "", fmt.Errorf("failed to get renderer: %w", err)
2117 }
@@ -8,14 +8,10 @@ import (
8 "gogs.bnema.dev/gordon-echo/config"8 "gogs.bnema.dev/gordon-echo/config"
9 )9 )
10 10
11-type AppConfig struct {
12- Config config.Provider
13-}
14-
15 // GetHTMLFragmentByID returns the HTML fragment with the specified id11 // GetHTMLFragmentByID returns the HTML fragment with the specified id
16-func (a *AppConfig) GetHTMLFragmentByID(id string, data interface{}) (string, error) {12+func GetHTMLFragmentByID(id string, data interface{}, fs config.Provider) (string, error) {
17 // 1. Render the template13 // 1. Render the template
18- renderer, err := GetRenderer("components.gohtml", a.Config.GetTemplateFS(), NewLogger())14+ renderer, err := GetRenderer("components.gohtml", fs.GetTemplateFS(), NewLogger())
19 if err != nil {15 if err != nil {
20 return "", fmt.Errorf("failed to get renderer: %w", err)16 return "", fmt.Errorf("failed to get renderer: %w", err)
21 }17 }
M pkg/utils/html-render.go +2 -0
@@ -3,6 +3,7 @@ package utils
33 import (
44 "bytes"
55 "errors"
6+ "fmt"
67 "html/template"
78 "io/fs"
89 )
@@ -39,6 +40,7 @@ func (r *Renderer) Render(data interface{}) (string, error) {
3940
4041 // GetRenderer function returns a new Renderer instance
4142 func GetRenderer(filename string, fs fs.FS, logger *Logger) (*Renderer, error) {
43+ fmt.Println(filename)
4244 // Check if the file exists in the provided fs.FS using fs.Open
4345 file, err := fs.Open(filename)
4446 if err != nil {
@@ -3,6 +3,7 @@ package utils
3 import (3 import (
4 "bytes"4 "bytes"
5 "errors"5 "errors"
6+ "fmt"
6 "html/template"7 "html/template"
7 "io/fs"8 "io/fs"
8 )9 )
@@ -39,6 +40,7 @@ func (r *Renderer) Render(data interface{}) (string, error) {
39 40
40 // GetRenderer function returns a new Renderer instance41 // GetRenderer function returns a new Renderer instance
41 func GetRenderer(filename string, fs fs.FS, logger *Logger) (*Renderer, error) {42 func GetRenderer(filename string, fs fs.FS, logger *Logger) (*Renderer, error) {
43+ fmt.Println(filename)
42 // Check if the file exists in the provided fs.FS using fs.Open44 // Check if the file exists in the provided fs.FS using fs.Open
43 file, err := fs.Open(filename)45 file, err := fs.Open(filename)
44 if err != nil {46 if err != nil {
M pkg/utils/parser.go +44 -24
@@ -3,6 +3,7 @@ package utils
33 import (
44 "fmt"
55 "io"
6+ "io/fs"
67 "os"
78
89 "github.com/tidwall/gjson"
@@ -11,52 +12,71 @@ import (
1112
1213 // Yaml Parser
1314 type LangData map[string]interface{}
14-type YamlData struct {
15- EN LangData `yaml:"en"`
16- FR LangData `yaml:"fr"`
17- CurrentLang LangData
15+
16+type StringsYamlData struct {
17+ StringsModel StringsModel `yaml:"strings_model"`
18+ EN LangData `yaml:"en"`
19+ FR LangData `yaml:"fr"`
20+ CurrentLang LangData
21+}
22+
23+type ComposeModelData struct {
24+ ComposeModel ComposeModel `yaml:"compose_model"`
1825 }
1926
20-// ReadDataFromYAML loads the data from the given YAML file
21-func (a *AppConfig) ReadDataFromYAML() (YamlData, error) {
22- var data YamlData
27+type StringsModel struct {
28+ Header string `yaml:"header"`
29+ Subheader string `yaml:"subheader"`
30+}
31+
32+type ComposeModel struct {
33+ Header string `yaml:"header"`
34+ Subheader string `yaml:"subheader"`
35+}
2336
24- // Open the YAML file inside the ui/components directory
25- file, err := a.Config.GetTemplateFS().Open("strings.yaml")
37+// ReadDataFromYAML loads the data from the given YAML file from any filesystem
38+func ReadDataFromYAML(fs fs.FS, filename string, out interface{}) error {
39+ // Open the YAML file
40+ file, err := fs.Open(filename)
2641 if err != nil {
27- return data, fmt.Errorf("failed to open strings.yaml: %w", err)
42+ return fmt.Errorf("failed to open %s: %w", filename, err)
2843 }
2944 defer file.Close()
3045
3146 // Read the content of the file
3247 content, err := io.ReadAll(file)
3348 if err != nil {
34- return data, fmt.Errorf("failed to read strings.yaml: %w", err)
49+ return fmt.Errorf("failed to read %s: %w", filename, err)
3550 }
3651
37- // Unmarshal the YAML content into the Data struct
38- if err := yaml.Unmarshal(content, &data); err != nil {
39- return data, fmt.Errorf("failed to unmarshal strings.yaml: %w", err)
52+ // Unmarshal the YAML content into the out interface
53+ if err := yaml.Unmarshal(content, out); err != nil {
54+ return fmt.Errorf("failed to unmarshal %s: %w", filename, err)
4055 }
4156
42- return data, nil
57+ return nil
4358 }
4459
4560 // PopulateDataFromYAML retrieves the data from the YAML file and sets the appropriate language data
46-func (a *AppConfig) PopulateDataFromYAML(currentLang string) (YamlData, error) {
47- data, err := a.ReadDataFromYAML()
61+func PopulateDataFromYAML(lang string, fs fs.FS, filename string, out interface{}) error {
62+ err := ReadDataFromYAML(fs, filename, out)
4863 if err != nil {
49- return data, err
64+ return fmt.Errorf("failed to read data from YAML: %w", err)
5065 }
5166
52- switch currentLang {
53- case "fr":
54- data.CurrentLang = data.FR
55- default: // default to English if no match
56- data.CurrentLang = data.EN
67+ // If the type assertion succeeds, we set the appropriate language data
68+ if data, ok := out.(*StringsYamlData); ok {
69+ switch lang {
70+ case "en":
71+ data.CurrentLang = data.EN
72+ case "fr":
73+ data.CurrentLang = data.FR
74+ default: // default to English if no match
75+ data.CurrentLang = data.EN
76+ }
5777 }
5878
59- return data, nil
79+ return nil
6080 }
6181
6282 // Json Parser
@@ -3,6 +3,7 @@ package utils
3 import (3 import (
4 "fmt"4 "fmt"
5 "io"5 "io"
6+ "io/fs"
6 "os"7 "os"
7 8
8 "github.com/tidwall/gjson"9 "github.com/tidwall/gjson"
@@ -11,52 +12,71 @@ import (
11 12
12 // Yaml Parser13 // Yaml Parser
13 type LangData map[string]interface{}14 type LangData map[string]interface{}
14-type YamlData struct {15+
15- EN LangData `yaml:"en"`16+type StringsYamlData struct {
16- FR LangData `yaml:"fr"`17+ StringsModel StringsModel `yaml:"strings_model"`
17- CurrentLang LangData18+ EN LangData `yaml:"en"`
19+ FR LangData `yaml:"fr"`
20+ CurrentLang LangData
21+}
22+
23+type ComposeModelData struct {
24+ ComposeModel ComposeModel `yaml:"compose_model"`
18 }25 }
19 26
20-// ReadDataFromYAML loads the data from the given YAML file27+type StringsModel struct {
21-func (a *AppConfig) ReadDataFromYAML() (YamlData, error) {28+ Header string `yaml:"header"`
22- var data YamlData29+ Subheader string `yaml:"subheader"`
30+}
31+
32+type ComposeModel struct {
33+ Header string `yaml:"header"`
34+ Subheader string `yaml:"subheader"`
35+}
23 36
24- // Open the YAML file inside the ui/components directory37+// ReadDataFromYAML loads the data from the given YAML file from any filesystem
25- file, err := a.Config.GetTemplateFS().Open("strings.yaml")38+func ReadDataFromYAML(fs fs.FS, filename string, out interface{}) error {
39+ // Open the YAML file
40+ file, err := fs.Open(filename)
26 if err != nil {41 if err != nil {
27- return data, fmt.Errorf("failed to open strings.yaml: %w", err)42+ return fmt.Errorf("failed to open %s: %w", filename, err)
28 }43 }
29 defer file.Close()44 defer file.Close()
30 45
31 // Read the content of the file46 // Read the content of the file
32 content, err := io.ReadAll(file)47 content, err := io.ReadAll(file)
33 if err != nil {48 if err != nil {
34- return data, fmt.Errorf("failed to read strings.yaml: %w", err)49+ return fmt.Errorf("failed to read %s: %w", filename, err)
35 }50 }
36 51
37- // Unmarshal the YAML content into the Data struct52+ // Unmarshal the YAML content into the out interface
38- if err := yaml.Unmarshal(content, &data); err != nil {53+ if err := yaml.Unmarshal(content, out); err != nil {
39- return data, fmt.Errorf("failed to unmarshal strings.yaml: %w", err)54+ return fmt.Errorf("failed to unmarshal %s: %w", filename, err)
40 }55 }
41 56
42- return data, nil57+ return nil
43 }58 }
44 59
45 // PopulateDataFromYAML retrieves the data from the YAML file and sets the appropriate language data60 // PopulateDataFromYAML retrieves the data from the YAML file and sets the appropriate language data
46-func (a *AppConfig) PopulateDataFromYAML(currentLang string) (YamlData, error) {61+func PopulateDataFromYAML(lang string, fs fs.FS, filename string, out interface{}) error {
47- data, err := a.ReadDataFromYAML()62+ err := ReadDataFromYAML(fs, filename, out)
48 if err != nil {63 if err != nil {
49- return data, err64+ return fmt.Errorf("failed to read data from YAML: %w", err)
50 }65 }
51 66
52- switch currentLang {67+ // If the type assertion succeeds, we set the appropriate language data
53- case "fr":68+ if data, ok := out.(*StringsYamlData); ok {
54- data.CurrentLang = data.FR69+ switch lang {
55- default: // default to English if no match70+ case "en":
56- data.CurrentLang = data.EN71+ data.CurrentLang = data.EN
72+ case "fr":
73+ data.CurrentLang = data.FR
74+ default: // default to English if no match
75+ data.CurrentLang = data.EN
76+ }
57 }77 }
58 78
59- return data, nil79+ return nil
60 }80 }
61 81
62 // Json Parser82 // Json Parser
D pkg/utils/txt-render.go +0 -60
@@ -1,60 +0,0 @@
1-package utils
2-
3-import (
4- "bytes"
5- "errors"
6- "io/fs"
7- "text/template"
8-)
9-
10-type TXTRenderer struct {
11- Models *template.Template
12- Format string
13- ParseError error
14- Logger *Logger
15-}
16-
17-// Render function renders the template with the given data
18-func (r *TXTRenderer) TXTRender(data interface{}) (string, error) {
19- if r.ParseError != nil {
20- r.Logger.Error().Err(r.ParseError).Msg("Parse error")
21- return "", r.ParseError
22- }
23-
24- if r.Models == nil {
25- err := errors.New("invalid or nil template")
26- r.Logger.Error().Err(r.ParseError).Msg("Parse error")
27-
28- return "", err
29- }
30-
31- buf := new(bytes.Buffer)
32- err := r.Models.Execute(buf, data)
33- if err != nil {
34- r.Logger.Error().Err(err).Msg("Render error")
35- return "", err
36- }
37-
38- return buf.String(), nil
39-}
40-
41-// GetTXTRenderer function returns a new Renderer instance
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-
51- mdls, err := template.New(filename).ParseFS(fs, filename)
52- if err != nil {
53- logger.Error().Err(err).Msg("Failed to parse template")
54- return nil, err
55- }
56- return &TXTRenderer{
57- Models: mdls,
58- Logger: logger,
59- }, nil
60-}
@@ -1,60 +0,0 @@
1-package utils
2-
3-import (
4- "bytes"
5- "errors"
6- "io/fs"
7- "text/template"
8-)
9-
10-type TXTRenderer struct {
11- Models *template.Template
12- Format string
13- ParseError error
14- Logger *Logger
15-}
16-
17-// Render function renders the template with the given data
18-func (r *TXTRenderer) TXTRender(data interface{}) (string, error) {
19- if r.ParseError != nil {
20- r.Logger.Error().Err(r.ParseError).Msg("Parse error")
21- return "", r.ParseError
22- }
23-
24- if r.Models == nil {
25- err := errors.New("invalid or nil template")
26- r.Logger.Error().Err(r.ParseError).Msg("Parse error")
27-
28- return "", err
29- }
30-
31- buf := new(bytes.Buffer)
32- err := r.Models.Execute(buf, data)
33- if err != nil {
34- r.Logger.Error().Err(err).Msg("Render error")
35- return "", err
36- }
37-
38- return buf.String(), nil
39-}
40-
41-// GetTXTRenderer function returns a new Renderer instance
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-
51- mdls, err := template.New(filename).ParseFS(fs, filename)
52- if err != nil {
53- logger.Error().Err(err).Msg("Failed to parse template")
54- return nil, err
55- }
56- return &TXTRenderer{
57- Models: mdls,
58- Logger: logger,
59- }, nil
60-}

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