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} ResponseEnvelope{data=model.Namespace} // @Failure 400 {object} ResponseEnvelope{data=gin.H} // @Failure 500 {object} ResponseEnvelope{data=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 {object} ResponseEnvelope{data=[]model.Namespace} // @Failure 500 {object} ResponseEnvelope{data=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} ResponseEnvelope{data=model.Namespace} // @Failure 400 {object} ResponseEnvelope{data=gin.H} // @Failure 404 {object} ResponseEnvelope{data=gin.H} // @Failure 500 {object} ResponseEnvelope{data=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} ResponseEnvelope{data=model.Namespace} // @Failure 400 {object} ResponseEnvelope{data=gin.H} // @Failure 404 {object} ResponseEnvelope{data=gin.H} // @Failure 500 {object} ResponseEnvelope{data=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} ResponseEnvelope{data=gin.H} // @Failure 400 {object} ResponseEnvelope{data=gin.H} // @Failure 404 {object} ResponseEnvelope{data=gin.H} // @Failure 500 {object} ResponseEnvelope{data=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"}) }