avatars.go
⎇
Raw
1package web
2
3import (
4 "net/http"
5 "os"
6 "strconv"
7 "strings"
8
9 "github.com/go-chi/chi/v5"
10
11 "hearthforge/internal/avatar"
12 "hearthforge/internal/highlight"
13)
14
15// immutableCache caches a response for one year. Both routes are safe to
16// cache that long because their URLs carry a version or change only with the
17// binary.
18const immutableCache = "public, max-age=31536000, immutable"
19
20// avatarRoutes serves user avatars and the generated syntax highlighting CSS.
21func (s *Server) avatarRoutes(r chi.Router) {
22 r.Get("/avatars/{id}", s.serveAvatar)
23 // The literal path wins over the /assets/* static file handler in chi's
24 // trie, so the order of registration does not matter.
25 // TestCodeThemeBeatsStaticMount covers that.
26 r.Get("/assets/code-theme.css", serveCodeTheme)
27}
28
29func (s *Server) serveAvatar(w http.ResponseWriter, r *http.Request) {
30 name := strings.TrimSuffix(chi.URLParam(r, "id"), ".png")
31 userID, err := strconv.ParseInt(name, 10, 64)
32 if err != nil {
33 http.Error(w, "Not found", http.StatusNotFound)
34 return
35 }
36 // Without the version parameter the answer could be cached forever under
37 // a URL that never changes, so refuse it.
38 if !r.URL.Query().Has("v") {
39 http.Error(w, "Not found", http.StatusNotFound)
40 return
41 }
42
43 path := avatar.Path(s.Cfg.AvatarsDir(), userID)
44 if _, err := os.Stat(path); err != nil {
45 // Users created before avatars existed have no file yet.
46 user, err := s.DB.UserByID(r.Context(), userID)
47 if err != nil || user == nil {
48 http.Error(w, "Not found", http.StatusNotFound)
49 return
50 }
51 if err := avatar.SaveDefault(s.Cfg.AvatarsDir(), userID, user.Username); err != nil {
52 http.Error(w, "Not found", http.StatusNotFound)
53 return
54 }
55 }
56
57 w.Header().Set("Content-Type", "image/png")
58 w.Header().Set("Cache-Control", immutableCache)
59 http.ServeFile(w, r, path)
60}
61
62func serveCodeTheme(w http.ResponseWriter, r *http.Request) {
63 w.Header().Set("Content-Type", "text/css; charset=utf-8")
64 w.Header().Set("Cache-Control", immutableCache)
65 w.Write([]byte(highlight.CSS()))
66}
67