detached 8ca5040

StaticFS served on the website root path

commit
8ca5040edc4c838748304be449c710456a23ca79
author
bnema · · 2023-09-13 17:06 UTC
parent
b0b2bc7
signature
unsigned
14 files · +247 -7
M go.mod +7 -0
@@ -4,11 +4,17 @@ go 1.21.1
44
55 require (
66 github.com/PuerkitoBio/goquery v1.8.1
7+ github.com/labstack/echo-contrib v0.15.0
78 github.com/labstack/echo/v4 v4.11.1
9+ gopkg.in/yaml.v3 v3.0.1
810 )
911
1012 require (
1113 github.com/andybalholm/cascadia v1.3.1 // indirect
14+ github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
15+ github.com/gorilla/context v1.1.1 // indirect
16+ github.com/gorilla/securecookie v1.1.1 // indirect
17+ github.com/gorilla/sessions v1.2.1 // indirect
1218 github.com/labstack/gommon v0.4.0 // indirect
1319 github.com/mattn/go-colorable v0.1.13 // indirect
1420 github.com/mattn/go-isatty v0.0.19 // indirect
@@ -18,4 +24,5 @@ require (
1824 golang.org/x/net v0.12.0 // indirect
1925 golang.org/x/sys v0.10.0 // indirect
2026 golang.org/x/text v0.11.0 // indirect
27+ golang.org/x/time v0.3.0 // indirect
2128 )
@@ -4,11 +4,17 @@ go 1.21.1
4 4
5 require (5 require (
6 github.com/PuerkitoBio/goquery v1.8.16 github.com/PuerkitoBio/goquery v1.8.1
7+ github.com/labstack/echo-contrib v0.15.0
7 github.com/labstack/echo/v4 v4.11.18 github.com/labstack/echo/v4 v4.11.1
9+ gopkg.in/yaml.v3 v3.0.1
8 )10 )
9 11
10 require (12 require (
11 github.com/andybalholm/cascadia v1.3.1 // indirect13 github.com/andybalholm/cascadia v1.3.1 // indirect
14+ github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
15+ github.com/gorilla/context v1.1.1 // indirect
16+ github.com/gorilla/securecookie v1.1.1 // indirect
17+ github.com/gorilla/sessions v1.2.1 // indirect
12 github.com/labstack/gommon v0.4.0 // indirect18 github.com/labstack/gommon v0.4.0 // indirect
13 github.com/mattn/go-colorable v0.1.13 // indirect19 github.com/mattn/go-colorable v0.1.13 // indirect
14 github.com/mattn/go-isatty v0.0.19 // indirect20 github.com/mattn/go-isatty v0.0.19 // indirect
@@ -18,4 +24,5 @@ require (
18 golang.org/x/net v0.12.0 // indirect24 golang.org/x/net v0.12.0 // indirect
19 golang.org/x/sys v0.10.0 // indirect25 golang.org/x/sys v0.10.0 // indirect
20 golang.org/x/text v0.11.0 // indirect26 golang.org/x/text v0.11.0 // indirect
27+ golang.org/x/time v0.3.0 // indirect
21 )28 )
M go.sum +13 -0 generated file: collapsed
M internal/httpserve/handler/adminroutes.go +8 -3
@@ -1,5 +1,10 @@
1-package handlers
2-
3-import ()
1+package handler
42
3+import (
4+ "github.com/labstack/echo/v4"
5+)
56
7+// Handle the admin route to display index.gohtml from the templateFS with the data from strings.yaml
8+func AdminRoute(c echo.Context) error {
9+ return c.Render(200, "index.gohtml", c.Get("data"))
10+}
@@ -1,5 +1,10 @@
1-package handlers1+package handler
2-
3-import ()
4 2
3+import (
4+ "github.com/labstack/echo/v4"
5+)
5 6
7+// Handle the admin route to display index.gohtml from the templateFS with the data from strings.yaml
8+func AdminRoute(c echo.Context) error {
9+ return c.Render(200, "index.gohtml", c.Get("data"))
10+}
M internal/httpserve/handler/htmx.go +1 -0
@@ -0,0 +1,1 @@
1+package handler
@@ -0,0 +1,1 @@
1+package handler
M internal/httpserve/handler/static.go +24 -0
@@ -0,0 +1,24 @@
1+package handler
2+
3+import (
4+ "net/http"
5+ "os"
6+
7+ "github.com/bnema/gordon/internal/app"
8+ "github.com/labstack/echo/v4"
9+)
10+
11+// StaticRoute serves static files from the embedded filesystem
12+func StaticRoute(c echo.Context, a *app.App) error {
13+ // Set the cache-control header based on PROD environment variable
14+ if os.Getenv("PROD") == "true" {
15+ c.Response().Header().Set("Cache-Control", "public, max-age=86400")
16+ } else {
17+ c.Response().Header().Set("Cache-Control", "no-cache")
18+ }
19+
20+ // Serve the file from the embedded filesystem
21+ publicFS := http.FileServer(http.FS(a.PublicFS))
22+ publicFS.ServeHTTP(c.Response(), c.Request())
23+ return nil
24+}
@@ -0,0 +1,24 @@
1+package handler
2+
3+import (
4+ "net/http"
5+ "os"
6+
7+ "github.com/bnema/gordon/internal/app"
8+ "github.com/labstack/echo/v4"
9+)
10+
11+// StaticRoute serves static files from the embedded filesystem
12+func StaticRoute(c echo.Context, a *app.App) error {
13+ // Set the cache-control header based on PROD environment variable
14+ if os.Getenv("PROD") == "true" {
15+ c.Response().Header().Set("Cache-Control", "public, max-age=86400")
16+ } else {
17+ c.Response().Header().Set("Cache-Control", "no-cache")
18+ }
19+
20+ // Serve the file from the embedded filesystem
21+ publicFS := http.FileServer(http.FS(a.PublicFS))
22+ publicFS.ServeHTTP(c.Response(), c.Request())
23+ return nil
24+}
A internal/httpserve/middleware/colorschemedetect.go +35 -0
@@ -0,0 +1,35 @@
1+package middleware
2+
3+import (
4+ "github.com/labstack/echo-contrib/session"
5+ "github.com/labstack/echo/v4"
6+)
7+
8+// ColorSchemeDetection detects the current color scheme and sets it in the context
9+func ColorSchemeDetection(next echo.HandlerFunc) echo.HandlerFunc {
10+ ColorSchemeKey := "CurrentColorScheme"
11+ return func(c echo.Context) error {
12+ colorScheme := detectCurrentColorScheme(c)
13+ c.Set(ColorSchemeKey, colorScheme)
14+ return next(c)
15+ }
16+}
17+
18+func detectCurrentColorScheme(c echo.Context) string {
19+ // Check if the color scheme key is set in the session storage
20+ sess, err := session.Get("session", c)
21+ if err != nil {
22+ return "light"
23+ }
24+
25+ if colorSchemeValue, ok := sess.Values["colorScheme"]; ok && colorSchemeValue != nil {
26+ return colorSchemeValue.(string) // Return the color scheme value from the session storage
27+ }
28+
29+ // If color scheme key is not set, proceed with "Sec-CH-Prefers-Color-Scheme" header detection
30+ header := c.Request().Header.Get("Sec-CH-Prefers-Color-Scheme")
31+ if header == "dark" {
32+ return "dark"
33+ }
34+ return "light" // Default to light mode
35+}
@@ -0,0 +1,35 @@
1+package middleware
2+
3+import (
4+ "github.com/labstack/echo-contrib/session"
5+ "github.com/labstack/echo/v4"
6+)
7+
8+// ColorSchemeDetection detects the current color scheme and sets it in the context
9+func ColorSchemeDetection(next echo.HandlerFunc) echo.HandlerFunc {
10+ ColorSchemeKey := "CurrentColorScheme"
11+ return func(c echo.Context) error {
12+ colorScheme := detectCurrentColorScheme(c)
13+ c.Set(ColorSchemeKey, colorScheme)
14+ return next(c)
15+ }
16+}
17+
18+func detectCurrentColorScheme(c echo.Context) string {
19+ // Check if the color scheme key is set in the session storage
20+ sess, err := session.Get("session", c)
21+ if err != nil {
22+ return "light"
23+ }
24+
25+ if colorSchemeValue, ok := sess.Values["colorScheme"]; ok && colorSchemeValue != nil {
26+ return colorSchemeValue.(string) // Return the color scheme value from the session storage
27+ }
28+
29+ // If color scheme key is not set, proceed with "Sec-CH-Prefers-Color-Scheme" header detection
30+ header := c.Request().Header.Get("Sec-CH-Prefers-Color-Scheme")
31+ if header == "dark" {
32+ return "dark"
33+ }
34+ return "light" // Default to light mode
35+}
D internal/httpserve/middleware/detectcolorscheme.go +0 -0

no textual change.

D internal/httpserve/middleware/detectlang.go +0 -0

no textual change.

A internal/httpserve/middleware/langdetect.go +40 -0
@@ -0,0 +1,40 @@
1+package middleware
2+
3+import (
4+ "strings"
5+
6+ "github.com/labstack/echo-contrib/session"
7+ "github.com/labstack/echo/v4"
8+)
9+
10+// LangKey is a key for setting and getting the language from the context
11+const LangKey = "CurrentLang"
12+
13+// LanguageDetectiondetects the current language and sets it in the context
14+func LanguageDetection(next echo.HandlerFunc) echo.HandlerFunc {
15+ return func(c echo.Context) error {
16+ lang := detectCurrentLanguage(c)
17+ c.Set(LangKey, lang)
18+ return next(c)
19+ }
20+}
21+
22+func detectCurrentLanguage(c echo.Context) string {
23+ // Check if the lang key is set in the session storage
24+ sess, err := session.Get("session", c)
25+ if err != nil {
26+ // If session storage is not available, proceed with "Accept-Language" header detection
27+ return "en"
28+ }
29+
30+ if langValue, ok := sess.Values["lang"]; ok && langValue != nil {
31+ return langValue.(string) // Return the lang value from the session storage
32+ }
33+
34+ // If lang key is not set, proceed with "Accept-Language" header detection
35+ header := c.Request().Header.Get("Accept-Language")
36+ if strings.Contains(header, "fr") {
37+ return "fr"
38+ }
39+ return "en" // Default to English
40+}
@@ -0,0 +1,40 @@
1+package middleware
2+
3+import (
4+ "strings"
5+
6+ "github.com/labstack/echo-contrib/session"
7+ "github.com/labstack/echo/v4"
8+)
9+
10+// LangKey is a key for setting and getting the language from the context
11+const LangKey = "CurrentLang"
12+
13+// LanguageDetectiondetects the current language and sets it in the context
14+func LanguageDetection(next echo.HandlerFunc) echo.HandlerFunc {
15+ return func(c echo.Context) error {
16+ lang := detectCurrentLanguage(c)
17+ c.Set(LangKey, lang)
18+ return next(c)
19+ }
20+}
21+
22+func detectCurrentLanguage(c echo.Context) string {
23+ // Check if the lang key is set in the session storage
24+ sess, err := session.Get("session", c)
25+ if err != nil {
26+ // If session storage is not available, proceed with "Accept-Language" header detection
27+ return "en"
28+ }
29+
30+ if langValue, ok := sess.Values["lang"]; ok && langValue != nil {
31+ return langValue.(string) // Return the lang value from the session storage
32+ }
33+
34+ // If lang key is not set, proceed with "Accept-Language" header detection
35+ header := c.Request().Header.Get("Accept-Language")
36+ if strings.Contains(header, "fr") {
37+ return "fr"
38+ }
39+ return "en" // Default to English
40+}
M internal/httpserve/middleware/secure.go +17 -0
@@ -0,0 +1,17 @@
1+package middleware
2+
3+import (
4+ "github.com/labstack/echo/v4"
5+ "github.com/labstack/echo/v4/middleware"
6+)
7+
8+func SecureRoutes() echo.MiddlewareFunc {
9+ return middleware.SecureWithConfig(middleware.SecureConfig{
10+ XSSProtection: "1; mode=block",
11+ ContentTypeNosniff: "nosniff",
12+ XFrameOptions: "SAMEORIGIN",
13+ HSTSMaxAge: 3600,
14+ HSTSExcludeSubdomains: false,
15+ ContentSecurityPolicy: "default-src 'self'",
16+ })
17+}
@@ -0,0 +1,17 @@
1+package middleware
2+
3+import (
4+ "github.com/labstack/echo/v4"
5+ "github.com/labstack/echo/v4/middleware"
6+)
7+
8+func SecureRoutes() echo.MiddlewareFunc {
9+ return middleware.SecureWithConfig(middleware.SecureConfig{
10+ XSSProtection: "1; mode=block",
11+ ContentTypeNosniff: "nosniff",
12+ XFrameOptions: "SAMEORIGIN",
13+ HSTSMaxAge: 3600,
14+ HSTSExcludeSubdomains: false,
15+ ContentSecurityPolicy: "default-src 'self'",
16+ })
17+}
M internal/httpserve/router.go +24 -4
@@ -1,17 +1,37 @@
11 package httpserve
22
33 import (
4+ "fmt"
5+
46 "github.com/bnema/gordon/internal/app"
7+ "github.com/bnema/gordon/internal/httpserve/handler"
8+ "github.com/bnema/gordon/internal/httpserve/middleware"
59 "github.com/labstack/echo/v4"
610 )
711
12+// RegisterRoutes registers all routes for the application
813 func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {
914 AdminPath := a.AdminPath
10- // Hello world on the admin path
11- e.GET(AdminPath, helloWorld)
15+ fmt.Println(AdminPath)
16+ // Use middlewares
17+ // e.Use(middleware.SecureRoutes())
18+ e.Use(middleware.ColorSchemeDetection)
19+ e.Use(middleware.LanguageDetection)
20+
21+ // Register routes
22+
23+ // Serve static files
24+ bindStaticRoute(e, a, "/*")
25+ // Protect the root path with a 403
26+ e.GET("/", func(c echo.Context) error {
27+ return c.String(403, "403 Forbidden")
28+ })
29+
1230 return e
1331 }
1432
15-func helloWorld(c echo.Context) error {
16- return c.String(200, "Hello world")
33+func bindStaticRoute(e *echo.Echo, a *app.App, path string) {
34+ e.GET(path, func(c echo.Context) error {
35+ return handler.StaticRoute(c, a)
36+ })
1737 }
@@ -1,17 +1,37 @@
1 package httpserve1 package httpserve
2 2
3 import (3 import (
4+ "fmt"
5+
4 "github.com/bnema/gordon/internal/app"6 "github.com/bnema/gordon/internal/app"
7+ "github.com/bnema/gordon/internal/httpserve/handler"
8+ "github.com/bnema/gordon/internal/httpserve/middleware"
5 "github.com/labstack/echo/v4"9 "github.com/labstack/echo/v4"
6 )10 )
7 11
12+// RegisterRoutes registers all routes for the application
8 func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {13 func RegisterRoutes(e *echo.Echo, a *app.App) *echo.Echo {
9 AdminPath := a.AdminPath14 AdminPath := a.AdminPath
10- // Hello world on the admin path15+ fmt.Println(AdminPath)
11- e.GET(AdminPath, helloWorld)16+ // Use middlewares
17+ // e.Use(middleware.SecureRoutes())
18+ e.Use(middleware.ColorSchemeDetection)
19+ e.Use(middleware.LanguageDetection)
20+
21+ // Register routes
22+
23+ // Serve static files
24+ bindStaticRoute(e, a, "/*")
25+ // Protect the root path with a 403
26+ e.GET("/", func(c echo.Context) error {
27+ return c.String(403, "403 Forbidden")
28+ })
29+
12 return e30 return e
13 }31 }
14 32
15-func helloWorld(c echo.Context) error {33+func bindStaticRoute(e *echo.Echo, a *app.App, path string) {
16- return c.String(200, "Hello world")34+ e.GET(path, func(c echo.Context) error {
35+ return handler.StaticRoute(c, a)
36+ })
17 }37 }
A internal/webui/strings.go +46 -0
@@ -0,0 +1,46 @@
1+package webui
2+
3+import (
4+ "fmt"
5+ "io/fs"
6+
7+ "github.com/bnema/gordon/pkg/utils/parser"
8+)
9+
10+type LangData map[string]interface{}
11+
12+type StringsModel struct {
13+ Header string `yaml:"header"`
14+ Subheader string `yaml:"subheader"`
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+// ReadStringsDataFromYAML reads the strings.yaml file and unmarshals it into the out interface
24+func ReadStringsDataFromYAML(lang string, fs fs.FS, filename string, out interface{}) error {
25+
26+ err := parser.OpenYamlFile(fs, filename, out)
27+ if err != nil {
28+ return err
29+ }
30+
31+ // If the type assertion succeeds, we set the appropriate language data
32+ if data, ok := out.(*StringsYamlData); ok {
33+ switch lang {
34+ case "en":
35+ data.CurrentLang = data.EN
36+ case "fr":
37+ data.CurrentLang = data.FR
38+ default: // default to English if no match
39+ data.CurrentLang = data.EN
40+ }
41+ } else {
42+ return fmt.Errorf("failed to assert type of out interface: %w", err)
43+ }
44+
45+ return nil
46+}
@@ -0,0 +1,46 @@
1+package webui
2+
3+import (
4+ "fmt"
5+ "io/fs"
6+
7+ "github.com/bnema/gordon/pkg/utils/parser"
8+)
9+
10+type LangData map[string]interface{}
11+
12+type StringsModel struct {
13+ Header string `yaml:"header"`
14+ Subheader string `yaml:"subheader"`
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+// ReadStringsDataFromYAML reads the strings.yaml file and unmarshals it into the out interface
24+func ReadStringsDataFromYAML(lang string, fs fs.FS, filename string, out interface{}) error {
25+
26+ err := parser.OpenYamlFile(fs, filename, out)
27+ if err != nil {
28+ return err
29+ }
30+
31+ // If the type assertion succeeds, we set the appropriate language data
32+ if data, ok := out.(*StringsYamlData); ok {
33+ switch lang {
34+ case "en":
35+ data.CurrentLang = data.EN
36+ case "fr":
37+ data.CurrentLang = data.FR
38+ default: // default to English if no match
39+ data.CurrentLang = data.EN
40+ }
41+ } else {
42+ return fmt.Errorf("failed to assert type of out interface: %w", err)
43+ }
44+
45+ return nil
46+}
R internal/gotemplate/models/html/strings.yml → internal/webui/strings.yml +0 -0 renamed, 100% similar

renamed, 100% similar

A pkg/utils/parser/yaml.go +32 -0
@@ -0,0 +1,32 @@
1+package parser
2+
3+import (
4+ "fmt"
5+ "io"
6+ "io/fs"
7+
8+ "gopkg.in/yaml.v3"
9+)
10+
11+// OpenYamlFile opens a YAML file and unmarshals it into the out interface
12+func OpenYamlFile(fs fs.FS, filename string, out interface{}) error {
13+ // Open the YAML file
14+ file, err := fs.Open(filename)
15+ if err != nil {
16+ return fmt.Errorf("failed to open %s: %w", filename, err)
17+ }
18+ defer file.Close()
19+
20+ // Read the content of the file
21+ content, err := io.ReadAll(file)
22+ if err != nil {
23+ return fmt.Errorf("failed to read %s: %w", filename, err)
24+ }
25+
26+ // Unmarshal the YAML content into the out interface
27+ if err := yaml.Unmarshal(content, out); err != nil {
28+ return fmt.Errorf("failed to unmarshal %s: %w", filename, err)
29+ }
30+
31+ return nil
32+}
@@ -0,0 +1,32 @@
1+package parser
2+
3+import (
4+ "fmt"
5+ "io"
6+ "io/fs"
7+
8+ "gopkg.in/yaml.v3"
9+)
10+
11+// OpenYamlFile opens a YAML file and unmarshals it into the out interface
12+func OpenYamlFile(fs fs.FS, filename string, out interface{}) error {
13+ // Open the YAML file
14+ file, err := fs.Open(filename)
15+ if err != nil {
16+ return fmt.Errorf("failed to open %s: %w", filename, err)
17+ }
18+ defer file.Close()
19+
20+ // Read the content of the file
21+ content, err := io.ReadAll(file)
22+ if err != nil {
23+ return fmt.Errorf("failed to read %s: %w", filename, err)
24+ }
25+
26+ // Unmarshal the YAML content into the out interface
27+ if err := yaml.Unmarshal(content, out); err != nil {
28+ return fmt.Errorf("failed to unmarshal %s: %w", filename, err)
29+ }
30+
31+ return nil
32+}

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