aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2020-07-16 15:32:30 -0400
committerGitHub <noreply@github.com>2020-07-16 15:32:30 -0400
commit385d25c9f96c0acd5d932488e3bd0ed36ceb4dd7 (patch)
treeaf789f7250519b23038a7e5ea0ae7f4f4c1ffdfc /cmd
parent449e934246c82d90dd0aac2644d67f928befeeb4 (diff)
downloadxesite-385d25c9f96c0acd5d932488e3bd0ed36ceb4dd7.tar.xz
xesite-385d25c9f96c0acd5d932488e3bd0ed36ceb4dd7.zip
Rewrite site backend in Rust (#178)
* add shell.nix changes for Rust #176 * set up base crate layout * add first set of dependencies * start adding basic app modules * start html templates * serve index page * add contact and feeds pages * add resume rendering support * resume cleanups * get signalboost page working * rewrite config to be in dhall * more work * basic generic post loading * more tests * initial blog index support * fix routing? * render blogposts * X-Clacks-Overhead * split blog handlers into blog.rs * gallery index * gallery posts * fix hashtags * remove instantpage (it messes up the metrics) * talk support + prometheus * Create rust.yml * Update rust.yml * Update codeql-analysis.yml * add jsonfeed library * jsonfeed support * rss/atom * go mod tidy * atom: add posted date * rss: add publishing date * nix: build rust program * rip out go code * rip out go templates * prepare for serving in docker * create kubernetes deployment * create automagic deployment * build docker images on non-master * more fixes * fix timestamps * fix RSS/Atom/JSONFeed validation errors * add go vanity import redirecting * templates/header: remove this * atom feed: fixes * fix? * fix?? * fix rust tests * Update rust.yml * automatically show snow during the winter * fix dates * show commit link in footer * sitemap support * fix compiler warning * start basic patreon client * integrate kankyo * fix patreon client * add patrons page * remove this * handle patron errors better * fix build * clean up deploy * sort envvars for deploy * remove deps.nix * shell.nix: remove go * update README * fix envvars for tests * nice * blog: add rewrite in rust post * blog/site-update: more words
Diffstat (limited to 'cmd')
-rw-r--r--cmd/site/clacks.go25
-rw-r--r--cmd/site/html.go245
-rw-r--r--cmd/site/internal/blog/blog.go137
-rw-r--r--cmd/site/internal/blog/blog_test.go66
-rw-r--r--cmd/site/internal/date.go10
-rw-r--r--cmd/site/internal/date_test.go28
-rw-r--r--cmd/site/internal/front/LICENSE19
-rw-r--r--cmd/site/internal/front/front.go24
-rw-r--r--cmd/site/internal/front/front_test.go42
-rw-r--r--cmd/site/internal/hash.go14
-rw-r--r--cmd/site/internal/middleware/metrics.go43
-rw-r--r--cmd/site/internal/middleware/requestid.go31
-rw-r--r--cmd/site/main.go296
-rw-r--r--cmd/site/pageview.go53
-rw-r--r--cmd/site/patreon.go112
-rw-r--r--cmd/site/rss.go91
-rw-r--r--cmd/site/signalboost.go29
-rw-r--r--cmd/site/signalboost_test.go28
18 files changed, 0 insertions, 1293 deletions
diff --git a/cmd/site/clacks.go b/cmd/site/clacks.go
deleted file mode 100644
index 3415d14..0000000
--- a/cmd/site/clacks.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package main
-
-import (
- "math/rand"
- "net/http"
- "time"
-)
-
-type ClackSet []string
-
-func (cs ClackSet) Name() string {
- return "GNU " + cs[rand.Intn(len(cs))]
-}
-
-func (cs ClackSet) Middleware(next http.Handler) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Add("X-Clacks-Overhead", cs.Name())
-
- next.ServeHTTP(w, r)
- })
-}
-
-func init() {
- rand.Seed(time.Now().Unix())
-}
diff --git a/cmd/site/html.go b/cmd/site/html.go
deleted file mode 100644
index 37acba8..0000000
--- a/cmd/site/html.go
+++ /dev/null
@@ -1,245 +0,0 @@
-package main
-
-import (
- "context"
- "fmt"
- "html/template"
- "net/http"
- "path/filepath"
- "strings"
- "time"
-
- "christine.website/cmd/site/internal"
- "christine.website/cmd/site/internal/blog"
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promauto"
- "within.website/ln"
- "within.website/ln/opname"
-)
-
-var (
- templateRenderTime = promauto.NewHistogramVec(prometheus.HistogramOpts{
- Name: "template_render_time",
- Help: "Template render time in nanoseconds",
- }, []string{"name"})
-)
-
-func logTemplateTime(ctx context.Context, name string, f ln.F, from time.Time) {
- dur := time.Since(from)
- templateRenderTime.With(prometheus.Labels{"name": name}).Observe(float64(dur))
- ln.Log(ctx, f, ln.F{"dur": dur, "name": name})
-}
-
-func (s *Site) renderTemplatePage(templateFname string, data interface{}) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- ctx := opname.With(r.Context(), "renderTemplatePage")
- fetag := "W/" + internal.Hash(templateFname, etag) + "-1"
-
- f := ln.F{"etag": fetag, "if_none_match": r.Header.Get("If-None-Match")}
-
- if r.Header.Get("If-None-Match") == fetag {
- http.Error(w, "Cached data OK", http.StatusNotModified)
- ln.Log(ctx, f, ln.Info("Cache hit"))
- return
- }
-
- defer logTemplateTime(ctx, templateFname, f, time.Now())
-
- var t *template.Template
- var err error
-
- t, err = template.ParseFiles("templates/base.html", "templates/"+templateFname)
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- ln.Error(ctx, err, ln.F{"action": "renderTemplatePage", "page": templateFname})
- fmt.Fprintf(w, "error: %v", err)
- }
-
- w.Header().Set("ETag", fetag)
- w.Header().Set("Cache-Control", "max-age=432000")
-
- err = t.Execute(w, data)
- if err != nil {
- panic(err)
- }
- })
-}
-
-var postView = promauto.NewCounterVec(prometheus.CounterOpts{
- Name: "posts_viewed",
- Help: "The number of views per post or talk",
-}, []string{"base"})
-
-func (s *Site) listSeries(w http.ResponseWriter, r *http.Request) {
- s.renderTemplatePage("series.html", s.Series).ServeHTTP(w, r)
-}
-
-func (s *Site) showSeries(w http.ResponseWriter, r *http.Request) {
- if r.RequestURI == "/blog/series/" {
- http.Redirect(w, r, "/blog/series", http.StatusSeeOther)
- return
- }
-
- series := filepath.Base(r.URL.Path)
- var posts []blog.Post
-
- for _, p := range s.Posts {
- if p.Series == series {
- posts = append(posts, p)
- }
- }
-
- s.renderTemplatePage("serieslist.html", struct {
- Name string
- Posts []blog.Post
- }{
- Name: series,
- Posts: posts,
- }).ServeHTTP(w, r)
-}
-
-func (s *Site) showGallery(w http.ResponseWriter, r *http.Request) {
- if r.RequestURI == "/gallery/" {
- http.Redirect(w, r, "/gallery", http.StatusSeeOther)
- return
- }
-
- cmp := r.URL.Path[1:]
- var p blog.Post
- var found bool
- for _, pst := range s.Gallery {
- if pst.Link == cmp {
- p = pst
- found = true
- }
- }
-
- if !found {
- w.WriteHeader(http.StatusNotFound)
- s.renderTemplatePage("error.html", "no such post found: "+r.RequestURI).ServeHTTP(w, r)
- return
- }
-
- var tags string
- if len(p.Tags) != 0 {
- for _, t := range p.Tags {
- tags = tags + " #" + strings.ReplaceAll(t, "-", "")
- }
- }
-
- h := s.renderTemplatePage("gallerypost.html", struct {
- Title string
- Link string
- BodyHTML template.HTML
- Date string
- Tags string
- Image string
- }{
- Title: p.Title,
- Link: p.Link,
- BodyHTML: p.BodyHTML,
- Date: internal.IOS13Detri(p.Date),
- Tags: tags,
- Image: p.ImageURL,
- })
-
- if h == nil {
- panic("how did we get here?")
- }
-
- h.ServeHTTP(w, r)
- postView.With(prometheus.Labels{"base": filepath.Base(p.Link)}).Inc()
-}
-
-func (s *Site) showTalk(w http.ResponseWriter, r *http.Request) {
- if r.RequestURI == "/talks/" {
- http.Redirect(w, r, "/talks", http.StatusSeeOther)
- return
- }
-
- cmp := r.URL.Path[1:]
- var p blog.Post
- var found bool
- for _, pst := range s.Talks {
- if pst.Link == cmp {
- p = pst
- found = true
- }
- }
-
- if !found {
- w.WriteHeader(http.StatusNotFound)
- s.renderTemplatePage("error.html", "no such post found: "+r.RequestURI).ServeHTTP(w, r)
- return
- }
-
- h := s.renderTemplatePage("talkpost.html", struct {
- Title string
- Link string
- BodyHTML template.HTML
- Date string
- SlidesLink string
- }{
- Title: p.Title,
- Link: p.Link,
- BodyHTML: p.BodyHTML,
- Date: internal.IOS13Detri(p.Date),
- SlidesLink: p.SlidesLink,
- })
-
- if h == nil {
- panic("how did we get here?")
- }
-
- h.ServeHTTP(w, r)
- postView.With(prometheus.Labels{"base": filepath.Base(p.Link)}).Inc()
-}
-
-func (s *Site) showPost(w http.ResponseWriter, r *http.Request) {
- if r.RequestURI == "/blog/" {
- http.Redirect(w, r, "/blog", http.StatusSeeOther)
- return
- }
-
- cmp := r.URL.Path[1:]
- var p blog.Post
- var found bool
- for _, pst := range s.Posts {
- if pst.Link == cmp {
- p = pst
- found = true
- }
- }
-
- if !found {
- w.WriteHeader(http.StatusNotFound)
- s.renderTemplatePage("error.html", "no such post found: "+r.RequestURI).ServeHTTP(w, r)
- return
- }
-
- var tags string
-
- if len(p.Tags) != 0 {
- for _, t := range p.Tags {
- tags = tags + " #" + strings.ReplaceAll(t, "-", "")
- }
- }
-
- s.renderTemplatePage("blogpost.html", struct {
- Title string
- Link string
- BodyHTML template.HTML
- Date string
- Series, SeriesTag string
- Tags string
- }{
- Title: p.Title,
- Link: p.Link,
- BodyHTML: p.BodyHTML,
- Date: internal.IOS13Detri(p.Date),
- Series: p.Series,
- SeriesTag: strings.ReplaceAll(p.Series, "-", ""),
- Tags: tags,
- }).ServeHTTP(w, r)
- postView.With(prometheus.Labels{"base": filepath.Base(p.Link)}).Inc()
-}
diff --git a/cmd/site/internal/blog/blog.go b/cmd/site/internal/blog/blog.go
deleted file mode 100644
index 93a77c2..0000000
--- a/cmd/site/internal/blog/blog.go
+++ /dev/null
@@ -1,137 +0,0 @@
-package blog
-
-import (
- "html/template"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "strings"
- "time"
-
- "christine.website/cmd/site/internal/front"
- "github.com/russross/blackfriday"
-)
-
-// Post is a single blogpost.
-type Post struct {
- Title string `json:"title"`
- Link string `json:"link"`
- Summary string `json:"summary,omitifempty"`
- Body string `json:"-"`
- BodyHTML template.HTML `json:"body"`
- Series string `json:"series"`
- Tags []string `json:"tags"`
- SlidesLink string `json:"slides_link"`
- ImageURL string `json:"image_url"`
- ThumbURL string `json:"thumb_url"`
- Date time.Time
- DateString string `json:"date"`
-}
-
-// Posts implements sort.Interface for a slice of Post objects.
-type Posts []Post
-
-func (p Posts) Series() []string {
- names := map[string]struct{}{}
-
- for _, ps := range p {
- if ps.Series != "" {
- names[ps.Series] = struct{}{}
- }
- }
-
- var result []string
-
- for name := range names {
- result = append(result, name)
- }
-
- return result
-}
-
-func (p Posts) Len() int { return len(p) }
-func (p Posts) Less(i, j int) bool {
- iDate := p[i].Date
- jDate := p[j].Date
-
- return iDate.Unix() < jDate.Unix()
-}
-func (p Posts) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
-
-// LoadPosts loads posts for a given directory.
-func LoadPosts(path string, prepend string) (Posts, error) {
- type postFM struct {
- Title string
- Date string
- Series string
- Tags []string
- SlidesLink string `yaml:"slides_link"`
- Image string
- Thumb string
- Show string
- }
- var result Posts
-
- err := filepath.Walk(path, func(path string, info os.FileInfo, err error) error {
- if err != nil {
- return err
- }
-
- if info.IsDir() {
- return nil
- }
-
- fin, err := os.Open(path)
- if err != nil {
- return err
- }
- defer fin.Close()
-
- content, err := ioutil.ReadAll(fin)
- if err != nil {
- return err
- }
-
- var fm postFM
- remaining, err := front.Unmarshal(content, &fm)
- if err != nil {
- return err
- }
-
- output := blackfriday.Run(remaining)
-
- const timeFormat = `2006-01-02`
- date, err := time.Parse(timeFormat, fm.Date)
- if err != nil {
- return err
- }
-
- fname := filepath.Base(path)
- fname = strings.TrimSuffix(fname, filepath.Ext(fname))
-
- p := Post{
- Title: fm.Title,
- Date: date,
- DateString: fm.Date,
- Link: filepath.Join(prepend, fname),
- Body: string(remaining),
- BodyHTML: template.HTML(output),
- SlidesLink: fm.SlidesLink,
- Series: fm.Series,
- Tags: fm.Tags,
- ImageURL: fm.Image,
- ThumbURL: fm.Thumb,
- }
- result = append(result, p)
-
- return nil
- })
- if err != nil {
- return nil, err
- }
-
- sort.Sort(sort.Reverse(result))
-
- return result, nil
-}
diff --git a/cmd/site/internal/blog/blog_test.go b/cmd/site/internal/blog/blog_test.go
deleted file mode 100644
index 6f44a30..0000000
--- a/cmd/site/internal/blog/blog_test.go
+++ /dev/null
@@ -1,66 +0,0 @@
-package blog
-
-import (
- "testing"
-)
-
-func TestLoadPosts(t *testing.T) {
- posts, err := LoadPosts("../../../../blog", "blog")
- if err != nil {
- t.Fatal(err)
- }
-
- for _, post := range posts {
- t.Run(post.Link, post.test)
- }
-}
-
-func TestLoadTalks(t *testing.T) {
- talks, err := LoadPosts("../../../../talks", "talks")
- if err != nil {
- t.Fatal(err)
- }
-
- for _, talk := range talks {
- t.Run(talk.Link, talk.test)
- if talk.SlidesLink == "" {
- t.Errorf("talk %s (%s) doesn't have a slides link", talk.Title, talk.DateString)
- }
- }
-}
-
-func TestLoadGallery(t *testing.T) {
- gallery, err := LoadPosts("../../../../gallery", "gallery")
- if err != nil {
- t.Fatal(err)
- }
-
- for _, art := range gallery {
- t.Run(art.Link, art.test)
- if art.ImageURL == "" {
- t.Errorf("art %s (%s) doesn't have an image link", art.Title, art.DateString)
- }
- if art.ThumbURL == "" {
- t.Errorf("art %s (%s) doesn't have a thumbnail link", art.Title, art.DateString)
- }
-
- }
-}
-
-func (p Post) test(t *testing.T) {
- if p.Title == "" {
- t.Error("no post title")
- }
-
- if p.DateString == "" {
- t.Error("no date")
- }
-
- if p.Link == "" {
- t.Error("no link")
- }
-
- if p.Body == "" {
- t.Error("no body")
- }
-}
diff --git a/cmd/site/internal/date.go b/cmd/site/internal/date.go
deleted file mode 100644
index 960cdc2..0000000
--- a/cmd/site/internal/date.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package internal
-
-import "time"
-
-const iOS13DetriFormat = `2006 M1 2`
-
-// IOS13Detri formats a datestamp like iOS 13 does with the Lojban locale.
-func IOS13Detri(t time.Time) string {
- return t.Format(iOS13DetriFormat)
-}
diff --git a/cmd/site/internal/date_test.go b/cmd/site/internal/date_test.go
deleted file mode 100644
index 60254f9..0000000
--- a/cmd/site/internal/date_test.go
+++ /dev/null
@@ -1,28 +0,0 @@
-package internal
-
-import (
- "fmt"
- "testing"
- "time"
-)
-
-func TestIOS13Detri(t *testing.T) {
- cases := []struct {
- in time.Time
- out string
- }{
- {
- in: time.Date(2019, time.March, 30, 0, 0, 0, 0, time.FixedZone("UTC", 0)),
- out: "2019 M3 30",
- },
- }
-
- for _, cs := range cases {
- t.Run(fmt.Sprintf("%s -> %s", cs.in.Format(time.RFC3339), cs.out), func(t *testing.T) {
- result := IOS13Detri(cs.in)
- if result != cs.out {
- t.Fatalf("wanted: %s, got: %s", cs.out, result)
- }
- })
- }
-}
diff --git a/cmd/site/internal/front/LICENSE b/cmd/site/internal/front/LICENSE
deleted file mode 100644
index e8152bb..0000000
--- a/cmd/site/internal/front/LICENSE
+++ /dev/null
@@ -1,19 +0,0 @@
-Copyright (c) 2017 TJ Holowaychuk <tj@vision-media.ca>
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE. \ No newline at end of file
diff --git a/cmd/site/internal/front/front.go b/cmd/site/internal/front/front.go
deleted file mode 100644
index b2c7f0e..0000000
--- a/cmd/site/internal/front/front.go
+++ /dev/null
@@ -1,24 +0,0 @@
-// Package front provides YAML frontmatter unmarshalling.
-package front
-
-import (
- "bytes"
-
- "gopkg.in/yaml.v2"
-)
-
-// Delimiter.
-var delim = []byte("---")
-
-// Unmarshal parses YAML frontmatter and returns the content. When no
-// frontmatter delimiters are present the original content is returned.
-func Unmarshal(b []byte, v interface{}) (content []byte, err error) {
- if !bytes.HasPrefix(b, delim) {
- return b, nil
- }
-
- parts := bytes.SplitN(b, delim, 3)
- content = parts[2]
- err = yaml.Unmarshal(parts[1], v)
- return
-}
diff --git a/cmd/site/internal/front/front_test.go b/cmd/site/internal/front/front_test.go
deleted file mode 100644
index bdc56d1..0000000
--- a/cmd/site/internal/front/front_test.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package front_test
-
-import (
- "fmt"
- "log"
-
- "christine.website/cmd/site/internal/front"
-)
-
-var markdown = []byte(`---
-title: Ferrets
-authors:
- - Tobi
- - Loki
- - Jane
----
-Some content here, so
-interesting, you just
-want to keep reading.`)
-
-type article struct {
- Title string
- Authors []string
-}
-
-func Example() {
- var a article
-
- content, err := front.Unmarshal(markdown, &a)
- if err != nil {
- log.Fatalf("error unmarshalling: %s", err)
- }
-
- fmt.Printf("%#v\n", a)
- fmt.Printf("%s\n", string(content))
- // Output:
- // front_test.article{Title:"Ferrets", Authors:[]string{"Tobi", "Loki", "Jane"}}
- //
- // Some content here, so
- // interesting, you just
- // want to keep reading.
-}
diff --git a/cmd/site/internal/hash.go b/cmd/site/internal/hash.go
deleted file mode 100644
index ee333da..0000000
--- a/cmd/site/internal/hash.go
+++ /dev/null
@@ -1,14 +0,0 @@
-package internal
-
-import (
- "crypto/md5"
- "fmt"
-)
-
-// Hash is a simple wrapper around the MD5 algorithm implementation in the
-// Go standard library. It takes in data and a salt and returns the hashed
-// representation.
-func Hash(data string, salt string) string {
- output := md5.Sum([]byte(data + salt))
- return fmt.Sprintf("%x", output)
-}
diff --git a/cmd/site/internal/middleware/metrics.go b/cmd/site/internal/middleware/metrics.go
deleted file mode 100644
index f9d7e0f..0000000
--- a/cmd/site/internal/middleware/metrics.go
+++ /dev/null
@@ -1,43 +0,0 @@
-package middleware
-
-import (
- "net/http"
-
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promhttp"
-)
-
-var (
- requestCounter = prometheus.NewCounterVec(
- prometheus.CounterOpts{
- Name: "handler_requests_total",
- Help: "Total number of request/responses by HTTP status code.",
- }, []string{"handler", "code"})
-
- requestDuration = prometheus.NewHistogramVec(prometheus.HistogramOpts{
- Name: "handler_request_duration",
- Help: "Handler request duration.",
- }, []string{"handler", "method"})
-
- requestInFlight = prometheus.NewGaugeVec(prometheus.GaugeOpts{
- Name: "handler_requests_in_flight",
- Help: "Current number of requests being served.",
- }, []string{"handler"})
-)
-
-func init() {
- _ = prometheus.Register(requestCounter)
- _ = prometheus.Register(requestDuration)
- _ = prometheus.Register(requestInFlight)
-}
-
-// Metrics captures request duration, request count and in-flight request count
-// metrics for HTTP handlers. The family field is used to discriminate handlers.
-func Metrics(family string, next http.Handler) http.Handler {
- return promhttp.InstrumentHandlerDuration(
- requestDuration.MustCurryWith(prometheus.Labels{"handler": family}),
- promhttp.InstrumentHandlerCounter(requestCounter.MustCurryWith(prometheus.Labels{"handler": family}),
- promhttp.InstrumentHandlerInFlight(requestInFlight.With(prometheus.Labels{"handler": family}), next),
- ),
- )
-}
diff --git a/cmd/site/internal/middleware/requestid.go b/cmd/site/internal/middleware/requestid.go
deleted file mode 100644
index 6914137..0000000
--- a/cmd/site/internal/middleware/requestid.go
+++ /dev/null
@@ -1,31 +0,0 @@
-package middleware
-
-import (
- "net/http"
-
- "github.com/celrenheit/sandflake"
- "within.website/ln"
-)
-
-// RequestID appends a unique (sandflake) request ID to each request's
-// X-Request-Id header field, much like Heroku's router does.
-func RequestID(next http.Handler) http.Handler {
- var g sandflake.Generator
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- id := g.Next().String()
-
- if rid := r.Header.Get("X-Request-Id"); rid != "" {
- id = rid + "," + id
- }
-
- ctx := ln.WithF(r.Context(), ln.F{
- "request_id": id,
- })
- r = r.WithContext(ctx)
-
- w.Header().Set("X-Request-Id", id)
- r.Header.Set("X-Request-Id", id)
-
- next.ServeHTTP(w, r)
- })
-}
diff --git a/cmd/site/main.go b/cmd/site/main.go
deleted file mode 100644
index 53f7a8d..0000000
--- a/cmd/site/main.go
+++ /dev/null
@@ -1,296 +0,0 @@
-package main
-
-import (
- "context"
- "html/template"
- "io/ioutil"
- "net/http"
- "os"
- "sort"
- "strings"
- "time"
-
- "christine.website/cmd/site/internal/blog"
- "christine.website/cmd/site/internal/middleware"
- "christine.website/jsonfeed"
- "github.com/gorilla/feeds"
- _ "github.com/joho/godotenv/autoload"
- "github.com/povilasv/prommod"
- "github.com/prometheus/client_golang/prometheus"
- "github.com/prometheus/client_golang/prometheus/promhttp"
- blackfriday "github.com/russross/blackfriday"
- "github.com/sebest/xff"
- "github.com/snabb/sitemap"
- "within.website/ln"
- "within.website/ln/ex"
- "within.website/ln/opname"
-)
-
-var port = os.Getenv("PORT")
-
-func main() {
- if port == "" {
- port = "29384"
- }
-
- ctx := ln.WithF(opname.With(context.Background(), "main"), ln.F{
- "port": port,
- "git_rev": gitRev,
- })
-
- _ = prometheus.Register(prommod.NewCollector("christine"))
-
- s, err := Build()
- if err != nil {
- ln.FatalErr(ctx, err, ln.Action("Build"))
- }
-
- mux := http.NewServeMux()
- mux.HandleFunc("/.within/health", func(w http.ResponseWriter, r *http.Request) {
- http.Error(w, "OK", http.StatusOK)
- })
- mux.Handle("/", s)
-
- ln.Log(ctx, ln.Action("http_listening"))
- ln.FatalErr(ctx, http.ListenAndServe(":"+port, mux))
-}
-
-// Site is the parent object for https://christine.website's backend.
-type Site struct {
- Posts blog.Posts
- Talks blog.Posts
- Gallery blog.Posts
- Resume template.HTML
- Series []string
- SignalBoost []Person
-
- clacks ClackSet
- patrons []string
- rssFeed *feeds.Feed
- jsonFeed *jsonfeed.Feed
-
- mux *http.ServeMux
- xffmw *xff.XFF
-}
-
-var gitRev = os.Getenv("GIT_REV")
-
-func envOr(key, or string) string {
- if result, ok := os.LookupEnv(key); ok {
- return result
- }
-
- return or
-}
-
-func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- ctx := opname.With(r.Context(), "site.ServeHTTP")
- ctx = ln.WithF(ctx, ln.F{
- "user_agent": r.Header.Get("User-Agent"),
- })
- r = r.WithContext(ctx)
- if gitRev != "" {
- w.Header().Add("X-Git-Rev", gitRev)
- }
-
- w.Header().Add("X-Hacker", "If you are reading this, check out /signalboost to find people for your team")
-
- s.clacks.Middleware(
- middleware.RequestID(
- s.xffmw.Handler(
- ex.HTTPLog(s.mux),
- ),
- ),
- ).ServeHTTP(w, r)
-}
-
-var arbDate = time.Date(2020, time.May, 21, 0, 0, 0, 0, time.UTC)
-
-// Build creates a new Site instance or fails.
-func Build() (*Site, error) {
- pc, err := NewPatreonClient()
- if err != nil {
- return nil, err
- }
-
- pledges, err := GetPledges(pc)
- if err != nil {
- return nil, err
- }
-
- people, err := loadPeople("./signalboost.dhall")
- if err != nil {
- return nil, err
- }
-
- smi := sitemap.New()
- smi.Add(&sitemap.URL{
- Loc: "https://christine.website/resume",
- LastMod: &arbDate,
- ChangeFreq: sitemap.Monthly,
- })
-
- smi.Add(&sitemap.URL{
- Loc: "https://christine.website/contact",
- LastMod: &arbDate,
- ChangeFreq: sitemap.Monthly,
- })
-
- smi.Add(&sitemap.URL{
- Loc: "https://christine.website/",
- LastMod: &arbDate,
- ChangeFreq: sitemap.Monthly,
- })
-
- smi.Add(&sitemap.URL{
- Loc: "https://christine.website/patrons",
- LastMod: &arbDate,
- ChangeFreq: sitemap.Weekly,
- })
-
- smi.Add(&sitemap.URL{
- Loc: "https://christine.website/blog",
- LastMod: &arbDate,
- ChangeFreq: sitemap.Weekly,
- })
-
- xffmw, err := xff.Default()
- if err != nil {
- return nil, err
- }
-
- s := &Site{
- rssFeed: &feeds.Feed{
- Title: "Christine Dodrill's Blog",
- Link: &feeds.Link{Href: "https://christine.website/blog"},
- Description: "My blog posts and rants about various technology things.",
- Author: &feeds.Author{Name: "Christine Dodrill", Email: "me@christine.website"},
- Created: bootTime,
- Copyright: "This work is copyright Christine Dodrill. My viewpoints are my own and not the view of any employer past, current or future.",
- },
- jsonFeed: &jsonfeed.Feed{
- Version: jsonfeed.CurrentVersion,
- Title: "Christine Dodrill's Blog",
- HomePageURL: "https://christine.website",
- FeedURL: "https://christine.website/blog.json",
- Description: "My blog posts and rants about various technology things.",
- UserComment: "This is a JSON feed of my blogposts. For more information read: https://jsonfeed.org/version/1",
- Icon: icon,
- Favicon: icon,
- Author: jsonfeed.Author{
- Name: "Christine Dodrill",
- Avatar: icon,
- },
- },
- mux: http.NewServeMux(),
- xffmw: xffmw,
-
- clacks: ClackSet(strings.Split(envOr("CLACK_SET", "Ashlynn"), ",")),
- patrons: pledges,
- SignalBoost: people,
- }
-
- posts, err := blog.LoadPosts("./blog/", "blog")
- if err != nil {
- return nil, err
- }
- s.Posts = posts
- s.Series = posts.Series()
- sort.Strings(s.Series)
-
- talks, err := blog.LoadPosts("./talks", "talks")
- if err != nil {
- return nil, err
- }
- s.Talks = talks
-
- gallery, err := blog.LoadPosts("./gallery", "gallery")
- if err != nil {
- return nil, err
- }
- s.Gallery = gallery
-
- var everything blog.Posts
- everything = append(everything, posts...)
- everything = append(everything, talks...)
- everything = append(everything, gallery...)
-
- sort.Sort(sort.Reverse(everything))
-
- resumeData, err := ioutil.ReadFile("./static/resume/resume.md")
- if err != nil {
- return nil, err
- }
-
- s.Resume = template.HTML(blackfriday.Run(resumeData))
-
- for _, item := range everything {
- s.rssFeed.Items = append(s.rssFeed.Items, &feeds.Item{
- Title: item.Title,
- Link: &feeds.Link{Href: "https://christine.website/" + item.Link},
- Description: item.Summary,
- Created: item.Date,
- Content: string(item.BodyHTML),
- })
-
- s.jsonFeed.Items = append(s.jsonFeed.Items, jsonfeed.Item{
- ID: "https://christine.website/" + item.Link,
- URL: "https://christine.website/" + item.Link,
- Title: item.Title,
- DatePublished: item.Date,
- ContentHTML: string(item.BodyHTML),
- Tags: item.Tags,
- })
-
- smi.Add(&sitemap.URL{
- Loc: "https://christine.website/" + item.Link,
- LastMod: &item.Date,
- ChangeFreq: sitemap.Monthly,
- })
- }
-
- // Add HTTP routes here
- s.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- if r.URL.Path != "/" {
- w.WriteHeader(http.StatusNotFound)
- s.renderTemplatePage("error.html", "can't find "+r.URL.Path).ServeHTTP(w, r)
- return
- }
-
- s.renderTemplatePage("index.html", nil).ServeHTTP(w, r)
- })
- s.mux.Handle("/metrics", promhttp.Handler())
- s.mux.Handle("/feeds", middleware.Metrics("feeds", s.renderTemplatePage("feeds.html", nil)))
- s.mux.Handle("/patrons", middleware.Metrics("patrons", s.renderTemplatePage("patrons.html", s.patrons)))
- s.mux.Handle("/signalboost", middleware.Metrics("signalboost", s.renderTemplatePage("signalboost.html", s.SignalBoost)))
- s.mux.Handle("/resume", middleware.M