package blog
import (
"html/template"
"log"
"net/http"
"path/filepath"
"strings"
)
const pageTpl = `
{{.Title}}
{{if .Post.HTML}}
{{else}}
{{end}}
{{if .Post.HTML}}
{{.Post.Title}}
{{.Post.HTML | safehtml}}
{{else}}
Field Notes
{{if .Posts}}
{{range .Posts}}
-
{{.Title}}
{{.Date.Format "Jan 2, 2006"}}
{{end}}
{{else}}
No posts yet.
{{end}}
{{end}}
`
type pageData struct {
Title string
Posts []Post
Post Post
ActiveSlug string
}
var tmpl *template.Template
func safehtml(s string) template.HTML {
return template.HTML(s)
}
func init() {
var err error
tmpl, err = template.New("blog").Funcs(template.FuncMap{"safehtml": safehtml}).Parse(pageTpl)
if err != nil {
panic(err)
}
}
func render(w http.ResponseWriter, data pageData) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
tmpl.Execute(w, data)
}
func RegisterRoutes(mux *http.ServeMux, contentDir string) error {
ogDir := filepath.Join(contentDir, "..", "og")
store := NewStore()
if err := store.Load(contentDir); err != nil {
log.Printf("blog: warning: could not load posts from %s: %v", contentDir, err)
}
store.GenerateOGImages(ogDir)
mux.Handle("/blog/og/", http.StripPrefix("/blog/og/", http.FileServer(http.Dir(ogDir))))
mux.HandleFunc("/blog/admin/reload", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "POST only", http.StatusMethodNotAllowed)
return
}
if err := store.Load(contentDir); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
store.GenerateOGImages(ogDir)
w.Write([]byte("reloaded\n"))
})
mux.HandleFunc("/blog/posts/", func(w http.ResponseWriter, r *http.Request) {
slug := strings.TrimPrefix(r.URL.Path, "/blog/posts/")
if slug == "" || strings.Contains(slug, "/") {
http.NotFound(w, r)
return
}
p, ok := store.Get(slug)
if !ok {
http.NotFound(w, r)
return
}
render(w, pageData{
Title: p.Title + " — Chaosmith Systems",
Posts: store.All(),
Post: p,
})
})
mux.HandleFunc("/blog/", func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/blog/" {
http.NotFound(w, r)
return
}
render(w, pageData{
Title: "Chaosmith Systems — Field Notes",
Posts: store.All(),
})
})
// RSS feed
mux.HandleFunc("/blog/feed.xml", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/rss+xml; charset=utf-8")
w.Write([]byte(generateRSS(store)))
})
// JSON feed
mux.HandleFunc("/blog/feed.json", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/feed+json; charset=utf-8")
w.Write([]byte(generateJSONFeed(store)))
})
// Sitemap
mux.HandleFunc("/sitemap.xml", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
w.Write([]byte(generateSitemap(store)))
})
// robots.txt
mux.HandleFunc("/robots.txt", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain")
w.Write([]byte(`User-agent: *
Allow: /
Sitemap: https://chaosmith.systems/sitemap.xml
# +---------------------+
# | |
# | |
# | |
# | _ |
# | .' '. |
# | .' '. |
# | .' '. |
# | .' '. |
# |.' ,-------. '.|
# | ,' . '. |
# | ( ( ) ) |
# | '. ' ,' |
# | '-------' |
# | |
# | +---------+ |
# | | AGIO | |
# | | RPC | |
# |\ +---------+ /|
# | \ .' |
# | '. / |
# | \ / |
# | \ / |
# | \ .' |
# | '. / |
# | \ / |
# | - |
# | |
# | |
# +---------------------+
`))
})
log.Printf("blog: loaded %d post(s) from %s, mounted at /blog/", store.Count(), contentDir)
return nil
}