Add option to filter for parameters on index

This commit is contained in:
Jan-Lukas Else 2023-09-07 17:01:53 +02:00
parent 272353ad7a
commit 9da5f0085a
8 changed files with 109 additions and 56 deletions

View File

@ -20,9 +20,9 @@ import (
func (a *goBlog) checkAllExternalLinks() error {
posts, err := a.getPosts(&postsRequestConfig{
status: []postStatus{statusPublished},
visibility: []postVisibility{visibilityPublic, visibilityUnlisted},
withoutParameters: true,
status: []postStatus{statusPublished},
visibility: []postVisibility{visibilityPublic, visibilityUnlisted},
fetchWithoutParams: true,
})
if err != nil {
return err

View File

@ -3,7 +3,6 @@ package main
import (
"io"
"net/http"
"strings"
"time"
"github.com/araddon/dateparse"
@ -24,14 +23,14 @@ const (
minJsonFeed feedType = "min.json"
)
func (a *goBlog) generateFeed(blog string, f feedType, w http.ResponseWriter, r *http.Request, posts []*post, title, description string) {
func (a *goBlog) generateFeed(blog string, f feedType, w http.ResponseWriter, r *http.Request, posts []*post, title, description, path, query string) {
now := time.Now()
title = a.renderMdTitle(defaultIfEmpty(title, a.cfg.Blogs[blog].Title))
description = defaultIfEmpty(description, a.cfg.Blogs[blog].Description)
feed := &feeds.Feed{
Title: title,
Description: description,
Link: &feeds.Link{Href: a.getFullAddress(strings.TrimSuffix(r.URL.Path, "."+string(f)))},
Link: &feeds.Link{Href: a.getFullAddress(path) + query},
Created: now,
Author: &feeds.Author{
Name: a.cfg.User.Name,

View File

@ -17,9 +17,9 @@ func (a *goBlog) serveGeoMap(w http.ResponseWriter, r *http.Request) {
canonical := a.getFullAddress(mapPath)
allPostsWithLocationRequestConfig := &postsRequestConfig{
blog: blog,
parameters: []string{a.cfg.Micropub.LocationParam, gpxParameter},
withOnlyParameters: []string{a.cfg.Micropub.LocationParam, gpxParameter},
blog: blog,
anyParams: []string{a.cfg.Micropub.LocationParam, gpxParameter},
fetchParams: []string{a.cfg.Micropub.LocationParam, gpxParameter},
}
allPostsWithLocationRequestConfig.status, allPostsWithLocationRequestConfig.visibility = a.getDefaultPostStates(r)
@ -58,8 +58,8 @@ func (a *goBlog) serveGeoMapTracks(w http.ResponseWriter, r *http.Request) {
allPostsWithTracksRequestConfig := &postsRequestConfig{
blog: blog,
parameters: []string{gpxParameter},
withOnlyParameters: []string{gpxParameter},
anyParams: []string{gpxParameter},
fetchParams: []string{gpxParameter},
excludeParameter: showRouteParam,
excludeParameterValue: "false", // Don't show hidden route tracks
}
@ -102,9 +102,9 @@ func (a *goBlog) serveGeoMapLocations(w http.ResponseWriter, r *http.Request) {
blog, _ := a.getBlog(r)
allPostsWithLocationRequestConfig := &postsRequestConfig{
blog: blog,
parameters: []string{a.cfg.Micropub.LocationParam},
withOnlyParameters: []string{a.cfg.Micropub.LocationParam},
blog: blog,
anyParams: []string{a.cfg.Micropub.LocationParam},
fetchParams: []string{a.cfg.Micropub.LocationParam},
}
allPostsWithLocationRequestConfig.status, allPostsWithLocationRequestConfig.visibility = a.getDefaultPostStates(r)

View File

@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/http"
"net/url"
"path"
"reflect"
"strings"
@ -314,12 +315,31 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
if len(visibility) == 0 {
visibility = defaultVisibility
}
// Parameter filter
params, paramValues := []string{}, []string{}
paramUrlValues := url.Values{}
for param, values := range r.URL.Query() {
if strings.HasPrefix(param, "p:") {
paramKey := strings.TrimPrefix(param, "p:")
for _, value := range values {
params, paramValues = append(params, paramKey), append(paramValues, value)
paramUrlValues.Add(param, value)
}
}
}
paramUrlQuery := ""
if len(paramUrlValues) > 0 {
paramUrlQuery += "?" + paramUrlValues.Encode()
}
// Create paginator
p := paginator.New(&postPaginationAdapter{config: &postsRequestConfig{
blog: blog,
sections: sections,
taxonomy: ic.tax,
taxonomyValue: ic.taxValue,
parameter: ic.parameter,
allParams: params,
allParamValues: paramValues,
search: ic.search,
publishedYear: ic.year,
publishedMonth: ic.month,
@ -356,11 +376,10 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
}
// Check if feed
if ft := feedType(chi.URLParam(r, "feed")); ft != noFeed {
a.generateFeed(blog, ft, w, r, posts, title, description)
a.generateFeed(blog, ft, w, r, posts, title, description, ic.path, paramUrlQuery)
return
}
// Navigation
path := ic.path
var hasPrev, hasNext bool
var prevPage, nextPage int
var prevPath, nextPath string
@ -371,9 +390,9 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
prevPage, _ = p.Page()
}
if prevPage < 2 {
prevPath = path
prevPath = ic.path
} else {
prevPath = fmt.Sprintf("%s/page/%d", strings.TrimSuffix(path, "/"), prevPage)
prevPath = fmt.Sprintf("%s/page/%d", strings.TrimSuffix(ic.path, "/"), prevPage)
}
hasNext, _ = p.HasNext()
if hasNext {
@ -381,23 +400,24 @@ func (a *goBlog) serveIndex(w http.ResponseWriter, r *http.Request) {
} else {
nextPage, _ = p.Page()
}
nextPath = fmt.Sprintf("%s/page/%d", strings.TrimSuffix(path, "/"), nextPage)
nextPath = fmt.Sprintf("%s/page/%d", strings.TrimSuffix(ic.path, "/"), nextPage)
summaryTemplate := ic.summaryTemplate
if summaryTemplate == "" {
summaryTemplate = defaultSummary
}
a.render(w, r, a.renderIndex, &renderData{
Canonical: a.getFullAddress(path),
Canonical: a.getFullAddress(ic.path) + paramUrlQuery,
Data: &indexRenderData{
title: title,
description: description,
posts: posts,
hasPrev: hasPrev,
hasNext: hasNext,
first: path,
first: ic.path,
prev: prevPath,
next: nextPath,
summaryTemplate: summaryTemplate,
paramUrlQuery: paramUrlQuery,
},
})
}

View File

@ -373,21 +373,23 @@ type postsRequestConfig struct {
visibility []postVisibility
taxonomy *configTaxonomy
taxonomyValue string
parameters []string // Ignores parameterValue
parameter string // Ignores parameters
parameterValue string
excludeParameter string // exclude posts that have a certain parameter (with non-empty value)
excludeParameterValue string // ... with exactly this value
anyParams []string // filter for posts that have any of these parameters (with non-empty values)
allParams []string // filter for posts that have all these parameters (with non-empty values)
allParamValues []string // ... with exactly these values
parameter string // filter for posts that have this parameter (with non-empty value)
parameterValue string // ... with exactly this value
excludeParameter string // exclude posts that have this parameter (with non-empty value)
excludeParameterValue string // ... with exactly this value
publishedYear, publishedMonth, publishedDay int
publishedBefore time.Time
randomOrder bool
priorityOrder bool
withoutParameters bool
withOnlyParameters []string
withoutRenderedTitle bool
fetchWithoutParams bool // fetch posts without parameters
fetchParams []string // only fetch these parameters
withoutRenderedTitle bool // fetch posts without rendered title
}
func buildPostsQuery(c *postsRequestConfig, selection string) (query string, args []any) {
func buildPostsQuery(c *postsRequestConfig, selection string) (query string, args []any, err error) {
queryBuilder := builderpool.Get()
defer builderpool.Put(queryBuilder)
// Selection
@ -437,21 +439,40 @@ func buildPostsQuery(c *postsRequestConfig, selection string) (query string, arg
queryBuilder.WriteString(" and blog = @blog")
args = append(args, sql.Named("blog", c.blog))
}
if c.parameter != "" {
if c.parameterValue != "" {
queryBuilder.WriteString(" and path in (select path from post_parameters where parameter = @param and value = @paramval)")
args = append(args, sql.Named("param", c.parameter), sql.Named("paramval", c.parameterValue))
} else {
queryBuilder.WriteString(" and path in (select path from post_parameters where parameter = @param and length(coalesce(value, '')) > 0)")
args = append(args, sql.Named("param", c.parameter))
allParams := append(c.allParams, c.parameter)
allParamValues := append(c.allParamValues, c.parameterValue)
if len(allParams) > 0 {
if len(allParamValues) > 0 && len(allParamValues) != len(allParams) {
return "", nil, errors.New("number of parameters != number of parameter values")
}
} else if len(c.parameters) > 0 {
for i, param := range allParams {
if param == "" {
continue
}
named := "allparam" + strconv.Itoa(i)
paramValue := allParamValues[i]
queryBuilder.WriteString(" and path in (select path from post_parameters where parameter = @")
queryBuilder.WriteString(named)
queryBuilder.WriteString(" and ")
if paramValue != "" {
namedVal := "allparamval" + strconv.Itoa(i)
queryBuilder.WriteString("value = @")
queryBuilder.WriteString(namedVal)
args = append(args, sql.Named(namedVal, paramValue))
} else {
queryBuilder.WriteString("length(coalesce(value, '')) > 0")
}
queryBuilder.WriteString(")")
args = append(args, sql.Named(named, param))
}
}
if len(c.anyParams) > 0 {
queryBuilder.WriteString(" and path in (select path from post_parameters where parameter in (")
for i, param := range c.parameters {
for i, param := range c.anyParams {
if i > 0 {
queryBuilder.WriteString(", ")
}
named := "param" + strconv.Itoa(i)
named := "anyparam" + strconv.Itoa(i)
queryBuilder.WriteString("@")
queryBuilder.WriteString(named)
args = append(args, param)
@ -514,7 +535,7 @@ func buildPostsQuery(c *postsRequestConfig, selection string) (query string, arg
queryBuilder.WriteString(" limit @limit offset @offset")
args = append(args, sql.Named("limit", c.limit), sql.Named("offset", c.offset))
}
return queryBuilder.String(), args
return queryBuilder.String(), args, nil
}
func (d *database) loadPostParameters(posts []*post, parameters ...string) (err error) {
@ -582,7 +603,10 @@ func (d *database) loadPostParameters(posts []*post, parameters ...string) (err
func (a *goBlog) getPosts(config *postsRequestConfig) (posts []*post, err error) {
// Query posts
query, queryParams := buildPostsQuery(config, "path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), blog, coalesce(section, ''), status, visibility, priority")
query, queryParams, err := buildPostsQuery(config, "path, coalesce(content, ''), coalesce(published, ''), coalesce(updated, ''), blog, coalesce(section, ''), status, visibility, priority")
if err != nil {
return nil, err
}
rows, err := a.db.Query(query, queryParams...)
if err != nil {
return nil, err
@ -608,8 +632,8 @@ func (a *goBlog) getPosts(config *postsRequestConfig) (posts []*post, err error)
}
posts = append(posts, p)
}
if !config.withoutParameters {
err = a.db.loadPostParameters(posts, config.withOnlyParameters...)
if !config.fetchWithoutParams {
err = a.db.loadPostParameters(posts, config.fetchParams...)
if err != nil {
return nil, err
}
@ -636,7 +660,10 @@ func (a *goBlog) getPost(path string) (*post, error) {
}
func (d *database) countPosts(config *postsRequestConfig) (count int, err error) {
query, params := buildPostsQuery(config, "path")
query, params, err := buildPostsQuery(config, "path")
if err != nil {
return
}
row, err := d.QueryRow("select count(distinct path) from ("+query+")", params...)
if err != nil {
return
@ -647,7 +674,7 @@ func (d *database) countPosts(config *postsRequestConfig) (count int, err error)
func (a *goBlog) getRandomPostPath(blog string) (path string, err error) {
sections := lo.Keys(a.cfg.Blogs[blog].Sections)
query, params := buildPostsQuery(&postsRequestConfig{
query, params, err := buildPostsQuery(&postsRequestConfig{
randomOrder: true,
limit: 1,
blog: blog,
@ -655,6 +682,9 @@ func (a *goBlog) getRandomPostPath(blog string) (path string, err error) {
visibility: []postVisibility{visibilityPublic},
status: []postStatus{statusPublished},
}, "path")
if err != nil {
return
}
row, err := a.db.QueryRow(query, params...)
if err != nil {
return

View File

@ -79,7 +79,7 @@ func Test_postsDb(t *testing.T) {
is.Equal(0, count)
// Check by multiple parameters
count, err = app.db.countPosts(&postsRequestConfig{parameters: []string{"tags", "title"}})
count, err = app.db.countPosts(&postsRequestConfig{anyParams: []string{"tags", "title"}})
must.NoError(err)
is.Equal(1, count)

View File

@ -162,10 +162,10 @@ func (a *goBlog) serveSitemapBlogPosts(w http.ResponseWriter, r *http.Request) {
// Request posts
blog, _ := a.getBlog(r)
posts, _ := a.getPosts(&postsRequestConfig{
status: []postStatus{statusPublished},
visibility: []postVisibility{visibilityPublic},
blog: blog,
withoutParameters: true,
status: []postStatus{statusPublished},
visibility: []postVisibility{visibilityPublic},
blog: blog,
fetchWithoutParams: true,
})
// Add posts to sitemap
for _, p := range posts {
@ -220,12 +220,15 @@ select distinct '/x/x/' || day from alldates;
`
func (a *goBlog) sitemapDatePaths(blog string, sections []string) (paths []string, err error) {
query, args := buildPostsQuery(&postsRequestConfig{
query, args, err := buildPostsQuery(&postsRequestConfig{
blog: blog,
sections: sections,
status: []postStatus{statusPublished},
visibility: []postVisibility{visibilityPublic},
}, "published")
if err != nil {
return
}
rows, err := a.db.Query(fmt.Sprintf(sitemapDatePathsSql, query), args...)
if err != nil {
return nil, err

9
ui.go
View File

@ -340,6 +340,7 @@ type indexRenderData struct {
posts []*post
hasPrev, hasNext bool
first, prev, next string
paramUrlQuery string
summaryTemplate summaryTyp
}
@ -359,9 +360,9 @@ func (a *goBlog) renderIndex(hb *htmlbuilder.HtmlBuilder, rd *renderData) {
if renderedIndexTitle != "" {
feedTitle = " (" + renderedIndexTitle + ")"
}
hb.WriteElementOpen("link", "rel", "alternate", "type", "application/rss+xml", "title", "RSS"+feedTitle, "href", a.getFullAddress(id.first+".rss"))
hb.WriteElementOpen("link", "rel", "alternate", "type", "application/atom+xml", "title", "ATOM"+feedTitle, "href", a.getFullAddress(id.first+".atom"))
hb.WriteElementOpen("link", "rel", "alternate", "type", "application/feed+json", "title", "JSON Feed"+feedTitle, "href", a.getFullAddress(id.first+".json"))
hb.WriteElementOpen("link", "rel", "alternate", "type", "application/rss+xml", "title", "RSS"+feedTitle, "href", a.getFullAddress(id.first+".rss")+id.paramUrlQuery)
hb.WriteElementOpen("link", "rel", "alternate", "type", "application/atom+xml", "title", "ATOM"+feedTitle, "href", a.getFullAddress(id.first+".atom")+id.paramUrlQuery)
hb.WriteElementOpen("link", "rel", "alternate", "type", "application/feed+json", "title", "JSON Feed"+feedTitle, "href", a.getFullAddress(id.first+".json")+id.paramUrlQuery)
},
func(hb *htmlbuilder.HtmlBuilder) {
hb.WriteElementOpen("main", "class", "h-feed")
@ -393,7 +394,7 @@ func (a *goBlog) renderIndex(hb *htmlbuilder.HtmlBuilder, rd *renderData) {
hb.WriteElementClose("p")
}
// Navigation
a.renderPagination(hb, rd.Blog, id.hasPrev, id.hasNext, id.prev, id.next)
a.renderPagination(hb, rd.Blog, id.hasPrev, id.hasNext, id.prev+id.paramUrlQuery, id.next+id.paramUrlQuery)
// Author
a.renderAuthor(hb)
hb.WriteElementClose("main")