Coffee journaling on ATProto (alpha) alpha.arabica.social
coffee

feat: 404 page

pdewey.com 8d528ce9 f6209f36

verified
+93 -10
-1
docs/auth-required-ui.md
··· 1 - # Deleted - Remove this file
+16 -1
docs/future-witness-cache.md
··· 1 - # Deleted - Remove this file 1 + # Witness Cache 2 + 3 + Paul Frazee: 4 + 5 + I'm increasingly convinced that many Atmosphere backends start with a local "witness cache" of the repositories. 6 + A witness cache is a copy of the repository records, plus a timestamp of when the record was indexed (the "witness time") which you want to keep 7 + 8 + The key feature is: you can replay it 9 + 10 + With local replay, you can add new tables or indexes to your backend and quickly backfill the data. If you don't have a witness cache, you would have to do backfill from the network, which is slow 11 + 12 + RocksDB or other LSMs are good candidates for a witness cache (good write throughput) 13 + 14 + Clickhouse and DuckDB are also good candidates (good compression ratio) 15 + 16 + ## TODO
-1
docs/implementation-progress.md
··· 1 - # Deleted - Remove this file
-1
docs/oauth-token-fix.md
··· 1 - # Deleted - Remove this file
-1
docs/phase0-summary.md
··· 1 - # Deleted - Remove this file
-1
docs/testing-pds-storage.md
··· 1 - # Deleted - Remove this file
+15
internal/bff/render.go
··· 317 317 } 318 318 return t.ExecuteTemplate(w, "layout", data) 319 319 } 320 + 321 + // Render404 renders the 404 not found page 322 + func Render404(w http.ResponseWriter, isAuthenticated bool, userDID string) error { 323 + t, err := parsePageTemplate("404.tmpl") 324 + if err != nil { 325 + return err 326 + } 327 + data := &PageData{ 328 + Title: "Page Not Found", 329 + IsAuthenticated: isAuthenticated, 330 + UserDID: userDID, 331 + } 332 + w.WriteHeader(http.StatusNotFound) 333 + return t.ExecuteTemplate(w, "layout", data) 334 + }
+2 -2
internal/feed/service.go
··· 44 44 } 45 45 } 46 46 47 - // GetRecentBrews fetches recent activity (brews and other records) from all registered users 47 + // GetRecentRecords fetches recent activity (brews and other records) from all registered users 48 48 // Returns up to `limit` items sorted by most recent first 49 - func (s *Service) GetRecentBrews(ctx context.Context, limit int) ([]*FeedItem, error) { 49 + func (s *Service) GetRecentRecords(ctx context.Context, limit int) ([]*FeedItem, error) { 50 50 dids := s.registry.List() 51 51 if len(dids) == 0 { 52 52 log.Debug().Msg("feed: no registered users")
+13 -1
internal/handlers/handlers.go
··· 98 98 func (h *Handler) HandleFeedPartial(w http.ResponseWriter, r *http.Request) { 99 99 var feedItems []*feed.FeedItem 100 100 if h.feedService != nil { 101 - feedItems, _ = h.feedService.GetRecentBrews(r.Context(), 20) 101 + feedItems, _ = h.feedService.GetRecentRecords(r.Context(), 20) 102 102 } 103 103 104 104 if err := bff.RenderFeedPartial(w, feedItems); err != nil { ··· 1263 1263 log.Error().Err(err).Msg("Failed to render profile page") 1264 1264 } 1265 1265 } 1266 + 1267 + // HandleNotFound renders the 404 page 1268 + func (h *Handler) HandleNotFound(w http.ResponseWriter, r *http.Request) { 1269 + // Check if current user is authenticated (for nav bar state) 1270 + didStr, err := atproto.GetAuthenticatedDID(r.Context()) 1271 + isAuthenticated := err == nil && didStr != "" 1272 + 1273 + if err := bff.Render404(w, isAuthenticated, didStr); err != nil { 1274 + http.Error(w, "Page not found", http.StatusNotFound) 1275 + log.Error().Err(err).Msg("Failed to render 404 page") 1276 + } 1277 + }
+29 -1
internal/middleware/logging.go
··· 1 1 package middleware 2 2 3 3 import ( 4 + "net" 4 5 "net/http" 6 + "strings" 5 7 "time" 6 8 7 9 "arabica/internal/atproto" 8 10 9 11 "github.com/rs/zerolog" 10 12 ) 13 + 14 + // getClientIP extracts the real client IP address from the request, 15 + // checking X-Forwarded-For and X-Real-IP headers for reverse proxy setups. 16 + func getClientIP(r *http.Request) string { 17 + // Check X-Forwarded-For header (can contain multiple IPs: client, proxy1, proxy2) 18 + if xff := r.Header.Get("X-Forwarded-For"); xff != "" { 19 + // Take the first IP (the original client) 20 + if idx := strings.Index(xff, ","); idx != -1 { 21 + return strings.TrimSpace(xff[:idx]) 22 + } 23 + return strings.TrimSpace(xff) 24 + } 25 + 26 + // Check X-Real-IP header (single IP set by some proxies) 27 + if xri := r.Header.Get("X-Real-IP"); xri != "" { 28 + return strings.TrimSpace(xri) 29 + } 30 + 31 + // Fall back to RemoteAddr (strip port if present) 32 + ip, _, err := net.SplitHostPort(r.RemoteAddr) 33 + if err != nil { 34 + // RemoteAddr might not have a port 35 + return r.RemoteAddr 36 + } 37 + return ip 38 + } 11 39 12 40 // LoggingMiddleware returns a middleware that logs HTTP request details with structured logging 13 41 func LoggingMiddleware(logger zerolog.Logger) func(http.Handler) http.Handler { ··· 45 73 Str("query", r.URL.RawQuery). 46 74 Int("status", rw.statusCode). 47 75 Dur("duration", duration). 48 - Str("remote_addr", r.RemoteAddr). 76 + Str("client_ip", getClientIP(r)). 49 77 Str("user_agent", r.UserAgent()). 50 78 Int64("bytes_written", rw.bytesWritten). 51 79 Str("proto", r.Proto)
+3
internal/routing/routing.go
··· 83 83 fs := http.FileServer(http.Dir("web/static")) 84 84 mux.Handle("GET /static/", http.StripPrefix("/static/", fs)) 85 85 86 + // Catch-all 404 handler - must be last, catches any unmatched routes 87 + mux.HandleFunc("/", h.HandleNotFound) 88 + 86 89 // Apply middleware in order (last added is executed first) 87 90 // 1. Apply OAuth middleware to add auth context to all requests 88 91 handler := cfg.OAuthManager.AuthMiddleware(mux)
+3
justfile
··· 4 4 run-production: 5 5 @LOG_FORMAT=json SECURE_COOKIES=true go run cmd/server/main.go 6 6 7 + test: 8 + @go test ./... -cover -coverprofile=cover.out 9 + 7 10 style: 8 11 @tailwindcss -i web/static/css/style.css -o web/static/css/output.css --minify
+12
templates/404.tmpl
··· 1 + {{define "content"}} 2 + <div class="max-w-4xl mx-auto"> 3 + <div class="bg-white rounded-lg shadow-md p-8 text-center"> 4 + <div class="text-6xl mb-4">404</div> 5 + <h2 class="text-2xl font-bold text-gray-800 mb-4">Page Not Found</h2> 6 + <p class="text-gray-600 mb-6">The page you're looking for doesn't exist or has been moved.</p> 7 + <a href="/" class="inline-block bg-brown-600 text-white py-3 px-6 rounded-lg hover:bg-brown-700 transition"> 8 + Back to Home 9 + </a> 10 + </div> 11 + </div> 12 + {{end}}