mirror of
https://github.com/EZ-Api/ez-api.git
synced 2026-01-13 17:47:51 +00:00
feat(api): add namespaces, batch ops, and admin logs
This commit is contained in:
219
internal/api/namespace_handler.go
Normal file
219
internal/api/namespace_handler.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/ez-api/ez-api/internal/model"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type NamespaceRequest struct {
|
||||
Name string `json:"name"`
|
||||
Status string `json:"status"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
// CreateNamespace godoc
|
||||
// @Summary Create namespace
|
||||
// @Description Create a namespace for bindings
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security AdminAuth
|
||||
// @Param namespace body NamespaceRequest true "Namespace payload"
|
||||
// @Success 201 {object} model.Namespace
|
||||
// @Failure 400 {object} gin.H
|
||||
// @Failure 500 {object} gin.H
|
||||
// @Router /admin/namespaces [post]
|
||||
func (h *Handler) CreateNamespace(c *gin.Context) {
|
||||
var req NamespaceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(req.Name)
|
||||
if name == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "name required"})
|
||||
return
|
||||
}
|
||||
status := strings.TrimSpace(req.Status)
|
||||
if status == "" {
|
||||
status = "active"
|
||||
}
|
||||
|
||||
ns := model.Namespace{
|
||||
Name: name,
|
||||
Status: status,
|
||||
Description: strings.TrimSpace(req.Description),
|
||||
}
|
||||
if err := h.db.Create(&ns).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create namespace", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusCreated, ns)
|
||||
}
|
||||
|
||||
// ListNamespaces godoc
|
||||
// @Summary List namespaces
|
||||
// @Description List all namespaces
|
||||
// @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 name/description"
|
||||
// @Success 200 {array} model.Namespace
|
||||
// @Failure 500 {object} gin.H
|
||||
// @Router /admin/namespaces [get]
|
||||
func (h *Handler) ListNamespaces(c *gin.Context) {
|
||||
var out []model.Namespace
|
||||
q := h.db.Model(&model.Namespace{}).Order("id desc")
|
||||
query := parseListQuery(c)
|
||||
q = applyListSearch(q, query.Search, "name", "description")
|
||||
q = applyListPagination(q, query)
|
||||
if err := q.Find(&out).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list namespaces", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, out)
|
||||
}
|
||||
|
||||
// GetNamespace godoc
|
||||
// @Summary Get namespace
|
||||
// @Description Get a namespace by id
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security AdminAuth
|
||||
// @Param id path int true "Namespace ID"
|
||||
// @Success 200 {object} model.Namespace
|
||||
// @Failure 400 {object} gin.H
|
||||
// @Failure 404 {object} gin.H
|
||||
// @Failure 500 {object} gin.H
|
||||
// @Router /admin/namespaces/{id} [get]
|
||||
func (h *Handler) GetNamespace(c *gin.Context) {
|
||||
id, ok := parseUintParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var ns model.Namespace
|
||||
if err := h.db.First(&ns, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "namespace not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, ns)
|
||||
}
|
||||
|
||||
type UpdateNamespaceRequest struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Status *string `json:"status,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateNamespace godoc
|
||||
// @Summary Update namespace
|
||||
// @Description Update a namespace
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security AdminAuth
|
||||
// @Param id path int true "Namespace ID"
|
||||
// @Param namespace body UpdateNamespaceRequest true "Update payload"
|
||||
// @Success 200 {object} model.Namespace
|
||||
// @Failure 400 {object} gin.H
|
||||
// @Failure 404 {object} gin.H
|
||||
// @Failure 500 {object} gin.H
|
||||
// @Router /admin/namespaces/{id} [put]
|
||||
func (h *Handler) UpdateNamespace(c *gin.Context) {
|
||||
id, ok := parseUintParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
var req UpdateNamespaceRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
var ns model.Namespace
|
||||
if err := h.db.First(&ns, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "namespace not found"})
|
||||
return
|
||||
}
|
||||
|
||||
update := map[string]any{}
|
||||
if req.Name != nil {
|
||||
name := strings.TrimSpace(*req.Name)
|
||||
if name == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "name required"})
|
||||
return
|
||||
}
|
||||
update["name"] = name
|
||||
}
|
||||
if req.Status != nil {
|
||||
status := strings.TrimSpace(*req.Status)
|
||||
if status == "" {
|
||||
status = "active"
|
||||
}
|
||||
update["status"] = status
|
||||
}
|
||||
if req.Description != nil {
|
||||
update["description"] = strings.TrimSpace(*req.Description)
|
||||
}
|
||||
if len(update) == 0 {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "no fields to update"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Model(&ns).Updates(update).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update namespace", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
if err := h.db.First(&ns, id).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to reload namespace", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, ns)
|
||||
}
|
||||
|
||||
// DeleteNamespace godoc
|
||||
// @Summary Delete namespace
|
||||
// @Description Delete a namespace and its bindings
|
||||
// @Tags admin
|
||||
// @Produce json
|
||||
// @Security AdminAuth
|
||||
// @Param id path int true "Namespace ID"
|
||||
// @Success 200 {object} gin.H
|
||||
// @Failure 400 {object} gin.H
|
||||
// @Failure 404 {object} gin.H
|
||||
// @Failure 500 {object} gin.H
|
||||
// @Router /admin/namespaces/{id} [delete]
|
||||
func (h *Handler) DeleteNamespace(c *gin.Context) {
|
||||
id, ok := parseUintParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var ns model.Namespace
|
||||
if err := h.db.First(&ns, id).Error; err != nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "namespace not found"})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Delete(&ns).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete namespace", "details": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.db.Where("namespace = ?", ns.Name).Delete(&model.Binding{}).Error; err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete namespace bindings", "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"})
|
||||
}
|
||||
Reference in New Issue
Block a user