diff options
| author | Christine Dodrill <me@christine.website> | 2020-07-16 15:32:30 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2020-07-16 15:32:30 -0400 |
| commit | 385d25c9f96c0acd5d932488e3bd0ed36ceb4dd7 (patch) | |
| tree | af789f7250519b23038a7e5ea0ae7f4f4c1ffdfc /cmd | |
| parent | 449e934246c82d90dd0aac2644d67f928befeeb4 (diff) | |
| download | xesite-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.go | 25 | ||||
| -rw-r--r-- | cmd/site/html.go | 245 | ||||
| -rw-r--r-- | cmd/site/internal/blog/blog.go | 137 | ||||
| -rw-r--r-- | cmd/site/internal/blog/blog_test.go | 66 | ||||
| -rw-r--r-- | cmd/site/internal/date.go | 10 | ||||
| -rw-r--r-- | cmd/site/internal/date_test.go | 28 | ||||
| -rw-r--r-- | cmd/site/internal/front/LICENSE | 19 | ||||
| -rw-r--r-- | cmd/site/internal/front/front.go | 24 | ||||
| -rw-r--r-- | cmd/site/internal/front/front_test.go | 42 | ||||
| -rw-r--r-- | cmd/site/internal/hash.go | 14 | ||||
| -rw-r--r-- | cmd/site/internal/middleware/metrics.go | 43 | ||||
| -rw-r--r-- | cmd/site/internal/middleware/requestid.go | 31 | ||||
| -rw-r--r-- | cmd/site/main.go | 296 | ||||
| -rw-r--r-- | cmd/site/pageview.go | 53 | ||||
| -rw-r--r-- | cmd/site/patreon.go | 112 | ||||
| -rw-r--r-- | cmd/site/rss.go | 91 | ||||
| -rw-r--r-- | cmd/site/signalboost.go | 29 | ||||
| -rw-r--r-- | cmd/site/signalboost_test.go | 28 |
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 |
