mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
feat(api): add feature flag management endpoints
Add FeatureHandler to manage lightweight runtime configuration toggles stored in the Redis `meta:features` hash. This enables dynamic control over system behavior (e.g., storage backends) via the admin API. - Add `GET /admin/features` to list flags - Add `PUT /admin/features` to update flags - Update README with feature flag documentation
This commit is contained in:
106
internal/api/feature_handler.go
Normal file
106
internal/api/feature_handler.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
const featuresKey = "meta:features"
|
||||
|
||||
// FeatureHandler manages lightweight feature flags stored in Redis for DP/CP runtime toggles.
|
||||
// Values are stored as plain strings in a Redis hash.
|
||||
type FeatureHandler struct {
|
||||
rdb *redis.Client
|
||||
}
|
||||
|
||||
func NewFeatureHandler(rdb *redis.Client) *FeatureHandler {
|
||||
return &FeatureHandler{rdb: rdb}
|
||||
}
|
||||
|
||||
// ListFeatures godoc
|
||||
// @Summary List feature flags
|
||||
// @Description Returns all feature flags stored in Redis (meta:features)
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security AdminAuth
|
||||
// @Success 200 {object} gin.H
|
||||
// @Failure 500 {object} gin.H
|
||||
// @Router /admin/features [get]
|
||||
func (h *FeatureHandler) ListFeatures(c *gin.Context) {
|
||||
if h.rdb == nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "redis not configured"})
|
||||
return
|
||||
}
|
||||
m, err := h.rdb.HGetAll(c.Request.Context(), featuresKey).Result()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to read features", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{"features": m})
|
||||
}
|
||||
|
||||
type UpdateFeaturesRequest map[string]any
|
||||
|
||||
// UpdateFeatures godoc
|
||||
// @Summary Update feature flags
|
||||
// @Description Updates selected feature flags (meta:features). Values are stored as strings.
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security AdminAuth
|
||||
// @Param request body object true "Feature map"
|
||||
// @Success 200 {object} gin.H
|
||||
// @Failure 400 {object} gin.H
|
||||
// @Failure 500 {object} gin.H
|
||||
// @Router /admin/features [put]
|
||||
func (h *FeatureHandler) UpdateFeatures(c *gin.Context) {
|
||||
if h.rdb == nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "redis not configured"})
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateFeaturesRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
updates := make(map[string]string, len(req))
|
||||
for k, v := range req {
|
||||
key := strings.TrimSpace(k)
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
switch vv := v.(type) {
|
||||
case string:
|
||||
updates[key] = strings.TrimSpace(vv)
|
||||
case bool:
|
||||
if vv {
|
||||
updates[key] = "true"
|
||||
} else {
|
||||
updates[key] = "false"
|
||||
}
|
||||
case float64:
|
||||
// JSON numbers decode as float64; keep as integer-looking string when possible.
|
||||
updates[key] = fmt.Sprintf("%v", vv)
|
||||
default:
|
||||
updates[key] = fmt.Sprintf("%v", vv)
|
||||
}
|
||||
}
|
||||
|
||||
if len(updates) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no valid feature updates"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.rdb.HSet(c.Request.Context(), featuresKey, updates).Err(); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to update features", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"updated": updates})
|
||||
}
|
||||
Reference in New Issue
Block a user