admin.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. // M13a W5: admin HTTP routes for deliverd-telegram. Same
  2. // shape as deliverd-fcm/admin.go but scoped to the
  3. // `telegram` channel. Cross-channel safety: rows for any
  4. // other channel return 404 (we don't leak data the
  5. // telegram deliverd shouldn't see).
  6. package main
  7. import (
  8. "encoding/json"
  9. "log/slog"
  10. "net/http"
  11. "strconv"
  12. "git3.techno-world.net/lrosales/broad-announce/internal/authd"
  13. "git3.techno-world.net/lrosales/broad-announce/internal/dlq"
  14. "git3.techno-world.net/lrosales/broad-announce/internal/postgres"
  15. )
  16. func wireAdminRoutes(mux *http.ServeMux, pool *postgres.Pool, logger *slog.Logger) {
  17. if !authd.EnvEnabled() {
  18. logger.Warn("admin routes are DISABLED (set BA_AUTHD_JWT_SECRET to enable)")
  19. return
  20. }
  21. ad, err := authd.NewFromEnv()
  22. if err != nil {
  23. logger.Error("BA_AUTHD_JWT_SECRET set but authd init failed; admin routes DISABLED", "err", err)
  24. return
  25. }
  26. logger.Info("admin routes enabled with JWT gate (telegram)")
  27. mux.Handle("GET /v1/admin/dlq",
  28. ad.RequireAuth(http.HandlerFunc(handleListDLQ(pool, logger))))
  29. mux.Handle("GET /v1/admin/dlq/{id}",
  30. ad.RequireAuth(http.HandlerFunc(handleGetDLQ(pool, logger))))
  31. }
  32. func handleListDLQ(pool *postgres.Pool, logger *slog.Logger) http.HandlerFunc {
  33. return func(w http.ResponseWriter, r *http.Request) {
  34. limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
  35. offset, _ := strconv.Atoi(r.URL.Query().Get("offset"))
  36. rows, err := dlq.List(r.Context(), pool, dlq.ListFilters{
  37. Channel: telegramChannel,
  38. CompanyID: r.URL.Query().Get("company_id"),
  39. AlertID: r.URL.Query().Get("alert_id"),
  40. IncludeDiscarded: r.URL.Query().Get("include") == "all",
  41. Limit: limit,
  42. Offset: offset,
  43. })
  44. if err != nil {
  45. logger.Error("dlq list (telegram)", "err", err)
  46. http.Error(w, "dlq list: "+err.Error(), http.StatusInternalServerError)
  47. return
  48. }
  49. w.Header().Set("Content-Type", "application/json")
  50. _ = json.NewEncoder(w).Encode(map[string]any{
  51. "channel": telegramChannel,
  52. "rows": rows,
  53. "limit": effectiveLimit(limit),
  54. "offset": effectiveOffset(offset),
  55. })
  56. }
  57. }
  58. func handleGetDLQ(pool *postgres.Pool, logger *slog.Logger) http.HandlerFunc {
  59. return func(w http.ResponseWriter, r *http.Request) {
  60. raw := r.PathValue("id")
  61. id, err := strconv.ParseInt(raw, 10, 64)
  62. if err != nil || id <= 0 {
  63. http.Error(w, "bad id", http.StatusBadRequest)
  64. return
  65. }
  66. row, err := dlq.Get(r.Context(), pool, id)
  67. if err != nil {
  68. logger.Error("dlq get (telegram)", "err", err, "id", id)
  69. http.Error(w, "dlq get: "+err.Error(), http.StatusInternalServerError)
  70. return
  71. }
  72. if row == nil {
  73. http.Error(w, "not found", http.StatusNotFound)
  74. return
  75. }
  76. // Cross-channel safety: this deliverd-telegram binary
  77. // only knows about the telegram channel.
  78. if row.Channel != telegramChannel {
  79. http.Error(w, "not found", http.StatusNotFound)
  80. return
  81. }
  82. w.Header().Set("Content-Type", "application/json")
  83. _ = json.NewEncoder(w).Encode(row)
  84. }
  85. }
  86. func effectiveLimit(n int) int {
  87. if n <= 0 || n > 500 {
  88. return 50
  89. }
  90. return n
  91. }
  92. func effectiveOffset(n int) int {
  93. if n < 0 {
  94. return 0
  95. }
  96. return n
  97. }