diff --git a/.gitignore b/.gitignore index fda3612..86a4e17 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ tijl.dev-core web/static/js/interactive.js web/static/css/styles.css config.yaml +blog/ diff --git a/go.mod b/go.mod index 2ab7ecf..2bd8a06 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,8 @@ require ( github.com/golang-migrate/migrate/v4 v4.17.1 github.com/jackc/pgx/v5 v5.6.0 github.com/rs/zerolog v1.33.0 + github.com/yuin/goldmark v1.7.4 + github.com/yuin/goldmark-meta v1.1.0 golang.org/x/oauth2 v0.21.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -41,4 +43,5 @@ require ( golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect ) diff --git a/go.sum b/go.sum index f67ed62..599d273 100644 --- a/go.sum +++ b/go.sum @@ -104,6 +104,10 @@ github.com/valyala/fasthttp v1.55.0 h1:Zkefzgt6a7+bVKHnu/YaYSOPfNYNisSVBo/unVCf8 github.com/valyala/fasthttp v1.55.0/go.mod h1:NkY9JtkrpPKmgwV3HTaS2HWaJss9RSIsRVfcxxoHiOM= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/yuin/goldmark v1.7.4 h1:BDXOHExt+A7gwPCJgPIIq7ENvceR7we7rOS9TNoLZeg= +github.com/yuin/goldmark v1.7.4/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= +github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= @@ -128,6 +132,8 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go index 8a5e129..737661b 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -29,12 +29,13 @@ func Load() { } type ConfigType struct { - Host string `yaml:"host"` - Port int `yaml:"port"` - UrlBase string `yaml:"url_base"` - JsonLogging bool `yaml:"log_json"` - DB string `yaml:"database"` - Oidc `yaml:"oidc"` + Host string `yaml:"host"` + Port int `yaml:"port"` + BlogLocation string `yaml:"blog_location"` + UrlBase string `yaml:"url_base"` + JsonLogging bool `yaml:"log_json"` + DB string `yaml:"database"` + Oidc `yaml:"oidc"` } type Oidc struct { diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index fb71c26..3140706 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -1,6 +1,8 @@ package handlers import ( + "net/http" + "git.tijl.dev/tijl/tijl.dev-core/internal/i18n" "git.tijl.dev/tijl/tijl.dev-core/internal/oidc" "git.tijl.dev/tijl/tijl.dev-core/internal/user" @@ -43,8 +45,8 @@ func logoutHandler(c *fiber.Ctx) error { _, err := user.GetSession(c) if err == nil { c.ClearCookie("session") - return c.Redirect("/login") + return c.Redirect("/login", http.StatusOK) } else { - return c.Redirect("/login") + return c.Redirect("/login", http.StatusUnauthorized) } } diff --git a/internal/handlers/blog.go b/internal/handlers/blog.go new file mode 100644 index 0000000..fe6979a --- /dev/null +++ b/internal/handlers/blog.go @@ -0,0 +1,142 @@ +package handlers + +// blog implementation concept + +import ( + "bytes" + "fmt" + "os" + "path/filepath" + "time" + + "git.tijl.dev/tijl/tijl.dev-core/internal/i18n" + log "git.tijl.dev/tijl/tijl.dev-core/modules/logger" + "git.tijl.dev/tijl/tijl.dev-core/modules/web" + "github.com/gofiber/fiber/v2" + "github.com/yuin/goldmark" + "github.com/yuin/goldmark-meta" + "github.com/yuin/goldmark/extension" + "github.com/yuin/goldmark/parser" +) + +type Post struct { + Meta PostMeta + Content string +} + +type PostMeta struct { + Title string + Slug string + Description string + Author string + Language string + PublishDate time.Time + UpdatedDate time.Time + Tags []string +} + +var posts []Post + +func LoadPosts() { + posts = []Post{} + + md := goldmark.New( + goldmark.WithExtensions(extension.Table, extension.Strikethrough, extension.Linkify, extension.TaskList, extension.GFM, extension.DefinitionList, extension.Footnote, extension.Typographer, meta.Meta), + goldmark.WithParserOptions( + parser.WithAutoHeadingID(), + ), + ) + + files, err := filepath.Glob("blog/*.md") + if err != nil { + log.Error().Err(err).Msg("handlers.LoadPosts(): error reading folder") + } + + for _, file := range files { + content, err := os.ReadFile(file) + if err != nil { + log.Error().Err(err).Msg("handlers.LoadPosts(): error reading file") + continue + } + + var buf bytes.Buffer + context := parser.NewContext() + if err := md.Convert(content, &buf, parser.WithContext(context)); err != nil { + log.Fatal().Err(err) + } + metaData := meta.Get(context) + postMeta, err := MapToPostMeta(metaData) + posts = append(posts, Post{ + Content: buf.String(), + Meta: postMeta, + }) + + } + + log.Debug().Msg("loaded blog posts") +} + +func blogIndexHandler(c *fiber.Ctx) error { + data := *web.Common(c) + data["Title"] = i18n.Translate(c, "blog") + data["Posts"] = posts + return c.Render("blog", data, "layouts/base") +} + +func MapToPostMeta(m map[string]interface{}) (PostMeta, error) { + var post PostMeta + var ok bool + var err error + + if post.Title, ok = m["title"].(string); !ok { + return post, fmt.Errorf("missing or invalid type for Title") + } + + if post.Slug, ok = m["slug"].(string); !ok { + return post, fmt.Errorf("missing or invalid type for Title") + } + + if post.Description, ok = m["description"].(string); !ok { + return post, fmt.Errorf("missing or invalid type for Description") + } + + if post.Author, ok = m["author"].(string); !ok { + return post, fmt.Errorf("missing or invalid type for Author") + } + + if post.Language, ok = m["language"].(string); !ok { + return post, fmt.Errorf("missing or invalid type for Language") + } + + if publishDate, ok := m["publish_date"].(string); ok { + post.PublishDate, err = time.Parse("02 Jan 2006", publishDate) + if err != nil { + return post, fmt.Errorf("invalid format for PublishDate: %v", err) + } + } else { + return post, fmt.Errorf("missing or invalid type for PublishDate") + } + + if updatedDate, ok := m["updated_date"].(string); ok { + post.UpdatedDate, err = time.Parse("02 Jan 2006", updatedDate) + if err != nil { + return post, fmt.Errorf("invalid format for PublishDate: %v", err) + } + } else { + return post, fmt.Errorf("missing or invalid type for PublishDate") + } + + if tags, ok := m["tags"].([]interface{}); ok { + for _, tag := range tags { + if strTag, ok := tag.(string); ok { + post.Tags = append(post.Tags, strTag) + } else { + return post, fmt.Errorf("invalid type in Tags slice") + } + } + } else { + return post, fmt.Errorf("missing or invalid type for Tags") + } + + return post, nil +} diff --git a/internal/handlers/routes.go b/internal/handlers/routes.go index 77679fb..7270c19 100644 --- a/internal/handlers/routes.go +++ b/internal/handlers/routes.go @@ -14,6 +14,7 @@ import ( ) func Setup() { + LoadPosts() web.RegisterAppSetupFunc(routes) } @@ -24,11 +25,7 @@ func routes(app *fiber.App) { data["Title"] = i18n.Translate(c, "home") return c.Render("index", data, "layouts/base") }) - app.Get("/blog", func(c *fiber.Ctx) error { - data := *web.Common(c) - data["Title"] = i18n.Translate(c, "blog") - return c.Render("blog", data, "layouts/base") - }) + app.Get("/blog", blogIndexHandler) app.Get("/projects", func(c *fiber.Ctx) error { data := *web.Common(c) data["Title"] = i18n.Translate(c, "projects") diff --git a/internal/oidc/handler.go b/internal/oidc/handler.go index 8f9cdee..5eba5b6 100644 --- a/internal/oidc/handler.go +++ b/internal/oidc/handler.go @@ -15,6 +15,9 @@ import ( ) func HandleRedirect(c *fiber.Ctx) error { + if c.Query("redirect") != "" { + setCallbackCookie(c, "internal_redirect", c.Query("redirect")) + } state := utils.RandString(16) setCallbackCookie(c, "state", state) return c.Redirect(Config.AuthCodeURL(state), http.StatusFound) @@ -78,5 +81,12 @@ func HandleCallback(c *fiber.Ctx) error { return err } + redirect := c.Cookies("internal_redirect") + log.Debug().Msg(redirect) + if redirect != "" { + c.ClearCookie("internal_redirect") + return c.Redirect(redirect) + } + return c.Redirect("/loggedin") } diff --git a/internal/service/main.go b/internal/service/main.go index 7c0bde9..3d299f5 100644 --- a/internal/service/main.go +++ b/internal/service/main.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "net/http" + "time" "git.tijl.dev/tijl/tijl.dev-core/internal/assets" "git.tijl.dev/tijl/tijl.dev-core/internal/config" @@ -24,6 +25,8 @@ func Listen() { // Load initial context ctx := context.Background() + start := time.Now() + // setup logger log.Load() // Load config @@ -58,8 +61,10 @@ func Listen() { // Setup routes web.Setup(app) + log.Info().Int64("ms", time.Since(start).Milliseconds()).Msg("started internal services") + // Listen web server - log.Info().Int("port", config.Config.Port).Str("host", config.Config.Host).Msg("listening...") + log.Info().Int("port", config.Config.Port).Str("host", config.Config.Host).Msg("listening") if err := app.Listen(fmt.Sprintf("%v:%v", config.Config.Host, config.Config.Port)); err != nil { log.Fatal().Err(err).Msg("Fiber app error") } diff --git a/locales/en.json b/locales/en.json index 21d53be..63c5de5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -9,5 +9,7 @@ "projects": "Projects", "account": "Account", "login": "Login", - "settings": "Settings" + "settings": "Settings", + "page_not_found": "Page not found", + "logged_in": "You are now logged in!" } diff --git a/locales/nl.json b/locales/nl.json index d0d3d02..c70668f 100644 --- a/locales/nl.json +++ b/locales/nl.json @@ -9,5 +9,7 @@ "projects": "Projecten", "account": "Account", "login": "Login", - "settings": "Instellingen" + "settings": "Instellingen", + "page_not_found": "Pagina niet gevonden", + "logged_in": "Je bent nu ingelogd!" } diff --git a/tailwind.config.ts b/tailwind.config.ts index 3504c79..0ffabe5 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,6 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ["./views/**/*.html", "./web/**/*.{js,ts,css}"], + content: ["./web/views/**/*.html", "./web/**/*.{js,ts,css}"], theme: { extend: {}, }, diff --git a/web/views/404.html b/web/views/404.html index ea65cc4..f65d1e5 100644 --- a/web/views/404.html +++ b/web/views/404.html @@ -1 +1,6 @@ -

404 not found

+
+
+

404

+
+

{{.T.page_not_found}}

+
diff --git a/web/views/about.html b/web/views/about.html index 2d7fe59..e69de29 100644 --- a/web/views/about.html +++ b/web/views/about.html @@ -1 +0,0 @@ -

TODO About

diff --git a/web/views/account.html b/web/views/account.html index d118a3b..c154463 100644 --- a/web/views/account.html +++ b/web/views/account.html @@ -1,4 +1,5 @@
+
Account
id: {{.User.Uid}}
username: {{.User.Username}}
email: {{.User.Email}}
diff --git a/web/views/blog.html b/web/views/blog.html index 95f52c0..9abd07f 100644 --- a/web/views/blog.html +++ b/web/views/blog.html @@ -1 +1,5 @@ -

TODO Blog

+
Posts
+ +{{range .Posts}} +
{{.Meta.Title}}
+{{end}} diff --git a/web/views/index.html b/web/views/index.html index 54609fe..e69de29 100644 --- a/web/views/index.html +++ b/web/views/index.html @@ -1,2 +0,0 @@ -
index
-
Signed In: {{.SignedIn}}
diff --git a/web/views/loggedin.html b/web/views/loggedin.html index 8883ecb..16c9cc2 100644 --- a/web/views/loggedin.html +++ b/web/views/loggedin.html @@ -1 +1,2 @@ -
you are now logged in
+
{{.T.logged_in}}
+{{.T.account}} diff --git a/web/views/partials/menu.html b/web/views/partials/menu.html index 846058a..a77fa71 100644 --- a/web/views/partials/menu.html +++ b/web/views/partials/menu.html @@ -92,7 +92,7 @@ {{.T.account}} {{else}} - + {{icon "login"}} diff --git a/web/views/projects.html b/web/views/projects.html index e30878d..e69de29 100644 --- a/web/views/projects.html +++ b/web/views/projects.html @@ -1 +0,0 @@ -

TODO Projects

diff --git a/web/views/settings.html b/web/views/settings.html index 1005b0a..e69de29 100644 --- a/web/views/settings.html +++ b/web/views/settings.html @@ -1 +0,0 @@ -
hello