aboutsummaryrefslogtreecommitdiff
path: root/avatar.go
diff options
context:
space:
mode:
authorLena <lena@omega>2026-08-01 00:00:00 +0000
committerLena <lena@omega>2026-08-01 00:00:00 +0000
commitbb6bc2387d58c737dbf92e73f8544bf39319dbd8 (patch)
tree6d5eaf5dac213074cd832da50f64260fce728fed /avatar.go
downloadgit-forge-bb6bc2387d58c737dbf92e73f8544bf39319dbd8.tar.gz
Enter git-forgeHEADmaster
A small self-hosted git forge: HTTP push and pull, cgit browsing, local and OIDC accounts, TOTP, scoped push tokens, per-repo permissions, issues, a wiki, and outbound mail. It does not reimplement git. git-http-backend and cgit run as CGI behind its own listener, which authorises every request before exec'ing either. One binary, one data directory, one listener: no database, no JavaScript, and state is JSON in a git repo with a working tree.
Diffstat (limited to 'avatar.go')
-rw-r--r--avatar.go45
1 files changed, 45 insertions, 0 deletions
diff --git a/avatar.go b/avatar.go
new file mode 100644
index 0000000..4342890
--- /dev/null
+++ b/avatar.go
@@ -0,0 +1,45 @@
+package main
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "net/http"
+ "strings"
+)
+
+// An avatar is drawn from the name alone: fetching one tells somewhere else
+// who reads what, and an upload needs a parser.
+func avatarSVG(name string) []byte {
+ sum := sha256.Sum256([]byte(name))
+ // Fixed saturation and lightness, so every one is readable.
+ hue := int(sum[0]) * 360 / 256
+ var b strings.Builder
+ fmt.Fprintf(&b, `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 5 5" width="20" height="20" role="img" aria-label="%s">`, name)
+ fmt.Fprintf(&b, `<rect width="5" height="5" fill="#eee"/>`)
+ fmt.Fprintf(&b, `<g fill="hsl(%d,45%%,35%%)">`, hue)
+ // Three columns mirrored into five.
+ for x := range 3 {
+ for y := range 5 {
+ if sum[1+x*5+y]&1 == 0 {
+ continue
+ }
+ fmt.Fprintf(&b, `<rect x="%d" y="%d" width="1" height="1"/>`, x, y)
+ if x < 2 {
+ fmt.Fprintf(&b, `<rect x="%d" y="%d" width="1" height="1"/>`, 4-x, y)
+ }
+ }
+ }
+ b.WriteString(`</g></svg>`)
+ return []byte(b.String())
+}
+
+func (f *Forge) handleAvatar(w http.ResponseWriter, r *http.Request) {
+ name := r.PathValue("name")
+ if !accountNameRE.MatchString(name) {
+ http.NotFound(w, r)
+ return
+ }
+ w.Header().Set("Content-Type", "image/svg+xml")
+ w.Header().Set("Cache-Control", "max-age=86400")
+ w.Write(avatarSVG(name))
+}