Spaces:
Sleeping
Sleeping
| package settings | |
| import ( | |
| "encoding/json" | |
| "net/http" | |
| "strings" | |
| "time" | |
| authn "ds2api/internal/auth" | |
| "ds2api/internal/config" | |
| ) | |
| func (h *Handler) updateSettings(w http.ResponseWriter, r *http.Request) { | |
| var req map[string]any | |
| if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | |
| writeJSON(w, http.StatusBadRequest, map[string]any{"detail": "invalid json"}) | |
| return | |
| } | |
| adminCfg, runtimeCfg, responsesCfg, embeddingsCfg, autoDeleteCfg, currentInputCfg, thinkingInjCfg, aliasMap, err := parseSettingsUpdateRequest(req) | |
| if err != nil { | |
| writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()}) | |
| return | |
| } | |
| if runtimeCfg != nil { | |
| if err := validateMergedRuntimeSettings(h.Store.Snapshot().Runtime, runtimeCfg); err != nil { | |
| writeJSON(w, http.StatusBadRequest, map[string]any{"detail": err.Error()}) | |
| return | |
| } | |
| } | |
| currentInputEnabledSet := hasNestedSettingsKey(req, "current_input_file", "enabled") | |
| currentInputMinCharsSet := hasNestedSettingsKey(req, "current_input_file", "min_chars") | |
| thinkingInjectionEnabledSet := hasNestedSettingsKey(req, "thinking_injection", "enabled") | |
| thinkingInjectionPromptSet := hasNestedSettingsKey(req, "thinking_injection", "prompt") | |
| if err := h.Store.Update(func(c *config.Config) error { | |
| if adminCfg != nil { | |
| if adminCfg.JWTExpireHours > 0 { | |
| c.Admin.JWTExpireHours = adminCfg.JWTExpireHours | |
| } | |
| } | |
| if runtimeCfg != nil { | |
| if runtimeCfg.AccountMaxInflight > 0 { | |
| c.Runtime.AccountMaxInflight = runtimeCfg.AccountMaxInflight | |
| } | |
| if runtimeCfg.AccountMaxQueue > 0 { | |
| c.Runtime.AccountMaxQueue = runtimeCfg.AccountMaxQueue | |
| } | |
| if runtimeCfg.GlobalMaxInflight > 0 { | |
| c.Runtime.GlobalMaxInflight = runtimeCfg.GlobalMaxInflight | |
| } | |
| if runtimeCfg.TokenRefreshIntervalHours > 0 { | |
| c.Runtime.TokenRefreshIntervalHours = runtimeCfg.TokenRefreshIntervalHours | |
| } | |
| } | |
| if responsesCfg != nil && responsesCfg.StoreTTLSeconds > 0 { | |
| c.Responses.StoreTTLSeconds = responsesCfg.StoreTTLSeconds | |
| } | |
| if embeddingsCfg != nil && strings.TrimSpace(embeddingsCfg.Provider) != "" { | |
| c.Embeddings.Provider = strings.TrimSpace(embeddingsCfg.Provider) | |
| } | |
| if autoDeleteCfg != nil { | |
| c.AutoDelete.Mode = autoDeleteCfg.Mode | |
| c.AutoDelete.Sessions = autoDeleteCfg.Sessions | |
| } | |
| if currentInputCfg != nil { | |
| if currentInputEnabledSet { | |
| c.CurrentInputFile.Enabled = currentInputCfg.Enabled | |
| } | |
| if currentInputMinCharsSet { | |
| c.CurrentInputFile.MinChars = currentInputCfg.MinChars | |
| } | |
| } | |
| if thinkingInjCfg != nil { | |
| if thinkingInjectionEnabledSet { | |
| c.ThinkingInjection.Enabled = thinkingInjCfg.Enabled | |
| } | |
| if thinkingInjectionPromptSet { | |
| c.ThinkingInjection.Prompt = thinkingInjCfg.Prompt | |
| } | |
| } | |
| if aliasMap != nil { | |
| c.ModelAliases = aliasMap | |
| } | |
| return nil | |
| }); err != nil { | |
| writeJSON(w, http.StatusInternalServerError, map[string]any{"detail": err.Error()}) | |
| return | |
| } | |
| h.applyRuntimeSettings() | |
| needsSync := config.IsVercel() || h.Store.IsEnvBacked() | |
| writeJSON(w, http.StatusOK, map[string]any{ | |
| "success": true, | |
| "message": "settings updated and hot reloaded", | |
| "env_backed": h.Store.IsEnvBacked(), | |
| "needs_vercel_sync": needsSync, | |
| "manual_sync_message": "配置已保存。Vercel 部署请在 Vercel Sync 页面手动同步。", | |
| }) | |
| } | |
| func (h *Handler) updateSettingsPassword(w http.ResponseWriter, r *http.Request) { | |
| var req map[string]any | |
| if err := json.NewDecoder(r.Body).Decode(&req); err != nil { | |
| writeJSON(w, http.StatusBadRequest, map[string]any{"detail": "invalid json"}) | |
| return | |
| } | |
| newPassword := strings.TrimSpace(fieldString(req, "new_password")) | |
| if newPassword == "" { | |
| newPassword = strings.TrimSpace(fieldString(req, "password")) | |
| } | |
| if len(newPassword) < 4 { | |
| writeJSON(w, http.StatusBadRequest, map[string]any{"detail": "new password must be at least 4 characters"}) | |
| return | |
| } | |
| now := time.Now().Unix() | |
| hash := authn.HashAdminPassword(newPassword) | |
| if err := h.Store.Update(func(c *config.Config) error { | |
| c.Admin.PasswordHash = hash | |
| c.Admin.JWTValidAfterUnix = now | |
| return nil | |
| }); err != nil { | |
| writeJSON(w, http.StatusInternalServerError, map[string]any{"detail": err.Error()}) | |
| return | |
| } | |
| writeJSON(w, http.StatusOK, map[string]any{ | |
| "success": true, | |
| "message": "password updated", | |
| "force_relogin": true, | |
| "jwt_valid_after_unix": now, | |
| }) | |
| } | |
| func hasNestedSettingsKey(req map[string]any, section, key string) bool { | |
| raw, ok := req[section].(map[string]any) | |
| if !ok { | |
| return false | |
| } | |
| _, exists := raw[key] | |
| return exists | |
| } | |