arimelody-web/view/blog.go

179 lines
5.4 KiB
Go
Raw Normal View History

2025-04-02 23:04:09 +01:00
package view
import (
"fmt"
2025-04-02 23:04:09 +01:00
"html/template"
"net/http"
"os"
"slices"
"strings"
2025-04-02 23:04:09 +01:00
"arimelody-web/controller"
2025-04-02 23:04:09 +01:00
"arimelody-web/model"
"arimelody-web/templates"
"github.com/gomarkdown/markdown"
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
)
type (
blogView struct {
Collections []*blogPostCollection
}
blogPostCollection struct {
Year int
Posts []*model.BlogPost
}
blogPostView struct {
*model.BlogPost
BlogHTML template.HTML
Comments []*model.ThreadViewPost
Likes int
Boosts int
BlueskyURL string
MastodonURL string
}
)
func (c *blogPostCollection) Clone() blogPostCollection {
return blogPostCollection{
Year: c.Year,
Posts: slices.Clone(c.Posts),
}
}
2025-04-02 23:04:09 +01:00
var mdRenderer = html.NewRenderer(html.RendererOptions{
Flags: html.CommonFlags | html.HrefTargetBlank,
})
func BlogHandler(app *model.AppState) http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/{id}", func(w http.ResponseWriter, r *http.Request) {
ServeBlogPost(app, r.PathValue("id")).ServeHTTP(w, r)
})
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
posts, err := controller.GetBlogPosts(app.DB, true, -1, 0)
if err != nil {
if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r)
return
}
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch blog posts: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
collections := []*blogPostCollection{}
collection := blogPostCollection{
Posts: []*model.BlogPost{},
Year: -1,
}
for i, post := range posts {
if i == 0 {
collection.Year = post.PublishDate.Year()
}
if post.PublishDate.Year() != collection.Year {
clone := collection.Clone()
collections = append(collections, &clone)
collection = blogPostCollection{
Year: post.PublishDate.Year(),
Posts: []*model.BlogPost{},
}
}
collection.Posts = append(collection.Posts, post)
if i == len(posts) - 1 {
collections = append(collections, &collection)
}
}
err = templates.BlogTemplate.Execute(w, blogView{
Collections: collections,
})
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Error rendering blog post: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
})
return mux
}
func ServeBlogPost(app *model.AppState, blogPostID string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
blog, err := controller.GetBlogPost(app.DB, blogPostID)
if err != nil {
if strings.Contains(err.Error(), "no rows") {
http.NotFound(w, r)
return
}
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch blog post %s: %v\n", blogPostID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
if blog == nil {
http.NotFound(w, r)
return
}
if !blog.Visible {
session, err := controller.GetSessionFromRequest(app, r)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to retrieve session: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
if session == nil || session.Account == nil {
http.NotFound(w, r)
return
}
}
2025-04-02 23:04:09 +01:00
// blog.Markdown += " <i class=\"end-mark\"></i>"
mdParser := parser.NewWithExtensions(parser.CommonExtensions | parser.AutoHeadingIDs)
md := mdParser.Parse([]byte(blog.Markdown))
blogHTML := template.HTML(markdown.Render(md, mdRenderer))
2025-04-02 23:04:09 +01:00
comments := []*model.ThreadViewPost{}
likeCount := 0
boostCount := 0
var blueskyURL string
var blueskyPost *model.ThreadViewPost
if blog.Bluesky != nil {
blueskyPost, err = controller.FetchThreadViewPost(blog.Bluesky.ActorDID, blog.Bluesky.RecordID)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to fetch blog post Bluesky thread: %v\n", err)
} else {
comments = append(comments, blueskyPost.Replies...)
likeCount += blueskyPost.Post.LikeCount
boostCount += blueskyPost.Post.RepostCount
blueskyURL = fmt.Sprintf("https://bsky.app/profile/%s/post/%s", blueskyPost.Post.Author.Handle, blog.Bluesky.RecordID)
}
}
err = templates.BlogPostTemplate.Execute(w, blogPostView{
BlogPost: blog,
BlogHTML: blogHTML,
Comments: comments,
Likes: likeCount,
Boosts: boostCount,
BlueskyURL: blueskyURL,
})
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Error rendering blog post: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
2025-04-02 23:04:09 +01:00
})
}