~bigbes/sr-ht-compare

ref: 07736d95cf181f89fcd8b0dbb54afe79dfa50352 sr-ht-compare/web/router.go -rw-r--r-- 3.1 KiB
07736d95 — bigbes logging: the instance's log policy instead of forty local lines 9 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package web

import (
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"
	"sourcecraft.dev/bigbes/sr-ht-ecore/assets"
	"sourcecraft.dev/bigbes/sr-ht-ecore/csrf"
	"sourcecraft.dev/bigbes/sr-ht-ecore/middleware"
)

// Register mounts every compare.sr.ht route onto r, inside a group carrying the
// three middlewares that need this Server. The chain documented on the package
// (config + authz at a minimum) is still the caller's, and stays outside this
// group.
//
//   - PrivateCache, because every page here is a page to its owner and a 404 to
//     everybody else, at a URL that says nothing about the viewer. The static
//     route opts back out per asset, from inside assets.Handler, and only once
//     the bytes are committed.
//   - RecoverPanics, so a panicking handler produces this service's own error
//     page instead of a dropped connection — and, when the response has already
//     started, a dropped connection instead of an error page appended to half a
//     rendered diff.
//   - csrf.Require, which guards a future rather than a present: every route
//     below is a GET, so nothing is refused by it today. It is installed anyway
//     because the day somebody adds the first POST is exactly the day nobody
//     remembers to add the check, and these services have no CSRF token to fall
//     back on — the session cookie is meta.sr.ht's, set on the parent domain,
//     with a SameSite no individual service can choose.
//
// The router's own 404 is set here too, so a mistyped URL lands on a page with a
// nav rather than on chi's plain-text dead end.
func (s *Server) Register(r chi.Router) {
	r.Group(func(r chi.Router) {
		r.Use(middleware.PrivateCache)
		r.Use(middleware.RecoverPanics(func(w http.ResponseWriter, r *http.Request, _ any) {
			s.renderError(w, r, http.StatusInternalServerError, "")
		}))
		r.Use(csrf.Require(s.chromeSvc.SelfOrigin(), nil))

		r.NotFound(s.handleNotFound)

		r.Get("/", s.handleIndex)
		r.Get("/jump", s.handleJump)
		r.Get("/healthz", s.handleHealthz)
		r.Handle(assets.DefaultPrefix+"*", s.static)

		r.Get("/~{owner}/{repo}", s.handleRepo)
		// A single wildcard route serves both the form target (empty wildcard ⇒
		// redirect to the canonical URL) and the compare view itself.
		r.Get("/~{owner}/{repo}/compare/*", s.handleCompare)
		r.Get("/~{owner}/{repo}/commit/{rev}", s.handleCommit)
	})
}

// handleHealthz is a dependency-free liveness probe.
func (s *Server) handleHealthz(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	_, _ = w.Write([]byte("ok\n"))
}

// handleJump powers the owner/repo jump form: it redirects to the canonical repo
// URL. A leading "~" on the owner is tolerated.
func (s *Server) handleJump(w http.ResponseWriter, r *http.Request) {
	owner := strings.TrimPrefix(strings.TrimSpace(r.URL.Query().Get("owner")), "~")
	repo := strings.TrimSpace(r.URL.Query().Get("repo"))
	if owner == "" || repo == "" {
		s.renderError(w, r, http.StatusBadRequest, "both owner and repository are required")
		return
	}
	http.Redirect(w, r, "/~"+owner+"/"+repo, http.StatusFound)
}