package api import ( "errors" "net/http" "strings" "github.com/ez-api/ez-api/internal/model" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type BatchActionRequest struct { Action string `json:"action"` IDs []uint `json:"ids"` } type BatchResult struct { ID uint `json:"id"` Error string `json:"error,omitempty"` } type BatchResponse struct { Action string `json:"action"` Success []uint `json:"success"` Failed []BatchResult `json:"failed"` } func normalizeBatchAction(raw string) string { raw = strings.ToLower(strings.TrimSpace(raw)) if raw == "" { return "delete" } return raw } func (h *AdminHandler) BatchMasters(c *gin.Context) { var req BatchActionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) return } action := normalizeBatchAction(req.Action) if action != "delete" { c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported action"}) return } if len(req.IDs) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "ids required"}) return } resp := BatchResponse{Action: action} for _, id := range req.IDs { if err := h.revokeMasterByID(id); err != nil { if isRecordNotFound(err) { resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: "not found"}) continue } resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: err.Error()}) continue } resp.Success = append(resp.Success, id) } c.JSON(http.StatusOK, resp) } func (h *Handler) BatchProviders(c *gin.Context) { var req BatchActionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) return } action := normalizeBatchAction(req.Action) if action != "delete" { c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported action"}) return } if len(req.IDs) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "ids required"}) return } resp := BatchResponse{Action: action} needsBindingSync := false for _, id := range req.IDs { var p model.Provider if err := h.db.First(&p, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: "not found"}) continue } resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: err.Error()}) continue } if err := h.db.Delete(&p).Error; err != nil { resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: err.Error()}) continue } if err := h.sync.SyncProviderDelete(&p); err != nil { resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: err.Error()}) continue } resp.Success = append(resp.Success, id) needsBindingSync = true } if needsBindingSync { 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, resp) } func (h *Handler) BatchModels(c *gin.Context) { var req BatchActionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) return } action := normalizeBatchAction(req.Action) if action != "delete" { c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported action"}) return } if len(req.IDs) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "ids required"}) return } resp := BatchResponse{Action: action} for _, id := range req.IDs { var m model.Model if err := h.db.First(&m, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: "not found"}) continue } resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: err.Error()}) continue } if err := h.db.Delete(&m).Error; err != nil { resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: err.Error()}) continue } if err := h.sync.SyncModelDelete(&m); err != nil { resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: err.Error()}) continue } resp.Success = append(resp.Success, id) } c.JSON(http.StatusOK, resp) } func (h *Handler) BatchBindings(c *gin.Context) { var req BatchActionRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request body"}) return } action := normalizeBatchAction(req.Action) if action != "delete" { c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported action"}) return } if len(req.IDs) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "ids required"}) return } resp := BatchResponse{Action: action} needsSync := false for _, id := range req.IDs { var b model.Binding if err := h.db.First(&b, id).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: "not found"}) continue } resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: err.Error()}) continue } if err := h.db.Delete(&b).Error; err != nil { resp.Failed = append(resp.Failed, BatchResult{ID: id, Error: err.Error()}) continue } resp.Success = append(resp.Success, id) needsSync = true } if needsSync { 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, resp) }