Add RSS/Atom/JSON Feed

This commit is contained in:
Jan-Lukas Else 2020-09-02 17:48:37 +02:00
parent 4cc33c91db
commit 341d9ab3f5
8 changed files with 101 additions and 18 deletions

59
feeds.go Normal file
View File

@ -0,0 +1,59 @@
package main
import (
"github.com/gorilla/feeds"
"net/http"
"time"
)
type feedType string
const (
RSS feedType = "rss"
ATOM feedType = "atom"
JSON feedType = "json"
)
func generateFeed(f feedType, w http.ResponseWriter, posts []*Post, title string, description string) {
now := time.Now()
feed := &feeds.Feed{
Title: title,
Description: description,
Link: &feeds.Link{},
Created: now,
}
for _, postItem := range posts {
htmlContent, _ := renderMarkdown(postItem.Content)
feed.Add(&feeds.Item{
Title: postItem.title(),
Link: &feeds.Link{Href: postItem.Path},
Description: string(htmlContent),
Id: postItem.Path,
Content: string(htmlContent),
})
}
var feedStr string
var err error
switch f {
case RSS:
feedStr, err = feed.ToRss()
case ATOM:
feedStr, err = feed.ToAtom()
case JSON:
feedStr, err = feed.ToJSON()
}
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
switch f {
case RSS:
w.Header().Add("Content-Type", "application/rss+xml; charset=utf-8")
case ATOM:
w.Header().Add("Content-Type", "application/atom+xml; charset=utf-8")
case JSON:
w.Header().Add("Content-Type", "application/feed+json; charset=utf-8")
}
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(feedStr))
}

6
go.mod
View File

@ -9,8 +9,10 @@ require (
github.com/go-chi/chi v4.1.2+incompatible
github.com/google/go-cmp v0.5.2 // indirect
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 // indirect
github.com/gorilla/feeds v1.1.1
github.com/jinzhu/gorm v1.9.16 // indirect
github.com/klauspost/cpuid v1.3.1 // indirect
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/kyokomi/emoji v2.2.4+incompatible
github.com/lib/pq v1.8.0 // indirect
@ -26,7 +28,7 @@ require (
github.com/spf13/cast v1.3.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/viper v1.7.1
github.com/tdewolff/minify/v2 v2.9.2
github.com/tdewolff/minify/v2 v2.9.3
github.com/vcraescu/go-paginator v0.0.0-20200304054438-86d84f27c0b3
github.com/yuin/goldmark v1.2.1
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
@ -34,7 +36,7 @@ require (
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect
golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
gopkg.in/ini.v1 v1.60.2 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 // indirect

12
go.sum
View File

@ -183,6 +183,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00 h1:l5lAOZEym3oK3SQ2HBHWsJUfbNBiTXJDeW2QDxw9AQ0=
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/feeds v1.1.1 h1:HwKXxqzcRNg9to+BbvJog4+f3s/xzvtZXICcQGutYfY=
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
@ -251,6 +253,8 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
@ -412,8 +416,8 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tdewolff/minify/v2 v2.9.2 h1:paeFMZEtrmWVJSmvZ+htMQ5RhbkBbLVyXdzs6f8Es+s=
github.com/tdewolff/minify/v2 v2.9.2/go.mod h1:njYNbXhVTAhI1hARVHCbHAgRd44j+AEt0LdW+menKsY=
github.com/tdewolff/minify/v2 v2.9.3 h1:5RMoa8DAqfI/Gne9rM1hnsQXZax24dnZhcQWHOFD7f4=
github.com/tdewolff/minify/v2 v2.9.3/go.mod h1:njYNbXhVTAhI1hARVHCbHAgRd44j+AEt0LdW+menKsY=
github.com/tdewolff/parse/v2 v2.5.1 h1:1PxbcgMxb8RWH1nmeQjBC+lZIOTUEjiYQ3u8RpzndN0=
github.com/tdewolff/parse/v2 v2.5.1/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
@ -682,8 +686,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/h2non/gock.v1 v1.0.15/go.mod h1:sX4zAkdYX1TRGJ2JY156cFspQn4yRWn6p9EMdODlynE=

View File

@ -110,10 +110,9 @@ func buildHandler() (http.Handler, error) {
}
}
routePatterns := routesToStringSlice(r.Routes())
rootPath := "/"
blogPath := "/blog"
if !routePatterns.has(rootPath) {
if routePatterns := routesToStringSlice(r.Routes()); !routePatterns.has(rootPath) {
r.With(cacheMiddleware, minifier.Middleware).Get(rootPath, serveHome(rootPath))
r.With(cacheMiddleware, minifier.Middleware).Get(paginationPath, serveHome(rootPath))
} else if !routePatterns.has(blogPath) {

View File

@ -5,7 +5,8 @@ import (
"github.com/tdewolff/minify/v2/css"
"github.com/tdewolff/minify/v2/html"
"github.com/tdewolff/minify/v2/js"
"regexp"
"github.com/tdewolff/minify/v2/json"
"github.com/tdewolff/minify/v2/xml"
)
var minifier *minify.M
@ -14,5 +15,8 @@ func initMinify() {
minifier = minify.New()
minifier.AddFunc("text/html", html.Minify)
minifier.AddFunc("text/css", css.Minify)
minifier.AddFuncRegexp(regexp.MustCompile("^(application|text)/(x-)?(java|ecma)script$"), js.Minify)
minifier.AddFunc("application/javascript", js.Minify)
minifier.AddFunc("application/rss+xml", xml.Minify)
minifier.AddFunc("application/atom+xml", xml.Minify)
minifier.AddFunc("application/feed+json", json.Minify)
}

View File

@ -23,6 +23,17 @@ type Post struct {
Parameters map[string][]string `json:"parameters"`
}
func (p *Post) firstParameter(parameter string) (result string) {
if pp := p.Parameters[parameter]; len(pp) > 0 {
result = pp[0]
}
return
}
func (p *Post) title() string {
return p.firstParameter("title")
}
func servePost(w http.ResponseWriter, r *http.Request) {
path := slashTrimmedPath(r)
post, err := getPost(r.Context(), path)
@ -140,6 +151,11 @@ func serveIndex(path string, sec *section, tax *taxonomy, taxonomyValue string)
title = appConfig.Blog.Title
description = appConfig.Blog.Description
}
f := feedType(r.URL.Query().Get("feed"))
if f != "" {
generateFeed(f, w, posts, title, description)
return
}
render(w, templateIndex, &indexTemplateData{
Title: title,
Description: description,

View File

@ -32,17 +32,16 @@ func initRendering() {
return template.HTML(htmlContent)
},
// First parameter value
"p": func(post Post, parameter string) string {
if len(post.Parameters[parameter]) > 0 {
return post.Parameters[parameter][0]
} else {
return ""
}
"p": func(post *Post, parameter string) string {
return post.firstParameter(parameter)
},
// All parameter values
"ps": func(post Post, parameter string) []string {
"ps": func(post *Post, parameter string) []string {
return post.Parameters[parameter]
},
"title": func(post *Post) string {
return post.title()
},
"include": func(templateName string, data interface{}) (template.HTML, error) {
buf := new(bytes.Buffer)
err := templates[templateName].ExecuteTemplate(buf, templateName, data)

View File

@ -5,7 +5,7 @@
{{ define "main" }}
<main class=h-entry>
<article>
{{ with p . "title" }}<h1 class=p-name>{{ . }}</h1>{{ end }}
{{ with title . }}<h1 class=p-name>{{ . }}</h1>{{ end }}
{{ with .Content }}
<div class=e-content>{{ md . }}</div>
{{ end }}