detached 42483f8

Better refactoring, better logging

Save
commit
42483f8c0404e5aeaf454d82bc410954ee92effb
author
bnema · · 2023-09-02 09:51 UTC
committer
bnema · · 2023-09-04 06:59 UTC
parent
60d6353
signature
unsigned
15 files · +219 -99
M cmd/main.go +12 -9
@@ -1,22 +1,25 @@
11 package main
22
33 import (
4- "gogs.bnema.dev/gordon-echo/internal"
4+ "gogs.bnema.dev/gordon-echo/internal/app"
55 "gogs.bnema.dev/gordon-echo/internal/http/routes"
66 )
77
88 func main() {
9- // Setup logging
10- internal.SetupLogging()
11-
12- // Initialize loggers
13- appLogger := internal.InitializeAppLogger()
14- echoLogger := internal.InitializeHTTPLogger()
9+ // Initialize Gordon
10+ gordon, err := app.NewApp()
11+ if err != nil {
12+ // Handle initialization error and exit
13+ gordon.APPLogger.Error().Err(err).Msg("Failed to initialize app")
14+ return
15+ }
1516
1617 // Initialize echo server with routes and middlewares
17- e := routes.NewRouter(appLogger, echoLogger)
18+ e := routes.NewRouter(gordon.APPLogger.Logger, gordon.HTTPLogger.Logger)
1819
1920 if err := e.Start(":1323"); err != nil {
20- appLogger.Error().Err(err).Msg("Failed to start the server")
21+ gordon.APPLogger.Error().Err(err).Msg("Failed to start the server")
2122 }
23+ // Graceful shutdown
24+ gordon.APPLogger.Info().Msg("Server is shutting down...")
2225 }
@@ -1,22 +1,25 @@
1 package main1 package main
2 2
3 import (3 import (
4- "gogs.bnema.dev/gordon-echo/internal"4+ "gogs.bnema.dev/gordon-echo/internal/app"
5 "gogs.bnema.dev/gordon-echo/internal/http/routes"5 "gogs.bnema.dev/gordon-echo/internal/http/routes"
6 )6 )
7 7
8 func main() {8 func main() {
9- // Setup logging9+ // Initialize Gordon
10- internal.SetupLogging()10+ gordon, err := app.NewApp()
11-11+ if err != nil {
12- // Initialize loggers12+ // Handle initialization error and exit
13- appLogger := internal.InitializeAppLogger()13+ gordon.APPLogger.Error().Err(err).Msg("Failed to initialize app")
14- echoLogger := internal.InitializeHTTPLogger()14+ return
15+ }
15 16
16 // Initialize echo server with routes and middlewares17 // Initialize echo server with routes and middlewares
17- e := routes.NewRouter(appLogger, echoLogger)18+ e := routes.NewRouter(gordon.APPLogger.Logger, gordon.HTTPLogger.Logger)
18 19
19 if err := e.Start(":1323"); err != nil {20 if err := e.Start(":1323"); err != nil {
20- appLogger.Error().Err(err).Msg("Failed to start the server")21+ gordon.APPLogger.Error().Err(err).Msg("Failed to start the server")
21 }22 }
23+ // Graceful shutdown
24+ gordon.APPLogger.Info().Msg("Server is shutting down...")
22 }25 }
D internal/app.go +0 -42
@@ -1,42 +0,0 @@
1-package internal
2-
3-import (
4- "os"
5-
6- "github.com/rs/zerolog"
7- "gogs.bnema.dev/gordon-echo/pkg/utils"
8-)
9-
10-// Define the main app struct
11-type App struct {
12- // Define the app logger
13- Logger *utils.Logger
14- // Define the app config
15-}
16-
17-func InitializeAppLogger() zerolog.Logger {
18- appLogFile, err := os.OpenFile("logs/app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
19- if err != nil {
20- panic(err)
21- }
22- appConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
23- appMulti := zerolog.MultiLevelWriter(appLogFile, appConsoleWriter)
24- return zerolog.New(appMulti).With().Timestamp().Str("type", "app").Logger()
25-}
26-
27-func InitializeHTTPLogger() zerolog.Logger {
28- httpLogFile, err := os.OpenFile("logs/http.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
29- if err != nil {
30- panic(err)
31- }
32- httpConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
33- httpMulti := zerolog.MultiLevelWriter(httpLogFile, httpConsoleWriter)
34- return zerolog.New(httpMulti).With().Timestamp().Logger()
35-}
36-
37-func SetupLogging() {
38- err := utils.CreateLogsDir()
39- if err != nil {
40- panic(err)
41- }
42-}
@@ -1,42 +0,0 @@
1-package internal
2-
3-import (
4- "os"
5-
6- "github.com/rs/zerolog"
7- "gogs.bnema.dev/gordon-echo/pkg/utils"
8-)
9-
10-// Define the main app struct
11-type App struct {
12- // Define the app logger
13- Logger *utils.Logger
14- // Define the app config
15-}
16-
17-func InitializeAppLogger() zerolog.Logger {
18- appLogFile, err := os.OpenFile("logs/app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
19- if err != nil {
20- panic(err)
21- }
22- appConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
23- appMulti := zerolog.MultiLevelWriter(appLogFile, appConsoleWriter)
24- return zerolog.New(appMulti).With().Timestamp().Str("type", "app").Logger()
25-}
26-
27-func InitializeHTTPLogger() zerolog.Logger {
28- httpLogFile, err := os.OpenFile("logs/http.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
29- if err != nil {
30- panic(err)
31- }
32- httpConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
33- httpMulti := zerolog.MultiLevelWriter(httpLogFile, httpConsoleWriter)
34- return zerolog.New(httpMulti).With().Timestamp().Logger()
35-}
36-
37-func SetupLogging() {
38- err := utils.CreateLogsDir()
39- if err != nil {
40- panic(err)
41- }
42-}
A internal/app/app.go +69 -0
@@ -0,0 +1,69 @@
1+package app
2+
3+import (
4+ "html/template"
5+ "os"
6+
7+ "github.com/rs/zerolog"
8+ "gogs.bnema.dev/gordon-echo/pkg/utils"
9+)
10+
11+// Define the main app struct
12+type App struct {
13+ // Define the app logger
14+ APPLogger *utils.Logger
15+ // Define the HTTP logger
16+ HTTPLogger *utils.Logger
17+ // Define the app config
18+ Template *template.Template
19+ // Define the app renderer
20+ Renderer *utils.Renderer
21+}
22+
23+func NewApp() (*App, error) {
24+ app := &App{}
25+
26+ // Initialize the general application logger
27+ appLogger, err := initializeAppLogger()
28+ if err != nil {
29+ return nil, err
30+ }
31+ app.APPLogger = &utils.Logger{Logger: appLogger}
32+
33+ // Initialize the HTTP logger
34+ httpLogger, err := initializeHTTPLogger()
35+ if err != nil {
36+ return nil, err
37+ }
38+ app.HTTPLogger = &utils.Logger{Logger: httpLogger}
39+
40+ return app, nil
41+}
42+func initializeAppLogger() (zerolog.Logger, error) {
43+ appLogFile, err := os.OpenFile("logs/app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
44+ if err != nil {
45+ return zerolog.Logger{}, err
46+ }
47+
48+ appConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
49+ appMulti := zerolog.MultiLevelWriter(appLogFile, appConsoleWriter)
50+ return zerolog.New(appMulti).With().Timestamp().Str("type", "app").Logger(), nil
51+}
52+
53+func initializeHTTPLogger() (zerolog.Logger, error) {
54+ httpLogFile, err := os.OpenFile("logs/http.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
55+ if err != nil {
56+ return zerolog.Logger{}, err
57+ }
58+
59+ httpConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
60+ httpMulti := zerolog.MultiLevelWriter(httpLogFile, httpConsoleWriter)
61+ return zerolog.New(httpMulti).With().Timestamp().Logger(), nil
62+}
63+
64+func SetupLogging() {
65+ err := utils.CreateLogsDir()
66+ if err != nil {
67+ panic(err)
68+ }
69+}
@@ -0,0 +1,69 @@
1+package app
2+
3+import (
4+ "html/template"
5+ "os"
6+
7+ "github.com/rs/zerolog"
8+ "gogs.bnema.dev/gordon-echo/pkg/utils"
9+)
10+
11+// Define the main app struct
12+type App struct {
13+ // Define the app logger
14+ APPLogger *utils.Logger
15+ // Define the HTTP logger
16+ HTTPLogger *utils.Logger
17+ // Define the app config
18+ Template *template.Template
19+ // Define the app renderer
20+ Renderer *utils.Renderer
21+}
22+
23+func NewApp() (*App, error) {
24+ app := &App{}
25+
26+ // Initialize the general application logger
27+ appLogger, err := initializeAppLogger()
28+ if err != nil {
29+ return nil, err
30+ }
31+ app.APPLogger = &utils.Logger{Logger: appLogger}
32+
33+ // Initialize the HTTP logger
34+ httpLogger, err := initializeHTTPLogger()
35+ if err != nil {
36+ return nil, err
37+ }
38+ app.HTTPLogger = &utils.Logger{Logger: httpLogger}
39+
40+ return app, nil
41+}
42+func initializeAppLogger() (zerolog.Logger, error) {
43+ appLogFile, err := os.OpenFile("logs/app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
44+ if err != nil {
45+ return zerolog.Logger{}, err
46+ }
47+
48+ appConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
49+ appMulti := zerolog.MultiLevelWriter(appLogFile, appConsoleWriter)
50+ return zerolog.New(appMulti).With().Timestamp().Str("type", "app").Logger(), nil
51+}
52+
53+func initializeHTTPLogger() (zerolog.Logger, error) {
54+ httpLogFile, err := os.OpenFile("logs/http.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
55+ if err != nil {
56+ return zerolog.Logger{}, err
57+ }
58+
59+ httpConsoleWriter := zerolog.ConsoleWriter{Out: os.Stdout}
60+ httpMulti := zerolog.MultiLevelWriter(httpLogFile, httpConsoleWriter)
61+ return zerolog.New(httpMulti).With().Timestamp().Logger(), nil
62+}
63+
64+func SetupLogging() {
65+ err := utils.CreateLogsDir()
66+ if err != nil {
67+ panic(err)
68+ }
69+}
M internal/http/handlers/default.go +1 -1
@@ -10,7 +10,7 @@ import (
1010 )
1111
1212 func DefaultHTTPErrorHandler(err error, c echo.Context) {
13- renderer, err := utils.GetRenderer("500.gohtml", ui.PublicFS)
13+ renderer, err := utils.GetRenderer("500.gohtml", ui.PublicFS, utils.NewLogger())
1414 if err != nil {
1515 c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
1616 }
@@ -10,7 +10,7 @@ import (
10 )10 )
11 11
12 func DefaultHTTPErrorHandler(err error, c echo.Context) {12 func DefaultHTTPErrorHandler(err error, c echo.Context) {
13- renderer, err := utils.GetRenderer("500.gohtml", ui.PublicFS)13+ renderer, err := utils.GetRenderer("500.gohtml", ui.PublicFS, utils.NewLogger())
14 if err != nil {14 if err != nil {
15 c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))15 c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
16 }16 }
R internal/http/handlers/404.go → internal/http/handlers/errors.go +22 -3 renamed, 54% similar
@@ -9,10 +9,16 @@ import (
99 "gogs.bnema.dev/gordon-echo/pkg/utils"
1010 )
1111
12-func Custom404Handler(err error, c echo.Context) {
12+type ErrorHandler struct {
13+ Err error
14+ C echo.Context
15+ ErrorNumber int
16+}
17+
18+func Error404Handler(err error, c echo.Context) {
1319 if he, ok := err.(*echo.HTTPError); ok {
1420 if he.Code == http.StatusNotFound {
15- renderer, err := utils.GetRenderer("404.gohtml", ui.PublicFS)
21+ renderer, err := utils.GetRenderer("404.gohtml", ui.PublicFS, utils.NewLogger())
1622 if err != nil {
1723 c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
1824 }
@@ -33,5 +39,18 @@ func Custom404Handler(err error, c echo.Context) {
3339 }
3440 }
3541
36- DefaultHTTPErrorHandler(err, c)
42+}
43+
44+func Error200Handler(err error, c echo.Context) {
45+ // Just print the error for now
46+ fmt.Println(err)
47+}
48+
49+func Error403Handler(err error, c echo.Context) {
50+ // Just print the error for now
51+ fmt.Println(err)
52+}
53+
54+func ErrorNumberHandler(err error, c echo.Context) {
55+ // For Each Error Code, we can have a different handler
3756 }
@@ -9,10 +9,16 @@ import (
9 "gogs.bnema.dev/gordon-echo/pkg/utils"9 "gogs.bnema.dev/gordon-echo/pkg/utils"
10 )10 )
11 11
12-func Custom404Handler(err error, c echo.Context) {12+type ErrorHandler struct {
13+ Err error
14+ C echo.Context
15+ ErrorNumber int
16+}
17+
18+func Error404Handler(err error, c echo.Context) {
13 if he, ok := err.(*echo.HTTPError); ok {19 if he, ok := err.(*echo.HTTPError); ok {
14 if he.Code == http.StatusNotFound {20 if he.Code == http.StatusNotFound {
15- renderer, err := utils.GetRenderer("404.gohtml", ui.PublicFS)21+ renderer, err := utils.GetRenderer("404.gohtml", ui.PublicFS, utils.NewLogger())
16 if err != nil {22 if err != nil {
17 c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))23 c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
18 }24 }
@@ -33,5 +39,18 @@ func Custom404Handler(err error, c echo.Context) {
33 }39 }
34 }40 }
35 41
36- DefaultHTTPErrorHandler(err, c)42+}
43+
44+func Error200Handler(err error, c echo.Context) {
45+ // Just print the error for now
46+ fmt.Println(err)
47+}
48+
49+func Error403Handler(err error, c echo.Context) {
50+ // Just print the error for now
51+ fmt.Println(err)
52+}
53+
54+func ErrorNumberHandler(err error, c echo.Context) {
55+ // For Each Error Code, we can have a different handler
37 }56 }
M internal/http/handlers/htmx.go +9 -2
@@ -1,6 +1,7 @@
11 package handlers
22
33 import (
4+ "fmt"
45 "net/http"
56
67 "github.com/labstack/echo/v4"
@@ -8,13 +9,18 @@ import (
89 "gogs.bnema.dev/gordon-echo/pkg/utils"
910 )
1011
12+var logger = utils.NewLogger()
13+
1114 func HTMXHandler(c echo.Context) error {
1215 // Detect and extract HTMX data from the request
13- htmxRequest := htmx.GetRequest(c)
16+ htmxRequest, err := htmx.GetRequest(c)
17+ if err != nil {
18+ return logger.Debug().GetCtx().Err()
19+ }
1420
1521 // If the request is not an HTMX request, return an error
1622 if !htmxRequest.Enabled {
17- return c.String(http.StatusBadRequest, "Non-HTMX request")
23+ return logger.Error().GetCtx().Err()
1824 }
1925
2026 // If it's a GET request, handle the fragment
@@ -29,6 +35,7 @@ func HTMXHandler(c echo.Context) error {
2935
3036 func HTMXFragmentHandler(c echo.Context) error {
3137 fragment := c.Request().Header.Get("X-Fragment")
38+ fmt.Println(fragment)
3239 if fragment == "" {
3340 return c.String(http.StatusBadRequest, "Missing fragment header")
3441 }
@@ -1,6 +1,7 @@
1 package handlers1 package handlers
2 2
3 import (3 import (
4+ "fmt"
4 "net/http"5 "net/http"
5 6
6 "github.com/labstack/echo/v4"7 "github.com/labstack/echo/v4"
@@ -8,13 +9,18 @@ import (
8 "gogs.bnema.dev/gordon-echo/pkg/utils"9 "gogs.bnema.dev/gordon-echo/pkg/utils"
9 )10 )
10 11
12+var logger = utils.NewLogger()
13+
11 func HTMXHandler(c echo.Context) error {14 func HTMXHandler(c echo.Context) error {
12 // Detect and extract HTMX data from the request15 // Detect and extract HTMX data from the request
13- htmxRequest := htmx.GetRequest(c)16+ htmxRequest, err := htmx.GetRequest(c)
17+ if err != nil {
18+ return logger.Debug().GetCtx().Err()
19+ }
14 20
15 // If the request is not an HTMX request, return an error21 // If the request is not an HTMX request, return an error
16 if !htmxRequest.Enabled {22 if !htmxRequest.Enabled {
17- return c.String(http.StatusBadRequest, "Non-HTMX request")23+ return logger.Error().GetCtx().Err()
18 }24 }
19 25
20 // If it's a GET request, handle the fragment26 // If it's a GET request, handle the fragment
@@ -29,6 +35,7 @@ func HTMXHandler(c echo.Context) error {
29 35
30 func HTMXFragmentHandler(c echo.Context) error {36 func HTMXFragmentHandler(c echo.Context) error {
31 fragment := c.Request().Header.Get("X-Fragment")37 fragment := c.Request().Header.Get("X-Fragment")
38+ fmt.Println(fragment)
32 if fragment == "" {39 if fragment == "" {
33 return c.String(http.StatusBadRequest, "Missing fragment header")40 return c.String(http.StatusBadRequest, "Missing fragment header")
34 }41 }
M internal/http/routes/admin_routes.go +19 -6
@@ -3,20 +3,31 @@ package routes
33 import (
44 "fmt"
55 "net/http"
6+ "strings"
67
78 "github.com/labstack/echo/v4"
89 "gogs.bnema.dev/gordon-echo/internal/ui"
910 "gogs.bnema.dev/gordon-echo/pkg/utils"
1011 )
1112
13+func getPreferredLanguage(c echo.Context) string {
14+ header := c.Request().Header.Get("Accept-Language")
15+ // Check the header value, parse it and determine the primary language.
16+ // For this example, let's assume English and French only.
17+ if strings.Contains(header, "fr") {
18+ return "fr"
19+ }
20+ return "en" // Default to English
21+}
22+
1223 func AdminRoute(c echo.Context) error {
13- staticData, err := utils.LoadDataFromYAML()
14- fmt.Println(staticData)
24+ staticData, err := utils.ReadDataFromYAML()
1525 if err != nil {
1626 return err
1727 }
1828
19- lang := c.Param("lang")
29+ lang := getPreferredLanguage(c)
30+ fmt.Println(lang)
2031
2132 switch lang {
2233 case "fr":
@@ -25,14 +36,16 @@ func AdminRoute(c echo.Context) error {
2536 staticData.CurrentLang = staticData.EN
2637 }
2738
28- renderer, err := utils.GetRenderer("index.gohtml", ui.PublicFS)
39+ renderer, err := utils.GetRenderer("index.gohtml", ui.PublicFS, utils.NewLogger())
2940 if err != nil {
3041 return err
3142 }
3243
33- html, err := renderer.Render(staticData)
44+ html, err := renderer.Render(staticData.CurrentLang)
3445 if err != nil {
35- return c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))
46+ // Return the error into NewLogger() to log it
47+ utils.NewLogger().Error()
48+ return err
3649 }
3750
3851 return c.HTML(http.StatusOK, html)
@@ -3,20 +3,31 @@ package routes
3 import (3 import (
4 "fmt"4 "fmt"
5 "net/http"5 "net/http"
6+ "strings"
6 7
7 "github.com/labstack/echo/v4"8 "github.com/labstack/echo/v4"
8 "gogs.bnema.dev/gordon-echo/internal/ui"9 "gogs.bnema.dev/gordon-echo/internal/ui"
9 "gogs.bnema.dev/gordon-echo/pkg/utils"10 "gogs.bnema.dev/gordon-echo/pkg/utils"
10 )11 )
11 12
13+func getPreferredLanguage(c echo.Context) string {
14+ header := c.Request().Header.Get("Accept-Language")
15+ // Check the header value, parse it and determine the primary language.
16+ // For this example, let's assume English and French only.
17+ if strings.Contains(header, "fr") {
18+ return "fr"
19+ }
20+ return "en" // Default to English
21+}
22+
12 func AdminRoute(c echo.Context) error {23 func AdminRoute(c echo.Context) error {
13- staticData, err := utils.LoadDataFromYAML()24+ staticData, err := utils.ReadDataFromYAML()
14- fmt.Println(staticData)
15 if err != nil {25 if err != nil {
16 return err26 return err
17 }27 }
18 28
19- lang := c.Param("lang")29+ lang := getPreferredLanguage(c)
30+ fmt.Println(lang)
20 31
21 switch lang {32 switch lang {
22 case "fr":33 case "fr":
@@ -25,14 +36,16 @@ func AdminRoute(c echo.Context) error {
25 staticData.CurrentLang = staticData.EN36 staticData.CurrentLang = staticData.EN
26 }37 }
27 38
28- renderer, err := utils.GetRenderer("index.gohtml", ui.PublicFS)39+ renderer, err := utils.GetRenderer("index.gohtml", ui.PublicFS, utils.NewLogger())
29 if err != nil {40 if err != nil {
30 return err41 return err
31 }42 }
32 43
33- html, err := renderer.Render(staticData)44+ html, err := renderer.Render(staticData.CurrentLang)
34 if err != nil {45 if err != nil {
35- return c.String(http.StatusInternalServerError, fmt.Sprintf("%v", err))46+ // Return the error into NewLogger() to log it
47+ utils.NewLogger().Error()
48+ return err
36 }49 }
37 50
38 return c.HTML(http.StatusOK, html)51 return c.HTML(http.StatusOK, html)
M internal/http/routes/router.go +2 -3
@@ -20,10 +20,9 @@ func NewRouter(appLogger, echoLogger zerolog.Logger) *echo.Echo {
2020 }
2121
2222 func bindStaticAdminUI(e *echo.Echo) *echo.Echo {
23- e.GET("/admin/:lang", AdminRoute)
23+ e.GET("/admin", AdminRoute)
2424 e.GET("/htmx", handlers.HTMXHandler)
2525 e.GET("/*", StaticRoute)
26- e.HTTPErrorHandler = handlers.Custom404Handler
27- e.HTTPErrorHandler = handlers.DefaultHTTPErrorHandler
26+ e.HTTPErrorHandler = handlers.ErrorNumberHandler
2827 return e
2928 }
@@ -20,10 +20,9 @@ func NewRouter(appLogger, echoLogger zerolog.Logger) *echo.Echo {
20 }20 }
21 21
22 func bindStaticAdminUI(e *echo.Echo) *echo.Echo {22 func bindStaticAdminUI(e *echo.Echo) *echo.Echo {
23- e.GET("/admin/:lang", AdminRoute)23+ e.GET("/admin", AdminRoute)
24 e.GET("/htmx", handlers.HTMXHandler)24 e.GET("/htmx", handlers.HTMXHandler)
25 e.GET("/*", StaticRoute)25 e.GET("/*", StaticRoute)
26- e.HTTPErrorHandler = handlers.Custom404Handler26+ e.HTTPErrorHandler = handlers.ErrorNumberHandler
27- e.HTTPErrorHandler = handlers.DefaultHTTPErrorHandler
28 return e27 return e
29 }28 }
M internal/ui/public/500.gohtml +1 -1
@@ -7,6 +7,6 @@
77 </head>
88 <body>
99 <h1>500 Internal Server Error</h1>
10- <p>Something went wrong</p>
10+ <p>Something went really wrong o/</p>
1111 </body>
1212 </html>
@@ -7,6 +7,6 @@
7 </head>7 </head>
8 <body>8 <body>
9 <h1>500 Internal Server Error</h1>9 <h1>500 Internal Server Error</h1>
10- <p>Something went wrong</p>10+ <p>Something went really wrong o/</p>
11 </body>11 </body>
12 </html>12 </html>
M internal/ui/public/index.gohtml +1 -1
@@ -3,7 +3,7 @@
33 <head>
44 <meta charset="UTF-8" />
55 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6- <title>{{.website.title}}</title>
6+ <title>{{ .Header.Meta.Title }}</title>
77 <!-- Custom CSS from assets/css -->
88 <link rel="stylesheet" href="assets/css/custom.css" />
99 <!-- Alpine -->
@@ -3,7 +3,7 @@
3 <head>3 <head>
4 <meta charset="UTF-8" />4 <meta charset="UTF-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />5 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6- <title>{{.website.title}}</title>6+ <title>{{ .Header.Meta.Title }}</title>
7 <!-- Custom CSS from assets/css -->7 <!-- Custom CSS from assets/css -->
8 <link rel="stylesheet" href="assets/css/custom.css" />8 <link rel="stylesheet" href="assets/css/custom.css" />
9 <!-- Alpine -->9 <!-- Alpine -->
M internal/ui/templates/components.gohtml +14 -5
@@ -1,6 +1,15 @@
1+<div id="header">
2+ <h1
3+ class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s"
4+ >
5+ {{ .Body.Header.HelloWorld }}
6+ </h1>
7+</div>
18
2-
3-
4-<div id ="hello">
5- <h1 class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s">Hello World from components.html</h1>
6-</div>⏎̸
9+<div id="hello">
10+ <h1
11+ class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s"
12+ >
13+ {{ .Body.Div.Hello }}
14+ </h1>
15+</div>
@@ -1,6 +1,15 @@
1+<div id="header">
2+ <h1
3+ class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s"
4+ >
5+ {{ .Body.Header.HelloWorld }}
6+ </h1>
7+</div>
1 8
2-9+<div id="hello">
3-10+ <h1
4-<div id ="hello">11+ class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s"
5- <h1 class="text-5xl fw100 animate-bounce-alt animate-count-infinite animate-duration-1s">Hello World from components.html</h1>12+ >
6-</div>⏎̸13+ {{ .Body.Div.Hello }}
14+ </h1>
15+</div>
M internal/ui/templates/strings.yaml +20 -6
@@ -1,9 +1,23 @@
11 en:
2- website:
3- title: Admin Dashboard
4- user: John Doe
2+ header:
3+ meta:
4+ title: Gordon - Admin Dashboard
5+ description: Admin dashboard for Gordon
6+ keywords: gordon, admin, dashboard
7+ body:
8+ header:
9+ helloworld: Hello World!
10+ div:
11+ hello: Hello component loaded from a template!
512
613 fr:
7- website:
8- title: Tableau de bord administrateur
9- user: Jean Dupont⏎̸
14+ header:
15+ meta:
16+ title: Gordon - Tableau de bord administrateur
17+ description: Tableau de bord administrateur pour Gordon
18+ keywords: gordon, admin, dashboard
19+ body:
20+ header:
21+ helloworld: Bonjour le monde!
22+ div:
23+ hello: Composant de salut chargé à partir d'un modèle!
@@ -1,9 +1,23 @@
1 en:1 en:
2- website:2+ header:
3- title: Admin Dashboard3+ meta:
4- user: John Doe4+ title: Gordon - Admin Dashboard
5+ description: Admin dashboard for Gordon
6+ keywords: gordon, admin, dashboard
7+ body:
8+ header:
9+ helloworld: Hello World!
10+ div:
11+ hello: Hello component loaded from a template!
5 12
6 fr:13 fr:
7- website:14+ header:
8- title: Tableau de bord administrateur15+ meta:
9- user: Jean Dupont⏎̸16+ title: Gordon - Tableau de bord administrateur
17+ description: Tableau de bord administrateur pour Gordon
18+ keywords: gordon, admin, dashboard
19+ body:
20+ header:
21+ helloworld: Bonjour le monde!
22+ div:
23+ hello: Composant de salut chargé à partir d'un modèle!
M pkg/htmx/htmx.go +7 -3
@@ -47,8 +47,8 @@ type (
4747 )
4848
4949 // GetRequest extracts HTMX data from the request
50-func GetRequest(ctx echo.Context) Request {
51- return Request{
50+func GetRequest(ctx echo.Context) (Request, error) {
51+ request := Request{
5252 Enabled: ctx.Request().Header.Get(HeaderRequest) == "true",
5353 Boosted: ctx.Request().Header.Get(HeaderBoosted) == "true",
5454 Trigger: ctx.Request().Header.Get(HeaderTrigger),
@@ -56,10 +56,12 @@ func GetRequest(ctx echo.Context) Request {
5656 Target: ctx.Request().Header.Get(HeaderTarget),
5757 Prompt: ctx.Request().Header.Get(HeaderPrompt),
5858 }
59+
60+ return request, nil
5961 }
6062
6163 // Apply applies data from a Response to a server response
62-func (r Response) Apply(ctx echo.Context) {
64+func (r Response) Apply(ctx echo.Context) error {
6365 if r.Push != "" {
6466 ctx.Response().Header().Set(HeaderPush, r.Push)
6567 }
@@ -81,4 +83,6 @@ func (r Response) Apply(ctx echo.Context) {
8183 if r.NoContent {
8284 ctx.Response().Status = http.StatusNoContent
8385 }
86+
87+ return nil
8488 }
@@ -47,8 +47,8 @@ type (
47 )47 )
48 48
49 // GetRequest extracts HTMX data from the request49 // GetRequest extracts HTMX data from the request
50-func GetRequest(ctx echo.Context) Request {50+func GetRequest(ctx echo.Context) (Request, error) {
51- return Request{51+ request := Request{
52 Enabled: ctx.Request().Header.Get(HeaderRequest) == "true",52 Enabled: ctx.Request().Header.Get(HeaderRequest) == "true",
53 Boosted: ctx.Request().Header.Get(HeaderBoosted) == "true",53 Boosted: ctx.Request().Header.Get(HeaderBoosted) == "true",
54 Trigger: ctx.Request().Header.Get(HeaderTrigger),54 Trigger: ctx.Request().Header.Get(HeaderTrigger),
@@ -56,10 +56,12 @@ func GetRequest(ctx echo.Context) Request {
56 Target: ctx.Request().Header.Get(HeaderTarget),56 Target: ctx.Request().Header.Get(HeaderTarget),
57 Prompt: ctx.Request().Header.Get(HeaderPrompt),57 Prompt: ctx.Request().Header.Get(HeaderPrompt),
58 }58 }
59+
60+ return request, nil
59 }61 }
60 62
61 // Apply applies data from a Response to a server response63 // Apply applies data from a Response to a server response
62-func (r Response) Apply(ctx echo.Context) {64+func (r Response) Apply(ctx echo.Context) error {
63 if r.Push != "" {65 if r.Push != "" {
64 ctx.Response().Header().Set(HeaderPush, r.Push)66 ctx.Response().Header().Set(HeaderPush, r.Push)
65 }67 }
@@ -81,4 +83,6 @@ func (r Response) Apply(ctx echo.Context) {
81 if r.NoContent {83 if r.NoContent {
82 ctx.Response().Status = http.StatusNoContent84 ctx.Response().Status = http.StatusNoContent
83 }85 }
86+
87+ return nil
84 }88 }
M pkg/utils/parser.go +31 -15
@@ -8,28 +8,42 @@ import (
88 "gopkg.in/yaml.v3"
99 )
1010
11-type Data struct {
12- EN StaticData `yaml:"en"`
13- FR StaticData `yaml:"fr"`
14- CurrentLang StaticData `yaml:"-"`
15- Website struct {
16- Title string `yaml:"title"`
17- User string `yaml:"user"`
18- } `yaml:"website"`
11+// Yaml Parser
12+type Meta struct {
13+ Title string `yaml:"title"`
14+ Description string `yaml:"description"`
15+ Keywords string `yaml:"keywords"`
1916 }
2017
21-type StaticData struct {
22- Title string `yaml:"title"`
23- User string `yaml:"user"`
18+type Header struct {
19+ Meta Meta `yaml:"meta"`
20+ HelloWorld string `yaml:"helloworld"`
2421 }
2522
26-// LoadDataFromYAML loads the data from the given YAML file
27-func LoadDataFromYAML() (Data, error) {
28- var data Data
23+type Body struct {
24+ Header Header `yaml:"header"`
25+ Div struct {
26+ Hello string `yaml:"hello"`
27+ } `yaml:"div"`
28+}
29+
30+type LangData struct {
31+ Header Header `yaml:"header"`
32+ Body Body `yaml:"body"`
33+}
34+
35+type YamlData struct {
36+ EN LangData `yaml:"en"`
37+ FR LangData `yaml:"fr"`
38+ CurrentLang LangData
39+}
40+
41+// ReadDataFromYAML loads the data from the given YAML file
42+func ReadDataFromYAML() (YamlData, error) {
43+ var data YamlData
2944
3045 // Open the YAML file inside the ui/components directory
3146 file, err := ui.TemplateFS.Open("strings.yaml")
32- fmt.Println(file)
3347 if err != nil {
3448 return data, fmt.Errorf("failed to open strings.yaml: %w", err)
3549 }
@@ -48,3 +62,5 @@ func LoadDataFromYAML() (Data, error) {
4862
4963 return data, nil
5064 }
65+
66+// Json Parser
@@ -8,28 +8,42 @@ import (
8 "gopkg.in/yaml.v3"8 "gopkg.in/yaml.v3"
9 )9 )
10 10
11-type Data struct {11+// Yaml Parser
12- EN StaticData `yaml:"en"`12+type Meta struct {
13- FR StaticData `yaml:"fr"`13+ Title string `yaml:"title"`
14- CurrentLang StaticData `yaml:"-"`14+ Description string `yaml:"description"`
15- Website struct {15+ Keywords string `yaml:"keywords"`
16- Title string `yaml:"title"`
17- User string `yaml:"user"`
18- } `yaml:"website"`
19 }16 }
20 17
21-type StaticData struct {18+type Header struct {
22- Title string `yaml:"title"`19+ Meta Meta `yaml:"meta"`
23- User string `yaml:"user"`20+ HelloWorld string `yaml:"helloworld"`
24 }21 }
25 22
26-// LoadDataFromYAML loads the data from the given YAML file23+type Body struct {
27-func LoadDataFromYAML() (Data, error) {24+ Header Header `yaml:"header"`
28- var data Data25+ Div struct {
26+ Hello string `yaml:"hello"`
27+ } `yaml:"div"`
28+}
29+
30+type LangData struct {
31+ Header Header `yaml:"header"`
32+ Body Body `yaml:"body"`
33+}
34+
35+type YamlData struct {
36+ EN LangData `yaml:"en"`
37+ FR LangData `yaml:"fr"`
38+ CurrentLang LangData
39+}
40+
41+// ReadDataFromYAML loads the data from the given YAML file
42+func ReadDataFromYAML() (YamlData, error) {
43+ var data YamlData
29 44
30 // Open the YAML file inside the ui/components directory45 // Open the YAML file inside the ui/components directory
31 file, err := ui.TemplateFS.Open("strings.yaml")46 file, err := ui.TemplateFS.Open("strings.yaml")
32- fmt.Println(file)
33 if err != nil {47 if err != nil {
34 return data, fmt.Errorf("failed to open strings.yaml: %w", err)48 return data, fmt.Errorf("failed to open strings.yaml: %w", err)
35 }49 }
@@ -48,3 +62,5 @@ func LoadDataFromYAML() (Data, error) {
48 62
49 return data, nil63 return data, nil
50 }64 }
65+
66+// Json Parser
M pkg/utils/render.go +11 -2
@@ -10,21 +10,28 @@ import (
1010 type Renderer struct {
1111 Template *template.Template
1212 ParseError error
13+ Logger *Logger
1314 }
1415
1516 // Render function renders the template with the given data
1617 func (r *Renderer) Render(data interface{}) (string, error) {
1718 if r.ParseError != nil {
19+ r.Logger.Error().Err(r.ParseError).Msg("Parse error")
20+
1821 return "", r.ParseError
1922 }
2023
2124 if r.Template == nil {
22- return "", errors.New("invalid or nil template")
25+ err := errors.New("invalid or nil template")
26+ r.Logger.Error().Err(r.ParseError).Msg("Parse error")
27+
28+ return "", err
2329 }
2430
2531 buf := new(bytes.Buffer)
2632 err := r.Template.Execute(buf, data)
2733 if err != nil {
34+ r.Logger.Error().Err(err).Msg("Render error")
2835 return "", err
2936 }
3037
@@ -32,12 +39,14 @@ func (r *Renderer) Render(data interface{}) (string, error) {
3239 }
3340
3441 // GetRenderer function returns a new Renderer instance
35-func GetRenderer(filename string, fs fs.FS) (*Renderer, error) {
42+func GetRenderer(filename string, fs fs.FS, logger *Logger) (*Renderer, error) {
3643 tmpl, err := template.New(filename).ParseFS(fs, filename)
3744 if err != nil {
45+ logger.Error().Err(err).Msg("Failed to parse template")
3846 return nil, err
3947 }
4048 return &Renderer{
4149 Template: tmpl,
50+ Logger: logger,
4251 }, nil
4352 }
@@ -10,21 +10,28 @@ import (
10 type Renderer struct {10 type Renderer struct {
11 Template *template.Template11 Template *template.Template
12 ParseError error12 ParseError error
13+ Logger *Logger
13 }14 }
14 15
15 // Render function renders the template with the given data16 // Render function renders the template with the given data
16 func (r *Renderer) Render(data interface{}) (string, error) {17 func (r *Renderer) Render(data interface{}) (string, error) {
17 if r.ParseError != nil {18 if r.ParseError != nil {
19+ r.Logger.Error().Err(r.ParseError).Msg("Parse error")
20+
18 return "", r.ParseError21 return "", r.ParseError
19 }22 }
20 23
21 if r.Template == nil {24 if r.Template == nil {
22- return "", errors.New("invalid or nil template")25+ err := errors.New("invalid or nil template")
26+ r.Logger.Error().Err(r.ParseError).Msg("Parse error")
27+
28+ return "", err
23 }29 }
24 30
25 buf := new(bytes.Buffer)31 buf := new(bytes.Buffer)
26 err := r.Template.Execute(buf, data)32 err := r.Template.Execute(buf, data)
27 if err != nil {33 if err != nil {
34+ r.Logger.Error().Err(err).Msg("Render error")
28 return "", err35 return "", err
29 }36 }
30 37
@@ -32,12 +39,14 @@ func (r *Renderer) Render(data interface{}) (string, error) {
32 }39 }
33 40
34 // GetRenderer function returns a new Renderer instance41 // GetRenderer function returns a new Renderer instance
35-func GetRenderer(filename string, fs fs.FS) (*Renderer, error) {42+func GetRenderer(filename string, fs fs.FS, logger *Logger) (*Renderer, error) {
36 tmpl, err := template.New(filename).ParseFS(fs, filename)43 tmpl, err := template.New(filename).ParseFS(fs, filename)
37 if err != nil {44 if err != nil {
45+ logger.Error().Err(err).Msg("Failed to parse template")
38 return nil, err46 return nil, err
39 }47 }
40 return &Renderer{48 return &Renderer{
41 Template: tmpl,49 Template: tmpl,
50+ Logger: logger,
42 }, nil51 }, nil
43 }52 }

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