package api import ( "net/http" "strconv" "strings" "github.com/ez-api/ez-api/internal/dto" "github.com/ez-api/ez-api/internal/model" "github.com/gin-gonic/gin" "gorm.io/gorm" ) // CreateProviderGroup godoc // @Summary Create a provider group // @Description Create a provider group definition // @Tags admin // @Accept json // @Produce json // @Security AdminAuth // @Param group body dto.ProviderGroupDTO true "Provider group payload" // @Success 201 {object} model.ProviderGroup // @Failure 400 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/provider-groups [post] func (h *Handler) CreateProviderGroup(c *gin.Context) { var req dto.ProviderGroupDTO 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 } group := model.ProviderGroup{ Name: name, Type: strings.TrimSpace(req.Type), BaseURL: strings.TrimSpace(req.BaseURL), GoogleProject: strings.TrimSpace(req.GoogleProject), GoogleLocation: strings.TrimSpace(req.GoogleLocation), Models: strings.Join(req.Models, ","), Status: strings.TrimSpace(req.Status), } normalized, err := h.groupManager.NormalizeGroup(group) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.db.Create(&normalized).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create provider group", "details": err.Error()}) return } if err := h.sync.SyncProvidersForGroup(h.db, normalized.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync providers", "details": err.Error()}) return } if err := h.sync.SyncBindingsForGroup(h.db, normalized.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()}) return } c.JSON(http.StatusCreated, normalized) } // ListProviderGroups godoc // @Summary List provider groups // @Description List all provider groups // @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/type" // @Success 200 {array} model.ProviderGroup // @Failure 500 {object} gin.H // @Router /admin/provider-groups [get] func (h *Handler) ListProviderGroups(c *gin.Context) { var groups []model.ProviderGroup q := h.db.Model(&model.ProviderGroup{}).Order("id desc") query := parseListQuery(c) q = applyListSearch(q, query.Search, "name", "type") q = applyListPagination(q, query) if err := q.Find(&groups).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to list provider groups", "details": err.Error()}) return } c.JSON(http.StatusOK, groups) } // GetProviderGroup godoc // @Summary Get provider group // @Description Get a provider group by id // @Tags admin // @Produce json // @Security AdminAuth // @Param id path int true "ProviderGroup ID" // @Success 200 {object} model.ProviderGroup // @Failure 400 {object} gin.H // @Failure 404 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/provider-groups/{id} [get] func (h *Handler) GetProviderGroup(c *gin.Context) { id, ok := parseUintParam(c, "id") if !ok { return } var group model.ProviderGroup if err := h.db.First(&group, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "provider group not found"}) return } c.JSON(http.StatusOK, group) } // UpdateProviderGroup godoc // @Summary Update provider group // @Description Update a provider group // @Tags admin // @Accept json // @Produce json // @Security AdminAuth // @Param id path int true "ProviderGroup ID" // @Param group body dto.ProviderGroupDTO true "Provider group payload" // @Success 200 {object} model.ProviderGroup // @Failure 400 {object} gin.H // @Failure 404 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/provider-groups/{id} [put] func (h *Handler) UpdateProviderGroup(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 group model.ProviderGroup if err := h.db.First(&group, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "provider group not found"}) return } var req dto.ProviderGroupDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } next := group if strings.TrimSpace(req.Name) != "" { next.Name = strings.TrimSpace(req.Name) } if strings.TrimSpace(req.Type) != "" { next.Type = strings.TrimSpace(req.Type) } if strings.TrimSpace(req.BaseURL) != "" { next.BaseURL = strings.TrimSpace(req.BaseURL) } if strings.TrimSpace(req.GoogleProject) != "" { next.GoogleProject = strings.TrimSpace(req.GoogleProject) } if strings.TrimSpace(req.GoogleLocation) != "" { next.GoogleLocation = strings.TrimSpace(req.GoogleLocation) } if req.Models != nil { next.Models = strings.Join(req.Models, ",") } if strings.TrimSpace(req.Status) != "" { next.Status = strings.TrimSpace(req.Status) } normalized, err := h.groupManager.NormalizeGroup(next) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } group.Name = normalized.Name group.Type = normalized.Type group.BaseURL = normalized.BaseURL group.GoogleProject = normalized.GoogleProject group.GoogleLocation = normalized.GoogleLocation group.Models = normalized.Models group.Status = normalized.Status if err := h.db.Save(&group).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update provider group", "details": err.Error()}) return } if err := h.sync.SyncProvidersForGroup(h.db, group.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync providers", "details": err.Error()}) return } if err := h.sync.SyncBindingsForGroup(h.db, group.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync bindings", "details": err.Error()}) return } c.JSON(http.StatusOK, group) } // DeleteProviderGroup godoc // @Summary Delete provider group // @Description Delete a provider group and its api keys/bindings // @Tags admin // @Produce json // @Security AdminAuth // @Param id path int true "ProviderGroup ID" // @Success 200 {object} gin.H // @Failure 400 {object} gin.H // @Failure 404 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/provider-groups/{id} [delete] func (h *Handler) DeleteProviderGroup(c *gin.Context) { id, ok := parseUintParam(c, "id") if !ok { return } var group model.ProviderGroup if err := h.db.First(&group, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "provider group not found"}) return } if err := h.db.Transaction(func(tx *gorm.DB) error { if err := tx.Where("group_id = ?", group.ID).Delete(&model.APIKey{}).Error; err != nil { return err } if err := tx.Where("group_id = ?", group.ID).Delete(&model.Binding{}).Error; err != nil { return err } return tx.Delete(&group).Error }); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete provider group", "details": err.Error()}) return } if err := h.sync.SyncProvidersForGroup(h.db, group.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync providers", "details": err.Error()}) return } if err := h.sync.SyncBindingsForGroup(h.db, group.ID); 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"}) }