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`,
- 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
`)
- articles := []*Post{}
-
- for _, post := range app.posts {
- if !post.Published || !slices.Contains(post.Tags, "article") {
- continue
- }
-
- articles = append(articles, post)
- }
-
- sort.Slice(articles, func(i, j int) bool {
- return articles[i].Created > articles[j].Created
- })
-
- for _, post := range articles {
- fmt.Fprintf(&html, `- %s
`, post.Slug, post.Title, post.Created, post.Created[:len("YYYY")])
- }
-
- html.WriteString(`
`)
- 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`,
+ 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
`)
+ articles := []*app.Post{}
+
+ for _, post := range app.Posts {
+ if !post.Published || !slices.Contains(post.Tags, "article") {
+ continue
+ }
+
+ articles = append(articles, post)
+ }
+
+ sort.Slice(articles, func(i, j int) bool {
+ return articles[i].Created > articles[j].Created
+ })
+
+ for _, post := range articles {
+ fmt.Fprintf(&html, `- %s
`, post.Slug, post.Title, post.Created, post.Created[:len("YYYY")])
+ }
+
+ html.WriteString(`
`)
+ 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)
+}