Files
warmbly/internal/api/handler/admin_settings.go
Matthew Meszaros e0e4a010a0 admin: storage_backends registry + settings/dek/worker-config endpoints
New storage_backends table is the runtime inventory of pluggable
infrastructure choices (KMS, encrypted_keys, blob, eventbus, cache).
Each kind has exactly one active row, enforced via a partial unique
index. Read-only rows are env-var driven; UI-mutable rows can be
flipped via SetActive.

settings.Registrar reflects boot-time backend choices into the table
so the admin UI sees what's actually running.

New admin endpoints under /admin/settings/backends:
  GET    /settings/backends?kind=...
  GET    /settings/backends/active/:kind
  POST   /settings/backends/:id/activate

New internal endpoints under /api/v1/internal:
  GET  /worker/config       - workers fetch runtime config on boot
  POST /worker/heartbeat    - liveness ping
(DEK endpoints added in the encryptedkeys commit.)

handler.Handler grows EncryptedKeys + StorageBackendRepo fields.

5 registrar tests cover create / update-and-activate / skip-when-active /
lookup-error propagation / RegisterAll stop-on-first-error using a
mock repository.
2026-05-27 14:43:36 +00:00

60 lines
1.8 KiB
Go

package handler
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// Admin endpoints for /admin/settings/backends — surface the storage_backends
// table to the admin UI. Today most backends are read-only (env-var driven for
// KMS / EncryptedKeys / EventBus); the Activate path exists so once
// admin-mutable backends (Blob primarily) are wired into runtime reload, the
// UI can flip the active row without code changes.
func (h *Handler) AdminListStorageBackends(c *gin.Context) {
if kind := c.Query("kind"); kind != "" {
rows, err := h.StorageBackendRepo.ListByKind(c.Request.Context(), kind)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"backends": rows})
return
}
rows, err := h.StorageBackendRepo.List(c.Request.Context())
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"backends": rows})
}
func (h *Handler) AdminGetActiveStorageBackend(c *gin.Context) {
kind := c.Param("kind")
row, err := h.StorageBackendRepo.GetActive(c.Request.Context(), kind)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
if row == nil {
c.JSON(http.StatusNotFound, gin.H{"error": "no active backend for kind"})
return
}
c.JSON(http.StatusOK, row)
}
func (h *Handler) AdminActivateStorageBackend(c *gin.Context) {
id, err := uuid.Parse(c.Param("id"))
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
if err := h.StorageBackendRepo.SetActive(c.Request.Context(), id); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"status": "activated"})
}