package api import ( "crypto/rand" "encoding/hex" "net/http" "strings" "github.com/ez-api/ez-api/internal/dto" "github.com/ez-api/ez-api/internal/model" groupx "github.com/ez-api/foundation/group" providerx "github.com/ez-api/foundation/provider" "github.com/gin-gonic/gin" ) // CreateProviderPreset godoc // @Summary Create a preset provider // @Description Create an official OpenAI/Anthropic/Gemini provider (only api_key is typically required) // @Tags admin // @Accept json // @Produce json // @Security AdminAuth // @Param provider body dto.ProviderPresetCreateDTO true "Provider preset payload" // @Success 201 {object} model.Provider // @Failure 400 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/providers/preset [post] func (h *Handler) CreateProviderPreset(c *gin.Context) { var req dto.ProviderPresetCreateDTO if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } preset := providerx.NormalizeType(req.Preset) if preset == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "preset required"}) return } var providerType string var baseURL string switch preset { case providerx.TypeOpenAI: providerType = providerx.TypeOpenAI baseURL = "https://api.openai.com" case providerx.TypeAnthropic, providerx.TypeClaude: providerType = providerx.TypeAnthropic baseURL = "https://api.anthropic.com" case providerx.TypeGemini, providerx.TypeAIStudio, providerx.TypeGoogle: // Gemini API / AI Studio (SDK transport). BaseURL is optional but we provide the official endpoint. providerType = providerx.TypeGemini baseURL = "https://generativelanguage.googleapis.com" default: c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported preset: " + preset}) return } name := strings.TrimSpace(req.Name) if name == "" { name = providerType + "-" + randomSuffix(4) } group := strings.TrimSpace(req.Group) if group == "" { group = "default" } status := strings.TrimSpace(req.Status) if status == "" { status = "active" } autoBan := true if req.AutoBan != nil { autoBan = *req.AutoBan } googleLocation := providerx.DefaultGoogleLocation(providerType, req.GoogleLocation) p := model.Provider{ Name: name, Type: providerType, BaseURL: baseURL, APIKey: strings.TrimSpace(req.APIKey), GoogleProject: strings.TrimSpace(req.GoogleProject), GoogleLocation: googleLocation, Group: groupx.Normalize(group), Models: strings.Join(req.Models, ","), Status: status, AutoBan: autoBan, } if req.Weight > 0 { p.Weight = req.Weight } if err := h.db.Create(&p).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create provider", "details": err.Error()}) return } if err := h.sync.SyncProvider(&p); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync provider", "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, p) } // CreateProviderCustom godoc // @Summary Create a custom provider // @Description Create an OpenAI-compatible provider (base_url + api_key required) // @Tags admin // @Accept json // @Produce json // @Security AdminAuth // @Param provider body dto.ProviderCustomCreateDTO true "Provider custom payload" // @Success 201 {object} model.Provider // @Failure 400 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/providers/custom [post] func (h *Handler) CreateProviderCustom(c *gin.Context) { var req dto.ProviderCustomCreateDTO 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 } baseURL := strings.TrimSpace(req.BaseURL) if baseURL == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "base_url required"}) return } group := strings.TrimSpace(req.Group) if group == "" { group = "default" } status := strings.TrimSpace(req.Status) if status == "" { status = "active" } autoBan := true if req.AutoBan != nil { autoBan = *req.AutoBan } p := model.Provider{ Name: name, Type: providerx.TypeCompatible, BaseURL: baseURL, APIKey: strings.TrimSpace(req.APIKey), Group: groupx.Normalize(group), Models: strings.Join(req.Models, ","), Status: status, AutoBan: autoBan, } if req.Weight > 0 { p.Weight = req.Weight } if err := h.db.Create(&p).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create provider", "details": err.Error()}) return } if err := h.sync.SyncProvider(&p); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to sync provider", "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, p) } func randomSuffix(bytesLen int) string { if bytesLen <= 0 { bytesLen = 4 } b := make([]byte, bytesLen) if _, err := rand.Read(b); err != nil { return "rand" } return hex.EncodeToString(b) }