From 9d0a49284d90152db27c8436f15e69c4490761c1 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 21 Jan 2025 14:00:40 +0100 Subject: [PATCH] Improved file structure --- App.go | 164 ------------------- go.mod | 2 +- main.go | 23 ++- posts/contact.md | 34 ++-- public/app.html | 1 - server/app/Post.go | 10 ++ server/app/Render.go | 15 ++ server/app/RenderPost.go | 34 ++++ server/app/init.go | 22 +++ server/app/load.go | 15 ++ server/app/loadClean.go | 10 ++ Post.go => server/app/loadPosts.go | 25 +-- Project.go => server/app/loadProjects.go | 2 +- server/app/parseFrontmatter.go | 17 ++ server/middleware/Recover.go | 19 +++ server/middleware/RedirectTrailingSlashes.go | 17 ++ server/pages/Blog.go | 36 ++++ server/pages/Frontpage.go | 25 +++ server/pages/Post.go | 11 ++ 19 files changed, 267 insertions(+), 215 deletions(-) delete mode 100644 App.go create mode 100644 server/app/Post.go create mode 100644 server/app/Render.go create mode 100644 server/app/RenderPost.go create mode 100644 server/app/init.go create mode 100644 server/app/load.go create mode 100644 server/app/loadClean.go rename Post.go => server/app/loadPosts.go (68%) rename Project.go => server/app/loadProjects.go (97%) create mode 100644 server/app/parseFrontmatter.go create mode 100644 server/middleware/Recover.go create mode 100644 server/middleware/RedirectTrailingSlashes.go create mode 100644 server/pages/Blog.go create mode 100644 server/pages/Frontpage.go create mode 100644 server/pages/Post.go diff --git a/App.go b/App.go deleted file mode 100644 index d9cdb14..0000000 --- a/App.go +++ /dev/null @@ -1,164 +0,0 @@ -package main - -import ( - "bytes" - "fmt" - "os" - "slices" - "sort" - "strings" - - "git.akyoto.dev/go/markdown" - "git.akyoto.dev/go/web" - "git.akyoto.dev/go/web/send" -) - -type App struct { - html string - posts map[string]*Post - projects []string -} - -func New() *App { - html := loadClean("public/app.html") - css := loadClean("public/app.css") - html = strings.Replace(html, "{head}", fmt.Sprintf("{head}", css), 1) - html = strings.ReplaceAll(html, "{avatar}", "https://gravatar.com/avatar/35f2c481f711f0a36bc0e930c4c15eb0bcc794aaeef405a060fe3e28d1c7b7e5.png?s=64") - posts := loadPosts("posts") - projects := loadProjects("projects") - - return &App{ - html: html, - posts: posts, - projects: projects, - } -} - -func (app *App) Run() { - s := web.NewServer() - - s.Use(func(ctx web.Context) error { - defer func() { - err := recover() - - if err != nil { - fmt.Println("Recovered panic:", err) - } - }() - - return ctx.Next() - }) - - s.Use(func(ctx web.Context) error { - path := ctx.Request().Path() - - if len(path) > 1 && strings.HasSuffix(path, "/") { - return ctx.Redirect(301, strings.TrimSuffix(path, "/")) - } - - return ctx.Next() - }) - - render := func(ctx web.Context, head string, body string) error { - html := app.html - html = strings.Replace(html, "{head}", head, 1) - html = strings.Replace(html, "{body}", body, 1) - return send.HTML(ctx, html) - } - - renderPost := func(ctx web.Context, id string) error { - post := app.posts[id] - head := fmt.Sprintf(`%s`, post.Title, strings.Join(post.Tags, ",")) - content := "" - - if slices.Contains(post.Tags, "article") { - content = fmt.Sprintf( - `

%s

%s
`, - post.Title, - post.Created, - post.Created[:len("YYYY-MM-DD")], - markdown.Render(post.Content), - ) - } else { - content = fmt.Sprintf( - `

%s

%s`, - post.Title, - markdown.Render(post.Content), - ) - } - - return render(ctx, head, content) - } - - s.Get("/", func(ctx web.Context) error { - head := fmt.Sprintf(`%s`, "Projects", "projects") - - body := strings.Builder{} - body.WriteString(`

Projects

`) - - for i, markdown := range app.projects { - body.WriteString(markdown) - - if i != len(app.projects)-1 { - body.WriteString(`
`) - } - } - - return render(ctx, head, body.String()) - }) - - s.Get("/blog", func(ctx web.Context) error { - html := bytes.Buffer{} - html.WriteString(`

Blog

`) - return render(ctx, "akyoto.dev", html.String()) - }) - - s.Get("/:post", func(ctx web.Context) error { - id := ctx.Request().Param("post") - return renderPost(ctx, id) - }) - - address := os.Getenv("LISTEN") - - if address == "" { - address = ":8080" - } - - s.Run(address) -} - -func load(path string) string { - dataBytes, err := os.ReadFile(path) - - if err != nil { - panic(err) - } - - return string(dataBytes) -} - -func loadClean(path string) string { - data := load(path) - data = strings.ReplaceAll(data, "\t", "") - data = strings.ReplaceAll(data, "\n", "") - return data -} diff --git a/go.mod b/go.mod index ec41a26..fad4601 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module git.akyoto.dev/web/akyoto.dev -go 1.22.1 +go 1.23 require ( git.akyoto.dev/go/markdown v0.0.0-20240403191359-809b89d689ab diff --git a/main.go b/main.go index 8427dc0..f1f0b29 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,25 @@ package main +import ( + "os" + + "git.akyoto.dev/go/web" + "git.akyoto.dev/web/akyoto.dev/server/middleware" + "git.akyoto.dev/web/akyoto.dev/server/pages" +) + func main() { - app := New() - app.Run() + address := os.Getenv("LISTEN") + + if address == "" { + address = ":8080" + } + + server := web.NewServer() + server.Use(middleware.Recover) + server.Use(middleware.RedirectTrailingSlashes) + server.Get("/", pages.Frontpage) + server.Get("/blog", pages.Blog) + server.Get("/:post", pages.Post) + server.Run(address) } diff --git a/posts/contact.md b/posts/contact.md index 498646d..da03191 100644 --- a/posts/contact.md +++ b/posts/contact.md @@ -5,25 +5,15 @@ created: 2023-07-23T12:43:21Z published: true --- -| | | -| ------------- | ---------------------------------------------------------- | -| Name | Eduard Urbach | -| Occupation | Software Engineer | -| Business type | Freelancer | -| Tax number | DE 362234011 | -| Mobile | +49 1520 4296914 | -| Address | [n/a](https://dserver.bundestag.de/btd/19/077/1907714.pdf) | - -## E-Mail - -| | | -| ------------------ | ------------------------ | -| Personal | admin [at] akyoto.dev | -| Business inquiries | business [at] akyoto.dev | - -## Chat - -| | | -| --------- | ------------------------------------------------------------------ | -| 1:1 | [@akyoto:akyoto.dev](https://matrix.to/#/@akyoto:akyoto.dev) | -| Community | [#community:akyoto.dev](https://matrix.to/#/#community:akyoto.dev) | +| | | +| ------------------ | ------------------------------------------------------------------ | +| Name | Eduard Urbach | +| Occupation | Software Engineer | +| Business type | Freelancer | +| Tax number | DE 362234011 | +| Mobile | +49 1520 4296914 | +| Address | [n/a](https://dserver.bundestag.de/btd/19/077/1907714.pdf) | +| Personal | admin [at] akyoto.dev | +| Business inquiries | business [at] akyoto.dev | +| 1:1 | [@akyoto:akyoto.dev](https://matrix.to/#/@akyoto:akyoto.dev) | +| Community | [#community:akyoto.dev](https://matrix.to/#/#community:akyoto.dev) | diff --git a/public/app.html b/public/app.html index 085e830..07c4af6 100644 --- a/public/app.html +++ b/public/app.html @@ -10,7 +10,6 @@
diff --git a/server/app/Post.go b/server/app/Post.go new file mode 100644 index 0000000..252dce6 --- /dev/null +++ b/server/app/Post.go @@ -0,0 +1,10 @@ +package app + +type Post struct { + Slug string + Content string + Title string + Tags []string + Created string + Published bool +} diff --git a/server/app/Render.go b/server/app/Render.go new file mode 100644 index 0000000..532d9b0 --- /dev/null +++ b/server/app/Render.go @@ -0,0 +1,15 @@ +package app + +import ( + "strings" + + "git.akyoto.dev/go/web" + "git.akyoto.dev/go/web/send" +) + +func Render(ctx web.Context, head string, body string) error { + html := HTML + html = strings.Replace(html, "{head}", head, 1) + html = strings.Replace(html, "{body}", body, 1) + return send.HTML(ctx, html) +} diff --git a/server/app/RenderPost.go b/server/app/RenderPost.go new file mode 100644 index 0000000..9008eca --- /dev/null +++ b/server/app/RenderPost.go @@ -0,0 +1,34 @@ +package app + +import ( + "fmt" + "slices" + "strings" + + "git.akyoto.dev/go/markdown" + "git.akyoto.dev/go/web" +) + +func RenderPost(ctx web.Context, id string) error { + post := Posts[id] + head := fmt.Sprintf(`%s`, post.Title, strings.Join(post.Tags, ",")) + content := "" + + if slices.Contains(post.Tags, "article") { + content = fmt.Sprintf( + `

%s

%s
`, + post.Title, + post.Created, + post.Created[:len("YYYY-MM-DD")], + markdown.Render(post.Content), + ) + } else { + content = fmt.Sprintf( + `

%s

%s`, + post.Title, + markdown.Render(post.Content), + ) + } + + return Render(ctx, head, content) +} diff --git a/server/app/init.go b/server/app/init.go new file mode 100644 index 0000000..e7b7ee8 --- /dev/null +++ b/server/app/init.go @@ -0,0 +1,22 @@ +package app + +import ( + "fmt" + "strings" +) + +var ( + HTML string + CSS string + Posts map[string]*Post + Projects []string +) + +func init() { + HTML = loadClean("public/app.html") + CSS = loadClean("public/app.css") + HTML = strings.Replace(HTML, "{head}", fmt.Sprintf("{head}", CSS), 1) + HTML = strings.ReplaceAll(HTML, "{avatar}", "https://gravatar.com/avatar/35f2c481f711f0a36bc0e930c4c15eb0bcc794aaeef405a060fe3e28d1c7b7e5.png?s=64") + Posts = loadPosts("posts") + Projects = loadProjects("projects") +} diff --git a/server/app/load.go b/server/app/load.go new file mode 100644 index 0000000..980024e --- /dev/null +++ b/server/app/load.go @@ -0,0 +1,15 @@ +package app + +import ( + "os" +) + +func load(path string) string { + dataBytes, err := os.ReadFile(path) + + if err != nil { + panic(err) + } + + return string(dataBytes) +} diff --git a/server/app/loadClean.go b/server/app/loadClean.go new file mode 100644 index 0000000..88bab8a --- /dev/null +++ b/server/app/loadClean.go @@ -0,0 +1,10 @@ +package app + +import "strings" + +func loadClean(path string) string { + data := load(path) + data = strings.ReplaceAll(data, "\t", "") + data = strings.ReplaceAll(data, "\n", "") + return data +} diff --git a/Post.go b/server/app/loadPosts.go similarity index 68% rename from Post.go rename to server/app/loadPosts.go index 97e3717..494b83a 100644 --- a/Post.go +++ b/server/app/loadPosts.go @@ -1,19 +1,10 @@ -package main +package app import ( "os" "strings" ) -type Post struct { - Slug string - Content string - Title string - Tags []string - Created string - Published bool -} - func loadPosts(directory string) map[string]*Post { entries, err := os.ReadDir(directory) @@ -62,17 +53,3 @@ func loadPosts(directory string) map[string]*Post { return posts } - -func parseFrontmatter(frontmatter string, assign func(key string, value string)) { - lines := strings.Split(frontmatter, "\n") - - for _, line := range lines { - colon := strings.Index(line, ":") - - if colon == -1 { - continue - } - - assign(line[:colon], strings.TrimSpace(line[colon+1:])) - } -} diff --git a/Project.go b/server/app/loadProjects.go similarity index 97% rename from Project.go rename to server/app/loadProjects.go index 1fb31c4..e431047 100644 --- a/Project.go +++ b/server/app/loadProjects.go @@ -1,4 +1,4 @@ -package main +package app import ( "os" diff --git a/server/app/parseFrontmatter.go b/server/app/parseFrontmatter.go new file mode 100644 index 0000000..c48a7fb --- /dev/null +++ b/server/app/parseFrontmatter.go @@ -0,0 +1,17 @@ +package app + +import "strings" + +func parseFrontmatter(frontmatter string, assign func(key string, value string)) { + lines := strings.Split(frontmatter, "\n") + + for _, line := range lines { + colon := strings.Index(line, ":") + + if colon == -1 { + continue + } + + assign(line[:colon], strings.TrimSpace(line[colon+1:])) + } +} diff --git a/server/middleware/Recover.go b/server/middleware/Recover.go new file mode 100644 index 0000000..7020b3e --- /dev/null +++ b/server/middleware/Recover.go @@ -0,0 +1,19 @@ +package middleware + +import ( + "fmt" + + "git.akyoto.dev/go/web" +) + +func Recover(ctx web.Context) error { + defer func() { + err := recover() + + if err != nil { + fmt.Println("Recovered panic:", err) + } + }() + + return ctx.Next() +} diff --git a/server/middleware/RedirectTrailingSlashes.go b/server/middleware/RedirectTrailingSlashes.go new file mode 100644 index 0000000..b40b15a --- /dev/null +++ b/server/middleware/RedirectTrailingSlashes.go @@ -0,0 +1,17 @@ +package middleware + +import ( + "strings" + + "git.akyoto.dev/go/web" +) + +func RedirectTrailingSlashes(ctx web.Context) error { + path := ctx.Request().Path() + + if len(path) > 1 && strings.HasSuffix(path, "/") { + return ctx.Redirect(301, strings.TrimSuffix(path, "/")) + } + + return ctx.Next() +} diff --git a/server/pages/Blog.go b/server/pages/Blog.go new file mode 100644 index 0000000..3fe3cfa --- /dev/null +++ b/server/pages/Blog.go @@ -0,0 +1,36 @@ +package pages + +import ( + "bytes" + "fmt" + "slices" + "sort" + + "git.akyoto.dev/go/web" + "git.akyoto.dev/web/akyoto.dev/server/app" +) + +func Blog(ctx web.Context) error { + html := bytes.Buffer{} + html.WriteString(`

Blog

`) + return app.Render(ctx, "akyoto.dev", html.String()) +} diff --git a/server/pages/Frontpage.go b/server/pages/Frontpage.go new file mode 100644 index 0000000..e64ef72 --- /dev/null +++ b/server/pages/Frontpage.go @@ -0,0 +1,25 @@ +package pages + +import ( + "fmt" + "strings" + + "git.akyoto.dev/go/web" + "git.akyoto.dev/web/akyoto.dev/server/app" +) + +func Frontpage(ctx web.Context) error { + head := fmt.Sprintf(`%s`, "Projects", "projects") + body := strings.Builder{} + body.WriteString(`

Projects

`) + + for i, markdown := range app.Projects { + body.WriteString(markdown) + + if i != len(app.Projects)-1 { + body.WriteString(`
`) + } + } + + return app.Render(ctx, head, body.String()) +} diff --git a/server/pages/Post.go b/server/pages/Post.go new file mode 100644 index 0000000..d2d5a59 --- /dev/null +++ b/server/pages/Post.go @@ -0,0 +1,11 @@ +package pages + +import ( + "git.akyoto.dev/go/web" + "git.akyoto.dev/web/akyoto.dev/server/app" +) + +func Post(ctx web.Context) error { + id := ctx.Request().Param("post") + return app.RenderPost(ctx, id) +}