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
|
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
23
main.go
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -5,25 +5,15 @@ created: 2023-07-23T12:43:21Z
|
|||||||
published: true
|
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) |
|
||||||
|
| Personal | admin [at] akyoto.dev |
|
||||||
## E-Mail
|
| 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) |
|
||||||
| ------------------ | ------------------------ |
|
|
||||||
| 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>
|
<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
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 (
|
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:]))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"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