web changes + blog implementation
All checks were successful
build / build (push) Successful in 51s

This commit is contained in:
Tijl 2024-08-22 18:04:08 +02:00
parent 2dc3d97029
commit d77d2cc94c
Signed by: tijl
GPG Key ID: DAE24BFCD722F053
21 changed files with 203 additions and 26 deletions

1
.gitignore vendored
View File

@ -5,4 +5,5 @@ tijl.dev-core
web/static/js/interactive.js web/static/js/interactive.js
web/static/css/styles.css web/static/css/styles.css
config.yaml config.yaml
blog/

3
go.mod
View File

@ -10,6 +10,8 @@ require (
github.com/golang-migrate/migrate/v4 v4.17.1 github.com/golang-migrate/migrate/v4 v4.17.1
github.com/jackc/pgx/v5 v5.6.0 github.com/jackc/pgx/v5 v5.6.0
github.com/rs/zerolog v1.33.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 golang.org/x/oauth2 v0.21.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@ -41,4 +43,5 @@ require (
golang.org/x/sync v0.7.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.22.0 // indirect golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.16.0 // indirect golang.org/x/text v0.16.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
) )

6
go.sum
View File

@ -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/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 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 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 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= 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 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -31,6 +31,7 @@ func Load() {
type ConfigType struct { type ConfigType struct {
Host string `yaml:"host"` Host string `yaml:"host"`
Port int `yaml:"port"` Port int `yaml:"port"`
BlogLocation string `yaml:"blog_location"`
UrlBase string `yaml:"url_base"` UrlBase string `yaml:"url_base"`
JsonLogging bool `yaml:"log_json"` JsonLogging bool `yaml:"log_json"`
DB string `yaml:"database"` DB string `yaml:"database"`

View File

@ -1,6 +1,8 @@
package handlers package handlers
import ( import (
"net/http"
"git.tijl.dev/tijl/tijl.dev-core/internal/i18n" "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/oidc"
"git.tijl.dev/tijl/tijl.dev-core/internal/user" "git.tijl.dev/tijl/tijl.dev-core/internal/user"
@ -43,8 +45,8 @@ func logoutHandler(c *fiber.Ctx) error {
_, err := user.GetSession(c) _, err := user.GetSession(c)
if err == nil { if err == nil {
c.ClearCookie("session") c.ClearCookie("session")
return c.Redirect("/login") return c.Redirect("/login", http.StatusOK)
} else { } else {
return c.Redirect("/login") return c.Redirect("/login", http.StatusUnauthorized)
} }
} }

142
internal/handlers/blog.go Normal file
View File

@ -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
}

View File

@ -14,6 +14,7 @@ import (
) )
func Setup() { func Setup() {
LoadPosts()
web.RegisterAppSetupFunc(routes) web.RegisterAppSetupFunc(routes)
} }
@ -24,11 +25,7 @@ func routes(app *fiber.App) {
data["Title"] = i18n.Translate(c, "home") data["Title"] = i18n.Translate(c, "home")
return c.Render("index", data, "layouts/base") return c.Render("index", data, "layouts/base")
}) })
app.Get("/blog", func(c *fiber.Ctx) error { app.Get("/blog", blogIndexHandler)
data := *web.Common(c)
data["Title"] = i18n.Translate(c, "blog")
return c.Render("blog", data, "layouts/base")
})
app.Get("/projects", func(c *fiber.Ctx) error { app.Get("/projects", func(c *fiber.Ctx) error {
data := *web.Common(c) data := *web.Common(c)
data["Title"] = i18n.Translate(c, "projects") data["Title"] = i18n.Translate(c, "projects")

View File

@ -15,6 +15,9 @@ import (
) )
func HandleRedirect(c *fiber.Ctx) error { func HandleRedirect(c *fiber.Ctx) error {
if c.Query("redirect") != "" {
setCallbackCookie(c, "internal_redirect", c.Query("redirect"))
}
state := utils.RandString(16) state := utils.RandString(16)
setCallbackCookie(c, "state", state) setCallbackCookie(c, "state", state)
return c.Redirect(Config.AuthCodeURL(state), http.StatusFound) return c.Redirect(Config.AuthCodeURL(state), http.StatusFound)
@ -78,5 +81,12 @@ func HandleCallback(c *fiber.Ctx) error {
return err 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") return c.Redirect("/loggedin")
} }

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"net/http" "net/http"
"time"
"git.tijl.dev/tijl/tijl.dev-core/internal/assets" "git.tijl.dev/tijl/tijl.dev-core/internal/assets"
"git.tijl.dev/tijl/tijl.dev-core/internal/config" "git.tijl.dev/tijl/tijl.dev-core/internal/config"
@ -24,6 +25,8 @@ func Listen() {
// Load initial context // Load initial context
ctx := context.Background() ctx := context.Background()
start := time.Now()
// setup logger // setup logger
log.Load() log.Load()
// Load config // Load config
@ -58,8 +61,10 @@ func Listen() {
// Setup routes // Setup routes
web.Setup(app) web.Setup(app)
log.Info().Int64("ms", time.Since(start).Milliseconds()).Msg("started internal services")
// Listen web server // 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 { if err := app.Listen(fmt.Sprintf("%v:%v", config.Config.Host, config.Config.Port)); err != nil {
log.Fatal().Err(err).Msg("Fiber app error") log.Fatal().Err(err).Msg("Fiber app error")
} }

View File

@ -9,5 +9,7 @@
"projects": "Projects", "projects": "Projects",
"account": "Account", "account": "Account",
"login": "Login", "login": "Login",
"settings": "Settings" "settings": "Settings",
"page_not_found": "Page not found",
"logged_in": "You are now logged in!"
} }

View File

@ -9,5 +9,7 @@
"projects": "Projecten", "projects": "Projecten",
"account": "Account", "account": "Account",
"login": "Login", "login": "Login",
"settings": "Instellingen" "settings": "Instellingen",
"page_not_found": "Pagina niet gevonden",
"logged_in": "Je bent nu ingelogd!"
} }

View File

@ -1,6 +1,6 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
module.exports = { module.exports = {
content: ["./views/**/*.html", "./web/**/*.{js,ts,css}"], content: ["./web/views/**/*.html", "./web/**/*.{js,ts,css}"],
theme: { theme: {
extend: {}, extend: {},
}, },

View File

@ -1 +1,6 @@
<h2>404 not found</h2> <div class="text-center">
<div class="text-[5rem] font-semibold">
<h1>404</h1>
</div>
<p>{{.T.page_not_found}}</p>
</div>

View File

@ -1 +0,0 @@
<h2>TODO About</h2>

View File

@ -1,4 +1,5 @@
<div> <div>
<div>Account</div>
<div>id: {{.User.Uid}}</div> <div>id: {{.User.Uid}}</div>
<div>username: {{.User.Username}}</div> <div>username: {{.User.Username}}</div>
<div>email: {{.User.Email}}</div> <div>email: {{.User.Email}}</div>

View File

@ -1 +1,5 @@
<h2>TODO Blog</h2> <div>Posts</div>
{{range .Posts}}
<div><a href="/blog/{{.Meta.Slug}}">{{.Meta.Title}}</a></div>
{{end}}

View File

@ -1,2 +0,0 @@
<div>index</div>
<div>Signed In: {{.SignedIn}}</div>

View File

@ -1 +1,2 @@
<div>you are now logged in</div> <div>{{.T.logged_in}}</div>
<a hx-boost="true" class="btn" href="/account">{{.T.account}}</a>

View File

@ -92,7 +92,7 @@
<span class="text-base">{{.T.account}}</span> <span class="text-base">{{.T.account}}</span>
</a> </a>
{{else}} {{else}}
<a class="active flex gap-4" href="/login"> <a hx-boost="false" class="active flex gap-4" href="/auth?redirect={{.Path}}">
<span class="w-5 text-center"> <span class="w-5 text-center">
{{icon "login"}} {{icon "login"}}
</span> </span>

View File

@ -1 +0,0 @@
<h2>TODO Projects</h2>

View File

@ -1 +0,0 @@
<div>hello</div>