mirror of https://github.com/jlelse/GoBlog
Add option to filter for parameters on index
parent
272353ad7a
commit
9da5f0085a
6
check.go
6
check.go
|
@ -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
|
||||
|
|
5
feeds.go
5
feeds.go
|
@ -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,
|
||||
|
|
16
geoMap.go
16
geoMap.go
|
@ -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)
|
||||
|
||||
|
|
34
posts.go
34
posts.go
|
@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
80
postsDb.go
80
postsDb.go
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
13
sitemap.go
13
sitemap.go
|
@ -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
9
ui.go
|
@ -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")
|
||||
|
|
Loading…
Reference in New Issue