| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- package admin
- import (
- "crypto/rand"
- "encoding/hex"
- "encoding/json"
- "fmt"
- "log/slog"
- "regexp"
- "strings"
- "github.com/Wei-Shaw/sub2api/internal/handler/dto"
- "github.com/Wei-Shaw/sub2api/internal/pkg/response"
- "github.com/Wei-Shaw/sub2api/internal/service"
- "github.com/gin-gonic/gin"
- )
- // semverPattern 预编译 semver 格式校验正则
- var semverPattern = regexp.MustCompile(`^\d+\.\d+\.\d+$`)
- // menuItemIDPattern validates custom menu item IDs: alphanumeric, hyphens, underscores only.
- var menuItemIDPattern = regexp.MustCompile(`^[a-zA-Z0-9_-]+$`)
- // generateMenuItemID generates a short random hex ID for a custom menu item.
- func generateMenuItemID() (string, error) {
- b := make([]byte, 8)
- if _, err := rand.Read(b); err != nil {
- return "", fmt.Errorf("generate menu item ID: %w", err)
- }
- return hex.EncodeToString(b), nil
- }
- func scopesContainOpenID(scopes string) bool {
- for _, scope := range strings.Fields(strings.ToLower(strings.TrimSpace(scopes))) {
- if scope == "openid" {
- return true
- }
- }
- return false
- }
- func firstNonEmpty(values ...string) string {
- for _, value := range values {
- if trimmed := strings.TrimSpace(value); trimmed != "" {
- return trimmed
- }
- }
- return ""
- }
- // SettingHandler 系统设置处理器
- type SettingHandler struct {
- settingService *service.SettingService
- emailService *service.EmailService
- turnstileService *service.TurnstileService
- aliyunCaptchaService *service.AliyunCaptchaService
- opsService *service.OpsService
- paymentConfigService *service.PaymentConfigService
- paymentService *service.PaymentService
- userAttributeService *service.UserAttributeService
- notificationEmailService *service.NotificationEmailService
- totpService *service.TotpService
- userService *service.UserService
- }
- // NewSettingHandler 创建系统设置处理器
- func NewSettingHandler(settingService *service.SettingService, emailService *service.EmailService, turnstileService *service.TurnstileService, opsService *service.OpsService, paymentConfigService *service.PaymentConfigService, paymentService *service.PaymentService, userAttributeService *service.UserAttributeService) *SettingHandler {
- return &SettingHandler{
- settingService: settingService,
- emailService: emailService,
- turnstileService: turnstileService,
- opsService: opsService,
- paymentConfigService: paymentConfigService,
- paymentService: paymentService,
- userAttributeService: userAttributeService,
- }
- }
- // SetNotificationEmailService attaches the notification template service without changing
- // the constructor signature used by existing unit tests.
- func (h *SettingHandler) SetNotificationEmailService(notificationEmailService *service.NotificationEmailService) {
- h.notificationEmailService = notificationEmailService
- }
- // SetAliyunCaptchaService attaches the Aliyun captcha credential validator without
- // changing the constructor signature used by existing unit tests.
- func (h *SettingHandler) SetAliyunCaptchaService(aliyunCaptchaService *service.AliyunCaptchaService) {
- h.aliyunCaptchaService = aliyunCaptchaService
- }
- // SetStepUpDeps attaches the services backing the step-up switch preconditions
- // (enable requires the acting admin to have TOTP enabled; disable is itself a
- // step-up gated operation), without changing the constructor signature used by
- // existing unit tests.
- func (h *SettingHandler) SetStepUpDeps(totpService *service.TotpService, userService *service.UserService) {
- h.totpService = totpService
- h.userService = userService
- }
- // GetSettings 获取所有系统设置
- // GET /api/v1/admin/settings
- func (h *SettingHandler) GetSettings(c *gin.Context) {
- settings, err := h.settingService.GetAllSettings(c.Request.Context())
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- authSourceDefaults, err := h.settingService.GetAuthSourceDefaultSettings(c.Request.Context())
- if err != nil {
- response.ErrorFrom(c, err)
- return
- }
- // Check if ops monitoring is enabled (respects config.ops.enabled)
- opsEnabled := h.opsService != nil && h.opsService.IsMonitoringEnabled(c.Request.Context())
- defaultSubscriptions := make([]dto.DefaultSubscriptionSetting, 0, len(settings.DefaultSubscriptions))
- for _, sub := range settings.DefaultSubscriptions {
- defaultSubscriptions = append(defaultSubscriptions, dto.DefaultSubscriptionSetting{
- GroupID: sub.GroupID,
- ValidityDays: sub.ValidityDays,
- })
- }
- // Load payment config
- var paymentCfg *service.PaymentConfig
- if h.paymentConfigService != nil {
- paymentCfg, _ = h.paymentConfigService.GetPaymentConfig(c.Request.Context())
- }
- if paymentCfg == nil {
- paymentCfg = &service.PaymentConfig{}
- }
- passkeyConfigured, passkeyRPID, passkeyRPOrigins := h.settingService.PasskeyConfiguration()
- payload := dto.SystemSettings{
- RegistrationEnabled: settings.RegistrationEnabled,
- EmailVerifyEnabled: settings.EmailVerifyEnabled,
- RegistrationEmailSuffixWhitelist: settings.RegistrationEmailSuffixWhitelist,
- RegistrationEmailDomainQuotaEnabled: settings.RegistrationEmailDomainQuotaEnabled,
- PromoCodeEnabled: settings.PromoCodeEnabled,
- PasswordResetEnabled: settings.PasswordResetEnabled,
- FrontendURL: settings.FrontendURL,
- InvitationCodeEnabled: settings.InvitationCodeEnabled,
- TotpEnabled: settings.TotpEnabled,
- TotpEncryptionKeyConfigured: h.settingService.IsTotpEncryptionKeyConfigured(),
- PasskeyEnabled: settings.PasskeyEnabled,
- PasskeyConfigured: passkeyConfigured,
- PasskeyRPID: passkeyRPID,
- PasskeyRPOrigins: passkeyRPOrigins,
- SessionBindingEnabled: settings.SessionBindingEnabled,
- StepUpEnabled: settings.StepUpEnabled,
- AuditLogRetentionDays: settings.AuditLogRetentionDays,
- LoginAgreementEnabled: settings.LoginAgreementEnabled,
- LoginAgreementMode: settings.LoginAgreementMode,
- LoginAgreementUpdatedAt: settings.LoginAgreementUpdatedAt,
- LoginAgreementDocuments: loginAgreementDocumentsToDTO(settings.LoginAgreementDocuments),
- SMTPHost: settings.SMTPHost,
- SMTPPort: settings.SMTPPort,
- SMTPUsername: settings.SMTPUsername,
- SMTPPasswordConfigured: settings.SMTPPasswordConfigured,
- SMTPFrom: settings.SMTPFrom,
- SMTPFromName: settings.SMTPFromName,
- SMTPUseTLS: settings.SMTPUseTLS,
- TurnstileEnabled: settings.TurnstileEnabled,
- TurnstileSiteKey: settings.TurnstileSiteKey,
- TurnstileSecretKeyConfigured: settings.TurnstileSecretKeyConfigured,
- TencentCaptchaEnabled: settings.TencentCaptchaEnabled,
- TencentCaptchaAppID: settings.TencentCaptchaAppID,
- TencentCaptchaAppSecretKeyConfigured: settings.TencentCaptchaAppSecretKeyConfigured,
- TencentCaptchaCloudSecretIDConfigured: settings.TencentCaptchaCloudSecretIDConfigured,
- TencentCaptchaCloudSecretKeyConfigured: settings.TencentCaptchaCloudSecretKeyConfigured,
- TencentCaptchaRegion: settings.TencentCaptchaRegion,
- AliyunCaptchaEnabled: settings.AliyunCaptchaEnabled,
- AliyunCaptchaAccessKeyID: settings.AliyunCaptchaAccessKeyID,
- AliyunCaptchaAccessKeySecretConfigured: settings.AliyunCaptchaAccessKeySecretConfigured,
- AliyunCaptchaSceneID: settings.AliyunCaptchaSceneID,
- AliyunCaptchaPrefix: settings.AliyunCaptchaPrefix,
- AliyunCaptchaRegion: settings.AliyunCaptchaRegion,
- APIKeyACLTrustForwardedIP: settings.APIKeyACLTrustForwardedIP,
- ForwardedClientIPHeaders: settings.ForwardedClientIPHeaders,
- LinuxDoConnectEnabled: settings.LinuxDoConnectEnabled,
- LinuxDoConnectClientID: settings.LinuxDoConnectClientID,
- LinuxDoConnectClientSecretConfigured: settings.LinuxDoConnectClientSecretConfigured,
- LinuxDoConnectRedirectURL: settings.LinuxDoConnectRedirectURL,
- DingTalkConnectEnabled: settings.DingTalkConnectEnabled,
- DingTalkConnectClientID: settings.DingTalkConnectClientID,
- DingTalkConnectClientSecretConfigured: settings.DingTalkConnectClientSecretConfigured,
- DingTalkConnectRedirectURL: settings.DingTalkConnectRedirectURL,
- DingTalkConnectCorpRestrictionPolicy: settings.DingTalkConnectCorpRestrictionPolicy,
- DingTalkConnectInternalCorpID: settings.DingTalkConnectInternalCorpID,
- DingTalkConnectBypassRegistration: settings.DingTalkConnectBypassRegistration,
- DingTalkConnectSyncCorpEmail: settings.DingTalkConnectSyncCorpEmail,
- DingTalkConnectSyncDisplayName: settings.DingTalkConnectSyncDisplayName,
- DingTalkConnectSyncDept: settings.DingTalkConnectSyncDept,
- DingTalkConnectSyncCorpEmailAttrKey: settings.DingTalkConnectSyncCorpEmailAttrKey,
- DingTalkConnectSyncDisplayNameAttrKey: settings.DingTalkConnectSyncDisplayNameAttrKey,
- DingTalkConnectSyncDeptAttrKey: settings.DingTalkConnectSyncDeptAttrKey,
- DingTalkConnectSyncCorpEmailAttrName: settings.DingTalkConnectSyncCorpEmailAttrName,
- DingTalkConnectSyncDisplayNameAttrName: settings.DingTalkConnectSyncDisplayNameAttrName,
- DingTalkConnectSyncDeptAttrName: settings.DingTalkConnectSyncDeptAttrName,
- WeChatConnectEnabled: settings.WeChatConnectEnabled,
- WeChatConnectAppID: settings.WeChatConnectAppID,
- WeChatConnectAppSecretConfigured: settings.WeChatConnectAppSecretConfigured,
- WeChatConnectOpenAppID: settings.WeChatConnectOpenAppID,
- WeChatConnectOpenAppSecretConfigured: settings.WeChatConnectOpenAppSecretConfigured,
- WeChatConnectMPAppID: settings.WeChatConnectMPAppID,
- WeChatConnectMPAppSecretConfigured: settings.WeChatConnectMPAppSecretConfigured,
- WeChatConnectMobileAppID: settings.WeChatConnectMobileAppID,
- WeChatConnectMobileAppSecretConfigured: settings.WeChatConnectMobileAppSecretConfigured,
- WeChatConnectOpenEnabled: settings.WeChatConnectOpenEnabled,
- WeChatConnectMPEnabled: settings.WeChatConnectMPEnabled,
- WeChatConnectMobileEnabled: settings.WeChatConnectMobileEnabled,
- WeChatConnectMode: settings.WeChatConnectMode,
- WeChatConnectScopes: settings.WeChatConnectScopes,
- WeChatConnectRedirectURL: settings.WeChatConnectRedirectURL,
- WeChatConnectFrontendRedirectURL: settings.WeChatConnectFrontendRedirectURL,
- OIDCConnectEnabled: settings.OIDCConnectEnabled,
- OIDCConnectProviderName: settings.OIDCConnectProviderName,
- OIDCConnectClientID: settings.OIDCConnectClientID,
- OIDCConnectClientSecretConfigured: settings.OIDCConnectClientSecretConfigured,
- OIDCConnectIssuerURL: settings.OIDCConnectIssuerURL,
- OIDCConnectDiscoveryURL: settings.OIDCConnectDiscoveryURL,
- OIDCConnectAuthorizeURL: settings.OIDCConnectAuthorizeURL,
- OIDCConnectTokenURL: settings.OIDCConnectTokenURL,
- OIDCConnectUserInfoURL: settings.OIDCConnectUserInfoURL,
- OIDCConnectJWKSURL: settings.OIDCConnectJWKSURL,
- OIDCConnectScopes: settings.OIDCConnectScopes,
- OIDCConnectRedirectURL: settings.OIDCConnectRedirectURL,
- OIDCConnectFrontendRedirectURL: settings.OIDCConnectFrontendRedirectURL,
- OIDCConnectTokenAuthMethod: settings.OIDCConnectTokenAuthMethod,
- OIDCConnectUsePKCE: settings.OIDCConnectUsePKCE,
- OIDCConnectValidateIDToken: settings.OIDCConnectValidateIDToken,
- OIDCConnectAllowedSigningAlgs: settings.OIDCConnectAllowedSigningAlgs,
- OIDCConnectClockSkewSeconds: settings.OIDCConnectClockSkewSeconds,
- OIDCConnectRequireEmailVerified: settings.OIDCConnectRequireEmailVerified,
- OIDCConnectUserInfoEmailPath: settings.OIDCConnectUserInfoEmailPath,
- OIDCConnectUserInfoIDPath: settings.OIDCConnectUserInfoIDPath,
- OIDCConnectUserInfoUsernamePath: settings.OIDCConnectUserInfoUsernamePath,
- GitHubOAuthEnabled: settings.GitHubOAuthEnabled,
- GitHubOAuthClientID: settings.GitHubOAuthClientID,
- GitHubOAuthClientSecretConfigured: settings.GitHubOAuthClientSecretConfigured,
- GitHubOAuthRedirectURL: settings.GitHubOAuthRedirectURL,
- GitHubOAuthFrontendRedirectURL: settings.GitHubOAuthFrontendRedirectURL,
- GoogleOAuthEnabled: settings.GoogleOAuthEnabled,
- GoogleOAuthClientID: settings.GoogleOAuthClientID,
- GoogleOAuthClientSecretConfigured: settings.GoogleOAuthClientSecretConfigured,
- GoogleOAuthRedirectURL: settings.GoogleOAuthRedirectURL,
- GoogleOAuthFrontendRedirectURL: settings.GoogleOAuthFrontendRedirectURL,
- SiteName: settings.SiteName,
- SiteLogo: settings.SiteLogo,
- SiteSubtitle: settings.SiteSubtitle,
- APIBaseURL: settings.APIBaseURL,
- ContactInfo: settings.ContactInfo,
- DocURL: settings.DocURL,
- HomeContent: settings.HomeContent,
- CompactHomeEnabled: settings.CompactHomeEnabled,
- HideCcsImportButton: settings.HideCcsImportButton,
- PurchaseSubscriptionEnabled: settings.PurchaseSubscriptionEnabled,
- PurchaseSubscriptionURL: settings.PurchaseSubscriptionURL,
- TableDefaultPageSize: settings.TableDefaultPageSize,
- TablePageSizeOptions: settings.TablePageSizeOptions,
- CustomMenuItems: dto.ParseCustomMenuItems(settings.CustomMenuItems),
- CustomEndpoints: dto.ParseCustomEndpoints(settings.CustomEndpoints),
- DefaultConcurrency: settings.DefaultConcurrency,
- DefaultBalance: settings.DefaultBalance,
- RiskControlEnabled: settings.RiskControlEnabled,
- CyberSessionBlockEnabled: settings.CyberSessionBlockEnabled,
- CyberSessionBlockTTLSeconds: settings.CyberSessionBlockTTLSeconds,
- AffiliateRebateRate: settings.AffiliateRebateRate,
- AffiliateRebateFreezeHours: settings.AffiliateRebateFreezeHours,
- AffiliateRebateDurationDays: settings.AffiliateRebateDurationDays,
- AffiliateRebatePerInviteeCap: settings.AffiliateRebatePerInviteeCap,
- AdminRechargeRebateEnabled: settings.AdminRechargeRebateEnabled,
- DefaultUserRPMLimit: settings.DefaultUserRPMLimit,
- DefaultSubscriptions: defaultSubscriptions,
- EnableModelFallback: settings.EnableModelFallback,
- FallbackModelAnthropic: settings.FallbackModelAnthropic,
- FallbackModelOpenAI: settings.FallbackModelOpenAI,
- FallbackModelGemini: settings.FallbackModelGemini,
- FallbackModelAntigravity: settings.FallbackModelAntigravity,
- EnableIdentityPatch: settings.EnableIdentityPatch,
- IdentityPatchPrompt: settings.IdentityPatchPrompt,
- OpsMonitoringEnabled: opsEnabled && settings.OpsMonitoringEnabled,
- OpsRealtimeMonitoringEnabled: settings.OpsRealtimeMonitoringEnabled,
- OpsQueryModeDefault: settings.OpsQueryModeDefault,
- OpsMetricsIntervalSeconds: settings.OpsMetricsIntervalSeconds,
- MinClaudeCodeVersion: settings.MinClaudeCodeVersion,
- MaxClaudeCodeVersion: settings.MaxClaudeCodeVersion,
- AllowUngroupedKeyScheduling: settings.AllowUngroupedKeyScheduling,
- BackendModeEnabled: settings.BackendModeEnabled,
- OpenAITTFTMode: settings.OpenAITTFTMode,
- EnableFingerprintUnification: settings.EnableFingerprintUnification,
- EnableMetadataPassthrough: settings.EnableMetadataPassthrough,
- EnableCCHSigning: settings.EnableCCHSigning,
- EnableClaudeOAuthSystemPromptInjection: settings.EnableClaudeOAuthSystemPromptInjection,
- ClaudeOAuthSystemPrompt: settings.ClaudeOAuthSystemPrompt,
- ClaudeOAuthSystemPromptBlocks: settings.ClaudeOAuthSystemPromptBlocks,
- EnableAnthropicCacheTTL1hInjection: settings.EnableAnthropicCacheTTL1hInjection,
- RewriteMessageCacheControl: settings.RewriteMessageCacheControl,
- EnableClientDatelineNormalization: settings.EnableClientDatelineNormalization,
- AntigravityUserAgentVersion: settings.AntigravityUserAgentVersion,
- OpenAICodexUserAgent: settings.OpenAICodexUserAgent,
- OpenAICodexClientVersion: settings.OpenAICodexClientVersion,
- OpenAICodexClientVersionSynced: settings.OpenAICodexClientVersionSynced,
- OpenAICodexVersionAutoSyncEnabled: settings.OpenAICodexVersionAutoSyncEnabled,
- MinCodexVersion: settings.MinCodexVersion,
- MaxCodexVersion: settings.MaxCodexVersion,
- CodexCLIOnlyBlacklist: settings.CodexCLIOnlyBlacklist,
- CodexCLIOnlyWhitelist: settings.CodexCLIOnlyWhitelist,
- CodexCLIOnlyAllowAppServerClients: settings.CodexCLIOnlyAllowAppServerClients,
- CodexCLIOnlyEngineFingerprintSignals: settings.CodexCLIOnlyEngineFingerprintSignals,
- WebSearchEmulationEnabled: settings.WebSearchEmulationEnabled,
- PaymentVisibleMethodAlipaySource: settings.PaymentVisibleMethodAlipaySource,
- PaymentVisibleMethodWxpaySource: settings.PaymentVisibleMethodWxpaySource,
- PaymentVisibleMethodAlipayEnabled: settings.PaymentVisibleMethodAlipayEnabled,
- PaymentVisibleMethodWxpayEnabled: settings.PaymentVisibleMethodWxpayEnabled,
- OpenAILowUpstreamRatePriorityEnabled: settings.OpenAILowUpstreamRatePriorityEnabled,
- OpenAIOAuthSchedulingRateMultiplier: settings.OpenAIOAuthSchedulingRateMultiplier,
- OpenAIAdvancedSchedulerEnabled: settings.OpenAIAdvancedSchedulerEnabled,
- OpenAIAdvancedSchedulerStickyWeightedEnabled: settings.OpenAIAdvancedSchedulerStickyWeightedEnabled,
- OpenAIAdvancedSchedulerSubscriptionPriorityEnabled: settings.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled,
- OpenAIAdvancedSchedulerLBTopK: settings.OpenAIAdvancedSchedulerLBTopK,
- OpenAIAdvancedSchedulerWeightPriority: settings.OpenAIAdvancedSchedulerWeightPriority,
- OpenAIAdvancedSchedulerWeightLoad: settings.OpenAIAdvancedSchedulerWeightLoad,
- OpenAIAdvancedSchedulerWeightQueue: settings.OpenAIAdvancedSchedulerWeightQueue,
- OpenAIAdvancedSchedulerWeightErrorRate: settings.OpenAIAdvancedSchedulerWeightErrorRate,
- OpenAIAdvancedSchedulerWeightTTFT: settings.OpenAIAdvancedSchedulerWeightTTFT,
- OpenAIAdvancedSchedulerWeightReset: settings.OpenAIAdvancedSchedulerWeightReset,
- OpenAIAdvancedSchedulerWeightQuotaHeadroom: settings.OpenAIAdvancedSchedulerWeightQuotaHeadroom,
- OpenAIAdvancedSchedulerWeightUpstreamCost: settings.OpenAIAdvancedSchedulerWeightUpstreamCost,
- OpenAIAdvancedSchedulerWeightPreviousResponse: settings.OpenAIAdvancedSchedulerWeightPreviousResponse,
- OpenAIAdvancedSchedulerWeightSessionSticky: settings.OpenAIAdvancedSchedulerWeightSessionSticky,
- OpenAIAdvancedSchedulerEffectiveLBTopK: settings.OpenAIAdvancedSchedulerEffectiveLBTopK,
- OpenAIAdvancedSchedulerEffectiveWeightPriority: settings.OpenAIAdvancedSchedulerEffectiveWeightPriority,
- OpenAIAdvancedSchedulerEffectiveWeightLoad: settings.OpenAIAdvancedSchedulerEffectiveWeightLoad,
- OpenAIAdvancedSchedulerEffectiveWeightQueue: settings.OpenAIAdvancedSchedulerEffectiveWeightQueue,
- OpenAIAdvancedSchedulerEffectiveWeightErrorRate: settings.OpenAIAdvancedSchedulerEffectiveWeightErrorRate,
- OpenAIAdvancedSchedulerEffectiveWeightTTFT: settings.OpenAIAdvancedSchedulerEffectiveWeightTTFT,
- OpenAIAdvancedSchedulerEffectiveWeightReset: settings.OpenAIAdvancedSchedulerEffectiveWeightReset,
- OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom: settings.OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom,
- OpenAIAdvancedSchedulerEffectiveWeightUpstreamCost: settings.OpenAIAdvancedSchedulerEffectiveWeightUpstreamCost,
- OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse: settings.OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse,
- OpenAIAdvancedSchedulerEffectiveWeightSessionSticky: settings.OpenAIAdvancedSchedulerEffectiveWeightSessionSticky,
- BalanceLowNotifyEnabled: settings.BalanceLowNotifyEnabled,
- BalanceLowNotifyThreshold: settings.BalanceLowNotifyThreshold,
- BalanceLowNotifyRechargeURL: settings.BalanceLowNotifyRechargeURL,
- SubscriptionExpiryNotifyEnabled: settings.SubscriptionExpiryNotifyEnabled,
- AccountQuotaNotifyEnabled: settings.AccountQuotaNotifyEnabled,
- AccountQuotaNotifyEmails: dto.NotifyEmailEntriesFromService(settings.AccountQuotaNotifyEmails),
- PaymentEnabled: paymentCfg.Enabled,
- PaymentMinAmount: paymentCfg.MinAmount,
- PaymentMaxAmount: paymentCfg.MaxAmount,
- PaymentDailyLimit: paymentCfg.DailyLimit,
- PaymentOrderTimeoutMin: paymentCfg.OrderTimeoutMin,
- PaymentMaxPendingOrders: paymentCfg.MaxPendingOrders,
- PaymentEnabledTypes: paymentCfg.EnabledTypes,
- PaymentBalanceDisabled: paymentCfg.BalanceDisabled,
- PaymentBalanceRechargeMultiplier: paymentCfg.BalanceRechargeMultiplier,
- PaymentSubscriptionUSDToCNYRate: paymentCfg.SubscriptionUSDToCNYRate,
- PaymentRechargeFeeRate: paymentCfg.RechargeFeeRate,
- PaymentLoadBalanceStrat: paymentCfg.LoadBalanceStrategy,
- PaymentProductNamePrefix: paymentCfg.ProductNamePrefix,
- PaymentProductNameSuffix: paymentCfg.ProductNameSuffix,
- PaymentHelpImageURL: paymentCfg.HelpImageURL,
- PaymentHelpText: paymentCfg.HelpText,
- PaymentCancelRateLimitEnabled: paymentCfg.CancelRateLimitEnabled,
- PaymentCancelRateLimitMax: paymentCfg.CancelRateLimitMax,
- PaymentCancelRateLimitWindow: paymentCfg.CancelRateLimitWindow,
- PaymentCancelRateLimitUnit: paymentCfg.CancelRateLimitUnit,
- PaymentCancelRateLimitMode: paymentCfg.CancelRateLimitMode,
- PaymentAlipayForceQRCode: paymentCfg.AlipayForceQRCode,
- PaymentAlipayMobilePrecreateDeepLink: paymentCfg.AlipayMobilePrecreateDeepLink,
- ChannelMonitorEnabled: settings.ChannelMonitorEnabled,
- ChannelMonitorMode: settings.ChannelMonitorMode,
- ChannelMonitorDefaultIntervalSeconds: settings.ChannelMonitorDefaultIntervalSeconds,
- ChannelMonitorHideThroughput: settings.ChannelMonitorHideThroughput,
- ChannelMonitorShowQuota: settings.ChannelMonitorShowQuota,
- ChannelMonitorHideUserRanking: settings.ChannelMonitorHideUserRanking,
- GrokDefaultTextModel: settings.GrokDefaultTextModel,
- GrokCrossClientModelMapEnabled: settings.GrokCrossClientModelMapEnabled,
- GrokDefaultBaseURLMode: settings.GrokDefaultBaseURLMode,
- AvailableChannelsEnabled: settings.AvailableChannelsEnabled,
- ModelPlazaEnabled: settings.ModelPlazaEnabled,
- ModelPlazaRequireAuth: settings.ModelPlazaRequireAuth,
- PluginManagementEnabled: settings.PluginManagementEnabled,
- ModelPlazaDescription: settings.ModelPlazaDescription,
- AffiliateEnabled: settings.AffiliateEnabled,
- AccountSchedulingThresholds: settings.AccountSchedulingThresholds,
- AllowUserViewErrorRequests: settings.AllowUserViewErrorRequests,
- }
- // OpenAI fast policy (stored under a dedicated setting key)
- if fastPolicy, err := h.settingService.GetOpenAIFastPolicySettings(c.Request.Context()); err != nil {
- slog.Error("openai_fast_policy_settings_get_failed", "error", err)
- } else if fastPolicy != nil {
- payload.OpenAIFastPolicySettings = openaiFastPolicySettingsToDTO(fastPolicy)
- }
- // Default platform quotas(JSON map)
- if platformQuotas, err := h.settingService.GetDefaultPlatformQuotas(c.Request.Context()); err != nil {
- slog.Error("default_platform_quotas_get_failed", "error", err)
- } else {
- payload.DefaultPlatformQuotas = platformQuotas
- }
- response.Success(c, systemSettingsResponseData(payload, authSourceDefaults))
- }
- // openaiFastPolicySettingsToDTO converts service -> dto for OpenAI fast policy.
- func openaiFastPolicySettingsToDTO(s *service.OpenAIFastPolicySettings) *dto.OpenAIFastPolicySettings {
- if s == nil {
- return nil
- }
- rules := make([]dto.OpenAIFastPolicyRule, len(s.Rules))
- for i, r := range s.Rules {
- rules[i] = dto.OpenAIFastPolicyRule(r)
- }
- return &dto.OpenAIFastPolicySettings{Rules: rules}
- }
- // openaiFastPolicySettingsFromDTO converts dto -> service for OpenAI fast policy.
- //
- // 规范化 ServiceTier:在 DTO 进入 service 层之前统一把空字符串归一为
- // service.OpenAIFastTierAny ("all"),避免管理员保存时空串与 "all" 同时
- // 表达"匹配任意 tier"造成数据库取值的二义性。其它非空值原样透传,由
- // service.SetOpenAIFastPolicySettings 负责合法值校验。
- func openaiFastPolicySettingsFromDTO(s *dto.OpenAIFastPolicySettings) *service.OpenAIFastPolicySettings {
- if s == nil {
- return nil
- }
- rules := make([]service.OpenAIFastPolicyRule, len(s.Rules))
- for i, r := range s.Rules {
- rules[i] = service.OpenAIFastPolicyRule(r)
- tier := strings.ToLower(strings.TrimSpace(rules[i].ServiceTier))
- if tier == "" {
- tier = service.OpenAIFastTierAny
- }
- rules[i].ServiceTier = tier
- }
- return &service.OpenAIFastPolicySettings{Rules: rules}
- }
- func loginAgreementDocumentsToDTO(items []service.LoginAgreementDocument) []dto.LoginAgreementDocument {
- result := make([]dto.LoginAgreementDocument, 0, len(items))
- for _, item := range items {
- result = append(result, dto.LoginAgreementDocument{
- ID: item.ID,
- Title: item.Title,
- ContentMD: item.ContentMD,
- })
- }
- return result
- }
- func loginAgreementDocumentsToService(items []dto.LoginAgreementDocument) []service.LoginAgreementDocument {
- result := make([]service.LoginAgreementDocument, 0, len(items))
- for _, item := range items {
- title := strings.TrimSpace(item.Title)
- content := strings.TrimSpace(item.ContentMD)
- if title == "" && content == "" {
- continue
- }
- result = append(result, service.LoginAgreementDocument{
- ID: strings.TrimSpace(item.ID),
- Title: title,
- ContentMD: content,
- })
- }
- return result
- }
- func systemSettingsResponseData(settings dto.SystemSettings, authSourceDefaults *service.AuthSourceDefaultSettings) map[string]any {
- data := make(map[string]any)
- raw, err := json.Marshal(settings)
- if err == nil {
- _ = json.Unmarshal(raw, &data)
- }
- if authSourceDefaults == nil {
- authSourceDefaults = &service.AuthSourceDefaultSettings{}
- }
- data["auth_source_default_email_balance"] = authSourceDefaults.Email.Balance
- data["auth_source_default_email_concurrency"] = authSourceDefaults.Email.Concurrency
- data["auth_source_default_email_subscriptions"] = authSourceDefaults.Email.Subscriptions
- data["auth_source_default_email_grant_on_signup"] = authSourceDefaults.Email.GrantOnSignup
- data["auth_source_default_email_grant_on_first_bind"] = authSourceDefaults.Email.GrantOnFirstBind
- data["auth_source_default_linuxdo_balance"] = authSourceDefaults.LinuxDo.Balance
- data["auth_source_default_linuxdo_concurrency"] = authSourceDefaults.LinuxDo.Concurrency
- data["auth_source_default_linuxdo_subscriptions"] = authSourceDefaults.LinuxDo.Subscriptions
- data["auth_source_default_linuxdo_grant_on_signup"] = authSourceDefaults.LinuxDo.GrantOnSignup
- data["auth_source_default_linuxdo_grant_on_first_bind"] = authSourceDefaults.LinuxDo.GrantOnFirstBind
- data["auth_source_default_dingtalk_balance"] = authSourceDefaults.DingTalk.Balance
- data["auth_source_default_dingtalk_concurrency"] = authSourceDefaults.DingTalk.Concurrency
- data["auth_source_default_dingtalk_subscriptions"] = authSourceDefaults.DingTalk.Subscriptions
- data["auth_source_default_dingtalk_grant_on_signup"] = authSourceDefaults.DingTalk.GrantOnSignup
- data["auth_source_default_dingtalk_grant_on_first_bind"] = authSourceDefaults.DingTalk.GrantOnFirstBind
- data["auth_source_default_oidc_balance"] = authSourceDefaults.OIDC.Balance
- data["auth_source_default_oidc_concurrency"] = authSourceDefaults.OIDC.Concurrency
- data["auth_source_default_oidc_subscriptions"] = authSourceDefaults.OIDC.Subscriptions
- data["auth_source_default_oidc_grant_on_signup"] = authSourceDefaults.OIDC.GrantOnSignup
- data["auth_source_default_oidc_grant_on_first_bind"] = authSourceDefaults.OIDC.GrantOnFirstBind
- data["auth_source_default_wechat_balance"] = authSourceDefaults.WeChat.Balance
- data["auth_source_default_wechat_concurrency"] = authSourceDefaults.WeChat.Concurrency
- data["auth_source_default_wechat_subscriptions"] = authSourceDefaults.WeChat.Subscriptions
- data["auth_source_default_wechat_grant_on_signup"] = authSourceDefaults.WeChat.GrantOnSignup
- data["auth_source_default_wechat_grant_on_first_bind"] = authSourceDefaults.WeChat.GrantOnFirstBind
- data["auth_source_default_github_balance"] = authSourceDefaults.GitHub.Balance
- data["auth_source_default_github_concurrency"] = authSourceDefaults.GitHub.Concurrency
- data["auth_source_default_github_subscriptions"] = authSourceDefaults.GitHub.Subscriptions
- data["auth_source_default_github_grant_on_signup"] = authSourceDefaults.GitHub.GrantOnSignup
- data["auth_source_default_github_grant_on_first_bind"] = authSourceDefaults.GitHub.GrantOnFirstBind
- data["auth_source_default_google_balance"] = authSourceDefaults.Google.Balance
- data["auth_source_default_google_concurrency"] = authSourceDefaults.Google.Concurrency
- data["auth_source_default_google_subscriptions"] = authSourceDefaults.Google.Subscriptions
- data["auth_source_default_google_grant_on_signup"] = authSourceDefaults.Google.GrantOnSignup
- data["auth_source_default_google_grant_on_first_bind"] = authSourceDefaults.Google.GrantOnFirstBind
- data["auth_source_default_email_platform_quotas"] = authSourceDefaults.Email.PlatformQuotas
- data["auth_source_default_linuxdo_platform_quotas"] = authSourceDefaults.LinuxDo.PlatformQuotas
- data["auth_source_default_oidc_platform_quotas"] = authSourceDefaults.OIDC.PlatformQuotas
- data["auth_source_default_wechat_platform_quotas"] = authSourceDefaults.WeChat.PlatformQuotas
- data["auth_source_default_github_platform_quotas"] = authSourceDefaults.GitHub.PlatformQuotas
- data["auth_source_default_google_platform_quotas"] = authSourceDefaults.Google.PlatformQuotas
- data["auth_source_default_dingtalk_platform_quotas"] = authSourceDefaults.DingTalk.PlatformQuotas
- data["force_email_on_third_party_signup"] = authSourceDefaults.ForceEmailOnThirdPartySignup
- return data
- }
|