From 99f8c1188ccd1580f58ad4c21cece040ed8e874c Mon Sep 17 00:00:00 2001 From: Brian Picciano Date: Fri, 20 May 2022 17:24:52 -0600 Subject: [PATCH] Add RSS feed generator --- config.nix | 2 +- srv/default.nix | 7 ++-- srv/src/http/api.go | 21 ++++++++-- srv/src/http/feed.go | 63 ++++++++++++++++++++++++++++++ srv/src/mailinglist/mailinglist.go | 5 ++- 5 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 srv/src/http/feed.go diff --git a/config.nix b/config.nix index 674ba37..5f4bc13 100644 --- a/config.nix +++ b/config.nix @@ -1,11 +1,11 @@ { runDir = "/tmp/mediocre-blog/run"; dataDir = "/tmp/mediocre-blog/data"; + publicURL = "http://localhost:4000"; powSecret = "ssshhh"; mlSMTPAddr = ""; mlSMTPAuth = ""; - mlPublicURL = "http://localhost:4000"; httpListenProto = "tcp"; httpListenAddr = ":4000"; diff --git a/srv/default.nix b/srv/default.nix index 7ca70e8..2f7d58c 100644 --- a/srv/default.nix +++ b/srv/default.nix @@ -16,7 +16,7 @@ # mailing list export MEDIOCRE_BLOG_ML_SMTP_ADDR="${config.mlSMTPAddr}" export MEDIOCRE_BLOG_ML_SMTP_AUTH="${config.mlSMTPAuth}" - export MEDIOCRE_BLOG_ML_PUBLIC_URL="${config.mlPublicURL}" + export MEDIOCRE_BLOG_ML_PUBLIC_URL="${config.publicURL}" # redis export MEDIOCRE_BLOG_REDIS_PROTO=unix @@ -26,8 +26,9 @@ export MEDIOCRE_BLOG_POW_SECRET="${config.powSecret}" # http - export MEDIOCRE_BLOG_LISTEN_PROTO="${config.httpListenProto}" - export MEDIOCRE_BLOG_LISTEN_ADDR="${config.httpListenAddr}" + export MEDIOCRE_BLOG_HTTP_PUBLIC_URL="${config.publicURL}" + export MEDIOCRE_BLOG_HTTP_LISTEN_PROTO="${config.httpListenProto}" + export MEDIOCRE_BLOG_HTTP_LISTEN_ADDR="${config.httpListenAddr}" export MEDIOCRE_BLOG_HTTP_AUTH_USERS='${builtins.toJSON config.httpAuthUsers}' export MEDIOCRE_BLOG_HTTP_AUTH_RATELIMIT='${config.httpAuthRatelimit}' ''; diff --git a/srv/src/http/api.go b/srv/src/http/api.go index ab0e972..83ea8ab 100644 --- a/srv/src/http/api.go +++ b/srv/src/http/api.go @@ -10,7 +10,9 @@ import ( "html/template" "net" "net/http" + "net/url" "os" + "strings" "time" "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg" @@ -40,6 +42,9 @@ type Params struct { GlobalRoom chat.Room UserIDCalculator *chat.UserIDCalculator + // PublicURL is the base URL which site visitors can navigate to. + PublicURL *url.URL + // ListenProto and ListenAddr are passed into net.Listen to create the // API's listener. Both "tcp" and "unix" protocols are explicitly // supported. @@ -57,6 +62,9 @@ type Params struct { // SetupCfg implement the cfg.Cfger interface. func (p *Params) SetupCfg(cfg *cfg.Cfg) { + + publicURLStr := cfg.String("http-public-url", "http://localhost:4000", "URL this service is accessible at") + cfg.StringVar(&p.ListenProto, "http-listen-proto", "tcp", "Protocol to listen for HTTP requests with") cfg.StringVar(&p.ListenAddr, "http-listen-addr", ":4000", "Address/path to listen for HTTP requests on") @@ -76,15 +84,21 @@ func (p *Params) SetupCfg(cfg *cfg.Cfg) { return fmt.Errorf("unmarshaling -http-auth-ratelimit: %w", err) } + *publicURLStr = strings.TrimSuffix(*publicURLStr, "/") + if p.PublicURL, err = url.Parse(*publicURLStr); err != nil { + return fmt.Errorf("parsing -http-public-url: %w", err) + } + return nil }) } // Annotate implements mctx.Annotator interface. func (p *Params) Annotate(a mctx.Annotations) { - a["listenProto"] = p.ListenProto - a["listenAddr"] = p.ListenAddr - a["authRatelimit"] = p.AuthRatelimit + a["httpPublicURL"] = p.PublicURL + a["httpListenProto"] = p.ListenProto + a["httpListenAddr"] = p.ListenAddr + a["httpAuthRatelimit"] = p.AuthRatelimit } // API will listen on the port configured for it, and serve HTTP requests for @@ -218,6 +232,7 @@ func (a *api) handler() http.Handler { mux.Handle("/static/", http.FileServer(http.FS(staticFS))) mux.Handle("/follow", a.renderDumbTplHandler("follow.html")) + mux.Handle("/feed.xml", a.renderFeedHandler()) mux.Handle("/", a.renderIndexHandler()) var globalHandler http.Handler = mux diff --git a/srv/src/http/feed.go b/srv/src/http/feed.go new file mode 100644 index 0000000..8c0ef1c --- /dev/null +++ b/srv/src/http/feed.go @@ -0,0 +1,63 @@ +package http + +import ( + "fmt" + "net/http" + "path/filepath" + + "github.com/gorilla/feeds" + "github.com/mediocregopher/blog.mediocregopher.com/srv/http/apiutil" +) + +func (a *api) renderFeedHandler() http.Handler { + + return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { + + author := &feeds.Author{ + Name: "mediocregopher", + } + + publicURL := a.params.PublicURL.String() + + feed := feeds.Feed{ + Title: "Mediocre Blog", + Link: &feeds.Link{Href: publicURL + "/"}, + Description: "A mix of tech, art, travel, and who knows what else.", + Author: author, + } + + recentPosts, _, err := a.params.PostStore.WithOrderDesc().Get(0, 20) + if err != nil { + apiutil.InternalServerError(rw, r, fmt.Errorf("fetching recent posts: %w", err)) + return + } + + for _, post := range recentPosts { + + if post.PublishedAt.After(feed.Updated) { + feed.Updated = post.PublishedAt + } + + if post.LastUpdatedAt.After(feed.Updated) { + feed.Updated = post.LastUpdatedAt + } + + postURL := publicURL + filepath.Join("/posts", post.ID) + + feed.Items = append(feed.Items, &feeds.Item{ + Title: post.Title, + Link: &feeds.Link{Href: postURL}, + Author: author, + Description: post.Description, + Id: postURL, + Updated: post.LastUpdatedAt, + Created: post.PublishedAt, + }) + } + + if err := feed.WriteAtom(rw); err != nil { + apiutil.InternalServerError(rw, r, fmt.Errorf("writing atom feed: %w", err)) + return + } + }) +} diff --git a/srv/src/mailinglist/mailinglist.go b/srv/src/mailinglist/mailinglist.go index fc6e014..28b44b0 100644 --- a/srv/src/mailinglist/mailinglist.go +++ b/srv/src/mailinglist/mailinglist.go @@ -57,8 +57,9 @@ func (p *Params) SetupCfg(cfg *cfg.Cfg) { cfg.OnInit(func(ctx context.Context) error { var err error + *publicURLStr = strings.TrimSuffix(*publicURLStr, "/") if p.PublicURL, err = url.Parse(*publicURLStr); err != nil { - return fmt.Errorf("parsing -public-url: %w", err) + return fmt.Errorf("parsing -ml-public-url: %w", err) } return nil @@ -67,7 +68,7 @@ func (p *Params) SetupCfg(cfg *cfg.Cfg) { // Annotate implements mctx.Annotator interface. func (p *Params) Annotate(a mctx.Annotations) { - a["publicURL"] = p.PublicURL + a["mlPublicURL"] = p.PublicURL } // New initializes and returns a MailingList instance using the given Params.