Added RSS and JSON feeds

This commit is contained in:
Leon Mika 2026-03-05 22:04:24 +11:00
parent 65e5ce2733
commit 21f181f83d
13 changed files with 192 additions and 31 deletions

View file

@ -6,11 +6,13 @@ import (
"fmt"
"html/template"
"io"
"iter"
"os"
"path/filepath"
"sort"
"strings"
"time"
"github.com/gopherlibs/feedhub/feedhub"
"golang.org/x/sync/errgroup"
"lmika.dev/lmika/weiro/models"
"lmika.dev/lmika/weiro/models/pubmodel"
@ -47,10 +49,14 @@ func (b *Builder) BuildSite(outDir string) error {
return err
}
eg := errgroup.Group{}
eg, ctx := errgroup.WithContext(context.Background())
eg.Go(func() error {
for _, post := range b.site.Posts {
for mp := range b.site.PostIter(ctx) {
post, err := mp.Get()
if err != nil {
return err
}
if err := b.writePost(buildCtx, post); err != nil {
return err
}
@ -59,7 +65,14 @@ func (b *Builder) BuildSite(outDir string) error {
})
eg.Go(func() error {
if err := b.renderPostList(buildCtx, b.site.Posts); err != nil {
if err := b.renderPostList(buildCtx, b.site.PostIter(ctx)); err != nil {
return err
}
return nil
})
eg.Go(func() error {
if err := b.renderFeeds(buildCtx, b.site.PostIter(ctx)); err != nil {
return err
}
return nil
@ -79,14 +92,16 @@ func (b *Builder) BuildSite(outDir string) error {
return nil
}
func (b *Builder) renderPostList(ctx buildContext, postList []*models.Post) error {
func (b *Builder) renderPostList(ctx buildContext, postIter iter.Seq[models.Maybe[*models.Post]]) error {
// TODO: paging
postCopy := make([]*models.Post, len(postList))
copy(postCopy, postList)
sort.Slice(postCopy, func(i, j int) bool {
return postCopy[i].PublishedAt.After(postCopy[j].PublishedAt)
})
postCopy := make([]*models.Post, 0)
for mp := range postIter {
post, err := mp.Get()
if err != nil {
return err
}
postCopy = append(postCopy, post)
}
pl := postListData{
commonData: commonData{Site: b.site},
@ -104,6 +119,68 @@ func (b *Builder) renderPostList(ctx buildContext, postList []*models.Post) erro
})
}
func (b *Builder) renderFeeds(ctx buildContext, postIter iter.Seq[models.Maybe[*models.Post]]) error {
now := time.Now()
feed := &feedhub.Feed{
Title: b.site.Title,
Link: &feedhub.Link{Href: b.site.BaseURL},
Description: b.site.Tagline,
// TO FIX: Author
Created: now,
}
items := 0
for mp := range postIter {
post, err := mp.Get()
if err != nil {
return fmt.Errorf("failed to get post: %w", err)
}
renderedPost, err := b.renderPost(post)
if err != nil {
return err
}
feed.Items = append(feed.Items, &feedhub.Item{
Id: filepath.Join(b.site.BaseURL, post.GUID),
Title: post.Title,
Link: &feedhub.Link{Href: renderedPost.PostURL},
Content: string(renderedPost.HTML),
// TO FIX: Created should be first published
Created: post.PublishedAt,
Updated: post.PublishedAt,
})
items++
if items >= b.opts.FeedItems {
break
}
}
if err := b.createAtPath(ctx, "/feed.xml", func(f io.Writer) error {
rss, err := feed.ToRss()
if err != nil {
return fmt.Errorf("failed to convert feed to RSS: %w", err)
}
_, err = io.WriteString(f, rss)
return err
}); err != nil {
return err
}
if err := b.createAtPath(ctx, "/feed.json", func(f io.Writer) error {
rss, err := feed.ToJSON()
if err != nil {
return fmt.Errorf("failed to convert feed to JSON feed: %w", err)
}
_, err = io.WriteString(f, rss)
return err
}); err != nil {
return err
}
return nil
}
func (b *Builder) renderPost(post *models.Post) (postSingleData, error) {
postPath := post.Slug
if b.opts.BasePosts != "" {
@ -115,10 +192,13 @@ func (b *Builder) renderPost(post *models.Post) (postSingleData, error) {
return postSingleData{}, fmt.Errorf("failed to write post %s: %w", post.Slug, err)
}
postURL := strings.TrimSuffix(b.site.BaseURL, "/") + "/" + strings.TrimPrefix(postPath, "/")
return postSingleData{
commonData: commonData{Site: b.site},
Path: postPath,
Post: post,
PostURL: postURL,
HTML: template.HTML(md.String()),
}, nil
}

View file

@ -29,6 +29,9 @@ type Options struct {
// TemplatesFS provides the raw templates for rendering the site.
TemplatesFS fs.FS
// FeedItems holds the number of posts to show in the feed.
FeedItems int
RenderTZ *time.Location
}
@ -38,9 +41,10 @@ type commonData struct {
type postSingleData struct {
commonData
Post *models.Post
HTML template.HTML
Path string
Post *models.Post
HTML template.HTML
Path string
PostURL string
}
type postListData struct {