Improved file structure
This commit is contained in:
parent
6ad3f11ce9
commit
9d0a49284d
164
App.go
164
App.go
@ -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
2
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
|
||||
|
23
main.go
23
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)
|
||||
}
|
||||
|
@ -6,24 +6,14 @@ 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) |
|
||||
|
@ -10,7 +10,6 @@
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/" class="title">akyoto.dev</a>
|
||||
<a href="/blog">blog</a>
|
||||
<a href="/contact">contact</a>
|
||||
</nav>
|
||||
</header>
|
||||
|
10
server/app/Post.go
Normal file
10
server/app/Post.go
Normal 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
15
server/app/Render.go
Normal 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
34
server/app/RenderPost.go
Normal 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
22
server/app/init.go
Normal 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
15
server/app/load.go
Normal 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
10
server/app/loadClean.go
Normal 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
|
||||
}
|
@ -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:]))
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package main
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
17
server/app/parseFrontmatter.go
Normal file
17
server/app/parseFrontmatter.go
Normal 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:]))
|
||||
}
|
||||
}
|
19
server/middleware/Recover.go
Normal file
19
server/middleware/Recover.go
Normal 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()
|
||||
}
|
17
server/middleware/RedirectTrailingSlashes.go
Normal file
17
server/middleware/RedirectTrailingSlashes.go
Normal 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
36
server/pages/Blog.go
Normal 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
25
server/pages/Frontpage.go
Normal 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
11
server/pages/Post.go
Normal 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)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user