management: allow prefix updates in provider PATCH handlers

This commit is contained in:
Supra4E8C
2025-12-21 02:14:28 +08:00
parent 9855615f1e
commit 24970baa57

View File

@@ -145,71 +145,74 @@ func (h *Handler) PutGeminiKeys(c *gin.Context) {
h.persist(c) h.persist(c)
} }
func (h *Handler) PatchGeminiKey(c *gin.Context) { func (h *Handler) PatchGeminiKey(c *gin.Context) {
type geminiKeyPatch struct {
APIKey *string `json:"api-key"`
Prefix *string `json:"prefix"`
BaseURL *string `json:"base-url"`
ProxyURL *string `json:"proxy-url"`
Headers *map[string]string `json:"headers"`
ExcludedModels *[]string `json:"excluded-models"`
}
var body struct { var body struct {
Index *int `json:"index"` Index *int `json:"index"`
Match *string `json:"match"` Match *string `json:"match"`
Value *config.GeminiKey `json:"value"` Value *geminiKeyPatch `json:"value"`
} }
if err := c.ShouldBindJSON(&body); err != nil || body.Value == nil { if err := c.ShouldBindJSON(&body); err != nil || body.Value == nil {
c.JSON(400, gin.H{"error": "invalid body"}) c.JSON(400, gin.H{"error": "invalid body"})
return return
} }
value := *body.Value targetIndex := -1
value.APIKey = strings.TrimSpace(value.APIKey) if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.GeminiKey) {
value.BaseURL = strings.TrimSpace(value.BaseURL) targetIndex = *body.Index
value.ProxyURL = strings.TrimSpace(value.ProxyURL) }
value.ExcludedModels = config.NormalizeExcludedModels(value.ExcludedModels) if targetIndex == -1 && body.Match != nil {
if value.APIKey == "" { match := strings.TrimSpace(*body.Match)
// Treat empty API key as delete. if match != "" {
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.GeminiKey) { for i := range h.cfg.GeminiKey {
h.cfg.GeminiKey = append(h.cfg.GeminiKey[:*body.Index], h.cfg.GeminiKey[*body.Index+1:]...) if h.cfg.GeminiKey[i].APIKey == match {
h.cfg.SanitizeGeminiKeys() targetIndex = i
h.persist(c) break
return
}
if body.Match != nil {
match := strings.TrimSpace(*body.Match)
if match != "" {
out := make([]config.GeminiKey, 0, len(h.cfg.GeminiKey))
removed := false
for i := range h.cfg.GeminiKey {
if !removed && h.cfg.GeminiKey[i].APIKey == match {
removed = true
continue
}
out = append(out, h.cfg.GeminiKey[i])
}
if removed {
h.cfg.GeminiKey = out
h.cfg.SanitizeGeminiKeys()
h.persist(c)
return
} }
} }
} }
}
if targetIndex == -1 {
c.JSON(404, gin.H{"error": "item not found"}) c.JSON(404, gin.H{"error": "item not found"})
return return
} }
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.GeminiKey) { entry := h.cfg.GeminiKey[targetIndex]
h.cfg.GeminiKey[*body.Index] = value if body.Value.APIKey != nil {
h.cfg.SanitizeGeminiKeys() trimmed := strings.TrimSpace(*body.Value.APIKey)
h.persist(c) if trimmed == "" {
return h.cfg.GeminiKey = append(h.cfg.GeminiKey[:targetIndex], h.cfg.GeminiKey[targetIndex+1:]...)
} h.cfg.SanitizeGeminiKeys()
if body.Match != nil { h.persist(c)
match := strings.TrimSpace(*body.Match) return
for i := range h.cfg.GeminiKey {
if h.cfg.GeminiKey[i].APIKey == match {
h.cfg.GeminiKey[i] = value
h.cfg.SanitizeGeminiKeys()
h.persist(c)
return
}
} }
entry.APIKey = trimmed
} }
c.JSON(404, gin.H{"error": "item not found"}) if body.Value.Prefix != nil {
entry.Prefix = strings.TrimSpace(*body.Value.Prefix)
}
if body.Value.BaseURL != nil {
entry.BaseURL = strings.TrimSpace(*body.Value.BaseURL)
}
if body.Value.ProxyURL != nil {
entry.ProxyURL = strings.TrimSpace(*body.Value.ProxyURL)
}
if body.Value.Headers != nil {
entry.Headers = config.NormalizeHeaders(*body.Value.Headers)
}
if body.Value.ExcludedModels != nil {
entry.ExcludedModels = config.NormalizeExcludedModels(*body.Value.ExcludedModels)
}
h.cfg.GeminiKey[targetIndex] = entry
h.cfg.SanitizeGeminiKeys()
h.persist(c)
} }
func (h *Handler) DeleteGeminiKey(c *gin.Context) { func (h *Handler) DeleteGeminiKey(c *gin.Context) {
if val := strings.TrimSpace(c.Query("api-key")); val != "" { if val := strings.TrimSpace(c.Query("api-key")); val != "" {
out := make([]config.GeminiKey, 0, len(h.cfg.GeminiKey)) out := make([]config.GeminiKey, 0, len(h.cfg.GeminiKey))
@@ -268,35 +271,70 @@ func (h *Handler) PutClaudeKeys(c *gin.Context) {
h.persist(c) h.persist(c)
} }
func (h *Handler) PatchClaudeKey(c *gin.Context) { func (h *Handler) PatchClaudeKey(c *gin.Context) {
type claudeKeyPatch struct {
APIKey *string `json:"api-key"`
Prefix *string `json:"prefix"`
BaseURL *string `json:"base-url"`
ProxyURL *string `json:"proxy-url"`
Models *[]config.ClaudeModel `json:"models"`
Headers *map[string]string `json:"headers"`
ExcludedModels *[]string `json:"excluded-models"`
}
var body struct { var body struct {
Index *int `json:"index"` Index *int `json:"index"`
Match *string `json:"match"` Match *string `json:"match"`
Value *config.ClaudeKey `json:"value"` Value *claudeKeyPatch `json:"value"`
} }
if err := c.ShouldBindJSON(&body); err != nil || body.Value == nil { if err := c.ShouldBindJSON(&body); err != nil || body.Value == nil {
c.JSON(400, gin.H{"error": "invalid body"}) c.JSON(400, gin.H{"error": "invalid body"})
return return
} }
value := *body.Value targetIndex := -1
normalizeClaudeKey(&value)
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.ClaudeKey) { if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.ClaudeKey) {
h.cfg.ClaudeKey[*body.Index] = value targetIndex = *body.Index
h.cfg.SanitizeClaudeKeys()
h.persist(c)
return
} }
if body.Match != nil { if targetIndex == -1 && body.Match != nil {
match := strings.TrimSpace(*body.Match)
for i := range h.cfg.ClaudeKey { for i := range h.cfg.ClaudeKey {
if h.cfg.ClaudeKey[i].APIKey == *body.Match { if h.cfg.ClaudeKey[i].APIKey == match {
h.cfg.ClaudeKey[i] = value targetIndex = i
h.cfg.SanitizeClaudeKeys() break
h.persist(c)
return
} }
} }
} }
c.JSON(404, gin.H{"error": "item not found"}) if targetIndex == -1 {
c.JSON(404, gin.H{"error": "item not found"})
return
}
entry := h.cfg.ClaudeKey[targetIndex]
if body.Value.APIKey != nil {
entry.APIKey = strings.TrimSpace(*body.Value.APIKey)
}
if body.Value.Prefix != nil {
entry.Prefix = strings.TrimSpace(*body.Value.Prefix)
}
if body.Value.BaseURL != nil {
entry.BaseURL = strings.TrimSpace(*body.Value.BaseURL)
}
if body.Value.ProxyURL != nil {
entry.ProxyURL = strings.TrimSpace(*body.Value.ProxyURL)
}
if body.Value.Models != nil {
entry.Models = append([]config.ClaudeModel(nil), (*body.Value.Models)...)
}
if body.Value.Headers != nil {
entry.Headers = config.NormalizeHeaders(*body.Value.Headers)
}
if body.Value.ExcludedModels != nil {
entry.ExcludedModels = config.NormalizeExcludedModels(*body.Value.ExcludedModels)
}
normalizeClaudeKey(&entry)
h.cfg.ClaudeKey[targetIndex] = entry
h.cfg.SanitizeClaudeKeys()
h.persist(c)
} }
func (h *Handler) DeleteClaudeKey(c *gin.Context) { func (h *Handler) DeleteClaudeKey(c *gin.Context) {
if val := c.Query("api-key"); val != "" { if val := c.Query("api-key"); val != "" {
out := make([]config.ClaudeKey, 0, len(h.cfg.ClaudeKey)) out := make([]config.ClaudeKey, 0, len(h.cfg.ClaudeKey))
@@ -356,62 +394,73 @@ func (h *Handler) PutOpenAICompat(c *gin.Context) {
h.persist(c) h.persist(c)
} }
func (h *Handler) PatchOpenAICompat(c *gin.Context) { func (h *Handler) PatchOpenAICompat(c *gin.Context) {
type openAICompatPatch struct {
Name *string `json:"name"`
Prefix *string `json:"prefix"`
BaseURL *string `json:"base-url"`
APIKeyEntries *[]config.OpenAICompatibilityAPIKey `json:"api-key-entries"`
Models *[]config.OpenAICompatibilityModel `json:"models"`
Headers *map[string]string `json:"headers"`
}
var body struct { var body struct {
Name *string `json:"name"` Name *string `json:"name"`
Index *int `json:"index"` Index *int `json:"index"`
Value *config.OpenAICompatibility `json:"value"` Value *openAICompatPatch `json:"value"`
} }
if err := c.ShouldBindJSON(&body); err != nil || body.Value == nil { if err := c.ShouldBindJSON(&body); err != nil || body.Value == nil {
c.JSON(400, gin.H{"error": "invalid body"}) c.JSON(400, gin.H{"error": "invalid body"})
return return
} }
normalizeOpenAICompatibilityEntry(body.Value) targetIndex := -1
// If base-url becomes empty, delete the provider instead of updating if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.OpenAICompatibility) {
if strings.TrimSpace(body.Value.BaseURL) == "" { targetIndex = *body.Index
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.OpenAICompatibility) { }
h.cfg.OpenAICompatibility = append(h.cfg.OpenAICompatibility[:*body.Index], h.cfg.OpenAICompatibility[*body.Index+1:]...) if targetIndex == -1 && body.Name != nil {
match := strings.TrimSpace(*body.Name)
for i := range h.cfg.OpenAICompatibility {
if h.cfg.OpenAICompatibility[i].Name == match {
targetIndex = i
break
}
}
}
if targetIndex == -1 {
c.JSON(404, gin.H{"error": "item not found"})
return
}
entry := h.cfg.OpenAICompatibility[targetIndex]
if body.Value.Name != nil {
entry.Name = strings.TrimSpace(*body.Value.Name)
}
if body.Value.Prefix != nil {
entry.Prefix = strings.TrimSpace(*body.Value.Prefix)
}
if body.Value.BaseURL != nil {
trimmed := strings.TrimSpace(*body.Value.BaseURL)
if trimmed == "" {
h.cfg.OpenAICompatibility = append(h.cfg.OpenAICompatibility[:targetIndex], h.cfg.OpenAICompatibility[targetIndex+1:]...)
h.cfg.SanitizeOpenAICompatibility() h.cfg.SanitizeOpenAICompatibility()
h.persist(c) h.persist(c)
return return
} }
if body.Name != nil { entry.BaseURL = trimmed
out := make([]config.OpenAICompatibility, 0, len(h.cfg.OpenAICompatibility))
removed := false
for i := range h.cfg.OpenAICompatibility {
if !removed && h.cfg.OpenAICompatibility[i].Name == *body.Name {
removed = true
continue
}
out = append(out, h.cfg.OpenAICompatibility[i])
}
if removed {
h.cfg.OpenAICompatibility = out
h.cfg.SanitizeOpenAICompatibility()
h.persist(c)
return
}
}
c.JSON(404, gin.H{"error": "item not found"})
return
} }
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.OpenAICompatibility) { if body.Value.APIKeyEntries != nil {
h.cfg.OpenAICompatibility[*body.Index] = *body.Value entry.APIKeyEntries = append([]config.OpenAICompatibilityAPIKey(nil), (*body.Value.APIKeyEntries)...)
h.cfg.SanitizeOpenAICompatibility()
h.persist(c)
return
} }
if body.Name != nil { if body.Value.Models != nil {
for i := range h.cfg.OpenAICompatibility { entry.Models = append([]config.OpenAICompatibilityModel(nil), (*body.Value.Models)...)
if h.cfg.OpenAICompatibility[i].Name == *body.Name {
h.cfg.OpenAICompatibility[i] = *body.Value
h.cfg.SanitizeOpenAICompatibility()
h.persist(c)
return
}
}
} }
c.JSON(404, gin.H{"error": "item not found"}) if body.Value.Headers != nil {
entry.Headers = config.NormalizeHeaders(*body.Value.Headers)
}
normalizeOpenAICompatibilityEntry(&entry)
h.cfg.OpenAICompatibility[targetIndex] = entry
h.cfg.SanitizeOpenAICompatibility()
h.persist(c)
} }
func (h *Handler) DeleteOpenAICompat(c *gin.Context) { func (h *Handler) DeleteOpenAICompat(c *gin.Context) {
if name := c.Query("name"); name != "" { if name := c.Query("name"); name != "" {
out := make([]config.OpenAICompatibility, 0, len(h.cfg.OpenAICompatibility)) out := make([]config.OpenAICompatibility, 0, len(h.cfg.OpenAICompatibility))
@@ -563,66 +612,72 @@ func (h *Handler) PutCodexKeys(c *gin.Context) {
h.persist(c) h.persist(c)
} }
func (h *Handler) PatchCodexKey(c *gin.Context) { func (h *Handler) PatchCodexKey(c *gin.Context) {
type codexKeyPatch struct {
APIKey *string `json:"api-key"`
Prefix *string `json:"prefix"`
BaseURL *string `json:"base-url"`
ProxyURL *string `json:"proxy-url"`
Headers *map[string]string `json:"headers"`
ExcludedModels *[]string `json:"excluded-models"`
}
var body struct { var body struct {
Index *int `json:"index"` Index *int `json:"index"`
Match *string `json:"match"` Match *string `json:"match"`
Value *config.CodexKey `json:"value"` Value *codexKeyPatch `json:"value"`
} }
if err := c.ShouldBindJSON(&body); err != nil || body.Value == nil { if err := c.ShouldBindJSON(&body); err != nil || body.Value == nil {
c.JSON(400, gin.H{"error": "invalid body"}) c.JSON(400, gin.H{"error": "invalid body"})
return return
} }
value := *body.Value targetIndex := -1
value.APIKey = strings.TrimSpace(value.APIKey) if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.CodexKey) {
value.BaseURL = strings.TrimSpace(value.BaseURL) targetIndex = *body.Index
value.ProxyURL = strings.TrimSpace(value.ProxyURL) }
value.Headers = config.NormalizeHeaders(value.Headers) if targetIndex == -1 && body.Match != nil {
value.ExcludedModels = config.NormalizeExcludedModels(value.ExcludedModels) match := strings.TrimSpace(*body.Match)
// If base-url becomes empty, delete instead of update for i := range h.cfg.CodexKey {
if value.BaseURL == "" { if h.cfg.CodexKey[i].APIKey == match {
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.CodexKey) { targetIndex = i
h.cfg.CodexKey = append(h.cfg.CodexKey[:*body.Index], h.cfg.CodexKey[*body.Index+1:]...) break
h.cfg.SanitizeCodexKeys()
h.persist(c)
return
}
if body.Match != nil {
out := make([]config.CodexKey, 0, len(h.cfg.CodexKey))
removed := false
for i := range h.cfg.CodexKey {
if !removed && h.cfg.CodexKey[i].APIKey == *body.Match {
removed = true
continue
}
out = append(out, h.cfg.CodexKey[i])
}
if removed {
h.cfg.CodexKey = out
h.cfg.SanitizeCodexKeys()
h.persist(c)
return
}
}
} else {
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.CodexKey) {
h.cfg.CodexKey[*body.Index] = value
h.cfg.SanitizeCodexKeys()
h.persist(c)
return
}
if body.Match != nil {
for i := range h.cfg.CodexKey {
if h.cfg.CodexKey[i].APIKey == *body.Match {
h.cfg.CodexKey[i] = value
h.cfg.SanitizeCodexKeys()
h.persist(c)
return
}
} }
} }
} }
c.JSON(404, gin.H{"error": "item not found"}) if targetIndex == -1 {
c.JSON(404, gin.H{"error": "item not found"})
return
}
entry := h.cfg.CodexKey[targetIndex]
if body.Value.APIKey != nil {
entry.APIKey = strings.TrimSpace(*body.Value.APIKey)
}
if body.Value.Prefix != nil {
entry.Prefix = strings.TrimSpace(*body.Value.Prefix)
}
if body.Value.BaseURL != nil {
trimmed := strings.TrimSpace(*body.Value.BaseURL)
if trimmed == "" {
h.cfg.CodexKey = append(h.cfg.CodexKey[:targetIndex], h.cfg.CodexKey[targetIndex+1:]...)
h.cfg.SanitizeCodexKeys()
h.persist(c)
return
}
entry.BaseURL = trimmed
}
if body.Value.ProxyURL != nil {
entry.ProxyURL = strings.TrimSpace(*body.Value.ProxyURL)
}
if body.Value.Headers != nil {
entry.Headers = config.NormalizeHeaders(*body.Value.Headers)
}
if body.Value.ExcludedModels != nil {
entry.ExcludedModels = config.NormalizeExcludedModels(*body.Value.ExcludedModels)
}
h.cfg.CodexKey[targetIndex] = entry
h.cfg.SanitizeCodexKeys()
h.persist(c)
} }
func (h *Handler) DeleteCodexKey(c *gin.Context) { func (h *Handler) DeleteCodexKey(c *gin.Context) {
if val := c.Query("api-key"); val != "" { if val := c.Query("api-key"); val != "" {
out := make([]config.CodexKey, 0, len(h.cfg.CodexKey)) out := make([]config.CodexKey, 0, len(h.cfg.CodexKey))