Merge pull request #2892 from router-for-me/fix-provider
feat(api): integrate auth index into key retrieval endpoints for Gemi…
This commit is contained in:
@@ -0,0 +1,241 @@
|
|||||||
|
package management
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/watcher/synthesizer"
|
||||||
|
)
|
||||||
|
|
||||||
|
type geminiKeyWithAuthIndex struct {
|
||||||
|
config.GeminiKey
|
||||||
|
AuthIndex string `json:"auth-index,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type claudeKeyWithAuthIndex struct {
|
||||||
|
config.ClaudeKey
|
||||||
|
AuthIndex string `json:"auth-index,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type codexKeyWithAuthIndex struct {
|
||||||
|
config.CodexKey
|
||||||
|
AuthIndex string `json:"auth-index,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type vertexCompatKeyWithAuthIndex struct {
|
||||||
|
config.VertexCompatKey
|
||||||
|
AuthIndex string `json:"auth-index,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type openAICompatibilityAPIKeyWithAuthIndex struct {
|
||||||
|
config.OpenAICompatibilityAPIKey
|
||||||
|
AuthIndex string `json:"auth-index,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type openAICompatibilityWithAuthIndex struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Priority int `json:"priority,omitempty"`
|
||||||
|
Prefix string `json:"prefix,omitempty"`
|
||||||
|
BaseURL string `json:"base-url"`
|
||||||
|
APIKeyEntries []openAICompatibilityAPIKeyWithAuthIndex `json:"api-key-entries,omitempty"`
|
||||||
|
Models []config.OpenAICompatibilityModel `json:"models,omitempty"`
|
||||||
|
Headers map[string]string `json:"headers,omitempty"`
|
||||||
|
AuthIndex string `json:"auth-index,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) liveAuthIndexByID() map[string]string {
|
||||||
|
out := map[string]string{}
|
||||||
|
if h == nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
h.mu.Lock()
|
||||||
|
manager := h.authManager
|
||||||
|
h.mu.Unlock()
|
||||||
|
if manager == nil {
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
// authManager.List() returns clones, so EnsureIndex only affects these copies.
|
||||||
|
for _, auth := range manager.List() {
|
||||||
|
if auth == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
id := strings.TrimSpace(auth.ID)
|
||||||
|
if id == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
idx := strings.TrimSpace(auth.Index)
|
||||||
|
if idx == "" {
|
||||||
|
idx = auth.EnsureIndex()
|
||||||
|
}
|
||||||
|
if idx == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
out[id] = idx
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) geminiKeysWithAuthIndex() []geminiKeyWithAuthIndex {
|
||||||
|
if h == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
liveIndexByID := h.liveAuthIndexByID()
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
if h.cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
idGen := synthesizer.NewStableIDGenerator()
|
||||||
|
out := make([]geminiKeyWithAuthIndex, len(h.cfg.GeminiKey))
|
||||||
|
for i := range h.cfg.GeminiKey {
|
||||||
|
entry := h.cfg.GeminiKey[i]
|
||||||
|
authIndex := ""
|
||||||
|
if key := strings.TrimSpace(entry.APIKey); key != "" {
|
||||||
|
id, _ := idGen.Next("gemini:apikey", key, entry.BaseURL)
|
||||||
|
authIndex = liveIndexByID[id]
|
||||||
|
}
|
||||||
|
out[i] = geminiKeyWithAuthIndex{
|
||||||
|
GeminiKey: entry,
|
||||||
|
AuthIndex: authIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) claudeKeysWithAuthIndex() []claudeKeyWithAuthIndex {
|
||||||
|
if h == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
liveIndexByID := h.liveAuthIndexByID()
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
if h.cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
idGen := synthesizer.NewStableIDGenerator()
|
||||||
|
out := make([]claudeKeyWithAuthIndex, len(h.cfg.ClaudeKey))
|
||||||
|
for i := range h.cfg.ClaudeKey {
|
||||||
|
entry := h.cfg.ClaudeKey[i]
|
||||||
|
authIndex := ""
|
||||||
|
if key := strings.TrimSpace(entry.APIKey); key != "" {
|
||||||
|
id, _ := idGen.Next("claude:apikey", key, entry.BaseURL)
|
||||||
|
authIndex = liveIndexByID[id]
|
||||||
|
}
|
||||||
|
out[i] = claudeKeyWithAuthIndex{
|
||||||
|
ClaudeKey: entry,
|
||||||
|
AuthIndex: authIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) codexKeysWithAuthIndex() []codexKeyWithAuthIndex {
|
||||||
|
if h == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
liveIndexByID := h.liveAuthIndexByID()
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
if h.cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
idGen := synthesizer.NewStableIDGenerator()
|
||||||
|
out := make([]codexKeyWithAuthIndex, len(h.cfg.CodexKey))
|
||||||
|
for i := range h.cfg.CodexKey {
|
||||||
|
entry := h.cfg.CodexKey[i]
|
||||||
|
authIndex := ""
|
||||||
|
if key := strings.TrimSpace(entry.APIKey); key != "" {
|
||||||
|
id, _ := idGen.Next("codex:apikey", key, entry.BaseURL)
|
||||||
|
authIndex = liveIndexByID[id]
|
||||||
|
}
|
||||||
|
out[i] = codexKeyWithAuthIndex{
|
||||||
|
CodexKey: entry,
|
||||||
|
AuthIndex: authIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) vertexCompatKeysWithAuthIndex() []vertexCompatKeyWithAuthIndex {
|
||||||
|
if h == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
liveIndexByID := h.liveAuthIndexByID()
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
if h.cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
idGen := synthesizer.NewStableIDGenerator()
|
||||||
|
out := make([]vertexCompatKeyWithAuthIndex, len(h.cfg.VertexCompatAPIKey))
|
||||||
|
for i := range h.cfg.VertexCompatAPIKey {
|
||||||
|
entry := h.cfg.VertexCompatAPIKey[i]
|
||||||
|
id, _ := idGen.Next("vertex:apikey", entry.APIKey, entry.BaseURL, entry.ProxyURL)
|
||||||
|
authIndex := liveIndexByID[id]
|
||||||
|
out[i] = vertexCompatKeyWithAuthIndex{
|
||||||
|
VertexCompatKey: entry,
|
||||||
|
AuthIndex: authIndex,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) openAICompatibilityWithAuthIndex() []openAICompatibilityWithAuthIndex {
|
||||||
|
if h == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
liveIndexByID := h.liveAuthIndexByID()
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
|
if h.cfg == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
normalized := normalizedOpenAICompatibilityEntries(h.cfg.OpenAICompatibility)
|
||||||
|
out := make([]openAICompatibilityWithAuthIndex, len(normalized))
|
||||||
|
idGen := synthesizer.NewStableIDGenerator()
|
||||||
|
for i := range normalized {
|
||||||
|
entry := normalized[i]
|
||||||
|
providerName := strings.ToLower(strings.TrimSpace(entry.Name))
|
||||||
|
if providerName == "" {
|
||||||
|
providerName = "openai-compatibility"
|
||||||
|
}
|
||||||
|
idKind := fmt.Sprintf("openai-compatibility:%s", providerName)
|
||||||
|
|
||||||
|
response := openAICompatibilityWithAuthIndex{
|
||||||
|
Name: entry.Name,
|
||||||
|
Priority: entry.Priority,
|
||||||
|
Prefix: entry.Prefix,
|
||||||
|
BaseURL: entry.BaseURL,
|
||||||
|
Models: entry.Models,
|
||||||
|
Headers: entry.Headers,
|
||||||
|
AuthIndex: "",
|
||||||
|
}
|
||||||
|
if len(entry.APIKeyEntries) == 0 {
|
||||||
|
id, _ := idGen.Next(idKind, entry.BaseURL)
|
||||||
|
response.AuthIndex = liveIndexByID[id]
|
||||||
|
} else {
|
||||||
|
response.APIKeyEntries = make([]openAICompatibilityAPIKeyWithAuthIndex, len(entry.APIKeyEntries))
|
||||||
|
for j := range entry.APIKeyEntries {
|
||||||
|
apiKeyEntry := entry.APIKeyEntries[j]
|
||||||
|
id, _ := idGen.Next(idKind, apiKeyEntry.APIKey, entry.BaseURL, apiKeyEntry.ProxyURL)
|
||||||
|
response.APIKeyEntries[j] = openAICompatibilityAPIKeyWithAuthIndex{
|
||||||
|
OpenAICompatibilityAPIKey: apiKeyEntry,
|
||||||
|
AuthIndex: liveIndexByID[id],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out[i] = response
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -120,7 +120,7 @@ func (h *Handler) DeleteAPIKeys(c *gin.Context) {
|
|||||||
|
|
||||||
// gemini-api-key: []GeminiKey
|
// gemini-api-key: []GeminiKey
|
||||||
func (h *Handler) GetGeminiKeys(c *gin.Context) {
|
func (h *Handler) GetGeminiKeys(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{"gemini-api-key": h.cfg.GeminiKey})
|
c.JSON(200, gin.H{"gemini-api-key": h.geminiKeysWithAuthIndex()})
|
||||||
}
|
}
|
||||||
func (h *Handler) PutGeminiKeys(c *gin.Context) {
|
func (h *Handler) PutGeminiKeys(c *gin.Context) {
|
||||||
data, err := c.GetRawData()
|
data, err := c.GetRawData()
|
||||||
@@ -139,9 +139,11 @@ func (h *Handler) PutGeminiKeys(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
arr = obj.Items
|
arr = obj.Items
|
||||||
}
|
}
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
h.cfg.GeminiKey = append([]config.GeminiKey(nil), arr...)
|
h.cfg.GeminiKey = append([]config.GeminiKey(nil), arr...)
|
||||||
h.cfg.SanitizeGeminiKeys()
|
h.cfg.SanitizeGeminiKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
}
|
}
|
||||||
func (h *Handler) PatchGeminiKey(c *gin.Context) {
|
func (h *Handler) PatchGeminiKey(c *gin.Context) {
|
||||||
type geminiKeyPatch struct {
|
type geminiKeyPatch struct {
|
||||||
@@ -161,6 +163,9 @@ func (h *Handler) PatchGeminiKey(c *gin.Context) {
|
|||||||
c.JSON(400, gin.H{"error": "invalid body"})
|
c.JSON(400, gin.H{"error": "invalid body"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
targetIndex := -1
|
targetIndex := -1
|
||||||
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.GeminiKey) {
|
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.GeminiKey) {
|
||||||
targetIndex = *body.Index
|
targetIndex = *body.Index
|
||||||
@@ -187,7 +192,7 @@ func (h *Handler) PatchGeminiKey(c *gin.Context) {
|
|||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
h.cfg.GeminiKey = append(h.cfg.GeminiKey[:targetIndex], h.cfg.GeminiKey[targetIndex+1:]...)
|
h.cfg.GeminiKey = append(h.cfg.GeminiKey[:targetIndex], h.cfg.GeminiKey[targetIndex+1:]...)
|
||||||
h.cfg.SanitizeGeminiKeys()
|
h.cfg.SanitizeGeminiKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
entry.APIKey = trimmed
|
entry.APIKey = trimmed
|
||||||
@@ -209,10 +214,12 @@ func (h *Handler) PatchGeminiKey(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
h.cfg.GeminiKey[targetIndex] = entry
|
h.cfg.GeminiKey[targetIndex] = entry
|
||||||
h.cfg.SanitizeGeminiKeys()
|
h.cfg.SanitizeGeminiKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) DeleteGeminiKey(c *gin.Context) {
|
func (h *Handler) DeleteGeminiKey(c *gin.Context) {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
if val := strings.TrimSpace(c.Query("api-key")); val != "" {
|
if val := strings.TrimSpace(c.Query("api-key")); val != "" {
|
||||||
if baseRaw, okBase := c.GetQuery("base-url"); okBase {
|
if baseRaw, okBase := c.GetQuery("base-url"); okBase {
|
||||||
base := strings.TrimSpace(baseRaw)
|
base := strings.TrimSpace(baseRaw)
|
||||||
@@ -226,7 +233,7 @@ func (h *Handler) DeleteGeminiKey(c *gin.Context) {
|
|||||||
if len(out) != len(h.cfg.GeminiKey) {
|
if len(out) != len(h.cfg.GeminiKey) {
|
||||||
h.cfg.GeminiKey = out
|
h.cfg.GeminiKey = out
|
||||||
h.cfg.SanitizeGeminiKeys()
|
h.cfg.SanitizeGeminiKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
} else {
|
} else {
|
||||||
c.JSON(404, gin.H{"error": "item not found"})
|
c.JSON(404, gin.H{"error": "item not found"})
|
||||||
}
|
}
|
||||||
@@ -253,7 +260,7 @@ func (h *Handler) DeleteGeminiKey(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
h.cfg.GeminiKey = append(h.cfg.GeminiKey[:matchIndex], h.cfg.GeminiKey[matchIndex+1:]...)
|
h.cfg.GeminiKey = append(h.cfg.GeminiKey[:matchIndex], h.cfg.GeminiKey[matchIndex+1:]...)
|
||||||
h.cfg.SanitizeGeminiKeys()
|
h.cfg.SanitizeGeminiKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if idxStr := c.Query("index"); idxStr != "" {
|
if idxStr := c.Query("index"); idxStr != "" {
|
||||||
@@ -261,7 +268,7 @@ func (h *Handler) DeleteGeminiKey(c *gin.Context) {
|
|||||||
if _, err := fmt.Sscanf(idxStr, "%d", &idx); err == nil && idx >= 0 && idx < len(h.cfg.GeminiKey) {
|
if _, err := fmt.Sscanf(idxStr, "%d", &idx); err == nil && idx >= 0 && idx < len(h.cfg.GeminiKey) {
|
||||||
h.cfg.GeminiKey = append(h.cfg.GeminiKey[:idx], h.cfg.GeminiKey[idx+1:]...)
|
h.cfg.GeminiKey = append(h.cfg.GeminiKey[:idx], h.cfg.GeminiKey[idx+1:]...)
|
||||||
h.cfg.SanitizeGeminiKeys()
|
h.cfg.SanitizeGeminiKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,7 +277,7 @@ func (h *Handler) DeleteGeminiKey(c *gin.Context) {
|
|||||||
|
|
||||||
// claude-api-key: []ClaudeKey
|
// claude-api-key: []ClaudeKey
|
||||||
func (h *Handler) GetClaudeKeys(c *gin.Context) {
|
func (h *Handler) GetClaudeKeys(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{"claude-api-key": h.cfg.ClaudeKey})
|
c.JSON(200, gin.H{"claude-api-key": h.claudeKeysWithAuthIndex()})
|
||||||
}
|
}
|
||||||
func (h *Handler) PutClaudeKeys(c *gin.Context) {
|
func (h *Handler) PutClaudeKeys(c *gin.Context) {
|
||||||
data, err := c.GetRawData()
|
data, err := c.GetRawData()
|
||||||
@@ -292,9 +299,11 @@ func (h *Handler) PutClaudeKeys(c *gin.Context) {
|
|||||||
for i := range arr {
|
for i := range arr {
|
||||||
normalizeClaudeKey(&arr[i])
|
normalizeClaudeKey(&arr[i])
|
||||||
}
|
}
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
h.cfg.ClaudeKey = arr
|
h.cfg.ClaudeKey = arr
|
||||||
h.cfg.SanitizeClaudeKeys()
|
h.cfg.SanitizeClaudeKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
}
|
}
|
||||||
func (h *Handler) PatchClaudeKey(c *gin.Context) {
|
func (h *Handler) PatchClaudeKey(c *gin.Context) {
|
||||||
type claudeKeyPatch struct {
|
type claudeKeyPatch struct {
|
||||||
@@ -315,6 +324,9 @@ func (h *Handler) PatchClaudeKey(c *gin.Context) {
|
|||||||
c.JSON(400, gin.H{"error": "invalid body"})
|
c.JSON(400, gin.H{"error": "invalid body"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
targetIndex := -1
|
targetIndex := -1
|
||||||
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) {
|
||||||
targetIndex = *body.Index
|
targetIndex = *body.Index
|
||||||
@@ -358,10 +370,12 @@ func (h *Handler) PatchClaudeKey(c *gin.Context) {
|
|||||||
normalizeClaudeKey(&entry)
|
normalizeClaudeKey(&entry)
|
||||||
h.cfg.ClaudeKey[targetIndex] = entry
|
h.cfg.ClaudeKey[targetIndex] = entry
|
||||||
h.cfg.SanitizeClaudeKeys()
|
h.cfg.SanitizeClaudeKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) DeleteClaudeKey(c *gin.Context) {
|
func (h *Handler) DeleteClaudeKey(c *gin.Context) {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
if val := strings.TrimSpace(c.Query("api-key")); val != "" {
|
if val := strings.TrimSpace(c.Query("api-key")); val != "" {
|
||||||
if baseRaw, okBase := c.GetQuery("base-url"); okBase {
|
if baseRaw, okBase := c.GetQuery("base-url"); okBase {
|
||||||
base := strings.TrimSpace(baseRaw)
|
base := strings.TrimSpace(baseRaw)
|
||||||
@@ -374,7 +388,7 @@ func (h *Handler) DeleteClaudeKey(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
h.cfg.ClaudeKey = out
|
h.cfg.ClaudeKey = out
|
||||||
h.cfg.SanitizeClaudeKeys()
|
h.cfg.SanitizeClaudeKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -396,7 +410,7 @@ func (h *Handler) DeleteClaudeKey(c *gin.Context) {
|
|||||||
h.cfg.ClaudeKey = append(h.cfg.ClaudeKey[:matchIndex], h.cfg.ClaudeKey[matchIndex+1:]...)
|
h.cfg.ClaudeKey = append(h.cfg.ClaudeKey[:matchIndex], h.cfg.ClaudeKey[matchIndex+1:]...)
|
||||||
}
|
}
|
||||||
h.cfg.SanitizeClaudeKeys()
|
h.cfg.SanitizeClaudeKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if idxStr := c.Query("index"); idxStr != "" {
|
if idxStr := c.Query("index"); idxStr != "" {
|
||||||
@@ -405,7 +419,7 @@ func (h *Handler) DeleteClaudeKey(c *gin.Context) {
|
|||||||
if err == nil && idx >= 0 && idx < len(h.cfg.ClaudeKey) {
|
if err == nil && idx >= 0 && idx < len(h.cfg.ClaudeKey) {
|
||||||
h.cfg.ClaudeKey = append(h.cfg.ClaudeKey[:idx], h.cfg.ClaudeKey[idx+1:]...)
|
h.cfg.ClaudeKey = append(h.cfg.ClaudeKey[:idx], h.cfg.ClaudeKey[idx+1:]...)
|
||||||
h.cfg.SanitizeClaudeKeys()
|
h.cfg.SanitizeClaudeKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,7 +428,7 @@ func (h *Handler) DeleteClaudeKey(c *gin.Context) {
|
|||||||
|
|
||||||
// openai-compatibility: []OpenAICompatibility
|
// openai-compatibility: []OpenAICompatibility
|
||||||
func (h *Handler) GetOpenAICompat(c *gin.Context) {
|
func (h *Handler) GetOpenAICompat(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{"openai-compatibility": normalizedOpenAICompatibilityEntries(h.cfg.OpenAICompatibility)})
|
c.JSON(200, gin.H{"openai-compatibility": h.openAICompatibilityWithAuthIndex()})
|
||||||
}
|
}
|
||||||
func (h *Handler) PutOpenAICompat(c *gin.Context) {
|
func (h *Handler) PutOpenAICompat(c *gin.Context) {
|
||||||
data, err := c.GetRawData()
|
data, err := c.GetRawData()
|
||||||
@@ -440,9 +454,11 @@ func (h *Handler) PutOpenAICompat(c *gin.Context) {
|
|||||||
filtered = append(filtered, arr[i])
|
filtered = append(filtered, arr[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
h.cfg.OpenAICompatibility = filtered
|
h.cfg.OpenAICompatibility = filtered
|
||||||
h.cfg.SanitizeOpenAICompatibility()
|
h.cfg.SanitizeOpenAICompatibility()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
}
|
}
|
||||||
func (h *Handler) PatchOpenAICompat(c *gin.Context) {
|
func (h *Handler) PatchOpenAICompat(c *gin.Context) {
|
||||||
type openAICompatPatch struct {
|
type openAICompatPatch struct {
|
||||||
@@ -462,6 +478,9 @@ func (h *Handler) PatchOpenAICompat(c *gin.Context) {
|
|||||||
c.JSON(400, gin.H{"error": "invalid body"})
|
c.JSON(400, gin.H{"error": "invalid body"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
targetIndex := -1
|
targetIndex := -1
|
||||||
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.OpenAICompatibility) {
|
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.OpenAICompatibility) {
|
||||||
targetIndex = *body.Index
|
targetIndex = *body.Index
|
||||||
@@ -492,7 +511,7 @@ func (h *Handler) PatchOpenAICompat(c *gin.Context) {
|
|||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
h.cfg.OpenAICompatibility = append(h.cfg.OpenAICompatibility[:targetIndex], h.cfg.OpenAICompatibility[targetIndex+1:]...)
|
h.cfg.OpenAICompatibility = append(h.cfg.OpenAICompatibility[:targetIndex], h.cfg.OpenAICompatibility[targetIndex+1:]...)
|
||||||
h.cfg.SanitizeOpenAICompatibility()
|
h.cfg.SanitizeOpenAICompatibility()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
entry.BaseURL = trimmed
|
entry.BaseURL = trimmed
|
||||||
@@ -509,10 +528,12 @@ func (h *Handler) PatchOpenAICompat(c *gin.Context) {
|
|||||||
normalizeOpenAICompatibilityEntry(&entry)
|
normalizeOpenAICompatibilityEntry(&entry)
|
||||||
h.cfg.OpenAICompatibility[targetIndex] = entry
|
h.cfg.OpenAICompatibility[targetIndex] = entry
|
||||||
h.cfg.SanitizeOpenAICompatibility()
|
h.cfg.SanitizeOpenAICompatibility()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) DeleteOpenAICompat(c *gin.Context) {
|
func (h *Handler) DeleteOpenAICompat(c *gin.Context) {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
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))
|
||||||
for _, v := range h.cfg.OpenAICompatibility {
|
for _, v := range h.cfg.OpenAICompatibility {
|
||||||
@@ -522,7 +543,7 @@ func (h *Handler) DeleteOpenAICompat(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
h.cfg.OpenAICompatibility = out
|
h.cfg.OpenAICompatibility = out
|
||||||
h.cfg.SanitizeOpenAICompatibility()
|
h.cfg.SanitizeOpenAICompatibility()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if idxStr := c.Query("index"); idxStr != "" {
|
if idxStr := c.Query("index"); idxStr != "" {
|
||||||
@@ -531,7 +552,7 @@ func (h *Handler) DeleteOpenAICompat(c *gin.Context) {
|
|||||||
if err == nil && idx >= 0 && idx < len(h.cfg.OpenAICompatibility) {
|
if err == nil && idx >= 0 && idx < len(h.cfg.OpenAICompatibility) {
|
||||||
h.cfg.OpenAICompatibility = append(h.cfg.OpenAICompatibility[:idx], h.cfg.OpenAICompatibility[idx+1:]...)
|
h.cfg.OpenAICompatibility = append(h.cfg.OpenAICompatibility[:idx], h.cfg.OpenAICompatibility[idx+1:]...)
|
||||||
h.cfg.SanitizeOpenAICompatibility()
|
h.cfg.SanitizeOpenAICompatibility()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -540,7 +561,7 @@ func (h *Handler) DeleteOpenAICompat(c *gin.Context) {
|
|||||||
|
|
||||||
// vertex-api-key: []VertexCompatKey
|
// vertex-api-key: []VertexCompatKey
|
||||||
func (h *Handler) GetVertexCompatKeys(c *gin.Context) {
|
func (h *Handler) GetVertexCompatKeys(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{"vertex-api-key": h.cfg.VertexCompatAPIKey})
|
c.JSON(200, gin.H{"vertex-api-key": h.vertexCompatKeysWithAuthIndex()})
|
||||||
}
|
}
|
||||||
func (h *Handler) PutVertexCompatKeys(c *gin.Context) {
|
func (h *Handler) PutVertexCompatKeys(c *gin.Context) {
|
||||||
data, err := c.GetRawData()
|
data, err := c.GetRawData()
|
||||||
@@ -566,9 +587,11 @@ func (h *Handler) PutVertexCompatKeys(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
h.cfg.VertexCompatAPIKey = append([]config.VertexCompatKey(nil), arr...)
|
h.cfg.VertexCompatAPIKey = append([]config.VertexCompatKey(nil), arr...)
|
||||||
h.cfg.SanitizeVertexCompatKeys()
|
h.cfg.SanitizeVertexCompatKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
}
|
}
|
||||||
func (h *Handler) PatchVertexCompatKey(c *gin.Context) {
|
func (h *Handler) PatchVertexCompatKey(c *gin.Context) {
|
||||||
type vertexCompatPatch struct {
|
type vertexCompatPatch struct {
|
||||||
@@ -589,6 +612,9 @@ func (h *Handler) PatchVertexCompatKey(c *gin.Context) {
|
|||||||
c.JSON(400, gin.H{"error": "invalid body"})
|
c.JSON(400, gin.H{"error": "invalid body"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
targetIndex := -1
|
targetIndex := -1
|
||||||
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.VertexCompatAPIKey) {
|
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.VertexCompatAPIKey) {
|
||||||
targetIndex = *body.Index
|
targetIndex = *body.Index
|
||||||
@@ -615,7 +641,7 @@ func (h *Handler) PatchVertexCompatKey(c *gin.Context) {
|
|||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
h.cfg.VertexCompatAPIKey = append(h.cfg.VertexCompatAPIKey[:targetIndex], h.cfg.VertexCompatAPIKey[targetIndex+1:]...)
|
h.cfg.VertexCompatAPIKey = append(h.cfg.VertexCompatAPIKey[:targetIndex], h.cfg.VertexCompatAPIKey[targetIndex+1:]...)
|
||||||
h.cfg.SanitizeVertexCompatKeys()
|
h.cfg.SanitizeVertexCompatKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
entry.APIKey = trimmed
|
entry.APIKey = trimmed
|
||||||
@@ -628,7 +654,7 @@ func (h *Handler) PatchVertexCompatKey(c *gin.Context) {
|
|||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
h.cfg.VertexCompatAPIKey = append(h.cfg.VertexCompatAPIKey[:targetIndex], h.cfg.VertexCompatAPIKey[targetIndex+1:]...)
|
h.cfg.VertexCompatAPIKey = append(h.cfg.VertexCompatAPIKey[:targetIndex], h.cfg.VertexCompatAPIKey[targetIndex+1:]...)
|
||||||
h.cfg.SanitizeVertexCompatKeys()
|
h.cfg.SanitizeVertexCompatKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
entry.BaseURL = trimmed
|
entry.BaseURL = trimmed
|
||||||
@@ -648,10 +674,12 @@ func (h *Handler) PatchVertexCompatKey(c *gin.Context) {
|
|||||||
normalizeVertexCompatKey(&entry)
|
normalizeVertexCompatKey(&entry)
|
||||||
h.cfg.VertexCompatAPIKey[targetIndex] = entry
|
h.cfg.VertexCompatAPIKey[targetIndex] = entry
|
||||||
h.cfg.SanitizeVertexCompatKeys()
|
h.cfg.SanitizeVertexCompatKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) DeleteVertexCompatKey(c *gin.Context) {
|
func (h *Handler) DeleteVertexCompatKey(c *gin.Context) {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
if val := strings.TrimSpace(c.Query("api-key")); val != "" {
|
if val := strings.TrimSpace(c.Query("api-key")); val != "" {
|
||||||
if baseRaw, okBase := c.GetQuery("base-url"); okBase {
|
if baseRaw, okBase := c.GetQuery("base-url"); okBase {
|
||||||
base := strings.TrimSpace(baseRaw)
|
base := strings.TrimSpace(baseRaw)
|
||||||
@@ -664,7 +692,7 @@ func (h *Handler) DeleteVertexCompatKey(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
h.cfg.VertexCompatAPIKey = out
|
h.cfg.VertexCompatAPIKey = out
|
||||||
h.cfg.SanitizeVertexCompatKeys()
|
h.cfg.SanitizeVertexCompatKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -686,7 +714,7 @@ func (h *Handler) DeleteVertexCompatKey(c *gin.Context) {
|
|||||||
h.cfg.VertexCompatAPIKey = append(h.cfg.VertexCompatAPIKey[:matchIndex], h.cfg.VertexCompatAPIKey[matchIndex+1:]...)
|
h.cfg.VertexCompatAPIKey = append(h.cfg.VertexCompatAPIKey[:matchIndex], h.cfg.VertexCompatAPIKey[matchIndex+1:]...)
|
||||||
}
|
}
|
||||||
h.cfg.SanitizeVertexCompatKeys()
|
h.cfg.SanitizeVertexCompatKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if idxStr := c.Query("index"); idxStr != "" {
|
if idxStr := c.Query("index"); idxStr != "" {
|
||||||
@@ -695,7 +723,7 @@ func (h *Handler) DeleteVertexCompatKey(c *gin.Context) {
|
|||||||
if errScan == nil && idx >= 0 && idx < len(h.cfg.VertexCompatAPIKey) {
|
if errScan == nil && idx >= 0 && idx < len(h.cfg.VertexCompatAPIKey) {
|
||||||
h.cfg.VertexCompatAPIKey = append(h.cfg.VertexCompatAPIKey[:idx], h.cfg.VertexCompatAPIKey[idx+1:]...)
|
h.cfg.VertexCompatAPIKey = append(h.cfg.VertexCompatAPIKey[:idx], h.cfg.VertexCompatAPIKey[idx+1:]...)
|
||||||
h.cfg.SanitizeVertexCompatKeys()
|
h.cfg.SanitizeVertexCompatKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -886,7 +914,7 @@ func (h *Handler) DeleteOAuthModelAlias(c *gin.Context) {
|
|||||||
|
|
||||||
// codex-api-key: []CodexKey
|
// codex-api-key: []CodexKey
|
||||||
func (h *Handler) GetCodexKeys(c *gin.Context) {
|
func (h *Handler) GetCodexKeys(c *gin.Context) {
|
||||||
c.JSON(200, gin.H{"codex-api-key": h.cfg.CodexKey})
|
c.JSON(200, gin.H{"codex-api-key": h.codexKeysWithAuthIndex()})
|
||||||
}
|
}
|
||||||
func (h *Handler) PutCodexKeys(c *gin.Context) {
|
func (h *Handler) PutCodexKeys(c *gin.Context) {
|
||||||
data, err := c.GetRawData()
|
data, err := c.GetRawData()
|
||||||
@@ -915,9 +943,11 @@ func (h *Handler) PutCodexKeys(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
filtered = append(filtered, entry)
|
filtered = append(filtered, entry)
|
||||||
}
|
}
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
h.cfg.CodexKey = filtered
|
h.cfg.CodexKey = filtered
|
||||||
h.cfg.SanitizeCodexKeys()
|
h.cfg.SanitizeCodexKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
}
|
}
|
||||||
func (h *Handler) PatchCodexKey(c *gin.Context) {
|
func (h *Handler) PatchCodexKey(c *gin.Context) {
|
||||||
type codexKeyPatch struct {
|
type codexKeyPatch struct {
|
||||||
@@ -938,6 +968,9 @@ func (h *Handler) PatchCodexKey(c *gin.Context) {
|
|||||||
c.JSON(400, gin.H{"error": "invalid body"})
|
c.JSON(400, gin.H{"error": "invalid body"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
targetIndex := -1
|
targetIndex := -1
|
||||||
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.CodexKey) {
|
if body.Index != nil && *body.Index >= 0 && *body.Index < len(h.cfg.CodexKey) {
|
||||||
targetIndex = *body.Index
|
targetIndex = *body.Index
|
||||||
@@ -968,7 +1001,7 @@ func (h *Handler) PatchCodexKey(c *gin.Context) {
|
|||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
h.cfg.CodexKey = append(h.cfg.CodexKey[:targetIndex], h.cfg.CodexKey[targetIndex+1:]...)
|
h.cfg.CodexKey = append(h.cfg.CodexKey[:targetIndex], h.cfg.CodexKey[targetIndex+1:]...)
|
||||||
h.cfg.SanitizeCodexKeys()
|
h.cfg.SanitizeCodexKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
entry.BaseURL = trimmed
|
entry.BaseURL = trimmed
|
||||||
@@ -988,10 +1021,12 @@ func (h *Handler) PatchCodexKey(c *gin.Context) {
|
|||||||
normalizeCodexKey(&entry)
|
normalizeCodexKey(&entry)
|
||||||
h.cfg.CodexKey[targetIndex] = entry
|
h.cfg.CodexKey[targetIndex] = entry
|
||||||
h.cfg.SanitizeCodexKeys()
|
h.cfg.SanitizeCodexKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) DeleteCodexKey(c *gin.Context) {
|
func (h *Handler) DeleteCodexKey(c *gin.Context) {
|
||||||
|
h.mu.Lock()
|
||||||
|
defer h.mu.Unlock()
|
||||||
if val := strings.TrimSpace(c.Query("api-key")); val != "" {
|
if val := strings.TrimSpace(c.Query("api-key")); val != "" {
|
||||||
if baseRaw, okBase := c.GetQuery("base-url"); okBase {
|
if baseRaw, okBase := c.GetQuery("base-url"); okBase {
|
||||||
base := strings.TrimSpace(baseRaw)
|
base := strings.TrimSpace(baseRaw)
|
||||||
@@ -1004,7 +1039,7 @@ func (h *Handler) DeleteCodexKey(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
h.cfg.CodexKey = out
|
h.cfg.CodexKey = out
|
||||||
h.cfg.SanitizeCodexKeys()
|
h.cfg.SanitizeCodexKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1026,7 +1061,7 @@ func (h *Handler) DeleteCodexKey(c *gin.Context) {
|
|||||||
h.cfg.CodexKey = append(h.cfg.CodexKey[:matchIndex], h.cfg.CodexKey[matchIndex+1:]...)
|
h.cfg.CodexKey = append(h.cfg.CodexKey[:matchIndex], h.cfg.CodexKey[matchIndex+1:]...)
|
||||||
}
|
}
|
||||||
h.cfg.SanitizeCodexKeys()
|
h.cfg.SanitizeCodexKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if idxStr := c.Query("index"); idxStr != "" {
|
if idxStr := c.Query("index"); idxStr != "" {
|
||||||
@@ -1035,7 +1070,7 @@ func (h *Handler) DeleteCodexKey(c *gin.Context) {
|
|||||||
if err == nil && idx >= 0 && idx < len(h.cfg.CodexKey) {
|
if err == nil && idx >= 0 && idx < len(h.cfg.CodexKey) {
|
||||||
h.cfg.CodexKey = append(h.cfg.CodexKey[:idx], h.cfg.CodexKey[idx+1:]...)
|
h.cfg.CodexKey = append(h.cfg.CodexKey[:idx], h.cfg.CodexKey[idx+1:]...)
|
||||||
h.cfg.SanitizeCodexKeys()
|
h.cfg.SanitizeCodexKeys()
|
||||||
h.persist(c)
|
h.persistLocked(c)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,10 +105,24 @@ func NewHandlerWithoutConfigFilePath(cfg *config.Config, manager *coreauth.Manag
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetConfig updates the in-memory config reference when the server hot-reloads.
|
// SetConfig updates the in-memory config reference when the server hot-reloads.
|
||||||
func (h *Handler) SetConfig(cfg *config.Config) { h.cfg = cfg }
|
func (h *Handler) SetConfig(cfg *config.Config) {
|
||||||
|
if h == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.mu.Lock()
|
||||||
|
h.cfg = cfg
|
||||||
|
h.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// SetAuthManager updates the auth manager reference used by management endpoints.
|
// SetAuthManager updates the auth manager reference used by management endpoints.
|
||||||
func (h *Handler) SetAuthManager(manager *coreauth.Manager) { h.authManager = manager }
|
func (h *Handler) SetAuthManager(manager *coreauth.Manager) {
|
||||||
|
if h == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.mu.Lock()
|
||||||
|
h.authManager = manager
|
||||||
|
h.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// SetUsageStatistics allows replacing the usage statistics reference.
|
// SetUsageStatistics allows replacing the usage statistics reference.
|
||||||
func (h *Handler) SetUsageStatistics(stats *usage.RequestStatistics) { h.usageStats = stats }
|
func (h *Handler) SetUsageStatistics(stats *usage.RequestStatistics) { h.usageStats = stats }
|
||||||
@@ -276,6 +290,12 @@ func (h *Handler) Middleware() gin.HandlerFunc {
|
|||||||
func (h *Handler) persist(c *gin.Context) bool {
|
func (h *Handler) persist(c *gin.Context) bool {
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer h.mu.Unlock()
|
||||||
|
return h.persistLocked(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// persistLocked saves the current in-memory config to disk.
|
||||||
|
// It expects the caller to hold h.mu.
|
||||||
|
func (h *Handler) persistLocked(c *gin.Context) bool {
|
||||||
// Preserve comments when writing
|
// Preserve comments when writing
|
||||||
if err := config.SaveConfigPreserveComments(h.configFilePath, h.cfg); err != nil {
|
if err := config.SaveConfigPreserveComments(h.configFilePath, h.cfg); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to save config: %v", err)})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to save config: %v", err)})
|
||||||
|
|||||||
Reference in New Issue
Block a user