about summary refs log tree commit diff stats
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/atom/atom.go6
-rw-r--r--internal/builder/builder.go5
-rw-r--r--internal/builder/posts.go2
-rw-r--r--internal/builder/template.go64
-rw-r--r--internal/config/config.go19
-rw-r--r--internal/config/cspgenerator.go28
-rw-r--r--internal/server/logging.go8
-rw-r--r--internal/server/server.go17
-rw-r--r--internal/website/filemap.go12
-rw-r--r--internal/website/mux.go3
10 files changed, 102 insertions, 62 deletions
diff --git a/internal/atom/atom.go b/internal/atom/atom.go
index f2ca4a9..37c53d9 100644
--- a/internal/atom/atom.go
+++ b/internal/atom/atom.go
@@ -4,10 +4,10 @@ import (
 	"encoding/xml"
 	"time"
 
-	. "website/internal/config"
+	"website/internal/config"
 )
 
-func MakeTagURI(config Config, specific string) string {
+func MakeTagURI(config config.Config, specific string) string {
 	return "tag:" + config.OriginalDomain + "," + config.DomainStartDate + ":" + specific
 }
 
@@ -35,7 +35,7 @@ type FeedEntry struct {
 	XMLName xml.Name    `xml:"entry"`
 	Title   string      `xml:"title"`
 	Link    Link        `xml:"link"`
-	Id      string      `xml:"id"`
+	ID      string      `xml:"id"`
 	Updated time.Time   `xml:"updated"`
 	Summary string      `xml:"summary,omitempty"`
 	Content FeedContent `xml:"content"`
diff --git a/internal/builder/builder.go b/internal/builder/builder.go
index a8205a3..55e68d2 100644
--- a/internal/builder/builder.go
+++ b/internal/builder/builder.go
@@ -23,7 +23,9 @@ type IOConfig struct {
 }
 
 func mkdirp(dirs ...string) error {
-	return os.MkdirAll(path.Join(dirs...), 0755)
+	err := os.MkdirAll(path.Join(dirs...), 0755)
+
+	return errors.Wrap(err, "could not create directory")
 }
 
 func outputToFile(output io.Reader, filename ...string) error {
@@ -37,6 +39,7 @@ func outputToFile(output io.Reader, filename ...string) error {
 	if _, err := file.ReadFrom(output); err != nil {
 		return errors.WithMessage(err, "could not write output file")
 	}
+
 	return nil
 }
 
diff --git a/internal/builder/posts.go b/internal/builder/posts.go
index 954e259..cfe4e6f 100644
--- a/internal/builder/posts.go
+++ b/internal/builder/posts.go
@@ -73,6 +73,7 @@ func renderMarkdown(content []byte) (string, error) {
 	if err := markdown.Convert(content, &buf); err != nil {
 		return "", errors.WithMessage(err, "could not convert markdown content")
 	}
+
 	return buf.String(), nil
 }
 
@@ -121,5 +122,6 @@ func readPosts(root string, inputDir string, outputDir string) ([]Post, Tags, er
 	slices.SortFunc(posts, func(a, b Post) int {
 		return b.Date.Compare(a.Date)
 	})
+
 	return posts, tags, nil
 }
diff --git a/internal/builder/template.go b/internal/builder/template.go
index c63e1c2..5e2af1a 100644
--- a/internal/builder/template.go
+++ b/internal/builder/template.go
@@ -18,6 +18,7 @@ import (
 	"github.com/antchfx/xmlquery"
 	"github.com/antchfx/xpath"
 	mapset "github.com/deckarep/golang-set/v2"
+	"github.com/pkg/errors"
 	"golang.org/x/net/html"
 )
 
@@ -26,18 +27,19 @@ var (
 	css            string
 	countHTML      *goquery.Document
 	liveReloadHTML *goquery.Document
-	templates      map[string]*os.File = make(map[string]*os.File)
+	templates      = make(map[string]*os.File)
 )
 
 func loadTemplate(path string) (file *os.File, err error) {
 	if templates[path] == nil {
 		file, err = os.OpenFile(path, os.O_RDONLY, 0)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrapf(err, "could not load template at path %s", path)
 		}
 		templates[path] = file
 	}
 	file = templates[path]
+
 	return
 }
 
@@ -57,7 +59,8 @@ type QueryDocument struct {
 
 func NewDocumentFromReader(r io.Reader) (*QueryDocument, error) {
 	doc, err := goquery.NewDocumentFromReader(r)
-	return &QueryDocument{doc}, err
+
+	return &QueryDocument{doc}, errors.Wrap(err, "could not create query document")
 }
 
 func (q *QueryDocument) Find(selector string) *QuerySelection {
@@ -66,7 +69,8 @@ func (q *QueryDocument) Find(selector string) *QuerySelection {
 
 func NewDocumentNoScript(r io.Reader) (*goquery.Document, error) {
 	root, err := html.ParseWithOptions(r, html.ParseOptionEnableScripting(false))
-	return goquery.NewDocumentFromNode(root), err
+
+	return goquery.NewDocumentFromNode(root), errors.Wrap(err, "could not parse HTML")
 }
 
 func (root QuerySelection) setImgURL(pageURL string, pageTitle string) QuerySelection {
@@ -90,6 +94,7 @@ func (root QuerySelection) setImgURL(pageURL string, pageTitle string) QuerySele
 	output := urlTemplate.String() + "?" + q.Encode()
 	clone.Find("img").SetAttr("src", output)
 	root.AppendSelection(clone.Find("body").Children())
+
 	return root
 }
 
@@ -103,7 +108,12 @@ func layout(
 	if err != nil {
 		return nil, err
 	}
-	defer html.Seek(0, io.SeekStart)
+	defer func() {
+		_, err := html.Seek(0, io.SeekStart)
+		if err != nil {
+			panic("could not reset template file offset: " + err.Error())
+		}
+	}()
 	assetsOnce.Do(func() {
 		var bytes []byte
 		bytes, err = os.ReadFile("templates/style.css")
@@ -133,7 +143,7 @@ func layout(
 		}
 	})
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "could not set up layout template")
 	}
 
 	doc, err := NewDocumentFromReader(html)
@@ -156,6 +166,7 @@ func layout(
 	for _, link := range config.Menus["main"] {
 		nav.AppendSelection(navLink.Clone().SetAttr("href", link.URL).SetText(link.Name))
 	}
+
 	return doc.Document, nil
 }
 
@@ -196,6 +207,7 @@ func renderTags(tags Tags, config config.Config, url string) (io.Reader, error)
 		li.Find("a").SetAttr("href", fmt.Sprintf("/tags/%s/", tag)).SetText("#" + tag)
 		tagList.AppendSelection(li)
 	}
+
 	return renderHTML(doc), nil
 }
 
@@ -288,6 +300,7 @@ func render404(config config.Config, url string) (io.Reader, error) {
 	if err != nil {
 		return nil, err
 	}
+
 	return renderHTML(doc), nil
 }
 
@@ -301,10 +314,15 @@ func renderFeed(
 	if err != nil {
 		return nil, err
 	}
-	defer reader.Seek(0, io.SeekStart)
+	defer func() {
+		_, err := reader.Seek(0, io.SeekStart)
+		if err != nil {
+			panic("could not reset reader: " + err.Error())
+		}
+	}()
 	doc, err := xmlquery.Parse(reader)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "could not parse XML")
 	}
 	feed := doc.SelectElement("feed")
 	feed.SelectElement("title").FirstChild.Data = title
@@ -312,21 +330,18 @@ func renderFeed(
 	feed.SelectElement("id").FirstChild.Data = atom.MakeTagURI(config, specific)
 	datetime, err := posts[0].Date.UTC().MarshalText()
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "could not convert post date to text")
 	}
 	feed.SelectElement("updated").FirstChild.Data = string(datetime)
 	tpl := feed.SelectElement("entry")
 	xmlquery.RemoveFromTree(tpl)
 
 	for _, post := range posts {
-		fullURL, err := url.JoinPath(config.BaseURL.String(), post.URL)
-		if err != nil {
-			return nil, err
-		}
+		fullURL := config.BaseURL.JoinPath(post.URL).String()
 		text, err := xml.MarshalIndent(&atom.FeedEntry{
 			Title:   post.Title,
 			Link:    atom.MakeLink(fullURL),
-			Id:      atom.MakeTagURI(config, post.Basename),
+			ID:      atom.MakeTagURI(config, post.Basename),
 			Updated: post.Date.UTC(),
 			Summary: post.Description,
 			Author:  config.Title,
@@ -336,7 +351,7 @@ func renderFeed(
 			},
 		}, "  ", "    ")
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "could not marshal xml")
 		}
 		entry, err := xmlquery.ParseWithOptions(
 			strings.NewReader(string(text)),
@@ -349,7 +364,7 @@ func renderFeed(
 			},
 		)
 		if err != nil {
-			return nil, err
+			return nil, errors.Wrap(err, "could not parse XML")
 		}
 		xmlquery.AddChild(feed, entry.SelectElement("entry"))
 	}
@@ -362,7 +377,12 @@ func renderFeedStyles() (io.Reader, error) {
 	if err != nil {
 		return nil, err
 	}
-	defer reader.Seek(0, io.SeekStart)
+	defer func() {
+		_, err := reader.Seek(0, io.SeekStart)
+		if err != nil {
+			panic("could not reset reader: " + err.Error())
+		}
+	}()
 	nsMap := map[string]string{
 		"xsl":   "http://www.w3.org/1999/XSL/Transform",
 		"atom":  "http://www.w3.org/2005/Atom",
@@ -370,24 +390,24 @@ func renderFeedStyles() (io.Reader, error) {
 	}
 	doc, err := xmlquery.Parse(reader)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "could not parse XML")
 	}
 	expr, err := xpath.CompileWithNS("//xhtml:style", nsMap)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "could not parse XML")
 	}
 	style := xmlquery.QuerySelector(doc, expr)
 	xmlquery.AddChild(style, &xmlquery.Node{
 		Type: xmlquery.TextNode,
-		Data: string(css),
+		Data: css,
 	})
+
 	return strings.NewReader(doc.OutputXML(true)), nil
 }
 
 func renderHTML(doc *goquery.Document) io.Reader {
 	r, w := io.Pipe()
 
-	// TODO: return errors to main thread
 	go func() {
 		_, err := w.Write([]byte("<!doctype html>\n"))
 		if err != nil {
@@ -398,9 +418,11 @@ func renderHTML(doc *goquery.Document) io.Reader {
 		if err != nil {
 			log.Error("error rendering html", "error", err)
 			w.CloseWithError(err)
+
 			return
 		}
 		defer w.Close()
 	}()
+
 	return r
 }
diff --git a/internal/config/config.go b/internal/config/config.go
index be7dcb9..df69bce 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -25,7 +25,8 @@ type URL struct {
 
 func (u *URL) UnmarshalText(text []byte) (err error) {
 	u.URL, err = url.Parse(string(text))
-	return err
+
+	return errors.Wrapf(err, "could not parse URL %s", string(text))
 }
 
 type Config struct {
@@ -51,15 +52,15 @@ func GetConfig() (*Config, error) {
 	log.Debug("reading config.toml")
 	_, err := toml.DecodeFile("config.toml", &config)
 	if err != nil {
-		var pathError *fs.PathError
-		var tomlError toml.ParseError
-		if errors.As(err, &pathError) {
-			return nil, errors.WithMessage(err, "could not read configuration")
-		} else if errors.As(err, &tomlError) {
-			return nil, errors.WithMessage(err, tomlError.ErrorWithUsage())
-		} else {
-			return nil, errors.Wrap(err, "config error")
+		switch t := err.(type) {
+		case *fs.PathError:
+			return nil, errors.WithMessage(t, "could not read configuration")
+		case *toml.ParseError:
+			return nil, errors.WithMessage(t, t.ErrorWithUsage())
 		}
+
+		return nil, errors.Wrap(err, "config error")
 	}
+
 	return &config, nil
 }
diff --git a/internal/config/cspgenerator.go b/internal/config/cspgenerator.go
index 5ad3ef0..40eca01 100644
--- a/internal/config/cspgenerator.go
+++ b/internal/config/cspgenerator.go
@@ -9,13 +9,15 @@ import (
 
 	"github.com/crewjam/csp"
 	"github.com/fatih/structtag"
+	"github.com/pkg/errors"
 )
 
 func GenerateCSP() error {
 	t := reflect.TypeFor[csp.Header]()
 	file, err := os.OpenFile("./csp.go", os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0)
 	if err != nil {
-		return err
+
+		return errors.Wrap(err, "could not write to output")
 	}
 	defer file.Close()
 
@@ -29,46 +31,43 @@ import (
 
 `)
 	if err != nil {
-		return err
+
+		return errors.Wrap(err, "could not write to output")
 	}
 
 	_, err = fmt.Fprintf(file, "type CSP struct {\n")
 	if err != nil {
-		return err
+		return errors.Wrap(err, "could not write to output")
 	}
 
 	for i := 0; i < t.NumField(); i++ {
 		field := t.Field(i)
 		var t reflect.Type
-		if field.Type.Kind() == reflect.Slice {
-			t = field.Type
-		} else {
-			t = field.Type
-		}
+		t = field.Type
 		tags, err := structtag.Parse(string(field.Tag))
 		if err != nil {
-			return err
+			return errors.Wrap(err, "could not write to output")
 		}
 		cspTag, err := tags.Get("csp")
 		if err != nil {
-			return err
+			return errors.Wrap(err, "could not get csp tag")
 		}
 		err = tags.Set(&structtag.Tag{
 			Key:  "toml",
 			Name: cspTag.Name,
 		})
 		if err != nil {
-			return err
+			return errors.Wrap(err, "could not set toml tag")
 		}
 
 		_, err = fmt.Fprintf(file, "\t%-23s %-28s `%s`\n", field.Name, t, tags.String())
 		if err != nil {
-			return err
+			return errors.Wrap(err, "could not write to output")
 		}
 	}
 	_, err = fmt.Fprintln(file, "}")
 	if err != nil {
-		return err
+		return errors.Wrap(err, "could not write to output")
 	}
 
 	_, err = fmt.Fprintln(file, `
@@ -76,7 +75,8 @@ func (c *CSP) String() string {
 	return csp.Header(*c).String()
 }`)
 	if err != nil {
-		return err
+		return errors.Wrap(err, "could not write to output")
 	}
+
 	return nil
 }
diff --git a/internal/server/logging.go b/internal/server/logging.go
index 65dcb87..afcce6d 100644
--- a/internal/server/logging.go
+++ b/internal/server/logging.go
@@ -5,12 +5,12 @@ import (
 	"website/internal/log"
 )
 
-type loggingResponseWriter struct {
+type LoggingResponseWriter struct {
 	http.ResponseWriter
 	statusCode int
 }
 
-func (lrw *loggingResponseWriter) WriteHeader(code int) {
+func (lrw *LoggingResponseWriter) WriteHeader(code int) {
 	lrw.statusCode = code
 	// avoids warning: superfluous response.WriteHeader call
 	if lrw.statusCode != http.StatusOK {
@@ -18,8 +18,8 @@ func (lrw *loggingResponseWriter) WriteHeader(code int) {
 	}
 }
 
-func NewLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter {
-	return &loggingResponseWriter{w, http.StatusOK}
+func NewLoggingResponseWriter(w http.ResponseWriter) *LoggingResponseWriter {
+	return &LoggingResponseWriter{w, http.StatusOK}
 }
 
 type wrappedHandlerOptions struct {
diff --git a/internal/server/server.go b/internal/server/server.go
index 4fda3dc..8838dbd 100644
--- a/internal/server/server.go
+++ b/internal/server/server.go
@@ -21,9 +21,9 @@ import (
 var config *cfg.Config
 
 var (
-	CommitSHA    string = "local"
-	ShortSHA     string = "local"
-	serverHeader string = fmt.Sprintf("website (%s)", ShortSHA)
+	CommitSHA    = "local"
+	ShortSHA     = "local"
+	serverHeader = fmt.Sprintf("website (%s)", ShortSHA)
 )
 
 type Config struct {
@@ -83,7 +83,7 @@ func New(runtimeConfig *Config) (*Server, error) {
 	top := http.NewServeMux()
 	mux, err := website.NewMux(config, runtimeConfig.Root)
 	if err != nil {
-		return nil, err
+		return nil, errors.Wrap(err, "could not create website mux")
 	}
 	log.Debug("binding main handler to", "host", runtimeConfig.BaseURL.Hostname()+"/")
 	hostname := runtimeConfig.BaseURL.Hostname()
@@ -95,14 +95,16 @@ func New(runtimeConfig *Config) (*Server, error) {
 		http.Redirect(w, r, newURL.String(), 301)
 	})
 
-	top.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
+	top.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
 		w.WriteHeader(http.StatusOK)
 	})
 
 	listenAddress := net.JoinHostPort(runtimeConfig.ListenAddress, runtimeConfig.Port)
+
 	return &Server{
 		&http.Server{
-			Addr: listenAddress,
+			Addr:              listenAddress,
+			ReadHeaderTimeout: 1 * time.Minute,
 			Handler: sentryHandler.Handle(
 				serverHeaderHandler(
 					wrapHandlerWithLogging(top, wrappedHandlerOptions{
@@ -116,8 +118,9 @@ func New(runtimeConfig *Config) (*Server, error) {
 
 func (s *Server) Start() error {
 	if err := s.ListenAndServe(); err != http.ErrServerClosed {
-		return err
+		return errors.Wrap(err, "error creating/closing server")
 	}
+
 	return nil
 }
 
diff --git a/internal/website/filemap.go b/internal/website/filemap.go
index 6daeb18..c657848 100644
--- a/internal/website/filemap.go
+++ b/internal/website/filemap.go
@@ -24,19 +24,21 @@ var files = map[string]File{}
 func hashFile(filename string) (string, error) {
 	f, err := os.Open(filename)
 	if err != nil {
-		return "", err
+		return "", errors.Wrapf(err, "could not open file %s for hashing", filename)
 	}
 	defer f.Close()
 	hash := fnv.New64a()
 	if _, err := io.Copy(hash, f); err != nil {
-		return "", err
+		return "", errors.Wrapf(err, "could not hash file %s", filename)
 	}
+
 	return fmt.Sprintf(`W/"%x"`, hash.Sum(nil)), nil
 }
 
 func registerFile(urlpath string, filepath string) error {
 	if files[urlpath] != (File{}) {
 		log.Info("registerFile called with duplicate file", "url_path", urlpath)
+
 		return nil
 	}
 	hash, err := hashFile(filepath)
@@ -47,6 +49,7 @@ func registerFile(urlpath string, filepath string) error {
 		filename: filepath,
 		etag:     hash,
 	}
+
 	return nil
 }
 
@@ -62,13 +65,16 @@ func registerContentFiles(root string) error {
 		urlPath, _ := strings.CutSuffix(relPath, "index.html")
 		if !f.IsDir() {
 			log.Debug("registering file", "urlpath", "/"+urlPath)
+
 			return registerFile("/"+urlPath, filePath)
 		}
+
 		return nil
 	})
 	if err != nil {
-		return err
+		return errors.Wrap(err, "could not walk directory")
 	}
+
 	return nil
 }
 
diff --git a/internal/website/mux.go b/internal/website/mux.go
index 0335f97..65a7e59 100644
--- a/internal/website/mux.go
+++ b/internal/website/mux.go
@@ -25,6 +25,7 @@ func canonicalisePath(path string) (cPath string, differs bool) {
 	} else if !strings.HasSuffix(path, "/") && files[path+"/"] != (File{}) {
 		cPath, differs = path+"/", true
 	}
+
 	return cPath, differs
 }
 
@@ -62,6 +63,7 @@ func NewMux(cfg *config.Config, root string) (mux *http.ServeMux, err error) {
 		urlPath, shouldRedirect := canonicalisePath(r.URL.Path)
 		if shouldRedirect {
 			http.Redirect(w, r, urlPath, 302)
+
 			return nil
 		}
 		file := GetFile(urlPath)
@@ -79,6 +81,7 @@ func NewMux(cfg *config.Config, root string) (mux *http.ServeMux, err error) {
 		}
 
 		http.ServeFile(w, r, files[urlPath].filename)
+
 		return nil
 	}))