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 }