prose/server/server.go
Naman Sood 1981a291aa
All checks were successful
/ Deploy blog to deuterium (push) Successful in 1m9s
rewrite code a bit
Signed-off-by: Naman Sood <mail@nsood.in>
2024-11-23 02:53:25 -05:00

201 lines
4.6 KiB
Go

package server
import (
"bytes"
"log"
"net/http"
"prose/common"
"prose/errorcatcher"
"prose/postmap"
"prose/stylemap"
"prose/tplmap"
"prose/watcher"
"strings"
"github.com/aymerick/raymond"
)
type server struct {
staticHandler http.Handler
templates watcher.AutoMap[string, *raymond.Template]
posts watcher.OrderedAutoMap[string, *postmap.Post]
styles watcher.AutoMap[string, string]
homeImage []byte
}
func New() error {
s := &server{
staticHandler: http.FileServer(http.Dir("static/")),
}
var imgBuffer bytes.Buffer
err := common.CreateImage(common.BlogTitle, common.BlogSummary, common.BlogURL, &imgBuffer)
if err != nil {
return err
}
s.homeImage = imgBuffer.Bytes()
s.posts, err = postmap.New()
if err != nil {
return err
}
s.templates, err = tplmap.New()
if err != nil {
return err
}
s.styles, err = stylemap.New()
if err != nil {
return err
}
handlers := map[string]http.HandlerFunc{
"GET /{$}": s.serveGetHome,
"GET /banner.png": s.serveGetImage(s.homeImage),
"GET /rss.xml": s.serveGetRSS,
"GET /{slug}": s.serveGetPost,
"GET /img/banner/{img}": s.serveGetPostImage,
"GET /css/{filename}": s.serveGetStylesheet,
"GET /": s.staticHandler.ServeHTTP,
}
for pattern, handler := range handlers {
http.Handle(pattern, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w = errorcatcher.New(w, r, s.templates)
handler(w, r)
}))
}
return nil
}
func (s *server) errorInRequest(res http.ResponseWriter, req *http.Request, err error) {
res.WriteHeader(http.StatusInternalServerError)
log.Printf("ERR %s: %s", req.URL.Path, err)
}
func (s *server) createWebPage(title, subtitle, contents, banner string) (string, error) {
ctx := map[string]interface{}{
"title": title,
"subtitle": subtitle,
"contents": contents,
"banner": banner,
}
tpl, _ := s.templates.Get("page.html")
return tpl.Exec(ctx)
}
func (s *server) serveGetPost(w http.ResponseWriter, r *http.Request) {
p, ok := s.posts.Get(r.PathValue("slug"))
if !ok {
w.WriteHeader(http.StatusNotFound)
return
}
w.Header().Add("content-type", "text/html; charset=utf-8")
tpl, _ := s.templates.Get("fullpost.html")
contents, err := p.Render(tpl)
if err != nil {
s.errorInRequest(w, r, err)
return
}
page, err := s.createWebPage(p.Metadata.Title, p.Metadata.Summary, contents, "/img/banner/"+p.Slug+".png")
if err != nil {
s.errorInRequest(w, r, err)
return
}
w.Write([]byte(page))
}
func (s *server) serveGetHome(w http.ResponseWriter, r *http.Request) {
w.Header().Add("content-type", "text/html; charset=utf-8")
var posts string
summaryTpl, _ := s.templates.Get("summary.html")
for _, p := range s.posts.All() {
summary, err := p.Render(summaryTpl)
if err != nil {
log.Printf("could not render post summary for %s", p.Slug)
}
posts = posts + summary
}
page, err := s.createWebPage("Home", common.BlogSummary, posts, "/banner.png")
if err != nil {
s.errorInRequest(w, r, err)
return
}
w.Write([]byte(page))
}
func (s *server) serveGetPostImage(w http.ResponseWriter, r *http.Request) {
slug, ok := strings.CutSuffix(r.PathValue("img"), ".png")
if !ok {
w.WriteHeader(http.StatusNotFound)
return
}
post, ok := s.posts.Get(slug)
if !ok {
w.WriteHeader(http.StatusNotFound)
return
}
s.serveGetImage(post.Image)(w, r)
}
func (s *server) serveGetImage(img []byte) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("content-type", "image/png")
w.Write(img)
}
}
func (s *server) serveGetRSS(w http.ResponseWriter, r *http.Request) {
w.Header().Add("content-type", "text/xml; charset=utf-8")
var posts, pubDate string
rssItemTpl, _ := s.templates.Get("rss-item.xml")
for _, p := range s.posts.All() {
if pubDate != "" {
pubDate = common.RSSDatetime(p.Metadata.Time)
}
summary, err := p.Render(rssItemTpl)
if err != nil {
log.Printf("could not render post summary for %s", p.Slug)
}
posts = posts + summary
}
if pubDate == "" {
pubDate = common.RSSDatetime(0)
}
rssPageTpl, _ := s.templates.Get("rss-channel.xml")
page, err := rssPageTpl.Exec(map[string]string{
"title": common.BlogTitle,
"description": common.BlogSummary,
"link": common.BlogURL,
"pubDate": pubDate,
"items": posts,
})
if err != nil {
s.errorInRequest(w, r, err)
return
}
w.Write([]byte(page))
}
func (s *server) serveGetStylesheet(w http.ResponseWriter, r *http.Request) {
contents, ok := s.styles.Get(r.PathValue("filename"))
if !ok {
w.WriteHeader(http.StatusNotFound)
return
}
w.Header().Add("content-type", "text/css")
w.Write([]byte(contents))
}