| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119 |
- // M13a W5: admin HTTP routes for deliverd-fcm. Two routes
- // are registered when BA_AUTHD_JWT_SECRET is set:
- //
- // GET /v1/admin/dlq — list DLQ rows for the
- // `fcm` channel. Filters: company_id, alert_id, include=
- // all. Read-only. Any authenticated user.
- // GET /v1/admin/dlq/{id} — fetch one row, including
- // payload (for inspection before replay). Read-only.
- //
- // These mirror the global /v1/dlq endpoints in admind, but
- // scoped to the FCM channel so a per-channel deliverd can
- // answer "what's stuck in my DLQ" without needing access
- // to admind.
- //
- // When BA_AUTHD_JWT_SECRET is unset, no admin routes are
- // registered.
- 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"
- )
- // wireAdminRoutes wires the admin routes onto mux.
- 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 (fcm)")
- 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: fcmChannel,
- 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 (fcm)", "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": fcmChannel,
- "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 (fcm)", "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-fcm binary only
- // knows about the fcm channel. If a row from another
- // channel ends up in the URL, treat it as not-found
- // rather than leaking data.
- if row.Channel != fcmChannel {
- 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
- }
|