Improved file structure

This commit is contained in:
Eduard Urbach 2025-01-21 14:00:40 +01:00
parent 6ad3f11ce9
commit 9d0a49284d
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
19 changed files with 267 additions and 215 deletions

164
App.go
View File

@ -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}<style>%s</style>", 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(`<title>%s</title><meta name="keywords" content="%s">`, post.Title, strings.Join(post.Tags, ","))
content := ""
if slices.Contains(post.Tags, "article") {
content = fmt.Sprintf(
`<article><header><h1>%s</h1><time datetime="%s">%s</time></header>%s</article>`,
post.Title,
post.Created,
post.Created[:len("YYYY-MM-DD")],
markdown.Render(post.Content),
)
} else {
content = fmt.Sprintf(
`<h2>%s</h2>%s`,
post.Title,
markdown.Render(post.Content),
)
}
return render(ctx, head, content)
}
s.Get("/", func(ctx web.Context) error {
head := fmt.Sprintf(`<title>%s</title><meta name="keywords" content="%s">`, "Projects", "projects")
body := strings.Builder{}
body.WriteString(`<h2>Projects</h2>`)
for i, markdown := range app.projects {
body.WriteString(markdown)
if i != len(app.projects)-1 {
body.WriteString(`<hr>`)
}
}
return render(ctx, head, body.String())
})
s.Get("/blog", func(ctx web.Context) error {
html := bytes.Buffer{}
html.WriteString(`<h2>Blog</h2><ul class="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, `<li><a href="/%s">%s</a><time datetime="%s">%s</time></li>`, post.Slug, post.Title, post.Created, post.Created[:len("YYYY")])
}
html.WriteString(`</ul>`)
return render(ctx, "<title>akyoto.dev</title>", 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
}

2
go.mod
View File

@ -1,6 +1,6 @@
module git.akyoto.dev/web/akyoto.dev module git.akyoto.dev/web/akyoto.dev
go 1.22.1 go 1.23
require ( require (
git.akyoto.dev/go/markdown v0.0.0-20240403191359-809b89d689ab git.akyoto.dev/go/markdown v0.0.0-20240403191359-809b89d689ab

23
main.go
View File

@ -1,6 +1,25 @@
package main 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() { func main() {
app := New() address := os.Getenv("LISTEN")
app.Run()
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)
} }

View File

@ -6,24 +6,14 @@ published: true
--- ---
| | | | | |
| ------------- | ---------------------------------------------------------- | | ------------------ | ------------------------------------------------------------------ |
| Name | Eduard Urbach | | Name | Eduard Urbach |
| Occupation | Software Engineer | | Occupation | Software Engineer |
| Business type | Freelancer | | Business type | Freelancer |
| Tax number | DE 362234011 | | Tax number | DE 362234011 |
| Mobile | +49 1520 4296914 | | Mobile | +49 1520 4296914 |
| Address | [n/a](https://dserver.bundestag.de/btd/19/077/1907714.pdf) | | Address | [n/a](https://dserver.bundestag.de/btd/19/077/1907714.pdf) |
## E-Mail
| | |
| ------------------ | ------------------------ |
| Personal | admin [at] akyoto.dev | | Personal | admin [at] akyoto.dev |
| Business inquiries | business [at] akyoto.dev | | Business inquiries | business [at] akyoto.dev |
## Chat
| | |
| --------- | ------------------------------------------------------------------ |
| 1:1 | [@akyoto:akyoto.dev](https://matrix.to/#/@akyoto:akyoto.dev) | | 1:1 | [@akyoto:akyoto.dev](https://matrix.to/#/@akyoto:akyoto.dev) |
| Community | [#community:akyoto.dev](https://matrix.to/#/#community:akyoto.dev) | | Community | [#community:akyoto.dev](https://matrix.to/#/#community:akyoto.dev) |

View File

@ -10,7 +10,6 @@
<header> <header>
<nav> <nav>
<a href="/" class="title">akyoto.dev</a> <a href="/" class="title">akyoto.dev</a>
<a href="/blog">blog</a>
<a href="/contact">contact</a> <a href="/contact">contact</a>
</nav> </nav>
</header> </header>

10
server/app/Post.go Normal file
View File

@ -0,0 +1,10 @@
package app
type Post struct {
Slug string
Content string
Title string
Tags []string
Created string
Published bool
}

15
server/app/Render.go Normal file
View File

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

34
server/app/RenderPost.go Normal file
View File

@ -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(`<title>%s</title><meta name="keywords" content="%s">`, post.Title, strings.Join(post.Tags, ","))
content := ""
if slices.Contains(post.Tags, "article") {
content = fmt.Sprintf(
`<article><header><h1>%s</h1><time datetime="%s">%s</time></header>%s</article>`,
post.Title,
post.Created,
post.Created[:len("YYYY-MM-DD")],
markdown.Render(post.Content),
)
} else {
content = fmt.Sprintf(
`<h2>%s</h2>%s`,
post.Title,
markdown.Render(post.Content),
)
}
return Render(ctx, head, content)
}

22
server/app/init.go Normal file
View File

@ -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}<style>%s</style>", CSS), 1)
HTML = strings.ReplaceAll(HTML, "{avatar}", "https://gravatar.com/avatar/35f2c481f711f0a36bc0e930c4c15eb0bcc794aaeef405a060fe3e28d1c7b7e5.png?s=64")
Posts = loadPosts("posts")
Projects = loadProjects("projects")
}

15
server/app/load.go Normal file
View File

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

10
server/app/loadClean.go Normal file
View File

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

View File

@ -1,19 +1,10 @@
package main package app
import ( import (
"os" "os"
"strings" "strings"
) )
type Post struct {
Slug string
Content string
Title string
Tags []string
Created string
Published bool
}
func loadPosts(directory string) map[string]*Post { func loadPosts(directory string) map[string]*Post {
entries, err := os.ReadDir(directory) entries, err := os.ReadDir(directory)
@ -62,17 +53,3 @@ func loadPosts(directory string) map[string]*Post {
return posts 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:]))
}
}

View File

@ -1,4 +1,4 @@
package main package app
import ( import (
"os" "os"

View File

@ -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:]))
}
}

View File

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

View File

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

36
server/pages/Blog.go Normal file
View File

@ -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(`<h2>Blog</h2><ul class="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, `<li><a href="/%s">%s</a><time datetime="%s">%s</time></li>`, post.Slug, post.Title, post.Created, post.Created[:len("YYYY")])
}
html.WriteString(`</ul>`)
return app.Render(ctx, "<title>akyoto.dev</title>", html.String())
}

25
server/pages/Frontpage.go Normal file
View File

@ -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(`<title>%s</title><meta name="keywords" content="%s">`, "Projects", "projects")
body := strings.Builder{}
body.WriteString(`<h2>Projects</h2>`)
for i, markdown := range app.Projects {
body.WriteString(markdown)
if i != len(app.Projects)-1 {
body.WriteString(`<hr>`)
}
}
return app.Render(ctx, head, body.String())
}

11
server/pages/Post.go Normal file
View File

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