ai cooking
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

can't believe that worked (#264)

Co-authored-by: paul miller <paul.miller>

authored by

Paul Miller
paul miller
and committed by
GitHub
affc0130 3d5e3742

+111 -7
+43 -7
internal/logs/handler.go
··· 5 5 "fmt" 6 6 "log/slog" 7 7 "net/http" 8 + "net/http/httputil" 9 + "net/url" 8 10 "strconv" 9 11 ) 10 12 11 13 type handler struct { 12 - reader *Reader 14 + reader *Reader 15 + datasette http.Handler 13 16 } 14 17 15 18 func NewHandler(cfg logsink.Config) (*handler, error) { ··· 18 21 return nil, fmt.Errorf("failed to create log reader: %w", err) 19 22 } 20 23 24 + datasetteProxy, err := newDatasetteProxy() 25 + if err != nil { 26 + return nil, fmt.Errorf("failed to create datasette proxy: %w", err) 27 + } 28 + 21 29 return &handler{ 22 - reader: reader, 30 + reader: reader, 31 + datasette: datasetteProxy, 23 32 }, nil 24 33 } 25 34 ··· 27 36 func (h *handler) Register(mux *http.ServeMux) { 28 37 mux.HandleFunc("/logs", h.handleLogsPage) 29 38 mux.HandleFunc("/api/logs", h.handleLogsAPI) 39 + mux.Handle("/datasette/", http.StripPrefix("/datasette", h.datasette)) 30 40 } 31 41 32 42 func (h *handler) handleLogsPage(w http.ResponseWriter, r *http.Request) { ··· 41 51 "frame-ancestors 'none'; "+ 42 52 "upgrade-insecure-requests;") 43 53 44 - _, err := w.Write([]byte(`<!doctype html> 54 + page := `<!doctype html> 45 55 <meta charset="utf-8" /> 46 56 <title>Logs</title> 47 57 <script> ··· 49 59 const qs = new URLSearchParams(location.search); 50 60 for (const k of ["hours"]) if (qs.has(k)) api.searchParams.set(k, qs.get(k)); 51 61 52 - const lite = new URL("https://lite.datasette.io/"); 62 + const lite = new URL("/admin/datasette/", location.origin); 53 63 lite.searchParams.set("json", api.toString()); 54 - // Optional: turn off analytics 55 - // lite.searchParams.set("analytics", "off"); 56 64 57 65 location.replace(lite.toString()); 58 - </script>`)) 66 + </script>` 67 + 68 + _, err := w.Write([]byte(page)) 59 69 if err != nil { 60 70 slog.ErrorContext(r.Context(), "failed to write logs page", "error", err) 61 71 } ··· 79 89 return 80 90 } 81 91 } 92 + 93 + func newDatasetteProxy() (http.Handler, error) { 94 + target, err := url.Parse("https://lite.datasette.io") 95 + if err != nil { 96 + return nil, err 97 + } 98 + 99 + proxy := newSingleHostProxy(target) 100 + proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) { 101 + slog.ErrorContext(r.Context(), "datasette proxy request failed", "error", err) 102 + http.Error(w, "Datasette is unavailable", http.StatusBadGateway) 103 + } 104 + 105 + return proxy, nil 106 + } 107 + 108 + func newSingleHostProxy(target *url.URL) *httputil.ReverseProxy { 109 + proxy := httputil.NewSingleHostReverseProxy(target) 110 + originalDirector := proxy.Director 111 + proxy.Director = func(req *http.Request) { 112 + originalDirector(req) 113 + // Ensure upstream host routing matches the target domain. 114 + req.Host = target.Host 115 + } 116 + return proxy 117 + }
+68
internal/logs/handler_test.go
··· 1 + package logs 2 + 3 + import ( 4 + "net/http" 5 + "net/http/httptest" 6 + "net/url" 7 + "strings" 8 + "testing" 9 + ) 10 + 11 + func TestHandleLogsPageRedirectsToLocalDatasette(t *testing.T) { 12 + t.Parallel() 13 + 14 + h := &handler{} 15 + req := httptest.NewRequest(http.MethodGet, "/admin/logs?hours=6", nil) 16 + rr := httptest.NewRecorder() 17 + h.handleLogsPage(rr, req) 18 + 19 + if rr.Code != http.StatusOK { 20 + t.Fatalf("expected status %d, got %d", http.StatusOK, rr.Code) 21 + } 22 + 23 + body := rr.Body.String() 24 + if !strings.Contains(body, `new URL("/admin/datasette/", location.origin)`) { 25 + t.Fatalf("expected local datasette URL in body, got: %s", body) 26 + } 27 + if strings.Contains(body, "https://lite.datasette.io/") { 28 + t.Fatalf("expected no direct lite.datasette.io redirect, got: %s", body) 29 + } 30 + } 31 + 32 + func TestNewDatasetteProxy(t *testing.T) { 33 + t.Parallel() 34 + 35 + proxy, err := newDatasetteProxy() 36 + if err != nil { 37 + t.Fatalf("expected proxy without error, got: %v", err) 38 + } 39 + if proxy == nil { 40 + t.Fatal("expected non-nil proxy") 41 + } 42 + } 43 + 44 + func TestNewSingleHostProxyRewritesHost(t *testing.T) { 45 + t.Parallel() 46 + 47 + target, err := url.Parse("https://example.test/sub") 48 + if err != nil { 49 + t.Fatalf("parse target URL: %v", err) 50 + } 51 + 52 + proxy := newSingleHostProxy(target) 53 + req := httptest.NewRequest(http.MethodGet, "http://localhost/admin/datasette/?json=http://localhost:8080/admin/api/logs", nil) 54 + proxy.Director(req) 55 + 56 + if req.Host != "example.test" { 57 + t.Fatalf("expected host example.test, got %s", req.Host) 58 + } 59 + if req.URL.Scheme != "https" { 60 + t.Fatalf("expected scheme https, got %s", req.URL.Scheme) 61 + } 62 + if req.URL.Host != "example.test" { 63 + t.Fatalf("expected url host example.test, got %s", req.URL.Host) 64 + } 65 + if req.URL.Path != "/sub/admin/datasette/" { 66 + t.Fatalf("expected path /sub/admin/datasette/, got %s", req.URL.Path) 67 + } 68 + }