diff --git a/handlers/posts.go b/handlers/posts.go index a133758..3326533 100644 --- a/handlers/posts.go +++ b/handlers/posts.go @@ -6,6 +6,7 @@ import ( "github.com/gofiber/fiber/v3" "lmika.dev/lmika/weiro/models" + "lmika.dev/lmika/weiro/providers/db" "lmika.dev/lmika/weiro/services/categories" "lmika.dev/lmika/weiro/services/posts" ) @@ -18,22 +19,43 @@ type PostsHandler struct { func (ph PostsHandler) Index(c fiber.Ctx) error { var req struct { Filter string `query:"filter"` + Page int `query:"page"` } if err := c.Bind().Query(&req); err != nil { return fiber.ErrBadRequest } - posts, err := ph.PostService.ListPosts(c.Context(), req.Filter == "deleted") + const perPage = 25 + if req.Page < 1 { + req.Page = 1 + } + + result, err := ph.PostService.ListPosts(c.Context(), req.Filter == "deleted", db.PagingParams{ + Offset: int64((req.Page - 1) * perPage), + Limit: perPage, + }) if err != nil { return err } + totalPages := int(result.TotalCount+int64(perPage)-1) / perPage + if totalPages < 1 { + totalPages = 1 + } + + pageInfo := models.PageInfo{ + CurrentPage: req.Page, + TotalPages: totalPages, + PostsPerPage: perPage, + } + return accepts(c, json(func() any { - return posts + return result.Posts }), html(func(c fiber.Ctx) error { return c.Render("posts/index", fiber.Map{ - "req": req, - "posts": posts, + "req": req, + "posts": result.Posts, + "pageInfo": pageInfo, }) })) } diff --git a/layouts/simplecss/templates/categories_single.html b/layouts/simplecss/templates/categories_single.html index deaeb02..e9e7116 100644 --- a/layouts/simplecss/templates/categories_single.html +++ b/layouts/simplecss/templates/categories_single.html @@ -8,4 +8,10 @@ {{ .HTML }} {{ template "_post_meta.html" . }} -{{ end }} \ No newline at end of file +{{ end }} +{{ if or .PrevURL .NextURL }} + +{{ end }} diff --git a/layouts/simplecss/templates/posts_list.html b/layouts/simplecss/templates/posts_list.html index 5f10f1e..6a2eca6 100644 --- a/layouts/simplecss/templates/posts_list.html +++ b/layouts/simplecss/templates/posts_list.html @@ -5,4 +5,10 @@ {{ template "_post_meta.html" . }} -{{ end }} \ No newline at end of file +{{ end }} +{{ if or .PrevURL .NextURL }} + +{{ end }} diff --git a/models/paging.go b/models/paging.go new file mode 100644 index 0000000..b4e514b --- /dev/null +++ b/models/paging.go @@ -0,0 +1,37 @@ +package models + +// PageInfo carries pagination state for templates. +type PageInfo struct { + CurrentPage int + TotalPages int + PostsPerPage int +} + +// HasPrevious returns true if there is a previous page. +func (p PageInfo) HasPrevious() bool { + return p.CurrentPage > 1 +} + +// HasNext returns true if there is a next page. +func (p PageInfo) HasNext() bool { + return p.CurrentPage < p.TotalPages +} + +// PreviousPage returns the previous page number. +func (p PageInfo) PreviousPage() int { + return p.CurrentPage - 1 +} + +// NextPage returns the next page number. +func (p PageInfo) NextPage() int { + return p.CurrentPage + 1 +} + +// Pages returns a slice of page numbers for rendering numbered pagination. +func (p PageInfo) Pages() []int { + pages := make([]int, p.TotalPages) + for i := range pages { + pages[i] = i + 1 + } + return pages +} diff --git a/models/sites.go b/models/sites.go index 16cbef4..81bf6be 100644 --- a/models/sites.go +++ b/models/sites.go @@ -27,9 +27,10 @@ type Site struct { GUID string Created time.Time - Title string - Tagline string - Timezone string + Title string + Tagline string + Timezone string + PostsPerPage int } type SitePublishTarget struct { diff --git a/providers/db/gen/sqlgen/models.go b/providers/db/gen/sqlgen/models.go index 788c292..ae58594 100644 --- a/providers/db/gen/sqlgen/models.go +++ b/providers/db/gen/sqlgen/models.go @@ -57,13 +57,14 @@ type PublishTarget struct { } type Site struct { - ID int64 - OwnerID int64 - Guid string - Title string - Tagline string - CreatedAt int64 - Timezone string + ID int64 + OwnerID int64 + Guid string + Title string + Tagline string + CreatedAt int64 + Timezone string + PostsPerPage int64 } type Upload struct { diff --git a/providers/db/gen/sqlgen/posts.sql.go b/providers/db/gen/sqlgen/posts.sql.go index 8bff191..ef3d170 100644 --- a/providers/db/gen/sqlgen/posts.sql.go +++ b/providers/db/gen/sqlgen/posts.sql.go @@ -9,6 +9,28 @@ import ( "context" ) +const countPostsOfSite = `-- name: CountPostsOfSite :one +SELECT COUNT(*) FROM posts +WHERE site_id = ?1 AND ( + CASE CAST (?2 AS TEXT) + WHEN 'deleted' THEN deleted_at > 0 + ELSE deleted_at = 0 + END +) +` + +type CountPostsOfSiteParams struct { + SiteID int64 + PostFilter string +} + +func (q *Queries) CountPostsOfSite(ctx context.Context, arg CountPostsOfSiteParams) (int64, error) { + row := q.db.QueryRowContext(ctx, countPostsOfSite, arg.SiteID, arg.PostFilter) + var count int64 + err := row.Scan(&count) + return count, err +} + const hardDeletePost = `-- name: HardDeletePost :exec DELETE FROM posts WHERE id = ? ` diff --git a/providers/db/gen/sqlgen/sites.sql.go b/providers/db/gen/sqlgen/sites.sql.go index bd80fb3..80ccbc0 100644 --- a/providers/db/gen/sqlgen/sites.sql.go +++ b/providers/db/gen/sqlgen/sites.sql.go @@ -28,18 +28,20 @@ INSERT INTO sites ( title, tagline, timezone, + posts_per_page, created_at -) VALUES (?, ?, ?, ?, ?, ?) +) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING id ` type InsertSiteParams struct { - OwnerID int64 - Guid string - Title string - Tagline string - Timezone string - CreatedAt int64 + OwnerID int64 + Guid string + Title string + Tagline string + Timezone string + PostsPerPage int64 + CreatedAt int64 } func (q *Queries) InsertSite(ctx context.Context, arg InsertSiteParams) (int64, error) { @@ -49,6 +51,7 @@ func (q *Queries) InsertSite(ctx context.Context, arg InsertSiteParams) (int64, arg.Title, arg.Tagline, arg.Timezone, + arg.PostsPerPage, arg.CreatedAt, ) var id int64 @@ -101,7 +104,7 @@ func (q *Queries) SelectAllSitesWithOwners(ctx context.Context) ([]SelectAllSite } const selectSiteByGUID = `-- name: SelectSiteByGUID :one -SELECT id, owner_id, guid, title, tagline, created_at, timezone FROM sites WHERE guid = ? +SELECT id, owner_id, guid, title, tagline, created_at, timezone, posts_per_page FROM sites WHERE guid = ? ` func (q *Queries) SelectSiteByGUID(ctx context.Context, guid string) (Site, error) { @@ -115,12 +118,13 @@ func (q *Queries) SelectSiteByGUID(ctx context.Context, guid string) (Site, erro &i.Tagline, &i.CreatedAt, &i.Timezone, + &i.PostsPerPage, ) return i, err } const selectSiteByID = `-- name: SelectSiteByID :one -SELECT id, owner_id, guid, title, tagline, created_at, timezone FROM sites WHERE id = ? +SELECT id, owner_id, guid, title, tagline, created_at, timezone, posts_per_page FROM sites WHERE id = ? ` func (q *Queries) SelectSiteByID(ctx context.Context, id int64) (Site, error) { @@ -134,12 +138,13 @@ func (q *Queries) SelectSiteByID(ctx context.Context, id int64) (Site, error) { &i.Tagline, &i.CreatedAt, &i.Timezone, + &i.PostsPerPage, ) return i, err } const selectSitesOwnedByUser = `-- name: SelectSitesOwnedByUser :many -SELECT id, owner_id, guid, title, tagline, created_at, timezone FROM sites WHERE owner_id = ? ORDER BY title ASC +SELECT id, owner_id, guid, title, tagline, created_at, timezone, posts_per_page FROM sites WHERE owner_id = ? ORDER BY title ASC ` func (q *Queries) SelectSitesOwnedByUser(ctx context.Context, ownerID int64) ([]Site, error) { @@ -159,6 +164,7 @@ func (q *Queries) SelectSitesOwnedByUser(ctx context.Context, ownerID int64) ([] &i.Tagline, &i.CreatedAt, &i.Timezone, + &i.PostsPerPage, ); err != nil { return nil, err } @@ -174,14 +180,15 @@ func (q *Queries) SelectSitesOwnedByUser(ctx context.Context, ownerID int64) ([] } const updateSite = `-- name: UpdateSite :exec -UPDATE sites SET title = ?, tagline = ?, timezone = ? WHERE id = ? +UPDATE sites SET title = ?, tagline = ?, timezone = ?, posts_per_page = ? WHERE id = ? ` type UpdateSiteParams struct { - Title string - Tagline string - Timezone string - ID int64 + Title string + Tagline string + Timezone string + PostsPerPage int64 + ID int64 } func (q *Queries) UpdateSite(ctx context.Context, arg UpdateSiteParams) error { @@ -189,6 +196,7 @@ func (q *Queries) UpdateSite(ctx context.Context, arg UpdateSiteParams) error { arg.Title, arg.Tagline, arg.Timezone, + arg.PostsPerPage, arg.ID, ) return err diff --git a/providers/db/posts.go b/providers/db/posts.go index 218e931..7f58d1a 100644 --- a/providers/db/posts.go +++ b/providers/db/posts.go @@ -13,6 +13,17 @@ type PagingParams struct { Offset int64 } +func (db *Provider) CountPostsOfSite(ctx context.Context, siteID int64, showDeleted bool) (int64, error) { + filter := "active" + if showDeleted { + filter = "deleted" + } + return db.queries.CountPostsOfSite(ctx, sqlgen.CountPostsOfSiteParams{ + SiteID: siteID, + PostFilter: filter, + }) +} + func (db *Provider) SelectPostsOfSite(ctx context.Context, siteID int64, showDeleted bool, pp PagingParams) ([]*models.Post, error) { var filter = "" if showDeleted { diff --git a/providers/db/provider_test.go b/providers/db/provider_test.go index 06f03c0..0a2e6df 100644 --- a/providers/db/provider_test.go +++ b/providers/db/provider_test.go @@ -3,6 +3,7 @@ package db_test import ( "context" "encoding/base64" + "fmt" "path/filepath" "testing" "time" @@ -229,6 +230,45 @@ func TestProvider_Posts(t *testing.T) { require.NoError(t, err) assert.Empty(t, posts) }) + + t.Run("count posts of site", func(t *testing.T) { + countSite := &models.Site{ + OwnerID: user.ID, + GUID: models.NewNanoID(), + Title: "Count Blog", + } + require.NoError(t, p.SaveSite(ctx, countSite)) + + now := time.Date(2026, 3, 22, 12, 0, 0, 0, time.UTC) + for i := 0; i < 3; i++ { + post := &models.Post{ + SiteID: countSite.ID, + GUID: models.NewNanoID(), + Title: fmt.Sprintf("Post %d", i), + Body: "body", + Slug: fmt.Sprintf("/post-%d", i), + CreatedAt: now, + } + require.NoError(t, p.SavePost(ctx, post)) + } + + count, err := p.CountPostsOfSite(ctx, countSite.ID, false) + require.NoError(t, err) + assert.Equal(t, int64(3), count) + + // Soft-delete one post + posts, err := p.SelectPostsOfSite(ctx, countSite.ID, false, db.PagingParams{Limit: 10, Offset: 0}) + require.NoError(t, err) + require.NoError(t, p.SoftDeletePost(ctx, posts[0].ID)) + + count, err = p.CountPostsOfSite(ctx, countSite.ID, false) + require.NoError(t, err) + assert.Equal(t, int64(2), count) + + count, err = p.CountPostsOfSite(ctx, countSite.ID, true) + require.NoError(t, err) + assert.Equal(t, int64(1), count) + }) } func TestProvider_PublishTargets(t *testing.T) { diff --git a/providers/db/sites.go b/providers/db/sites.go index 28d83f6..d1167ca 100644 --- a/providers/db/sites.go +++ b/providers/db/sites.go @@ -42,12 +42,13 @@ func (db *Provider) SelectSitesOwnedByUser(ctx context.Context, ownerID int64) ( func (db *Provider) SaveSite(ctx context.Context, site *models.Site) error { if site.ID == 0 { newID, err := db.queries.InsertSite(ctx, sqlgen.InsertSiteParams{ - OwnerID: site.OwnerID, - Guid: site.GUID, - Title: site.Title, - Tagline: site.Tagline, - Timezone: site.Timezone, - CreatedAt: timeToInt(site.Created), + OwnerID: site.OwnerID, + Guid: site.GUID, + Title: site.Title, + Tagline: site.Tagline, + Timezone: site.Timezone, + PostsPerPage: int64(site.PostsPerPage), + CreatedAt: timeToInt(site.Created), }) if err != nil { return err @@ -57,10 +58,11 @@ func (db *Provider) SaveSite(ctx context.Context, site *models.Site) error { } return db.queries.UpdateSite(ctx, sqlgen.UpdateSiteParams{ - Title: site.Title, - Tagline: site.Tagline, - Timezone: site.Timezone, - ID: site.ID, + Title: site.Title, + Tagline: site.Tagline, + Timezone: site.Timezone, + PostsPerPage: int64(site.PostsPerPage), + ID: site.ID, }) } @@ -101,12 +103,13 @@ func (db *Provider) SelectAllSitesWithOwners(ctx context.Context) ([]SiteWithOwn func dbSiteToSite(row sqlgen.Site) models.Site { return models.Site{ - ID: row.ID, - OwnerID: row.OwnerID, - GUID: row.Guid, - Title: row.Title, - Timezone: row.Timezone, - Tagline: row.Tagline, - Created: time.Unix(row.CreatedAt, 0).UTC(), + ID: row.ID, + OwnerID: row.OwnerID, + GUID: row.Guid, + Title: row.Title, + Timezone: row.Timezone, + Tagline: row.Tagline, + PostsPerPage: int(row.PostsPerPage), + Created: time.Unix(row.CreatedAt, 0).UTC(), } } diff --git a/providers/sitebuilder/builder.go b/providers/sitebuilder/builder.go index 1a4275d..7523d13 100644 --- a/providers/sitebuilder/builder.go +++ b/providers/sitebuilder/builder.go @@ -122,7 +122,8 @@ func (b *Builder) BuildSite(outDir string) error { } func (b *Builder) renderPostListWithCategories(bctx buildContext, ctx context.Context) error { - var posts []postSingleData + // Collect all posts + var allPosts []postSingleData for mp := range b.site.PostIter(ctx) { post, err := mp.Get() if err != nil { @@ -132,17 +133,70 @@ func (b *Builder) renderPostListWithCategories(bctx buildContext, ctx context.Co if err != nil { return err } - posts = append(posts, rp) + allPosts = append(allPosts, rp) } - pl := postListData{ - commonData: commonData{Site: b.site}, - Posts: posts, + postsPerPage := b.site.PostsPerPage + if postsPerPage < 1 { + postsPerPage = 10 } - return b.createAtPath(bctx, "", func(f io.Writer) error { - return b.renderTemplate(f, tmplNamePostList, pl) - }) + totalPages := (len(allPosts) + postsPerPage - 1) / postsPerPage + if totalPages < 1 { + totalPages = 1 + } + + for page := 1; page <= totalPages; page++ { + start := (page - 1) * postsPerPage + end := start + postsPerPage + if end > len(allPosts) { + end = len(allPosts) + } + + pageInfo := models.PageInfo{ + CurrentPage: page, + TotalPages: totalPages, + PostsPerPage: postsPerPage, + } + + var prevURL, nextURL string + if page > 1 { + if page == 2 { + prevURL = "/posts/" + } else { + prevURL = fmt.Sprintf("/posts/page/%d/", page-1) + } + } + if page < totalPages { + nextURL = fmt.Sprintf("/posts/page/%d/", page+1) + } + + pl := postListData{ + commonData: commonData{Site: b.site}, + Posts: allPosts[start:end], + PageInfo: pageInfo, + PrevURL: prevURL, + NextURL: nextURL, + } + + // Page 1 renders at both root and /posts/ + var paths []string + if page == 1 { + paths = []string{"", "/posts"} + } else { + paths = []string{fmt.Sprintf("/posts/page/%d", page)} + } + + for _, path := range paths { + if err := b.createAtPath(bctx, path, func(f io.Writer) error { + return b.renderTemplate(f, tmplNamePostList, pl) + }); err != nil { + return err + } + } + } + + return nil } func (b *Builder) renderFeeds(ctx buildContext, postIter iter.Seq[models.Maybe[*models.Post]], opts feedOptions) error { @@ -318,7 +372,8 @@ func (b *Builder) renderCategoryPages(ctx buildContext, goCtx context.Context) e continue } - var posts []postSingleData + // Collect all posts for this category + var allPosts []postSingleData for mp := range b.site.PostIterByCategory(goCtx, cwc.ID) { post, err := mp.Get() if err != nil { @@ -328,7 +383,7 @@ func (b *Builder) renderCategoryPages(ctx buildContext, goCtx context.Context) e if err != nil { return err } - posts = append(posts, rp) + allPosts = append(allPosts, rp) } var descHTML bytes.Buffer @@ -338,22 +393,68 @@ func (b *Builder) renderCategoryPages(ctx buildContext, goCtx context.Context) e } } - data := categorySingleData{ - commonData: commonData{Site: b.site}, - Category: &cwc.Category, - DescriptionHTML: template.HTML(descHTML.String()), - Posts: posts, - Path: fmt.Sprintf("/categories/%s", cwc.Slug), + postsPerPage := b.site.PostsPerPage + if postsPerPage < 1 { + postsPerPage = 10 } - if err := b.createAtPath(ctx, data.Path, func(f io.Writer) error { - return b.renderTemplate(f, tmplNameCategorySingle, data) - }); err != nil { - return err + totalPages := (len(allPosts) + postsPerPage - 1) / postsPerPage + if totalPages < 1 { + totalPages = 1 } - // Per-category feeds - if err := b.renderCategoryFeed(ctx, cwc, posts); err != nil { + basePath := fmt.Sprintf("/categories/%s", cwc.Slug) + + for page := 1; page <= totalPages; page++ { + start := (page - 1) * postsPerPage + end := start + postsPerPage + if end > len(allPosts) { + end = len(allPosts) + } + + pageInfo := models.PageInfo{ + CurrentPage: page, + TotalPages: totalPages, + PostsPerPage: postsPerPage, + } + + var prevURL, nextURL string + if page > 1 { + if page == 2 { + prevURL = basePath + "/" + } else { + prevURL = fmt.Sprintf("%s/page/%d/", basePath, page-1) + } + } + if page < totalPages { + nextURL = fmt.Sprintf("%s/page/%d/", basePath, page+1) + } + + path := basePath + if page > 1 { + path = fmt.Sprintf("%s/page/%d", basePath, page) + } + + data := categorySingleData{ + commonData: commonData{Site: b.site}, + Category: &cwc.Category, + DescriptionHTML: template.HTML(descHTML.String()), + Posts: allPosts[start:end], + Path: path, + PageInfo: pageInfo, + PrevURL: prevURL, + NextURL: nextURL, + } + + if err := b.createAtPath(ctx, path, func(f io.Writer) error { + return b.renderTemplate(f, tmplNameCategorySingle, data) + }); err != nil { + return err + } + } + + // Per-category feeds (use all posts, not paginated) + if err := b.renderCategoryFeed(ctx, cwc, allPosts); err != nil { return err } } @@ -471,6 +572,9 @@ func (b *Builder) writeUploads(ctx buildContext, uploads []models.Upload) error } func (b *Builder) writeStaticAssets(ctx buildContext) error { + if b.opts.StaticFS == nil { + return nil + } return fs.WalkDir(b.opts.StaticFS, ".", func(path string, d os.DirEntry, err error) error { if err != nil { return err diff --git a/providers/sitebuilder/builder_test.go b/providers/sitebuilder/builder_test.go index cbe116b..a5a9bbf 100644 --- a/providers/sitebuilder/builder_test.go +++ b/providers/sitebuilder/builder_test.go @@ -38,6 +38,7 @@ func TestBuilder_BuildSite(t *testing.T) { } site := pubmodel.Site{ + Site: models.Site{PostsPerPage: 10}, BaseURL: "https://example.com", PostIter: func(ctx context.Context) iter.Seq[models.Maybe[*models.Post]] { return func(yield func(models.Maybe[*models.Post]) bool) { diff --git a/providers/sitebuilder/tmpls.go b/providers/sitebuilder/tmpls.go index cea02f5..e0ece37 100644 --- a/providers/sitebuilder/tmpls.go +++ b/providers/sitebuilder/tmpls.go @@ -61,7 +61,10 @@ type postSingleData struct { type postListData struct { commonData - Posts []postSingleData + Posts []postSingleData + PageInfo models.PageInfo + PrevURL string + NextURL string } type layoutData struct { @@ -85,4 +88,7 @@ type categorySingleData struct { DescriptionHTML template.HTML Posts []postSingleData Path string + PageInfo models.PageInfo + PrevURL string + NextURL string } diff --git a/services/posts/list.go b/services/posts/list.go index 15e14d3..dd25bae 100644 --- a/services/posts/list.go +++ b/services/posts/list.go @@ -12,29 +12,36 @@ type PostWithCategories struct { Categories []*models.Category } -func (s *Service) ListPosts(ctx context.Context, showDeleted bool) ([]*PostWithCategories, error) { +type ListPostsResult struct { + Posts []*PostWithCategories + TotalCount int64 +} + +func (s *Service) ListPosts(ctx context.Context, showDeleted bool, paging db.PagingParams) (ListPostsResult, error) { site, ok := models.GetSite(ctx) if !ok { - return nil, models.SiteRequiredError + return ListPostsResult{}, models.SiteRequiredError } - posts, err := s.db.SelectPostsOfSite(ctx, site.ID, showDeleted, db.PagingParams{ - Offset: 0, - Limit: 25, - }) + posts, err := s.db.SelectPostsOfSite(ctx, site.ID, showDeleted, paging) if err != nil { - return nil, err + return ListPostsResult{}, err + } + + count, err := s.db.CountPostsOfSite(ctx, site.ID, showDeleted) + if err != nil { + return ListPostsResult{}, err } result := make([]*PostWithCategories, len(posts)) for i, post := range posts { cats, err := s.db.SelectCategoriesOfPost(ctx, post.ID) if err != nil { - return nil, err + return ListPostsResult{}, err } result[i] = &PostWithCategories{Post: post, Categories: cats} } - return result, nil + return ListPostsResult{Posts: result, TotalCount: count}, nil } func (s *Service) GetPost(ctx context.Context, pid int64) (*models.Post, error) { diff --git a/services/sites/services.go b/services/sites/services.go index 06afe15..86e34b2 100644 --- a/services/sites/services.go +++ b/services/sites/services.go @@ -77,11 +77,12 @@ func (s *Service) FirstRun(ctx context.Context, req FirstRunRequest) (newUser mo } newSite = models.Site{ - Title: defaultIfEmpty(req.SiteName, "New Site"), - GUID: models.NewNanoID(), - OwnerID: newUser.ID, - Timezone: "UTC", - Created: time.Now(), + Title: defaultIfEmpty(req.SiteName, "New Site"), + GUID: models.NewNanoID(), + OwnerID: newUser.ID, + Timezone: "UTC", + PostsPerPage: 10, + Created: time.Now(), } if err := s.db.SaveSite(ctx, &newSite); err != nil { return newUser, newSite, err @@ -129,10 +130,11 @@ func (s *Service) ListAllSitesWithOwners(ctx context.Context) ([]db.SiteWithOwne } type UpdateSiteSettingsParams struct { - SiteID int64 `form:"siteID"` - Name string `form:"name"` - Tagline string `form:"tagline"` - Timezone string `form:"timezone"` + SiteID int64 `form:"siteID"` + Name string `form:"name"` + Tagline string `form:"tagline"` + Timezone string `form:"timezone"` + PostsPerPage int `form:"postsPerPage"` } func (s *Service) UpdateSiteSettings(ctx context.Context, params UpdateSiteSettingsParams) (models.Site, error) { @@ -146,9 +148,17 @@ func (s *Service) UpdateSiteSettings(ctx context.Context, params UpdateSiteSetti return models.Site{}, errors.Wrap(err, "invalid timezone") } + postsPerPage := params.PostsPerPage + if postsPerPage < 1 { + postsPerPage = 1 + } else if postsPerPage > 100 { + postsPerPage = 100 + } + site.Title = params.Name site.Tagline = params.Tagline site.Timezone = params.Timezone + site.PostsPerPage = postsPerPage if err := s.db.SaveSite(ctx, &site); err != nil { return models.Site{}, err diff --git a/sql/queries/posts.sql b/sql/queries/posts.sql index dae1f39..5a4c18e 100644 --- a/sql/queries/posts.sql +++ b/sql/queries/posts.sql @@ -1,3 +1,12 @@ +-- name: CountPostsOfSite :one +SELECT COUNT(*) FROM posts +WHERE site_id = sqlc.arg(site_id) AND ( + CASE CAST (sqlc.arg(post_filter) AS TEXT) + WHEN 'deleted' THEN deleted_at > 0 + ELSE deleted_at = 0 + END +); + -- name: SelectPostsOfSite :many SELECT * FROM posts diff --git a/sql/queries/sites.sql b/sql/queries/sites.sql index 8fe2469..0609b12 100644 --- a/sql/queries/sites.sql +++ b/sql/queries/sites.sql @@ -14,15 +14,16 @@ INSERT INTO sites ( title, tagline, timezone, + posts_per_page, created_at -) VALUES (?, ?, ?, ?, ?, ?) +) VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING id; -- name: HasUsersAndSites :one SELECT (SELECT COUNT(*) FROM users) > 0 AND (SELECT COUNT(*) FROM sites) > 0 AS has_users_and_sites; -- name: UpdateSite :exec -UPDATE sites SET title = ?, tagline = ?, timezone = ? WHERE id = ?; +UPDATE sites SET title = ?, tagline = ?, timezone = ?, posts_per_page = ? WHERE id = ?; -- name: SelectAllSitesWithOwners :many SELECT s.id, s.guid, s.title, s.owner_id, u.username diff --git a/sql/schema/05_posts_per_page.up.sql b/sql/schema/05_posts_per_page.up.sql new file mode 100644 index 0000000..1bea8f9 --- /dev/null +++ b/sql/schema/05_posts_per_page.up.sql @@ -0,0 +1 @@ +ALTER TABLE sites ADD COLUMN posts_per_page INTEGER NOT NULL DEFAULT 10; diff --git a/views/posts/index.html b/views/posts/index.html index bbf445d..7786539 100644 --- a/views/posts/index.html +++ b/views/posts/index.html @@ -62,4 +62,22 @@ {{ end }} {{ end }} + + {{ if gt .pageInfo.TotalPages 1 }} + + {{ end }} \ No newline at end of file diff --git a/views/sitesettings/general.html b/views/sitesettings/general.html index ca3e7a9..6f1833b 100644 --- a/views/sitesettings/general.html +++ b/views/sitesettings/general.html @@ -41,6 +41,13 @@ +