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

Configure Feed

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

feat: healthcheck endpoint

+50
+1
cmd/server/main.go
··· 491 491 OAuthManager: oauthManager, 492 492 Logger: log.Logger, 493 493 ModerationService: moderationSvc, 494 + FirehoseConsumer: firehoseConsumer, 494 495 }) 495 496 496 497 // Start internal metrics server on localhost only (not publicly accessible)
+5
internal/handlers/handlers.go
··· 84 84 h.feedIndex = idx 85 85 } 86 86 87 + // FeedIndex returns the feed index for health checks. 88 + func (h *Handler) FeedIndex() *firehose.FeedIndex { 89 + return h.feedIndex 90 + } 91 + 87 92 // SetWitnessCache configures the handler to use the witness cache for cache-first reads. 88 93 func (h *Handler) SetWitnessCache(wc atproto.WitnessCache) { 89 94 h.witnessCache = wc
+44
internal/routing/routing.go
··· 1 1 package routing 2 2 3 3 import ( 4 + "encoding/json" 4 5 "net/http" 5 6 "strings" 6 7 7 8 "tangled.org/arabica.social/arabica/internal/atproto" 9 + "tangled.org/arabica.social/arabica/internal/firehose" 8 10 "tangled.org/arabica.social/arabica/internal/handlers" 9 11 "tangled.org/arabica.social/arabica/internal/middleware" 10 12 "tangled.org/arabica.social/arabica/internal/moderation" ··· 21 23 OAuthManager *atproto.OAuthManager 22 24 Logger zerolog.Logger 23 25 ModerationService *moderation.Service 26 + FirehoseConsumer *firehose.Consumer 24 27 } 25 28 26 29 // SetupRouter creates and configures the HTTP router with all routes and middleware ··· 45 48 mux.HandleFunc("GET /robots.txt", func(w http.ResponseWriter, r *http.Request) { 46 49 http.ServeFile(w, r, "static/robots.txt") 47 50 }) 51 + mux.HandleFunc("GET /healthz", handleHealthz(h, cfg.FirehoseConsumer)) 48 52 49 53 // API routes for handle resolution (used by login autocomplete) 50 54 // These are intentionally public and don't require HTMX headers ··· 235 239 ) 236 240 237 241 return handler 242 + } 243 + 244 + func handleHealthz(h *handlers.Handler, consumer *firehose.Consumer) http.HandlerFunc { 245 + return func(w http.ResponseWriter, r *http.Request) { 246 + status := "ok" 247 + httpStatus := http.StatusOK 248 + 249 + // Check firehose connection 250 + firehoseCheck := map[string]any{"connected": false} 251 + if consumer != nil { 252 + connected := consumer.IsConnected() 253 + firehoseCheck["connected"] = connected 254 + if !connected { 255 + status = "degraded" 256 + } 257 + } 258 + 259 + // Check SQLite feed index 260 + feedIndexCheck := map[string]any{"healthy": false, "ready": false} 261 + if idx := h.FeedIndex(); idx != nil { 262 + feedIndexCheck["ready"] = idx.IsReady() 263 + if err := idx.DB().PingContext(r.Context()); err != nil { 264 + feedIndexCheck["healthy"] = false 265 + status = "error" 266 + httpStatus = http.StatusServiceUnavailable 267 + } else { 268 + feedIndexCheck["healthy"] = true 269 + } 270 + } 271 + 272 + resp := map[string]any{ 273 + "status": status, 274 + "firehose": firehoseCheck, 275 + "feed_index": feedIndexCheck, 276 + } 277 + 278 + w.Header().Set("Content-Type", "application/json") 279 + w.WriteHeader(httpStatus) 280 + json.NewEncoder(w).Encode(resp) 281 + } 238 282 } 239 283 240 284 // pageContextMiddleware reads the X-Page-Context header (set by client-side JS)