setting_handler_audit.go 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. package admin
  2. import (
  3. "log/slog"
  4. "github.com/Wei-Shaw/sub2api/internal/handler/dto"
  5. "github.com/Wei-Shaw/sub2api/internal/server/middleware"
  6. "github.com/Wei-Shaw/sub2api/internal/service"
  7. "github.com/gin-gonic/gin"
  8. )
  9. func (h *SettingHandler) auditSettingsUpdate(c *gin.Context, before *service.SystemSettings, after *service.SystemSettings, beforeAuthSourceDefaults *service.AuthSourceDefaultSettings, afterAuthSourceDefaults *service.AuthSourceDefaultSettings, req UpdateSettingsRequest) {
  10. if before == nil || after == nil {
  11. return
  12. }
  13. changed := diffSettings(before, after, beforeAuthSourceDefaults, afterAuthSourceDefaults, req)
  14. if len(changed) == 0 {
  15. return
  16. }
  17. subject, _ := middleware.GetAuthSubjectFromContext(c)
  18. role, _ := middleware.GetUserRoleFromContext(c)
  19. slog.Info("settings updated",
  20. "audit", true,
  21. "user_id", subject.UserID,
  22. "role", role,
  23. "changed", changed,
  24. )
  25. }
  26. func diffSettings(before *service.SystemSettings, after *service.SystemSettings, beforeAuthSourceDefaults *service.AuthSourceDefaultSettings, afterAuthSourceDefaults *service.AuthSourceDefaultSettings, req UpdateSettingsRequest) []string {
  27. changed := make([]string, 0, 20)
  28. if before.RegistrationEnabled != after.RegistrationEnabled {
  29. changed = append(changed, "registration_enabled")
  30. }
  31. if before.EmailVerifyEnabled != after.EmailVerifyEnabled {
  32. changed = append(changed, "email_verify_enabled")
  33. }
  34. if !equalStringSlice(before.RegistrationEmailSuffixWhitelist, after.RegistrationEmailSuffixWhitelist) {
  35. changed = append(changed, "registration_email_suffix_whitelist")
  36. }
  37. if before.RegistrationEmailDomainQuotaEnabled != after.RegistrationEmailDomainQuotaEnabled {
  38. changed = append(changed, "registration_email_domain_quota_enabled")
  39. }
  40. if before.PromoCodeEnabled != after.PromoCodeEnabled {
  41. changed = append(changed, "promo_code_enabled")
  42. }
  43. if before.InvitationCodeEnabled != after.InvitationCodeEnabled {
  44. changed = append(changed, "invitation_code_enabled")
  45. }
  46. if before.PasswordResetEnabled != after.PasswordResetEnabled {
  47. changed = append(changed, "password_reset_enabled")
  48. }
  49. if before.FrontendURL != after.FrontendURL {
  50. changed = append(changed, "frontend_url")
  51. }
  52. if before.TotpEnabled != after.TotpEnabled {
  53. changed = append(changed, "totp_enabled")
  54. }
  55. if before.PasskeyEnabled != after.PasskeyEnabled {
  56. changed = append(changed, "passkey_enabled")
  57. }
  58. if before.SessionBindingEnabled != after.SessionBindingEnabled {
  59. changed = append(changed, "session_binding_enabled")
  60. }
  61. if before.StepUpEnabled != after.StepUpEnabled {
  62. changed = append(changed, "step_up_enabled")
  63. }
  64. if before.LoginAgreementEnabled != after.LoginAgreementEnabled {
  65. changed = append(changed, "login_agreement_enabled")
  66. }
  67. if before.LoginAgreementMode != after.LoginAgreementMode {
  68. changed = append(changed, "login_agreement_mode")
  69. }
  70. if before.LoginAgreementUpdatedAt != after.LoginAgreementUpdatedAt {
  71. changed = append(changed, "login_agreement_updated_at")
  72. }
  73. if !equalLoginAgreementDocuments(before.LoginAgreementDocuments, after.LoginAgreementDocuments) {
  74. changed = append(changed, "login_agreement_documents")
  75. }
  76. if before.SMTPHost != after.SMTPHost {
  77. changed = append(changed, "smtp_host")
  78. }
  79. if before.SMTPPort != after.SMTPPort {
  80. changed = append(changed, "smtp_port")
  81. }
  82. if before.SMTPUsername != after.SMTPUsername {
  83. changed = append(changed, "smtp_username")
  84. }
  85. if req.SMTPPassword != "" {
  86. changed = append(changed, "smtp_password")
  87. }
  88. if before.SMTPFrom != after.SMTPFrom {
  89. changed = append(changed, "smtp_from_email")
  90. }
  91. if before.SMTPFromName != after.SMTPFromName {
  92. changed = append(changed, "smtp_from_name")
  93. }
  94. if before.SMTPUseTLS != after.SMTPUseTLS {
  95. changed = append(changed, "smtp_use_tls")
  96. }
  97. if before.TurnstileEnabled != after.TurnstileEnabled {
  98. changed = append(changed, "turnstile_enabled")
  99. }
  100. if before.TurnstileSiteKey != after.TurnstileSiteKey {
  101. changed = append(changed, "turnstile_site_key")
  102. }
  103. if req.TurnstileSecretKey != "" {
  104. changed = append(changed, "turnstile_secret_key")
  105. }
  106. if before.TencentCaptchaEnabled != after.TencentCaptchaEnabled {
  107. changed = append(changed, "tencent_captcha_enabled")
  108. }
  109. if before.TencentCaptchaAppID != after.TencentCaptchaAppID {
  110. changed = append(changed, "tencent_captcha_app_id")
  111. }
  112. if req.TencentCaptchaAppSecretKey != "" {
  113. changed = append(changed, "tencent_captcha_app_secret_key")
  114. }
  115. if req.TencentCaptchaCloudSecretID != "" {
  116. changed = append(changed, "tencent_captcha_cloud_secret_id")
  117. }
  118. if req.TencentCaptchaCloudSecretKey != "" {
  119. changed = append(changed, "tencent_captcha_cloud_secret_key")
  120. }
  121. if before.TencentCaptchaRegion != after.TencentCaptchaRegion {
  122. changed = append(changed, "tencent_captcha_region")
  123. }
  124. if before.AliyunCaptchaEnabled != after.AliyunCaptchaEnabled {
  125. changed = append(changed, "aliyun_captcha_enabled")
  126. }
  127. if before.AliyunCaptchaAccessKeyID != after.AliyunCaptchaAccessKeyID {
  128. changed = append(changed, "aliyun_captcha_access_key_id")
  129. }
  130. if req.AliyunCaptchaAccessKeySecret != "" {
  131. changed = append(changed, "aliyun_captcha_access_key_secret")
  132. }
  133. if before.AliyunCaptchaSceneID != after.AliyunCaptchaSceneID {
  134. changed = append(changed, "aliyun_captcha_scene_id")
  135. }
  136. if before.AliyunCaptchaPrefix != after.AliyunCaptchaPrefix {
  137. changed = append(changed, "aliyun_captcha_prefix")
  138. }
  139. if before.AliyunCaptchaRegion != after.AliyunCaptchaRegion {
  140. changed = append(changed, "aliyun_captcha_region")
  141. }
  142. if before.APIKeyACLTrustForwardedIP != after.APIKeyACLTrustForwardedIP {
  143. changed = append(changed, "api_key_acl_trust_forwarded_ip")
  144. }
  145. if !equalStringSlice(before.ForwardedClientIPHeaders, after.ForwardedClientIPHeaders) {
  146. changed = append(changed, "forwarded_client_ip_headers")
  147. }
  148. if before.LinuxDoConnectEnabled != after.LinuxDoConnectEnabled {
  149. changed = append(changed, "linuxdo_connect_enabled")
  150. }
  151. if before.LinuxDoConnectClientID != after.LinuxDoConnectClientID {
  152. changed = append(changed, "linuxdo_connect_client_id")
  153. }
  154. if req.LinuxDoConnectClientSecret != "" {
  155. changed = append(changed, "linuxdo_connect_client_secret")
  156. }
  157. if before.LinuxDoConnectRedirectURL != after.LinuxDoConnectRedirectURL {
  158. changed = append(changed, "linuxdo_connect_redirect_url")
  159. }
  160. if before.DingTalkConnectEnabled != after.DingTalkConnectEnabled {
  161. changed = append(changed, "dingtalk_connect_enabled")
  162. }
  163. if before.DingTalkConnectClientID != after.DingTalkConnectClientID {
  164. changed = append(changed, "dingtalk_connect_client_id")
  165. }
  166. if req.DingTalkConnectClientSecret != "" {
  167. changed = append(changed, "dingtalk_connect_client_secret")
  168. }
  169. if before.DingTalkConnectRedirectURL != after.DingTalkConnectRedirectURL {
  170. changed = append(changed, "dingtalk_connect_redirect_url")
  171. }
  172. if before.DingTalkConnectCorpRestrictionPolicy != after.DingTalkConnectCorpRestrictionPolicy {
  173. changed = append(changed, "dingtalk_connect_corp_restriction_policy")
  174. }
  175. if before.DingTalkConnectInternalCorpID != after.DingTalkConnectInternalCorpID {
  176. changed = append(changed, "dingtalk_connect_internal_corp_id")
  177. }
  178. if before.DingTalkConnectBypassRegistration != after.DingTalkConnectBypassRegistration {
  179. changed = append(changed, "dingtalk_connect_bypass_registration")
  180. }
  181. if before.DingTalkConnectSyncCorpEmail != after.DingTalkConnectSyncCorpEmail {
  182. changed = append(changed, "dingtalk_connect_sync_corp_email")
  183. }
  184. if before.DingTalkConnectSyncDisplayName != after.DingTalkConnectSyncDisplayName {
  185. changed = append(changed, "dingtalk_connect_sync_display_name")
  186. }
  187. if before.DingTalkConnectSyncDept != after.DingTalkConnectSyncDept {
  188. changed = append(changed, "dingtalk_connect_sync_dept")
  189. }
  190. if before.DingTalkConnectSyncCorpEmailAttrKey != after.DingTalkConnectSyncCorpEmailAttrKey {
  191. changed = append(changed, "dingtalk_connect_sync_corp_email_attr_key")
  192. }
  193. if before.DingTalkConnectSyncDisplayNameAttrKey != after.DingTalkConnectSyncDisplayNameAttrKey {
  194. changed = append(changed, "dingtalk_connect_sync_display_name_attr_key")
  195. }
  196. if before.DingTalkConnectSyncDeptAttrKey != after.DingTalkConnectSyncDeptAttrKey {
  197. changed = append(changed, "dingtalk_connect_sync_dept_attr_key")
  198. }
  199. if before.WeChatConnectEnabled != after.WeChatConnectEnabled {
  200. changed = append(changed, "wechat_connect_enabled")
  201. }
  202. if before.WeChatConnectAppID != after.WeChatConnectAppID {
  203. changed = append(changed, "wechat_connect_app_id")
  204. }
  205. if req.WeChatConnectAppSecret != "" {
  206. changed = append(changed, "wechat_connect_app_secret")
  207. }
  208. if before.WeChatConnectOpenAppID != after.WeChatConnectOpenAppID {
  209. changed = append(changed, "wechat_connect_open_app_id")
  210. }
  211. if req.WeChatConnectOpenAppSecret != "" {
  212. changed = append(changed, "wechat_connect_open_app_secret")
  213. }
  214. if before.WeChatConnectMPAppID != after.WeChatConnectMPAppID {
  215. changed = append(changed, "wechat_connect_mp_app_id")
  216. }
  217. if req.WeChatConnectMPAppSecret != "" {
  218. changed = append(changed, "wechat_connect_mp_app_secret")
  219. }
  220. if before.WeChatConnectMobileAppID != after.WeChatConnectMobileAppID {
  221. changed = append(changed, "wechat_connect_mobile_app_id")
  222. }
  223. if req.WeChatConnectMobileAppSecret != "" {
  224. changed = append(changed, "wechat_connect_mobile_app_secret")
  225. }
  226. if before.WeChatConnectOpenEnabled != after.WeChatConnectOpenEnabled {
  227. changed = append(changed, "wechat_connect_open_enabled")
  228. }
  229. if before.WeChatConnectMPEnabled != after.WeChatConnectMPEnabled {
  230. changed = append(changed, "wechat_connect_mp_enabled")
  231. }
  232. if before.WeChatConnectMobileEnabled != after.WeChatConnectMobileEnabled {
  233. changed = append(changed, "wechat_connect_mobile_enabled")
  234. }
  235. if before.WeChatConnectMode != after.WeChatConnectMode {
  236. changed = append(changed, "wechat_connect_mode")
  237. }
  238. if before.WeChatConnectScopes != after.WeChatConnectScopes {
  239. changed = append(changed, "wechat_connect_scopes")
  240. }
  241. if before.WeChatConnectRedirectURL != after.WeChatConnectRedirectURL {
  242. changed = append(changed, "wechat_connect_redirect_url")
  243. }
  244. if before.WeChatConnectFrontendRedirectURL != after.WeChatConnectFrontendRedirectURL {
  245. changed = append(changed, "wechat_connect_frontend_redirect_url")
  246. }
  247. if before.OIDCConnectEnabled != after.OIDCConnectEnabled {
  248. changed = append(changed, "oidc_connect_enabled")
  249. }
  250. if before.OIDCConnectProviderName != after.OIDCConnectProviderName {
  251. changed = append(changed, "oidc_connect_provider_name")
  252. }
  253. if before.OIDCConnectClientID != after.OIDCConnectClientID {
  254. changed = append(changed, "oidc_connect_client_id")
  255. }
  256. if req.OIDCConnectClientSecret != "" {
  257. changed = append(changed, "oidc_connect_client_secret")
  258. }
  259. if before.OIDCConnectIssuerURL != after.OIDCConnectIssuerURL {
  260. changed = append(changed, "oidc_connect_issuer_url")
  261. }
  262. if before.OIDCConnectDiscoveryURL != after.OIDCConnectDiscoveryURL {
  263. changed = append(changed, "oidc_connect_discovery_url")
  264. }
  265. if before.OIDCConnectAuthorizeURL != after.OIDCConnectAuthorizeURL {
  266. changed = append(changed, "oidc_connect_authorize_url")
  267. }
  268. if before.OIDCConnectTokenURL != after.OIDCConnectTokenURL {
  269. changed = append(changed, "oidc_connect_token_url")
  270. }
  271. if before.OIDCConnectUserInfoURL != after.OIDCConnectUserInfoURL {
  272. changed = append(changed, "oidc_connect_userinfo_url")
  273. }
  274. if before.OIDCConnectJWKSURL != after.OIDCConnectJWKSURL {
  275. changed = append(changed, "oidc_connect_jwks_url")
  276. }
  277. if before.OIDCConnectScopes != after.OIDCConnectScopes {
  278. changed = append(changed, "oidc_connect_scopes")
  279. }
  280. if before.OIDCConnectRedirectURL != after.OIDCConnectRedirectURL {
  281. changed = append(changed, "oidc_connect_redirect_url")
  282. }
  283. if before.OIDCConnectFrontendRedirectURL != after.OIDCConnectFrontendRedirectURL {
  284. changed = append(changed, "oidc_connect_frontend_redirect_url")
  285. }
  286. if before.OIDCConnectTokenAuthMethod != after.OIDCConnectTokenAuthMethod {
  287. changed = append(changed, "oidc_connect_token_auth_method")
  288. }
  289. if before.OIDCConnectUsePKCE != after.OIDCConnectUsePKCE {
  290. changed = append(changed, "oidc_connect_use_pkce")
  291. }
  292. if before.OIDCConnectValidateIDToken != after.OIDCConnectValidateIDToken {
  293. changed = append(changed, "oidc_connect_validate_id_token")
  294. }
  295. if before.OIDCConnectAllowedSigningAlgs != after.OIDCConnectAllowedSigningAlgs {
  296. changed = append(changed, "oidc_connect_allowed_signing_algs")
  297. }
  298. if before.OIDCConnectClockSkewSeconds != after.OIDCConnectClockSkewSeconds {
  299. changed = append(changed, "oidc_connect_clock_skew_seconds")
  300. }
  301. if before.OIDCConnectRequireEmailVerified != after.OIDCConnectRequireEmailVerified {
  302. changed = append(changed, "oidc_connect_require_email_verified")
  303. }
  304. if before.OIDCConnectUserInfoEmailPath != after.OIDCConnectUserInfoEmailPath {
  305. changed = append(changed, "oidc_connect_userinfo_email_path")
  306. }
  307. if before.OIDCConnectUserInfoIDPath != after.OIDCConnectUserInfoIDPath {
  308. changed = append(changed, "oidc_connect_userinfo_id_path")
  309. }
  310. if before.OIDCConnectUserInfoUsernamePath != after.OIDCConnectUserInfoUsernamePath {
  311. changed = append(changed, "oidc_connect_userinfo_username_path")
  312. }
  313. if before.SiteName != after.SiteName {
  314. changed = append(changed, "site_name")
  315. }
  316. if before.SiteLogo != after.SiteLogo {
  317. changed = append(changed, "site_logo")
  318. }
  319. if before.SiteSubtitle != after.SiteSubtitle {
  320. changed = append(changed, "site_subtitle")
  321. }
  322. if before.APIBaseURL != after.APIBaseURL {
  323. changed = append(changed, "api_base_url")
  324. }
  325. if before.ContactInfo != after.ContactInfo {
  326. changed = append(changed, "contact_info")
  327. }
  328. if before.DocURL != after.DocURL {
  329. changed = append(changed, "doc_url")
  330. }
  331. if before.HomeContent != after.HomeContent {
  332. changed = append(changed, "home_content")
  333. }
  334. if before.CompactHomeEnabled != after.CompactHomeEnabled {
  335. changed = append(changed, "compact_home_enabled")
  336. }
  337. if before.HideCcsImportButton != after.HideCcsImportButton {
  338. changed = append(changed, "hide_ccs_import_button")
  339. }
  340. if before.DefaultConcurrency != after.DefaultConcurrency {
  341. changed = append(changed, "default_concurrency")
  342. }
  343. if before.DefaultBalance != after.DefaultBalance {
  344. changed = append(changed, "default_balance")
  345. }
  346. if before.AffiliateRebateRate != after.AffiliateRebateRate {
  347. changed = append(changed, "affiliate_rebate_rate")
  348. }
  349. if before.AffiliateRebateFreezeHours != after.AffiliateRebateFreezeHours {
  350. changed = append(changed, "affiliate_rebate_freeze_hours")
  351. }
  352. if before.AffiliateRebateDurationDays != after.AffiliateRebateDurationDays {
  353. changed = append(changed, "affiliate_rebate_duration_days")
  354. }
  355. if before.AffiliateRebatePerInviteeCap != after.AffiliateRebatePerInviteeCap {
  356. changed = append(changed, "affiliate_rebate_per_invitee_cap")
  357. }
  358. if before.AdminRechargeRebateEnabled != after.AdminRechargeRebateEnabled {
  359. changed = append(changed, "affiliate_admin_recharge_enabled")
  360. }
  361. if !equalDefaultSubscriptions(before.DefaultSubscriptions, after.DefaultSubscriptions) {
  362. changed = append(changed, "default_subscriptions")
  363. }
  364. if before.EnableModelFallback != after.EnableModelFallback {
  365. changed = append(changed, "enable_model_fallback")
  366. }
  367. if before.FallbackModelAnthropic != after.FallbackModelAnthropic {
  368. changed = append(changed, "fallback_model_anthropic")
  369. }
  370. if before.FallbackModelOpenAI != after.FallbackModelOpenAI {
  371. changed = append(changed, "fallback_model_openai")
  372. }
  373. if before.FallbackModelGemini != after.FallbackModelGemini {
  374. changed = append(changed, "fallback_model_gemini")
  375. }
  376. if before.FallbackModelAntigravity != after.FallbackModelAntigravity {
  377. changed = append(changed, "fallback_model_antigravity")
  378. }
  379. if before.EnableIdentityPatch != after.EnableIdentityPatch {
  380. changed = append(changed, "enable_identity_patch")
  381. }
  382. if before.IdentityPatchPrompt != after.IdentityPatchPrompt {
  383. changed = append(changed, "identity_patch_prompt")
  384. }
  385. if before.OpsMonitoringEnabled != after.OpsMonitoringEnabled {
  386. changed = append(changed, "ops_monitoring_enabled")
  387. }
  388. if before.OpsRealtimeMonitoringEnabled != after.OpsRealtimeMonitoringEnabled {
  389. changed = append(changed, "ops_realtime_monitoring_enabled")
  390. }
  391. if before.OpsQueryModeDefault != after.OpsQueryModeDefault {
  392. changed = append(changed, "ops_query_mode_default")
  393. }
  394. if before.OpsMetricsIntervalSeconds != after.OpsMetricsIntervalSeconds {
  395. changed = append(changed, "ops_metrics_interval_seconds")
  396. }
  397. if before.MinClaudeCodeVersion != after.MinClaudeCodeVersion {
  398. changed = append(changed, "min_claude_code_version")
  399. }
  400. if before.MaxClaudeCodeVersion != after.MaxClaudeCodeVersion {
  401. changed = append(changed, "max_claude_code_version")
  402. }
  403. if before.MinCodexVersion != after.MinCodexVersion {
  404. changed = append(changed, "min_codex_version")
  405. }
  406. if before.MaxCodexVersion != after.MaxCodexVersion {
  407. changed = append(changed, "max_codex_version")
  408. }
  409. if before.CodexCLIOnlyAllowAppServerClients != after.CodexCLIOnlyAllowAppServerClients {
  410. changed = append(changed, "codex_cli_only_allow_app_server_clients")
  411. }
  412. if before.CodexCLIOnlyEngineFingerprintSignals != after.CodexCLIOnlyEngineFingerprintSignals {
  413. changed = append(changed, "codex_cli_only_engine_fingerprint_signals")
  414. }
  415. if before.CodexCLIOnlyBlacklist != after.CodexCLIOnlyBlacklist {
  416. changed = append(changed, "codex_cli_only_blacklist")
  417. }
  418. if before.CodexCLIOnlyWhitelist != after.CodexCLIOnlyWhitelist {
  419. changed = append(changed, "codex_cli_only_whitelist")
  420. }
  421. if before.AllowUngroupedKeyScheduling != after.AllowUngroupedKeyScheduling {
  422. changed = append(changed, "allow_ungrouped_key_scheduling")
  423. }
  424. if before.BackendModeEnabled != after.BackendModeEnabled {
  425. changed = append(changed, "backend_mode_enabled")
  426. }
  427. if before.PurchaseSubscriptionEnabled != after.PurchaseSubscriptionEnabled {
  428. changed = append(changed, "purchase_subscription_enabled")
  429. }
  430. if before.PurchaseSubscriptionURL != after.PurchaseSubscriptionURL {
  431. changed = append(changed, "purchase_subscription_url")
  432. }
  433. if before.TableDefaultPageSize != after.TableDefaultPageSize {
  434. changed = append(changed, "table_default_page_size")
  435. }
  436. if !equalIntSlice(before.TablePageSizeOptions, after.TablePageSizeOptions) {
  437. changed = append(changed, "table_page_size_options")
  438. }
  439. if before.CustomMenuItems != after.CustomMenuItems {
  440. changed = append(changed, "custom_menu_items")
  441. }
  442. if before.CustomEndpoints != after.CustomEndpoints {
  443. changed = append(changed, "custom_endpoints")
  444. }
  445. if before.EnableFingerprintUnification != after.EnableFingerprintUnification {
  446. changed = append(changed, "enable_fingerprint_unification")
  447. }
  448. if before.OpenAITTFTMode != after.OpenAITTFTMode {
  449. changed = append(changed, "openai_ttft_mode")
  450. }
  451. if before.EnableMetadataPassthrough != after.EnableMetadataPassthrough {
  452. changed = append(changed, "enable_metadata_passthrough")
  453. }
  454. if before.EnableCCHSigning != after.EnableCCHSigning {
  455. changed = append(changed, "enable_cch_signing")
  456. }
  457. if before.EnableClaudeOAuthSystemPromptInjection != after.EnableClaudeOAuthSystemPromptInjection {
  458. changed = append(changed, "enable_claude_oauth_system_prompt_injection")
  459. }
  460. if before.ClaudeOAuthSystemPrompt != after.ClaudeOAuthSystemPrompt {
  461. changed = append(changed, "claude_oauth_system_prompt")
  462. }
  463. if before.ClaudeOAuthSystemPromptBlocks != after.ClaudeOAuthSystemPromptBlocks {
  464. changed = append(changed, "claude_oauth_system_prompt_blocks")
  465. }
  466. if before.EnableAnthropicCacheTTL1hInjection != after.EnableAnthropicCacheTTL1hInjection {
  467. changed = append(changed, "enable_anthropic_cache_ttl_1h_injection")
  468. }
  469. if before.RewriteMessageCacheControl != after.RewriteMessageCacheControl {
  470. changed = append(changed, "rewrite_message_cache_control")
  471. }
  472. if before.EnableClientDatelineNormalization != after.EnableClientDatelineNormalization {
  473. changed = append(changed, "enable_client_dateline_normalization")
  474. }
  475. if before.AntigravityUserAgentVersion != after.AntigravityUserAgentVersion {
  476. changed = append(changed, "antigravity_user_agent_version")
  477. }
  478. if before.OpenAICodexUserAgent != after.OpenAICodexUserAgent {
  479. changed = append(changed, "openai_codex_user_agent")
  480. }
  481. if before.OpenAICodexClientVersion != after.OpenAICodexClientVersion {
  482. changed = append(changed, "openai_codex_client_version")
  483. }
  484. if before.OpenAICodexVersionAutoSyncEnabled != after.OpenAICodexVersionAutoSyncEnabled {
  485. changed = append(changed, "openai_codex_version_auto_sync_enabled")
  486. }
  487. if before.PaymentVisibleMethodAlipaySource != after.PaymentVisibleMethodAlipaySource {
  488. changed = append(changed, "payment_visible_method_alipay_source")
  489. }
  490. if before.PaymentVisibleMethodWxpaySource != after.PaymentVisibleMethodWxpaySource {
  491. changed = append(changed, "payment_visible_method_wxpay_source")
  492. }
  493. if before.PaymentVisibleMethodAlipayEnabled != after.PaymentVisibleMethodAlipayEnabled {
  494. changed = append(changed, "payment_visible_method_alipay_enabled")
  495. }
  496. if before.PaymentVisibleMethodWxpayEnabled != after.PaymentVisibleMethodWxpayEnabled {
  497. changed = append(changed, "payment_visible_method_wxpay_enabled")
  498. }
  499. if before.OpenAILowUpstreamRatePriorityEnabled != after.OpenAILowUpstreamRatePriorityEnabled {
  500. changed = append(changed, "openai_low_upstream_rate_priority_enabled")
  501. }
  502. if before.OpenAIOAuthSchedulingRateMultiplier != after.OpenAIOAuthSchedulingRateMultiplier {
  503. changed = append(changed, "openai_oauth_scheduling_rate_multiplier")
  504. }
  505. if before.OpenAIAdvancedSchedulerEnabled != after.OpenAIAdvancedSchedulerEnabled {
  506. changed = append(changed, "openai_advanced_scheduler_enabled")
  507. }
  508. if before.OpenAIAdvancedSchedulerStickyWeightedEnabled != after.OpenAIAdvancedSchedulerStickyWeightedEnabled {
  509. changed = append(changed, "openai_advanced_scheduler_sticky_weighted_enabled")
  510. }
  511. if before.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled != after.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled {
  512. changed = append(changed, "openai_advanced_scheduler_subscription_priority_enabled")
  513. }
  514. if before.OpenAIAdvancedSchedulerLBTopK != after.OpenAIAdvancedSchedulerLBTopK {
  515. changed = append(changed, "openai_advanced_scheduler_lb_top_k")
  516. }
  517. if before.OpenAIAdvancedSchedulerWeightPriority != after.OpenAIAdvancedSchedulerWeightPriority {
  518. changed = append(changed, "openai_advanced_scheduler_weight_priority")
  519. }
  520. if before.OpenAIAdvancedSchedulerWeightLoad != after.OpenAIAdvancedSchedulerWeightLoad {
  521. changed = append(changed, "openai_advanced_scheduler_weight_load")
  522. }
  523. if before.OpenAIAdvancedSchedulerWeightQueue != after.OpenAIAdvancedSchedulerWeightQueue {
  524. changed = append(changed, "openai_advanced_scheduler_weight_queue")
  525. }
  526. if before.OpenAIAdvancedSchedulerWeightErrorRate != after.OpenAIAdvancedSchedulerWeightErrorRate {
  527. changed = append(changed, "openai_advanced_scheduler_weight_error_rate")
  528. }
  529. if before.OpenAIAdvancedSchedulerWeightTTFT != after.OpenAIAdvancedSchedulerWeightTTFT {
  530. changed = append(changed, "openai_advanced_scheduler_weight_ttft")
  531. }
  532. if before.OpenAIAdvancedSchedulerWeightReset != after.OpenAIAdvancedSchedulerWeightReset {
  533. changed = append(changed, "openai_advanced_scheduler_weight_reset")
  534. }
  535. if before.OpenAIAdvancedSchedulerWeightQuotaHeadroom != after.OpenAIAdvancedSchedulerWeightQuotaHeadroom {
  536. changed = append(changed, "openai_advanced_scheduler_weight_quota_headroom")
  537. }
  538. if before.OpenAIAdvancedSchedulerWeightUpstreamCost != after.OpenAIAdvancedSchedulerWeightUpstreamCost {
  539. changed = append(changed, "openai_advanced_scheduler_weight_upstream_cost")
  540. }
  541. if before.OpenAIAdvancedSchedulerWeightPreviousResponse != after.OpenAIAdvancedSchedulerWeightPreviousResponse {
  542. changed = append(changed, "openai_advanced_scheduler_weight_previous_response")
  543. }
  544. if before.OpenAIAdvancedSchedulerWeightSessionSticky != after.OpenAIAdvancedSchedulerWeightSessionSticky {
  545. changed = append(changed, "openai_advanced_scheduler_weight_session_sticky")
  546. }
  547. // 余额、订阅到期与账号限额通知
  548. if before.BalanceLowNotifyEnabled != after.BalanceLowNotifyEnabled {
  549. changed = append(changed, "balance_low_notify_enabled")
  550. }
  551. if before.BalanceLowNotifyThreshold != after.BalanceLowNotifyThreshold {
  552. changed = append(changed, "balance_low_notify_threshold")
  553. }
  554. if before.BalanceLowNotifyRechargeURL != after.BalanceLowNotifyRechargeURL {
  555. changed = append(changed, "balance_low_notify_recharge_url")
  556. }
  557. if before.SubscriptionExpiryNotifyEnabled != after.SubscriptionExpiryNotifyEnabled {
  558. changed = append(changed, "subscription_expiry_notify_enabled")
  559. }
  560. if before.AccountQuotaNotifyEnabled != after.AccountQuotaNotifyEnabled {
  561. changed = append(changed, "account_quota_notify_enabled")
  562. }
  563. if !equalNotifyEmailEntries(before.AccountQuotaNotifyEmails, after.AccountQuotaNotifyEmails) {
  564. changed = append(changed, "account_quota_notify_emails")
  565. }
  566. if before.ChannelMonitorEnabled != after.ChannelMonitorEnabled {
  567. changed = append(changed, "channel_monitor_enabled")
  568. }
  569. if before.ChannelMonitorDefaultIntervalSeconds != after.ChannelMonitorDefaultIntervalSeconds {
  570. changed = append(changed, "channel_monitor_default_interval_seconds")
  571. }
  572. if before.AvailableChannelsEnabled != after.AvailableChannelsEnabled {
  573. changed = append(changed, "available_channels_enabled")
  574. }
  575. if before.ModelPlazaEnabled != after.ModelPlazaEnabled {
  576. changed = append(changed, "model_plaza_enabled")
  577. }
  578. if before.ModelPlazaRequireAuth != after.ModelPlazaRequireAuth {
  579. changed = append(changed, "model_plaza_require_auth")
  580. }
  581. if before.ModelPlazaDescription != after.ModelPlazaDescription {
  582. changed = append(changed, "model_plaza_description")
  583. }
  584. if before.AffiliateEnabled != after.AffiliateEnabled {
  585. changed = append(changed, "affiliate_enabled")
  586. }
  587. if before.RiskControlEnabled != after.RiskControlEnabled {
  588. changed = append(changed, "risk_control_enabled")
  589. }
  590. if before.CyberSessionBlockEnabled != after.CyberSessionBlockEnabled {
  591. changed = append(changed, "cyber_session_block_enabled")
  592. }
  593. if before.CyberSessionBlockTTLSeconds != after.CyberSessionBlockTTLSeconds {
  594. changed = append(changed, "cyber_session_block_ttl_seconds")
  595. }
  596. // Default platform quotas(JSON map,整体比较)
  597. if !equalPlatformQuotaSettings(before.DefaultPlatformQuotas, after.DefaultPlatformQuotas) {
  598. changed = append(changed, service.SettingKeyDefaultPlatformQuotas)
  599. }
  600. if !equalAccountSchedulingThresholds(before.AccountSchedulingThresholds, after.AccountSchedulingThresholds) {
  601. changed = append(changed, service.SettingKeyAccountSchedulingThresholds)
  602. }
  603. changed = appendAuthSourceDefaultChanges(changed, beforeAuthSourceDefaults, afterAuthSourceDefaults)
  604. return changed
  605. }
  606. func appendAuthSourceDefaultChanges(changed []string, before *service.AuthSourceDefaultSettings, after *service.AuthSourceDefaultSettings) []string {
  607. if before == nil {
  608. before = &service.AuthSourceDefaultSettings{}
  609. }
  610. if after == nil {
  611. after = &service.AuthSourceDefaultSettings{}
  612. }
  613. type providerDefaultGrantField struct {
  614. name string
  615. before service.ProviderDefaultGrantSettings
  616. after service.ProviderDefaultGrantSettings
  617. }
  618. fields := []providerDefaultGrantField{
  619. {name: "email", before: before.Email, after: after.Email},
  620. {name: "linuxdo", before: before.LinuxDo, after: after.LinuxDo},
  621. {name: "oidc", before: before.OIDC, after: after.OIDC},
  622. {name: "wechat", before: before.WeChat, after: after.WeChat},
  623. {name: "github", before: before.GitHub, after: after.GitHub},
  624. {name: "google", before: before.Google, after: after.Google},
  625. {name: "dingtalk", before: before.DingTalk, after: after.DingTalk},
  626. }
  627. for _, field := range fields {
  628. if field.before.Balance != field.after.Balance {
  629. changed = append(changed, "auth_source_default_"+field.name+"_balance")
  630. }
  631. if field.before.Concurrency != field.after.Concurrency {
  632. changed = append(changed, "auth_source_default_"+field.name+"_concurrency")
  633. }
  634. if !equalDefaultSubscriptions(field.before.Subscriptions, field.after.Subscriptions) {
  635. changed = append(changed, "auth_source_default_"+field.name+"_subscriptions")
  636. }
  637. if field.before.GrantOnSignup != field.after.GrantOnSignup {
  638. changed = append(changed, "auth_source_default_"+field.name+"_grant_on_signup")
  639. }
  640. if field.before.GrantOnFirstBind != field.after.GrantOnFirstBind {
  641. changed = append(changed, "auth_source_default_"+field.name+"_grant_on_first_bind")
  642. }
  643. // Platform quotas diff:整体替换语义,发单个 JSON key。
  644. if !equalPlatformQuotaSettings(field.before.PlatformQuotas, field.after.PlatformQuotas) {
  645. changed = append(changed, service.SettingKeyAuthSourcePlatformQuotas(field.name))
  646. }
  647. }
  648. if before.ForceEmailOnThirdPartySignup != after.ForceEmailOnThirdPartySignup {
  649. changed = append(changed, "force_email_on_third_party_signup")
  650. }
  651. return changed
  652. }
  653. func normalizeDefaultSubscriptions(input []dto.DefaultSubscriptionSetting) []dto.DefaultSubscriptionSetting {
  654. if len(input) == 0 {
  655. return nil
  656. }
  657. normalized := make([]dto.DefaultSubscriptionSetting, 0, len(input))
  658. for _, item := range input {
  659. if item.GroupID <= 0 || item.ValidityDays <= 0 {
  660. continue
  661. }
  662. if item.ValidityDays > service.MaxValidityDays {
  663. item.ValidityDays = service.MaxValidityDays
  664. }
  665. normalized = append(normalized, item)
  666. }
  667. return normalized
  668. }
  669. func normalizeOptionalDefaultSubscriptions(input *[]dto.DefaultSubscriptionSetting) *[]dto.DefaultSubscriptionSetting {
  670. if input == nil {
  671. return nil
  672. }
  673. normalized := normalizeDefaultSubscriptions(*input)
  674. return &normalized
  675. }
  676. func float64ValueOrDefault(value *float64, fallback float64) float64 {
  677. if value == nil {
  678. return fallback
  679. }
  680. return *value
  681. }
  682. func intValueOrDefault(value *int, fallback int) int {
  683. if value == nil {
  684. return fallback
  685. }
  686. return *value
  687. }
  688. func boolValueOrDefault(value *bool, fallback bool) bool {
  689. if value == nil {
  690. return fallback
  691. }
  692. return *value
  693. }
  694. func defaultSubscriptionsValueOrDefault(input *[]dto.DefaultSubscriptionSetting, fallback []service.DefaultSubscriptionSetting) []service.DefaultSubscriptionSetting {
  695. if input == nil {
  696. return fallback
  697. }
  698. result := make([]service.DefaultSubscriptionSetting, 0, len(*input))
  699. for _, item := range *input {
  700. result = append(result, service.DefaultSubscriptionSetting{
  701. GroupID: item.GroupID,
  702. ValidityDays: item.ValidityDays,
  703. })
  704. }
  705. return result
  706. }
  707. // platformQuotasValueOrDefault 处理 auth-source platform quota 的 nil 语义:
  708. // nil = 请求未包含该字段(保留 fallback),non-nil(含 empty map)= 整体覆盖。
  709. // 注意:JSON null 与字段省略等价——两者均反序列化为 nil map,因此都保留旧值;
  710. // 若要清空某 source 的所有 quota 配置,须显式发空对象 {}。
  711. func platformQuotasValueOrDefault(value, fallback map[string]*service.DefaultPlatformQuotaSetting) map[string]*service.DefaultPlatformQuotaSetting {
  712. if value == nil {
  713. return fallback
  714. }
  715. return value
  716. }
  717. func equalStringSlice(a, b []string) bool {
  718. if len(a) != len(b) {
  719. return false
  720. }
  721. for i := range a {
  722. if a[i] != b[i] {
  723. return false
  724. }
  725. }
  726. return true
  727. }
  728. func equalDefaultSubscriptions(a, b []service.DefaultSubscriptionSetting) bool {
  729. if len(a) != len(b) {
  730. return false
  731. }
  732. for i := range a {
  733. if a[i].GroupID != b[i].GroupID || a[i].ValidityDays != b[i].ValidityDays {
  734. return false
  735. }
  736. }
  737. return true
  738. }
  739. func equalLoginAgreementDocuments(a, b []service.LoginAgreementDocument) bool {
  740. if len(a) != len(b) {
  741. return false
  742. }
  743. for i := range a {
  744. if a[i].ID != b[i].ID || a[i].Title != b[i].Title || a[i].ContentMD != b[i].ContentMD {
  745. return false
  746. }
  747. }
  748. return true
  749. }
  750. func equalIntSlice(a, b []int) bool {
  751. if len(a) != len(b) {
  752. return false
  753. }
  754. for i := range a {
  755. if a[i] != b[i] {
  756. return false
  757. }
  758. }
  759. return true
  760. }
  761. func equalNotifyEmailEntries(a, b []service.NotifyEmailEntry) bool {
  762. if len(a) != len(b) {
  763. return false
  764. }
  765. for i := range a {
  766. if a[i].Email != b[i].Email || a[i].Verified != b[i].Verified || a[i].Disabled != b[i].Disabled {
  767. return false
  768. }
  769. }
  770. return true
  771. }
  772. // equalNullableFloat compares two *float64 values treating nil as a distinct case.
  773. func equalNullableFloat(a, b *float64) bool {
  774. if a == nil && b == nil {
  775. return true
  776. }
  777. if a == nil || b == nil {
  778. return false
  779. }
  780. return *a == *b
  781. }
  782. // slotOf returns the *float64 for the given window from a DefaultPlatformQuotaSetting.
  783. func slotOf(s *service.DefaultPlatformQuotaSetting, win string) *float64 {
  784. if s == nil {
  785. return nil
  786. }
  787. switch win {
  788. case "daily":
  789. return s.DailyLimitUSD
  790. case "weekly":
  791. return s.WeeklyLimitUSD
  792. case "monthly":
  793. return s.MonthlyLimitUSD
  794. }
  795. return nil
  796. }
  797. // equalPlatformQuotaSettings reports whether two platform-quota maps are identical across all allowed slots.
  798. func equalAccountSchedulingThresholds(before, after map[string]int) bool {
  799. for _, platform := range service.AllowedSchedulingThresholdPlatforms {
  800. beforeValue := 100
  801. if before != nil {
  802. if value, ok := before[platform]; ok {
  803. beforeValue = value
  804. }
  805. }
  806. afterValue := 100
  807. if after != nil {
  808. if value, ok := after[platform]; ok {
  809. afterValue = value
  810. }
  811. }
  812. if beforeValue != afterValue {
  813. return false
  814. }
  815. }
  816. return true
  817. }
  818. func equalPlatformQuotaSettings(before, after map[string]*service.DefaultPlatformQuotaSetting) bool {
  819. for _, platform := range service.AllowedQuotaPlatforms {
  820. b := before[platform]
  821. a := after[platform]
  822. if !equalNullableFloat(slotOf(b, "daily"), slotOf(a, "daily")) {
  823. return false
  824. }
  825. if !equalNullableFloat(slotOf(b, "weekly"), slotOf(a, "weekly")) {
  826. return false
  827. }
  828. if !equalNullableFloat(slotOf(b, "monthly"), slotOf(a, "monthly")) {
  829. return false
  830. }
  831. }
  832. return true
  833. }
  834. func stringSetting(value *string, fallback string) string {
  835. if value == nil {
  836. return fallback
  837. }
  838. return *value
  839. }