Add categories feature #3
9
layouts/simplecss/categories_list.html
Normal file
9
layouts/simplecss/categories_list.html
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
<h2>Categories</h2>
|
||||||
|
<ul>
|
||||||
|
{{ range .Categories }}
|
||||||
|
<li>
|
||||||
|
<a href="{{ url_abs .Path }}">{{ .Name }}</a> ({{ .PostCount }})
|
||||||
|
{{ if .DescriptionBrief }}<br><small>{{ .DescriptionBrief }}</small>{{ end }}
|
||||||
|
</li>
|
||||||
|
{{ end }}
|
||||||
|
</ul>
|
||||||
16
layouts/simplecss/categories_single.html
Normal file
16
layouts/simplecss/categories_single.html
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<h2>{{ .Category.Name }}</h2>
|
||||||
|
{{ if .DescriptionHTML }}
|
||||||
|
<div>{{ .DescriptionHTML }}</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ range .Posts }}
|
||||||
|
{{ if .Post.Title }}<h3>{{ .Post.Title }}</h3>{{ end }}
|
||||||
|
{{ .HTML }}
|
||||||
|
<a href="{{ url_abs .Path }}">{{ format_date .Post.PublishedAt }}</a>
|
||||||
|
{{ if .Categories }}
|
||||||
|
<p>
|
||||||
|
{{ range .Categories }}
|
||||||
|
<a href="{{ url_abs (printf "/categories/%s" .Slug) }}">{{ .Name }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</p>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
|
@ -2,4 +2,11 @@
|
||||||
{{ if .Post.Title }}<h3>{{ .Post.Title }}</h3>{{ end }}
|
{{ if .Post.Title }}<h3>{{ .Post.Title }}</h3>{{ end }}
|
||||||
{{ .HTML }}
|
{{ .HTML }}
|
||||||
<a href="{{ url_abs .Path }}">{{ format_date .Post.PublishedAt }}</a>
|
<a href="{{ url_abs .Path }}">{{ format_date .Post.PublishedAt }}</a>
|
||||||
|
{{ if .Categories }}
|
||||||
|
<p>
|
||||||
|
{{ range .Categories }}
|
||||||
|
<a href="{{ url_abs (printf "/categories/%s" .Slug) }}">{{ .Name }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</p>
|
||||||
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
@ -1,3 +1,10 @@
|
||||||
{{ if .Post.Title }}<h3>{{ .Post.Title }}</h3>{{ end }}
|
{{ if .Post.Title }}<h3>{{ .Post.Title }}</h3>{{ end }}
|
||||||
{{ .HTML }}
|
{{ .HTML }}
|
||||||
<a href="{{ url_abs .Path }}">{{ format_date .Post.PublishedAt }}</a>
|
<a href="{{ url_abs .Path }}">{{ format_date .Post.PublishedAt }}</a>
|
||||||
|
{{ if .Categories }}
|
||||||
|
<p>
|
||||||
|
{{ range .Categories }}
|
||||||
|
<a href="{{ url_abs (printf "/categories/%s" .Slug) }}">{{ .Name }}</a>
|
||||||
|
{{ end }}
|
||||||
|
</p>
|
||||||
|
{{ end }}
|
||||||
|
|
@ -11,11 +11,11 @@ import (
|
||||||
type Site struct {
|
type Site struct {
|
||||||
models.Site
|
models.Site
|
||||||
BaseURL string
|
BaseURL string
|
||||||
//Posts []*models.Post
|
|
||||||
Uploads []models.Upload
|
Uploads []models.Upload
|
||||||
|
|
||||||
OpenUpload func(u models.Upload) (io.ReadCloser, error)
|
OpenUpload func(u models.Upload) (io.ReadCloser, error)
|
||||||
|
PostIter func(ctx context.Context) iter.Seq[models.Maybe[*models.Post]]
|
||||||
// PostItr returns a new post iterator
|
Categories []models.CategoryWithCount
|
||||||
PostIter func(ctx context.Context) iter.Seq[models.Maybe[*models.Post]]
|
PostIterByCategory func(ctx context.Context, categoryID int64) iter.Seq[models.Maybe[*models.Post]]
|
||||||
|
CategoriesOfPost func(ctx context.Context, postID int64) ([]*models.Category, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ type Builder struct {
|
||||||
func New(site pubmodel.Site, opts Options) (*Builder, error) {
|
func New(site pubmodel.Site, opts Options) (*Builder, error) {
|
||||||
tmpls, err := template.New("").
|
tmpls, err := template.New("").
|
||||||
Funcs(templateFns(site, opts)).
|
Funcs(templateFns(site, opts)).
|
||||||
ParseFS(opts.TemplatesFS, tmplNamePostSingle, tmplNamePostList, tmplNameLayoutMain)
|
ParseFS(opts.TemplatesFS, tmplNamePostSingle, tmplNamePostList, tmplNameLayoutMain, tmplNameCategoryList, tmplNameCategorySingle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +62,13 @@ func (b *Builder) BuildSite(outDir string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := b.writePost(buildCtx, post); err != nil {
|
rp, err := b.renderPostWithCategories(ctx, post)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := b.createAtPath(buildCtx, rp.Path, func(f io.Writer) error {
|
||||||
|
return b.renderTemplate(f, tmplNamePostSingle, rp)
|
||||||
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -70,10 +76,7 @@ func (b *Builder) BuildSite(outDir string) error {
|
||||||
})
|
})
|
||||||
|
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
if err := b.renderPostList(buildCtx, b.site.PostIter(ctx)); err != nil {
|
return b.renderPostListWithCategories(buildCtx, ctx)
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
|
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
|
|
@ -93,43 +96,42 @@ func (b *Builder) BuildSite(outDir string) error {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
// Copy uploads
|
// Category pages
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
if err := b.writeUploads(buildCtx, b.site.Uploads); err != nil {
|
if err := b.renderCategoryList(buildCtx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return b.renderCategoryPages(buildCtx, ctx)
|
||||||
})
|
})
|
||||||
if err := eg.Wait(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
// Copy uploads
|
||||||
|
eg.Go(func() error {
|
||||||
|
return b.writeUploads(buildCtx, b.site.Uploads)
|
||||||
|
})
|
||||||
|
|
||||||
|
return eg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) renderPostList(ctx buildContext, postIter iter.Seq[models.Maybe[*models.Post]]) error {
|
func (b *Builder) renderPostListWithCategories(bctx buildContext, ctx context.Context) error {
|
||||||
// TODO: paging
|
var posts []postSingleData
|
||||||
postCopy := make([]*models.Post, 0)
|
for mp := range b.site.PostIter(ctx) {
|
||||||
for mp := range postIter {
|
|
||||||
post, err := mp.Get()
|
post, err := mp.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
postCopy = append(postCopy, post)
|
rp, err := b.renderPostWithCategories(ctx, post)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
posts = append(posts, rp)
|
||||||
}
|
}
|
||||||
|
|
||||||
pl := postListData{
|
pl := postListData{
|
||||||
commonData: commonData{Site: b.site},
|
commonData: commonData{Site: b.site},
|
||||||
}
|
Posts: posts,
|
||||||
for _, post := range postCopy {
|
|
||||||
rp, err := b.renderPost(post)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
pl.Posts = append(pl.Posts, rp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.createAtPath(ctx, "", func(f io.Writer) error {
|
return b.createAtPath(bctx, "", func(f io.Writer) error {
|
||||||
return b.renderTemplate(f, tmplNamePostList, pl)
|
return b.renderTemplate(f, tmplNamePostList, pl)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -156,16 +158,29 @@ func (b *Builder) renderFeeds(ctx buildContext, postIter iter.Seq[models.Maybe[*
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var catName string
|
||||||
|
if b.site.CategoriesOfPost != nil {
|
||||||
|
cats, err := b.site.CategoriesOfPost(context.Background(), post.ID)
|
||||||
|
if err == nil && len(cats) > 0 {
|
||||||
|
names := make([]string, len(cats))
|
||||||
|
for i, c := range cats {
|
||||||
|
names[i] = c.Name
|
||||||
|
}
|
||||||
|
catName = strings.Join(names, ", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
postTitle := post.Title
|
postTitle := post.Title
|
||||||
if postTitle != "" {
|
if postTitle != "" {
|
||||||
postTitle = opts.titlePrefix + postTitle
|
postTitle = opts.titlePrefix + postTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
feed.Items = append(feed.Items, &feedhub.Item{
|
feed.Items = append(feed.Items, &feedhub.Item{
|
||||||
Id: filepath.Join(b.site.BaseURL, post.GUID),
|
Id: filepath.Join(b.site.BaseURL, post.GUID),
|
||||||
Title: postTitle,
|
Title: postTitle,
|
||||||
Link: &feedhub.Link{Href: renderedPost.PostURL},
|
Link: &feedhub.Link{Href: renderedPost.PostURL},
|
||||||
Content: string(renderedPost.HTML),
|
Content: string(renderedPost.HTML),
|
||||||
|
Category: catName,
|
||||||
// TO FIX: Created should be first published
|
// TO FIX: Created should be first published
|
||||||
Created: post.PublishedAt,
|
Created: post.PublishedAt,
|
||||||
Updated: post.UpdatedAt,
|
Updated: post.UpdatedAt,
|
||||||
|
|
@ -243,14 +258,142 @@ func (b *Builder) renderPost(post *models.Post) (postSingleData, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Builder) writePost(ctx buildContext, post *models.Post) error {
|
// renderPostWithCategories renders a post and attaches its categories.
|
||||||
|
func (b *Builder) renderPostWithCategories(ctx context.Context, post *models.Post) (postSingleData, error) {
|
||||||
rp, err := b.renderPost(post)
|
rp, err := b.renderPost(post)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return postSingleData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.site.CategoriesOfPost != nil {
|
||||||
|
cats, err := b.site.CategoriesOfPost(ctx, post.ID)
|
||||||
|
if err != nil {
|
||||||
|
return postSingleData{}, err
|
||||||
|
}
|
||||||
|
rp.Categories = cats
|
||||||
|
}
|
||||||
|
|
||||||
|
return rp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) renderCategoryList(ctx buildContext) error {
|
||||||
|
var items []categoryListItem
|
||||||
|
for _, cwc := range b.site.Categories {
|
||||||
|
if cwc.PostCount == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
items = append(items, categoryListItem{
|
||||||
|
CategoryWithCount: cwc,
|
||||||
|
Path: fmt.Sprintf("/categories/%s", cwc.Slug),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(items) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
data := categoryListData{
|
||||||
|
commonData: commonData{Site: b.site},
|
||||||
|
Categories: items,
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.createAtPath(ctx, "/categories", func(f io.Writer) error {
|
||||||
|
return b.renderTemplate(f, tmplNameCategoryList, data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) renderCategoryPages(ctx buildContext, goCtx context.Context) error {
|
||||||
|
for _, cwc := range b.site.Categories {
|
||||||
|
if cwc.PostCount == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var posts []postSingleData
|
||||||
|
for mp := range b.site.PostIterByCategory(goCtx, cwc.ID) {
|
||||||
|
post, err := mp.Get()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rp, err := b.renderPostWithCategories(goCtx, post)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
posts = append(posts, rp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var descHTML bytes.Buffer
|
||||||
|
if cwc.Description != "" {
|
||||||
|
if err := b.mdRenderer.RenderTo(goCtx, &descHTML, cwc.Description); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data := categorySingleData{
|
||||||
|
commonData: commonData{Site: b.site},
|
||||||
|
Category: &cwc.Category,
|
||||||
|
DescriptionHTML: template.HTML(descHTML.String()),
|
||||||
|
Posts: posts,
|
||||||
|
Path: fmt.Sprintf("/categories/%s", cwc.Slug),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := b.createAtPath(ctx, data.Path, func(f io.Writer) error {
|
||||||
|
return b.renderTemplate(f, tmplNameCategorySingle, data)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Per-category feeds
|
||||||
|
if err := b.renderCategoryFeed(ctx, cwc, posts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Builder) renderCategoryFeed(ctx buildContext, cwc models.CategoryWithCount, posts []postSingleData) error {
|
||||||
|
now := time.Now()
|
||||||
|
feed := &feedhub.Feed{
|
||||||
|
Title: b.site.Title + " - " + cwc.Name,
|
||||||
|
Link: &feedhub.Link{Href: b.site.BaseURL},
|
||||||
|
Description: cwc.DescriptionBrief,
|
||||||
|
Created: now,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, rp := range posts {
|
||||||
|
if i >= b.opts.FeedItems {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
feed.Items = append(feed.Items, &feedhub.Item{
|
||||||
|
Id: filepath.Join(b.site.BaseURL, rp.Post.GUID),
|
||||||
|
Title: rp.Post.Title,
|
||||||
|
Link: &feedhub.Link{Href: rp.PostURL},
|
||||||
|
Content: string(rp.HTML),
|
||||||
|
Created: rp.Post.PublishedAt,
|
||||||
|
Updated: rp.Post.UpdatedAt,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := fmt.Sprintf("/categories/%s/feed", cwc.Slug)
|
||||||
|
|
||||||
|
if err := b.createAtPath(ctx, prefix+".xml", func(f io.Writer) error {
|
||||||
|
rss, err := feed.ToRss()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.WriteString(f, rss)
|
||||||
|
return err
|
||||||
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return b.createAtPath(ctx, rp.Path, func(f io.Writer) error {
|
return b.createAtPath(ctx, prefix+".json", func(f io.Writer) error {
|
||||||
return b.renderTemplate(f, tmplNamePostSingle, rp)
|
j, err := feed.ToJSON()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.WriteString(f, j)
|
||||||
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package sitebuilder_test
|
package sitebuilder_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"iter"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -15,24 +17,36 @@ import (
|
||||||
func TestBuilder_BuildSite(t *testing.T) {
|
func TestBuilder_BuildSite(t *testing.T) {
|
||||||
t.Run("build site", func(t *testing.T) {
|
t.Run("build site", func(t *testing.T) {
|
||||||
tmpls := fstest.MapFS{
|
tmpls := fstest.MapFS{
|
||||||
"posts_single.html": {Data: []byte(`{{ .HTML }}`)},
|
"posts_single.html": {Data: []byte(`{{ .HTML }}`)},
|
||||||
"posts_list.html": {Data: []byte(`{{ range .Posts}}<a href="{{url_abs .Path}}">{{.Post.Title}}</a>,{{ end }}`)},
|
"posts_list.html": {Data: []byte(`{{ range .Posts}}<a href="{{url_abs .Path}}">{{.Post.Title}}</a>,{{ end }}`)},
|
||||||
"layout_main.html": {Data: []byte(`{{ .Body }}`)},
|
"layout_main.html": {Data: []byte(`{{ .Body }}`)},
|
||||||
|
"categories_list.html": {Data: []byte(`{{ range .Categories}}<a href="{{url_abs .Path}}">{{.Name}}</a>,{{ end }}`)},
|
||||||
|
"categories_single.html": {Data: []byte(`<h2>{{.Category.Name}}</h2>`)},
|
||||||
|
}
|
||||||
|
|
||||||
|
posts := []*models.Post{
|
||||||
|
{
|
||||||
|
Title: "Test Post",
|
||||||
|
Slug: "/2026/02/18/test-post",
|
||||||
|
Body: "This is a test post",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Title: "Another Post",
|
||||||
|
Slug: "/2026/02/20/another-post",
|
||||||
|
Body: "This is **another** test post",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
site := pubmodel.Site{
|
site := pubmodel.Site{
|
||||||
BaseURL: "https://example.com",
|
BaseURL: "https://example.com",
|
||||||
Posts: []*models.Post{
|
PostIter: func(ctx context.Context) iter.Seq[models.Maybe[*models.Post]] {
|
||||||
{
|
return func(yield func(models.Maybe[*models.Post]) bool) {
|
||||||
Title: "Test Post",
|
for _, p := range posts {
|
||||||
Slug: "/2026/02/18/test-post",
|
if !yield(models.Maybe[*models.Post]{Value: p}) {
|
||||||
Body: "This is a test post",
|
return
|
||||||
},
|
}
|
||||||
{
|
}
|
||||||
Title: "Another Post",
|
}
|
||||||
Slug: "/2026/02/20/another-post",
|
|
||||||
Body: "This is **another** test post",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
wantFiles := map[string]string{
|
wantFiles := map[string]string{
|
||||||
|
|
@ -58,5 +72,4 @@ func TestBuilder_BuildSite(t *testing.T) {
|
||||||
assert.Equal(t, content, string(fileContent))
|
assert.Equal(t, content, string(fileContent))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,12 @@ const (
|
||||||
|
|
||||||
// tmplNameLayoutMain is the template for the main layout (layoutMainData)
|
// tmplNameLayoutMain is the template for the main layout (layoutMainData)
|
||||||
tmplNameLayoutMain = "layout_main.html"
|
tmplNameLayoutMain = "layout_main.html"
|
||||||
|
|
||||||
|
// tmplNameCategoryList is the template for the category index page
|
||||||
|
tmplNameCategoryList = "categories_list.html"
|
||||||
|
|
||||||
|
// tmplNameCategorySingle is the template for a single category page
|
||||||
|
tmplNameCategorySingle = "categories_single.html"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct {
|
||||||
|
|
@ -41,10 +47,11 @@ type commonData struct {
|
||||||
|
|
||||||
type postSingleData struct {
|
type postSingleData struct {
|
||||||
commonData
|
commonData
|
||||||
Post *models.Post
|
Post *models.Post
|
||||||
HTML template.HTML
|
HTML template.HTML
|
||||||
Path string
|
Path string
|
||||||
PostURL string
|
PostURL string
|
||||||
|
Categories []*models.Category
|
||||||
}
|
}
|
||||||
|
|
||||||
type postListData struct {
|
type postListData struct {
|
||||||
|
|
@ -56,3 +63,21 @@ type layoutData struct {
|
||||||
commonData
|
commonData
|
||||||
Body template.HTML
|
Body template.HTML
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type categoryListData struct {
|
||||||
|
commonData
|
||||||
|
Categories []categoryListItem
|
||||||
|
}
|
||||||
|
|
||||||
|
type categoryListItem struct {
|
||||||
|
models.CategoryWithCount
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
type categorySingleData struct {
|
||||||
|
commonData
|
||||||
|
Category *models.Category
|
||||||
|
DescriptionHTML template.HTML
|
||||||
|
Posts []postSingleData
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"lmika.dev/lmika/weiro/providers/db"
|
"lmika.dev/lmika/weiro/providers/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PostIter returns a post iterator which returns posts in reverse chronological order.
|
// postIter returns a post iterator which returns posts in reverse chronological order.
|
||||||
func (s *Publisher) postIter(ctx context.Context, site int64) iter.Seq[models.Maybe[*models.Post]] {
|
func (s *Publisher) postIter(ctx context.Context, site int64) iter.Seq[models.Maybe[*models.Post]] {
|
||||||
return func(yield func(models.Maybe[*models.Post]) bool) {
|
return func(yield func(models.Maybe[*models.Post]) bool) {
|
||||||
paging := db.PagingParams{Offset: 0, Limit: 50}
|
paging := db.PagingParams{Offset: 0, Limit: 50}
|
||||||
|
|
@ -39,3 +39,26 @@ func (s *Publisher) postIter(ctx context.Context, site int64) iter.Seq[models.Ma
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// postIterByCategory returns a post iterator for posts in a specific category.
|
||||||
|
func (s *Publisher) postIterByCategory(ctx context.Context, categoryID int64) iter.Seq[models.Maybe[*models.Post]] {
|
||||||
|
return func(yield func(models.Maybe[*models.Post]) bool) {
|
||||||
|
paging := db.PagingParams{Offset: 0, Limit: 50}
|
||||||
|
for {
|
||||||
|
page, err := s.db.SelectPostsOfCategory(ctx, categoryID, paging)
|
||||||
|
if err != nil {
|
||||||
|
yield(models.Maybe[*models.Post]{Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(page) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, post := range page {
|
||||||
|
if !yield(models.Maybe[*models.Post]{Value: post}) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paging.Offset += paging.Limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,24 @@ func (p *Publisher) Publish(ctx context.Context, site models.Site) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fetch categories with counts
|
||||||
|
cats, err := p.db.SelectCategoriesOfSite(ctx, site.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var catsWithCounts []models.CategoryWithCount
|
||||||
|
for _, cat := range cats {
|
||||||
|
count, err := p.db.CountPostsOfCategory(ctx, cat.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
catsWithCounts = append(catsWithCounts, models.CategoryWithCount{
|
||||||
|
Category: *cat,
|
||||||
|
PostCount: int(count),
|
||||||
|
DescriptionBrief: models.BriefDescription(cat.Description),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
for _, target := range targets {
|
for _, target := range targets {
|
||||||
if !target.Enabled {
|
if !target.Enabled {
|
||||||
continue
|
continue
|
||||||
|
|
@ -56,8 +74,15 @@ func (p *Publisher) Publish(ctx context.Context, site models.Site) error {
|
||||||
PostIter: func(ctx context.Context) iter.Seq[models.Maybe[*models.Post]] {
|
PostIter: func(ctx context.Context) iter.Seq[models.Maybe[*models.Post]] {
|
||||||
return p.postIter(ctx, site.ID)
|
return p.postIter(ctx, site.ID)
|
||||||
},
|
},
|
||||||
BaseURL: target.BaseURL,
|
BaseURL: target.BaseURL,
|
||||||
Uploads: uploads,
|
Uploads: uploads,
|
||||||
|
Categories: catsWithCounts,
|
||||||
|
PostIterByCategory: func(ctx context.Context, categoryID int64) iter.Seq[models.Maybe[*models.Post]] {
|
||||||
|
return p.postIterByCategory(ctx, categoryID)
|
||||||
|
},
|
||||||
|
CategoriesOfPost: func(ctx context.Context, postID int64) ([]*models.Category, error) {
|
||||||
|
return p.db.SelectCategoriesOfPost(ctx, postID)
|
||||||
|
},
|
||||||
OpenUpload: func(u models.Upload) (io.ReadCloser, error) {
|
OpenUpload: func(u models.Upload) (io.ReadCloser, error) {
|
||||||
return p.up.OpenUpload(site, u)
|
return p.up.OpenUpload(site, u)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue