Files
ez-api/internal/api/binding_handler.go

232 lines
6.8 KiB
Go

package api
import (
"net/http"
"strconv"
"strings"
"github.com/ez-api/ez-api/internal/dto"
"github.com/ez-api/ez-api/internal/model"
groupx "github.com/ez-api/foundation/group"
"github.com/gin-gonic/gin"
)
// CreateBinding godoc
// @Summary Create a new binding
// @Description Create a new (namespace, public_model) binding to a route group and selector
// @Tags admin
// @Accept json
// @Produce json
// @Security AdminAuth
// @Param binding body dto.BindingDTO true "Binding Info"
// @Success 201 {object} model.Binding
// @Failure 400 {object} gin.H
// @Failure 500 {object} gin.H
// @Router /admin/bindings [post]
func (h *Handler) CreateBinding(c *gin.Context) {
var req dto.BindingDTO
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ns := strings.TrimSpace(req.Namespace)
pm := strings.TrimSpace(req.PublicModel)
if ns == "" || pm == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "namespace and public_model required"})
return
}
rg := groupx.Normalize(req.RouteGroup)
if strings.TrimSpace(rg) == "" {
rg = "default"
}
st := strings.TrimSpace(req.Status)
if st == "" {
st = "active"
}
selectorType := strings.TrimSpace(req.SelectorType)
if selectorType == "" {
selectorType = "exact"
}
b := model.Binding{
Namespace: ns,
PublicModel: pm,
RouteGroup: rg,
SelectorType: selectorType,
SelectorValue: strings.TrimSpace(req.SelectorValue),
Status: st,
}
if err := h.db.Create(&b).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create binding", "details": err.Error()})
return
}
if err := h.sync.SyncBindings(h.db); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()})
return
}
c.JSON(http.StatusCreated, b)
}
// ListBindings godoc
// @Summary List bindings
// @Description List all configured bindings
// @Tags admin
// @Produce json
// @Security AdminAuth
// @Param page query int false "page (1-based)"
// @Param limit query int false "limit (default 50, max 200)"
// @Param search query string false "search by namespace/public_model/route_group"
// @Success 200 {array} model.Binding
// @Failure 500 {object} gin.H
// @Router /admin/bindings [get]
func (h *Handler) ListBindings(c *gin.Context) {
var out []model.Binding
q := h.db.Model(&model.Binding{}).Order("id desc")
query := parseListQuery(c)
q = applyListSearch(q, query.Search, "namespace", "public_model", "route_group")
q = applyListPagination(q, query)
if err := q.Find(&out).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list bindings", "details": err.Error()})
return
}
c.JSON(http.StatusOK, out)
}
// UpdateBinding godoc
// @Summary Update a binding
// @Description Update an existing binding
// @Tags admin
// @Accept json
// @Produce json
// @Security AdminAuth
// @Param id path int true "Binding ID"
// @Param binding body dto.BindingDTO true "Binding Info"
// @Success 200 {object} model.Binding
// @Failure 400 {object} gin.H
// @Failure 404 {object} gin.H
// @Failure 500 {object} gin.H
// @Router /admin/bindings/{id} [put]
func (h *Handler) UpdateBinding(c *gin.Context) {
idParam := c.Param("id")
id, err := strconv.Atoi(idParam)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
var existing model.Binding
if err := h.db.First(&existing, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "binding not found"})
return
}
var req dto.BindingDTO
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if ns := strings.TrimSpace(req.Namespace); ns != "" {
existing.Namespace = ns
}
if pm := strings.TrimSpace(req.PublicModel); pm != "" {
existing.PublicModel = pm
}
if rg := strings.TrimSpace(req.RouteGroup); rg != "" {
existing.RouteGroup = groupx.Normalize(rg)
}
if st := strings.TrimSpace(req.Status); st != "" {
existing.Status = st
}
if t := strings.TrimSpace(req.SelectorType); t != "" {
existing.SelectorType = t
}
if req.SelectorValue != "" {
existing.SelectorValue = strings.TrimSpace(req.SelectorValue)
}
if err := h.db.Save(&existing).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update binding", "details": err.Error()})
return
}
if err := h.sync.SyncBindings(h.db); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()})
return
}
c.JSON(http.StatusOK, existing)
}
// GetBinding godoc
// @Summary Get a binding
// @Description Get a binding by id
// @Tags admin
// @Produce json
// @Security AdminAuth
// @Param id path int true "Binding ID"
// @Success 200 {object} model.Binding
// @Failure 400 {object} gin.H
// @Failure 404 {object} gin.H
// @Failure 500 {object} gin.H
// @Router /admin/bindings/{id} [get]
func (h *Handler) GetBinding(c *gin.Context) {
idParam := c.Param("id")
id, err := strconv.Atoi(idParam)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
var existing model.Binding
if err := h.db.First(&existing, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "binding not found"})
return
}
c.JSON(http.StatusOK, existing)
}
// DeleteBinding godoc
// @Summary Delete a binding
// @Description Delete a binding by id and rebuild bindings snapshot
// @Tags admin
// @Produce json
// @Security AdminAuth
// @Param id path int true "Binding ID"
// @Success 200 {object} gin.H
// @Failure 400 {object} gin.H
// @Failure 404 {object} gin.H
// @Failure 500 {object} gin.H
// @Router /admin/bindings/{id} [delete]
func (h *Handler) DeleteBinding(c *gin.Context) {
idParam := c.Param("id")
id, err := strconv.Atoi(idParam)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return
}
var existing model.Binding
if err := h.db.First(&existing, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "binding not found"})
return
}
if err := h.db.Delete(&existing).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to delete binding", "details": err.Error()})
return
}
if err := h.sync.SyncBindings(h.db); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"status": "deleted"})
}