// M13a W5: admin HTTP routes for deliverd-telegram. Same // shape as deliverd-fcm/admin.go but scoped to the // `telegram` channel. Cross-channel safety: rows for any // other channel return 404 (we don't leak data the // telegram deliverd shouldn't see). package main import ( "encoding/json" "log/slog" "net/http" "strconv" "git3.techno-world.net/lrosales/broad-announce/internal/authd" "git3.techno-world.net/lrosales/broad-announce/internal/dlq" "git3.techno-world.net/lrosales/broad-announce/internal/postgres" ) func wireAdminRoutes(mux *http.ServeMux, pool *postgres.Pool, logger *slog.Logger) { if !authd.EnvEnabled() { logger.Warn("admin routes are DISABLED (set BA_AUTHD_JWT_SECRET to enable)") return } ad, err := authd.NewFromEnv() if err != nil { logger.Error("BA_AUTHD_JWT_SECRET set but authd init failed; admin routes DISABLED", "err", err) return } logger.Info("admin routes enabled with JWT gate (telegram)") mux.Handle("GET /v1/admin/dlq", ad.RequireAuth(http.HandlerFunc(handleListDLQ(pool, logger)))) mux.Handle("GET /v1/admin/dlq/{id}", ad.RequireAuth(http.HandlerFunc(handleGetDLQ(pool, logger)))) } func handleListDLQ(pool *postgres.Pool, logger *slog.Logger) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) rows, err := dlq.List(r.Context(), pool, dlq.ListFilters{ Channel: telegramChannel, CompanyID: r.URL.Query().Get("company_id"), AlertID: r.URL.Query().Get("alert_id"), IncludeDiscarded: r.URL.Query().Get("include") == "all", Limit: limit, Offset: offset, }) if err != nil { logger.Error("dlq list (telegram)", "err", err) http.Error(w, "dlq list: "+err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]any{ "channel": telegramChannel, "rows": rows, "limit": effectiveLimit(limit), "offset": effectiveOffset(offset), }) } } func handleGetDLQ(pool *postgres.Pool, logger *slog.Logger) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { raw := r.PathValue("id") id, err := strconv.ParseInt(raw, 10, 64) if err != nil || id <= 0 { http.Error(w, "bad id", http.StatusBadRequest) return } row, err := dlq.Get(r.Context(), pool, id) if err != nil { logger.Error("dlq get (telegram)", "err", err, "id", id) http.Error(w, "dlq get: "+err.Error(), http.StatusInternalServerError) return } if row == nil { http.Error(w, "not found", http.StatusNotFound) return } // Cross-channel safety: this deliverd-telegram binary // only knows about the telegram channel. if row.Channel != telegramChannel { http.Error(w, "not found", http.StatusNotFound) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(row) } } func effectiveLimit(n int) int { if n <= 0 || n > 500 { return 50 } return n } func effectiveOffset(n int) int { if n < 0 { return 0 } return n }