mirror of https://github.com/jlelse/GoBlog
ActivityStreams variant for posts
This commit is contained in:
parent
9002142524
commit
b50f4f55bb
|
@ -0,0 +1,95 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/araddon/dateparse"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type asPost struct {
|
||||
Context []string `json:"@context"`
|
||||
To []string `json:"to"`
|
||||
InReplyTo string `json:"inReplyTo,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Content string `json:"content"`
|
||||
MediaType string `json:"mediaType"`
|
||||
Attachment []*asAttachment `json:"attachment,omitempty"`
|
||||
Published string `json:"published"`
|
||||
Updated string `json:"updated,omitempty"`
|
||||
Id string `json:"id"`
|
||||
Url string `json:"url"`
|
||||
AttributedTo string `json:"attributedTo"`
|
||||
}
|
||||
|
||||
type asAttachment struct {
|
||||
Type string `json:"type"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
func servePostActivityStreams(w http.ResponseWriter, r *http.Request) {
|
||||
// Remove ".as" from path again
|
||||
r.URL.Path = strings.TrimSuffix(r.URL.Path, ".as")
|
||||
// Fetch post from db
|
||||
post, err := getPost(r.Context(), slashTrimmedPath(r))
|
||||
if err == errPostNotFound {
|
||||
serve404(w, r)
|
||||
return
|
||||
} else if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Create a Note object
|
||||
as := &asPost{
|
||||
Context: []string{"https://www.w3.org/ns/activitystreams"},
|
||||
To: []string{"https://www.w3.org/ns/activitystreams#Public"},
|
||||
MediaType: "text/html",
|
||||
Id: appConfig.Server.PublicAddress + post.Path,
|
||||
Url: appConfig.Server.PublicAddress + post.Path,
|
||||
AttributedTo: appConfig.Server.PublicAddress,
|
||||
}
|
||||
// Name and Type
|
||||
if title := post.title(); title != "" {
|
||||
as.Name = title
|
||||
as.Type = "Article"
|
||||
} else {
|
||||
as.Type = "Note"
|
||||
}
|
||||
// Content
|
||||
if rendered, err := renderMarkdown(post.Content); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
} else {
|
||||
as.Content = string(rendered)
|
||||
}
|
||||
// Attachments
|
||||
if images := post.Parameters[appConfig.Blog.ActivityStreams.ImagesParameter]; len(images) > 0 {
|
||||
for _, image := range images {
|
||||
as.Attachment = append(as.Attachment, &asAttachment{
|
||||
Type: "Image",
|
||||
Url: image,
|
||||
})
|
||||
}
|
||||
}
|
||||
// Dates
|
||||
dateFormat := "2006-01-02T15:04:05-07:00"
|
||||
if post.Published != "" {
|
||||
if t, err := dateparse.ParseIn(post.Published, time.Local); err == nil {
|
||||
as.Published = t.Format(dateFormat)
|
||||
}
|
||||
}
|
||||
if post.Updated != "" {
|
||||
if t, err := dateparse.ParseIn(post.Updated, time.Local); err == nil {
|
||||
as.Published = t.Format(dateFormat)
|
||||
}
|
||||
}
|
||||
// Reply
|
||||
if replyLink := post.firstParameter(appConfig.Blog.ActivityStreams.ReplyParameter); replyLink != "" {
|
||||
as.InReplyTo = replyLink
|
||||
}
|
||||
// Send JSON
|
||||
w.Header().Add("Content-Type", contentTypeJSON)
|
||||
_ = json.NewEncoder(w).Encode(as)
|
||||
}
|
12
cache.go
12
cache.go
|
@ -16,6 +16,18 @@ func initCache() {
|
|||
cacheMutexes = map[string]*sync.Mutex{}
|
||||
}
|
||||
|
||||
func cacheWithCheckMiddleware(cache func(r *http.Request) bool) func(next http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
if cache(request) {
|
||||
cacheMiddleware(next).ServeHTTP(writer, request)
|
||||
} else {
|
||||
next.ServeHTTP(writer, request)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func cacheMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requestUrl, _ := url.ParseRequestURI(r.RequestURI)
|
||||
|
|
11
config.go
11
config.go
|
@ -51,6 +51,8 @@ type configBlog struct {
|
|||
Menus map[string]*menu `mapstructure:"menus"`
|
||||
// Photos
|
||||
Photos *photos `mapstructure:"photos"`
|
||||
// ActivityStreams
|
||||
ActivityStreams *activityStreams `mapstructure:"activitystreams"`
|
||||
}
|
||||
|
||||
type section struct {
|
||||
|
@ -82,6 +84,12 @@ type photos struct {
|
|||
Description string `mapstructure:"description"`
|
||||
}
|
||||
|
||||
type activityStreams struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
ReplyParameter string `mapstructure:"replyParameter"`
|
||||
ImagesParameter string `mapstructure:"imagesParameter"`
|
||||
}
|
||||
|
||||
type configUser struct {
|
||||
Nick string `mapstructure:"nick"`
|
||||
Name string `mapstructure:"name"`
|
||||
|
@ -134,6 +142,9 @@ func initConfig() error {
|
|||
viper.SetDefault("blog.photos.path", "/photos")
|
||||
viper.SetDefault("blog.photos.title", "Photos")
|
||||
viper.SetDefault("blog.photos.description", "Photos on this blog")
|
||||
viper.SetDefault("blog.activitystreams.enabled", false)
|
||||
viper.SetDefault("blog.activitystreams.replyParameter", "replylink")
|
||||
viper.SetDefault("blog.activitystreams.imagesParameter", "images")
|
||||
viper.SetDefault("user.nick", "admin")
|
||||
viper.SetDefault("user.name", "Admin")
|
||||
viper.SetDefault("user.password", "secret")
|
||||
|
|
|
@ -36,6 +36,10 @@ blog:
|
|||
path: /photos
|
||||
title: Photos
|
||||
description: "Photos on this blog"
|
||||
activitystreams:
|
||||
enable: true
|
||||
replyParameter: replylink
|
||||
imagesParameter: images
|
||||
user:
|
||||
nick: admin
|
||||
name: Admin
|
||||
|
|
12
go.mod
12
go.mod
|
@ -18,7 +18,7 @@ require (
|
|||
github.com/kyokomi/emoji v2.2.4+incompatible
|
||||
github.com/lib/pq v1.8.0 // indirect
|
||||
github.com/lopezator/migrator v0.3.0
|
||||
github.com/magiconair/properties v1.8.3 // indirect
|
||||
github.com/magiconair/properties v1.8.4 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.3
|
||||
github.com/miekg/dns v1.1.31 // indirect
|
||||
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||
|
@ -32,17 +32,17 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/viper v1.7.1
|
||||
github.com/stretchr/testify v1.6.1 // indirect
|
||||
github.com/tdewolff/minify/v2 v2.9.4
|
||||
github.com/vcraescu/go-paginator v0.0.0-20200304054438-86d84f27c0b3
|
||||
github.com/tdewolff/minify/v2 v2.9.5
|
||||
github.com/vcraescu/go-paginator v0.0.0-20200923074551-426b20f3ae8a
|
||||
github.com/yuin/goldmark v1.2.1
|
||||
github.com/yuin/goldmark-emoji v1.0.1
|
||||
go.uber.org/multierr v1.6.0 // indirect
|
||||
go.uber.org/zap v1.16.0 // indirect
|
||||
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73 // indirect
|
||||
golang.org/x/sys v0.0.0-20200922070232-aee5d888a860 // indirect
|
||||
golang.org/x/tools v0.0.0-20200921210052-fa0125251cc4 // indirect
|
||||
golang.org/x/net v0.0.0-20200923182212-328152dc79b1 // indirect
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d // indirect
|
||||
golang.org/x/tools v0.0.0-20200923182640-463111b69878 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
|
||||
gopkg.in/ini.v1 v1.61.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||
|
|
28
go.sum
28
go.sum
|
@ -170,8 +170,8 @@ github.com/lopezator/migrator v0.3.0 h1:VW/rR+J8NYwPdkBxjrFdjwejpgvP59LbmANJxXuN
|
|||
github.com/lopezator/migrator v0.3.0/go.mod h1:bpVAVPkWSvTw8ya2Pk7E/KiNAyDWNImgivQY79o8/8I=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.3 h1:kJSsc6EXkBLgr3SphHk9w5mtjn0bjlR4JYEXKrJ45rQ=
|
||||
github.com/magiconair/properties v1.8.3/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/magiconair/properties v1.8.4 h1:8KGKTcQQGm0Kv7vEbKFErAoAOFyyacLStRtQSeYtvkY=
|
||||
github.com/magiconair/properties v1.8.4/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
|
||||
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
|
@ -270,15 +270,15 @@ 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.4 h1:sOqgmowmkZWmHZ0AqIFS300VvCCkgDNTw1eWw1tnNCY=
|
||||
github.com/tdewolff/minify/v2 v2.9.4/go.mod h1:4SrPavRSPLpv4U4jqV8jzSjiEuq2BH+BPgxorMkGrhc=
|
||||
github.com/tdewolff/parse/v2 v2.5.2 h1:OIUAejEkj9Oj6N1q18xg7ByYkpQ0xf4nA1aAH5nqxks=
|
||||
github.com/tdewolff/parse/v2 v2.5.2/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/minify/v2 v2.9.5 h1:+fHvqLencVdv14B+zgxQGhetF9qXl/nRTN/1mcyQwpM=
|
||||
github.com/tdewolff/minify/v2 v2.9.5/go.mod h1:jshtBj/uUJH6JX1fuxTLnnHOA1RVJhF5MM+leJzDKb4=
|
||||
github.com/tdewolff/parse/v2 v2.5.3 h1:fnPIstKgEfxd3+wwHnH73sAYydsR0o/jYhcQ6c5PkrA=
|
||||
github.com/tdewolff/parse/v2 v2.5.3/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho=
|
||||
github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4=
|
||||
github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/vcraescu/go-paginator v0.0.0-20200304054438-86d84f27c0b3 h1:bPXD4QZj4+7QflTJRcIvh/6HqE5L48Msc3XBPsyVtzc=
|
||||
github.com/vcraescu/go-paginator v0.0.0-20200304054438-86d84f27c0b3/go.mod h1:sHc8LeBbnKYptJK1WULqJfvqW1SWNzjPAFigjSV/wf4=
|
||||
github.com/vcraescu/go-paginator v0.0.0-20200923074551-426b20f3ae8a h1:aoulfbnIH3sVn80BQxrYKEBkVlbkixGCXQBQlHQT/04=
|
||||
github.com/vcraescu/go-paginator v0.0.0-20200923074551-426b20f3ae8a/go.mod h1:sHc8LeBbnKYptJK1WULqJfvqW1SWNzjPAFigjSV/wf4=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.2.1 h1:ruQGxdhGHe7FWOJPT0mKs5+pD2Xs1Bm/kdGlHO04FmM=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
@ -362,8 +362,8 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7
|
|||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73 h1:MXfv8rhZWmFeqX3GNZRsd6vOLoaCHjYEX3qkRo3YBUA=
|
||||
golang.org/x/net v0.0.0-20200904194848-62affa334b73/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/net v0.0.0-20200923182212-328152dc79b1 h1:Iu68XRPd67wN4aRGGWwwq6bZo/25jR6uu52l/j2KkUE=
|
||||
golang.org/x/net v0.0.0-20200923182212-328152dc79b1/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
|
@ -393,8 +393,8 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c h1:UIcGWL6/wpCfyGuJnRFJRurA+yj8RrW7Q6x2YMCXt6c=
|
||||
golang.org/x/sys v0.0.0-20200724161237-0e2f3a69832c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200922070232-aee5d888a860 h1:YEu4SMq7D0cmT7CBbXfcH0NZeuChAXwsHe/9XueUO6o=
|
||||
golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d h1:L/IKR6COd7ubZrs2oTnTi73IhgqJ71c9s80WsQnh0Es=
|
||||
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
|
@ -426,8 +426,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
|
|||
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200921210052-fa0125251cc4 h1:v8Jgq9X6Es9K9otVr9jxENEJigepKMZgA9OmrIZDtFA=
|
||||
golang.org/x/tools v0.0.0-20200921210052-fa0125251cc4/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/tools v0.0.0-20200923182640-463111b69878 h1:VUw1+Jf6KJPf82mbTQMia6HCnNMv2BbAipkEZ4KTcqQ=
|
||||
golang.org/x/tools v0.0.0-20200923182640-463111b69878/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
|
|
9
http.go
9
http.go
|
@ -76,7 +76,14 @@ func buildHandler() (http.Handler, error) {
|
|||
}
|
||||
for _, path := range allPostPaths {
|
||||
if path != "" {
|
||||
r.With(cacheMiddleware, minifier.Middleware).Get(path, servePost)
|
||||
r.With(cacheWithCheckMiddleware(func(r *http.Request) bool {
|
||||
if lowerAccept := strings.ToLower(r.Header.Get("Accept")); (strings.Contains(lowerAccept, "application/activity+json") || strings.Contains(lowerAccept, "application/ld+json")) &&
|
||||
!strings.Contains(lowerAccept, "text/html") {
|
||||
// Is ActivityStream, add ".as" to differentiate cache and also trigger as function
|
||||
r.URL.Path += ".as"
|
||||
}
|
||||
return true
|
||||
}), minifier.Middleware).Get(path, servePost)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
5
posts.go
5
posts.go
|
@ -10,6 +10,7 @@ import (
|
|||
"net/http"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var errPostNotFound = errors.New("post not found")
|
||||
|
@ -23,6 +24,10 @@ type Post struct {
|
|||
}
|
||||
|
||||
func servePost(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(r.URL.Path, ".as") {
|
||||
servePostActivityStreams(w, r)
|
||||
return
|
||||
}
|
||||
path := slashTrimmedPath(r)
|
||||
post, err := getPost(r.Context(), path)
|
||||
if err == errPostNotFound {
|
||||
|
|
Loading…
Reference in New Issue