nandi/gleanpublic Fork 0
3093622
Commits
Clone
git clone https://git.rickub.com/nandi/glean.git
git clone ssh://git@rickub.com/nandi/glean.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Add sitemap.xml endpointUnverified

Julien Robert committed 2026-05-18T12:36:01+02:00 Browse files
3093622 parent: a934412
modified docs/specs.md +2 -0
@@ -966,6 +966,7 @@ The server renders HTML fragments that htmx swaps into the page. No JSON API nee
966966 | `/auth/start` | POST | Start OAuth authorization flow |
967967 | `/auth/callback` | GET | OAuth callback |
968968 | `/terms` | GET | Terms of service |
969+| `/sitemap.xml` | GET | XML sitemap for search engines |
969970
970971 ### 8.2 htmx Patterns
971972
@@ -1048,6 +1049,7 @@ glean/
10481049 │ │ ├── settings_handler.go # User settings (language preferences, digest toggle)
10491050 │ │ ├── digest_handler.go # Daily digest handler (LLM summary, mark-read)
10501051 │ │ ├── recs_handler.go # Recommendation dismiss handlers
1052+│ │ ├── sitemap_handler.go # XML sitemap handler (public pages)
10511053 │ │ ├── terms_handler.go # Terms of service handler
10521054 │ │ ├── pagination.go # Pagination helpers
10531055 │ │ ├── middleware.go # Auth, logging, CSRF middleware
@@ -966,6 +966,7 @@ The server renders HTML fragments that htmx swaps into the page. No JSON API nee
966 | `/auth/start` | POST | Start OAuth authorization flow |966 | `/auth/start` | POST | Start OAuth authorization flow |
967 | `/auth/callback` | GET | OAuth callback |967 | `/auth/callback` | GET | OAuth callback |
968 | `/terms` | GET | Terms of service |968 | `/terms` | GET | Terms of service |
969+| `/sitemap.xml` | GET | XML sitemap for search engines |
969 970
970 ### 8.2 htmx Patterns971 ### 8.2 htmx Patterns
971 972
@@ -1048,6 +1049,7 @@ glean/
1048 │ │ ├── settings_handler.go # User settings (language preferences, digest toggle)1049 │ │ ├── settings_handler.go # User settings (language preferences, digest toggle)
1049 │ │ ├── digest_handler.go # Daily digest handler (LLM summary, mark-read)1050 │ │ ├── digest_handler.go # Daily digest handler (LLM summary, mark-read)
1050 │ │ ├── recs_handler.go # Recommendation dismiss handlers1051 │ │ ├── recs_handler.go # Recommendation dismiss handlers
1052+│ │ ├── sitemap_handler.go # XML sitemap handler (public pages)
1051 │ │ ├── terms_handler.go # Terms of service handler1053 │ │ ├── terms_handler.go # Terms of service handler
1052 │ │ ├── pagination.go # Pagination helpers1054 │ │ ├── pagination.go # Pagination helpers
1053 │ │ ├── middleware.go # Auth, logging, CSRF middleware1055 │ │ ├── middleware.go # Auth, logging, CSRF middleware
modified internal/server/server.go +1 -0
@@ -246,6 +246,7 @@ func (s *Server) setupRoutes() {
246246 s.router.Get("/xrpc/at.glean.listFeedLists", xrpc.ListFeedLists)
247247
248248 s.router.Get("/terms", s.handleTerms)
249+ s.router.Get("/sitemap.xml", s.handleSitemap)
249250 s.router.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.FS(static.Files))))
250251 s.router.Handle("/metrics", promhttp.Handler())
251252 s.router.Get("/stats", s.handleStats)
@@ -246,6 +246,7 @@ func (s *Server) setupRoutes() {
246 s.router.Get("/xrpc/at.glean.listFeedLists", xrpc.ListFeedLists)246 s.router.Get("/xrpc/at.glean.listFeedLists", xrpc.ListFeedLists)
247 247
248 s.router.Get("/terms", s.handleTerms)248 s.router.Get("/terms", s.handleTerms)
249+ s.router.Get("/sitemap.xml", s.handleSitemap)
249 s.router.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.FS(static.Files))))250 s.router.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.FS(static.Files))))
250 s.router.Handle("/metrics", promhttp.Handler())251 s.router.Handle("/metrics", promhttp.Handler())
251 s.router.Get("/stats", s.handleStats)252 s.router.Get("/stats", s.handleStats)
added internal/server/sitemap_handler.go +55 -0
new file mode 100644
@@ -0,0 +1,55 @@
1+package server
2+
3+import (
4+ "encoding/xml"
5+ "net/http"
6+ "strings"
7+ "time"
8+)
9+
10+type urlset struct {
11+ XMLName xml.Name `xml:"urlset"`
12+ Xmlns string `xml:"xmlns,attr"`
13+ URLs []sitemapURL `xml:"url"`
14+}
15+
16+type sitemapURL struct {
17+ Loc string `xml:"loc"`
18+ Lastmod string `xml:"lastmod,omitempty"`
19+ Changefreq string `xml:"changefreq,omitempty"`
20+ Priority string `xml:"priority,omitempty"`
21+}
22+
23+func (s *Server) handleSitemap(w http.ResponseWriter, _ *http.Request) {
24+ baseURL := s.baseURL()
25+
26+ now := time.Now().UTC().Format("2006-01-02")
27+
28+ urls := []sitemapURL{
29+ {Loc: baseURL + "/", Lastmod: now, Changefreq: "daily", Priority: "1.0"},
30+ {Loc: baseURL + "/trending", Lastmod: now, Changefreq: "hourly", Priority: "0.9"},
31+ {Loc: baseURL + "/terms", Changefreq: "monthly", Priority: "0.3"},
32+ }
33+
34+ set := urlset{
35+ Xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9",
36+ URLs: urls,
37+ }
38+
39+ w.Header().Set("Content-Type", "application/xml")
40+ w.Write([]byte(xml.Header))
41+ xml.NewEncoder(w).Encode(set)
42+}
43+
44+func (s *Server) baseURL() string {
45+ if s.clientID == "" {
46+ return "http://localhost:8080"
47+ }
48+ host := s.clientID
49+ host, _ = strings.CutPrefix(host, "https://")
50+ host, _ = strings.CutPrefix(host, "http://")
51+ if i := strings.Index(host, "/"); i >= 0 {
52+ host = host[:i]
53+ }
54+ return "https://" + host
55+}
new file mode 100644
@@ -0,0 +1,55 @@
1+package server
2+
3+import (
4+ "encoding/xml"
5+ "net/http"
6+ "strings"
7+ "time"
8+)
9+
10+type urlset struct {
11+ XMLName xml.Name `xml:"urlset"`
12+ Xmlns string `xml:"xmlns,attr"`
13+ URLs []sitemapURL `xml:"url"`
14+}
15+
16+type sitemapURL struct {
17+ Loc string `xml:"loc"`
18+ Lastmod string `xml:"lastmod,omitempty"`
19+ Changefreq string `xml:"changefreq,omitempty"`
20+ Priority string `xml:"priority,omitempty"`
21+}
22+
23+func (s *Server) handleSitemap(w http.ResponseWriter, _ *http.Request) {
24+ baseURL := s.baseURL()
25+
26+ now := time.Now().UTC().Format("2006-01-02")
27+
28+ urls := []sitemapURL{
29+ {Loc: baseURL + "/", Lastmod: now, Changefreq: "daily", Priority: "1.0"},
30+ {Loc: baseURL + "/trending", Lastmod: now, Changefreq: "hourly", Priority: "0.9"},
31+ {Loc: baseURL + "/terms", Changefreq: "monthly", Priority: "0.3"},
32+ }
33+
34+ set := urlset{
35+ Xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9",
36+ URLs: urls,
37+ }
38+
39+ w.Header().Set("Content-Type", "application/xml")
40+ w.Write([]byte(xml.Header))
41+ xml.NewEncoder(w).Encode(set)
42+}
43+
44+func (s *Server) baseURL() string {
45+ if s.clientID == "" {
46+ return "http://localhost:8080"
47+ }
48+ host := s.clientID
49+ host, _ = strings.CutPrefix(host, "https://")
50+ host, _ = strings.CutPrefix(host, "http://")
51+ if i := strings.Index(host, "/"); i >= 0 {
52+ host = host[:i]
53+ }
54+ return "https://" + host
55+}