Add RSS feed generator

main
Brian Picciano 2 years ago
parent b4ca8853a9
commit 99f8c1188c
  1. 2
      config.nix
  2. 7
      srv/default.nix
  3. 21
      srv/src/http/api.go
  4. 63
      srv/src/http/feed.go
  5. 5
      srv/src/mailinglist/mailinglist.go

@ -1,11 +1,11 @@
{ {
runDir = "/tmp/mediocre-blog/run"; runDir = "/tmp/mediocre-blog/run";
dataDir = "/tmp/mediocre-blog/data"; dataDir = "/tmp/mediocre-blog/data";
publicURL = "http://localhost:4000";
powSecret = "ssshhh"; powSecret = "ssshhh";
mlSMTPAddr = ""; mlSMTPAddr = "";
mlSMTPAuth = ""; mlSMTPAuth = "";
mlPublicURL = "http://localhost:4000";
httpListenProto = "tcp"; httpListenProto = "tcp";
httpListenAddr = ":4000"; httpListenAddr = ":4000";

@ -16,7 +16,7 @@
# mailing list # mailing list
export MEDIOCRE_BLOG_ML_SMTP_ADDR="${config.mlSMTPAddr}" export MEDIOCRE_BLOG_ML_SMTP_ADDR="${config.mlSMTPAddr}"
export MEDIOCRE_BLOG_ML_SMTP_AUTH="${config.mlSMTPAuth}" 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 # redis
export MEDIOCRE_BLOG_REDIS_PROTO=unix export MEDIOCRE_BLOG_REDIS_PROTO=unix
@ -26,8 +26,9 @@
export MEDIOCRE_BLOG_POW_SECRET="${config.powSecret}" export MEDIOCRE_BLOG_POW_SECRET="${config.powSecret}"
# http # http
export MEDIOCRE_BLOG_LISTEN_PROTO="${config.httpListenProto}" export MEDIOCRE_BLOG_HTTP_PUBLIC_URL="${config.publicURL}"
export MEDIOCRE_BLOG_LISTEN_ADDR="${config.httpListenAddr}" 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_USERS='${builtins.toJSON config.httpAuthUsers}'
export MEDIOCRE_BLOG_HTTP_AUTH_RATELIMIT='${config.httpAuthRatelimit}' export MEDIOCRE_BLOG_HTTP_AUTH_RATELIMIT='${config.httpAuthRatelimit}'
''; '';

@ -10,7 +10,9 @@ import (
"html/template" "html/template"
"net" "net"
"net/http" "net/http"
"net/url"
"os" "os"
"strings"
"time" "time"
"github.com/mediocregopher/blog.mediocregopher.com/srv/cfg" "github.com/mediocregopher/blog.mediocregopher.com/srv/cfg"
@ -40,6 +42,9 @@ type Params struct {
GlobalRoom chat.Room GlobalRoom chat.Room
UserIDCalculator *chat.UserIDCalculator 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 // ListenProto and ListenAddr are passed into net.Listen to create the
// API's listener. Both "tcp" and "unix" protocols are explicitly // API's listener. Both "tcp" and "unix" protocols are explicitly
// supported. // supported.
@ -57,6 +62,9 @@ type Params struct {
// SetupCfg implement the cfg.Cfger interface. // SetupCfg implement the cfg.Cfger interface.
func (p *Params) SetupCfg(cfg *cfg.Cfg) { 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.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") 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) 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 return nil
}) })
} }
// Annotate implements mctx.Annotator interface. // Annotate implements mctx.Annotator interface.
func (p *Params) Annotate(a mctx.Annotations) { func (p *Params) Annotate(a mctx.Annotations) {
a["listenProto"] = p.ListenProto a["httpPublicURL"] = p.PublicURL
a["listenAddr"] = p.ListenAddr a["httpListenProto"] = p.ListenProto
a["authRatelimit"] = p.AuthRatelimit a["httpListenAddr"] = p.ListenAddr
a["httpAuthRatelimit"] = p.AuthRatelimit
} }
// API will listen on the port configured for it, and serve HTTP requests for // 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("/static/", http.FileServer(http.FS(staticFS)))
mux.Handle("/follow", a.renderDumbTplHandler("follow.html")) mux.Handle("/follow", a.renderDumbTplHandler("follow.html"))
mux.Handle("/feed.xml", a.renderFeedHandler())
mux.Handle("/", a.renderIndexHandler()) mux.Handle("/", a.renderIndexHandler())
var globalHandler http.Handler = mux var globalHandler http.Handler = mux

@ -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
}
})
}

@ -57,8 +57,9 @@ func (p *Params) SetupCfg(cfg *cfg.Cfg) {
cfg.OnInit(func(ctx context.Context) error { cfg.OnInit(func(ctx context.Context) error {
var err error var err error
*publicURLStr = strings.TrimSuffix(*publicURLStr, "/")
if p.PublicURL, err = url.Parse(*publicURLStr); err != nil { 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 return nil
@ -67,7 +68,7 @@ func (p *Params) SetupCfg(cfg *cfg.Cfg) {
// Annotate implements mctx.Annotator interface. // Annotate implements mctx.Annotator interface.
func (p *Params) Annotate(a mctx.Annotations) { 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. // New initializes and returns a MailingList instance using the given Params.

Loading…
Cancel
Save