2025-12-18 13:50:39 +08:00
package admin
import (
2026-01-02 17:40:57 +08:00
"log"
2026-01-09 13:52:27 +08:00
"strings"
2026-01-02 17:40:57 +08:00
"time"
2026-01-09 19:32:06 +08:00
"github.com/Wei-Shaw/sub2api/internal/config"
2025-12-26 15:40:24 +08:00
"github.com/Wei-Shaw/sub2api/internal/handler/dto"
2025-12-24 21:07:21 +08:00
"github.com/Wei-Shaw/sub2api/internal/pkg/response"
2026-01-02 17:40:57 +08:00
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
2025-12-24 21:07:21 +08:00
"github.com/Wei-Shaw/sub2api/internal/service"
2025-12-18 13:50:39 +08:00
"github.com/gin-gonic/gin"
)
// SettingHandler 系统设置处理器
type SettingHandler struct {
2025-12-31 21:05:33 +08:00
settingService * service . SettingService
emailService * service . EmailService
turnstileService * service . TurnstileService
2026-01-11 20:10:08 +08:00
opsService * service . OpsService
2025-12-18 13:50:39 +08:00
}
// NewSettingHandler 创建系统设置处理器
2026-01-11 20:10:08 +08:00
func NewSettingHandler ( settingService * service . SettingService , emailService * service . EmailService , turnstileService * service . TurnstileService , opsService * service . OpsService ) * SettingHandler {
2025-12-18 13:50:39 +08:00
return & SettingHandler {
2025-12-31 21:05:33 +08:00
settingService : settingService ,
emailService : emailService ,
turnstileService : turnstileService ,
2026-01-11 20:10:08 +08:00
opsService : opsService ,
2025-12-18 13:50:39 +08:00
}
}
// GetSettings 获取所有系统设置
// GET /api/v1/admin/settings
func ( h * SettingHandler ) GetSettings ( c * gin . Context ) {
settings , err := h . settingService . GetAllSettings ( c . Request . Context ( ) )
if err != nil {
2025-12-25 20:52:47 +08:00
response . ErrorFrom ( c , err )
2025-12-18 13:50:39 +08:00
return
}
2026-01-11 20:10:08 +08:00
// Check if ops monitoring is enabled (respects config.ops.enabled)
opsEnabled := h . opsService != nil && h . opsService . IsMonitoringEnabled ( c . Request . Context ( ) )
2025-12-26 15:40:24 +08:00
response . Success ( c , dto . SystemSettings {
2026-01-09 13:52:27 +08:00
RegistrationEnabled : settings . RegistrationEnabled ,
EmailVerifyEnabled : settings . EmailVerifyEnabled ,
2026-01-20 15:56:26 +08:00
PromoCodeEnabled : settings . PromoCodeEnabled ,
2026-01-24 22:33:45 +08:00
PasswordResetEnabled : settings . PasswordResetEnabled ,
2026-01-26 08:45:43 +08:00
TotpEnabled : settings . TotpEnabled ,
TotpEncryptionKeyConfigured : h . settingService . IsTotpEncryptionKeyConfigured ( ) ,
2026-01-09 13:52:27 +08:00
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 ,
LinuxDoConnectEnabled : settings . LinuxDoConnectEnabled ,
LinuxDoConnectClientID : settings . LinuxDoConnectClientID ,
LinuxDoConnectClientSecretConfigured : settings . LinuxDoConnectClientSecretConfigured ,
LinuxDoConnectRedirectURL : settings . LinuxDoConnectRedirectURL ,
SiteName : settings . SiteName ,
SiteLogo : settings . SiteLogo ,
SiteSubtitle : settings . SiteSubtitle ,
APIBaseURL : settings . APIBaseURL ,
ContactInfo : settings . ContactInfo ,
DocURL : settings . DocURL ,
2026-01-10 18:37:44 +08:00
HomeContent : settings . HomeContent ,
2026-01-19 19:25:16 +08:00
HideCcsImportButton : settings . HideCcsImportButton ,
2026-01-09 13:52:27 +08:00
DefaultConcurrency : settings . DefaultConcurrency ,
DefaultBalance : settings . DefaultBalance ,
EnableModelFallback : settings . EnableModelFallback ,
FallbackModelAnthropic : settings . FallbackModelAnthropic ,
FallbackModelOpenAI : settings . FallbackModelOpenAI ,
FallbackModelGemini : settings . FallbackModelGemini ,
FallbackModelAntigravity : settings . FallbackModelAntigravity ,
EnableIdentityPatch : settings . EnableIdentityPatch ,
IdentityPatchPrompt : settings . IdentityPatchPrompt ,
2026-01-11 20:10:08 +08:00
OpsMonitoringEnabled : opsEnabled && settings . OpsMonitoringEnabled ,
2026-01-11 11:11:37 +08:00
OpsRealtimeMonitoringEnabled : settings . OpsRealtimeMonitoringEnabled ,
OpsQueryModeDefault : settings . OpsQueryModeDefault ,
OpsMetricsIntervalSeconds : settings . OpsMetricsIntervalSeconds ,
2025-12-26 15:40:24 +08:00
} )
2025-12-18 13:50:39 +08:00
}
// UpdateSettingsRequest 更新设置请求
type UpdateSettingsRequest struct {
// 注册设置
2026-01-24 22:33:45 +08:00
RegistrationEnabled bool ` json:"registration_enabled" `
EmailVerifyEnabled bool ` json:"email_verify_enabled" `
PromoCodeEnabled bool ` json:"promo_code_enabled" `
PasswordResetEnabled bool ` json:"password_reset_enabled" `
2026-01-26 08:45:43 +08:00
TotpEnabled bool ` json:"totp_enabled" ` // TOTP 双因素认证
2025-12-18 13:50:39 +08:00
// 邮件服务设置
2026-01-04 17:02:38 +08:00
SMTPHost string ` json:"smtp_host" `
SMTPPort int ` json:"smtp_port" `
SMTPUsername string ` json:"smtp_username" `
SMTPPassword string ` json:"smtp_password" `
SMTPFrom string ` json:"smtp_from_email" `
SMTPFromName string ` json:"smtp_from_name" `
SMTPUseTLS bool ` json:"smtp_use_tls" `
2025-12-18 13:50:39 +08:00
// Cloudflare Turnstile 设置
TurnstileEnabled bool ` json:"turnstile_enabled" `
TurnstileSiteKey string ` json:"turnstile_site_key" `
TurnstileSecretKey string ` json:"turnstile_secret_key" `
2026-01-12 09:14:32 +08:00
// LinuxDo Connect OAuth 登录
2026-01-09 13:52:27 +08:00
LinuxDoConnectEnabled bool ` json:"linuxdo_connect_enabled" `
LinuxDoConnectClientID string ` json:"linuxdo_connect_client_id" `
LinuxDoConnectClientSecret string ` json:"linuxdo_connect_client_secret" `
LinuxDoConnectRedirectURL string ` json:"linuxdo_connect_redirect_url" `
2025-12-18 13:50:39 +08:00
// OEM设置
2026-01-19 19:25:16 +08:00
SiteName string ` json:"site_name" `
SiteLogo string ` json:"site_logo" `
SiteSubtitle string ` json:"site_subtitle" `
APIBaseURL string ` json:"api_base_url" `
ContactInfo string ` json:"contact_info" `
DocURL string ` json:"doc_url" `
HomeContent string ` json:"home_content" `
HideCcsImportButton bool ` json:"hide_ccs_import_button" `
2025-12-18 13:50:39 +08:00
// 默认配置
DefaultConcurrency int ` json:"default_concurrency" `
DefaultBalance float64 ` json:"default_balance" `
2026-01-03 06:37:08 -08:00
// Model fallback configuration
EnableModelFallback bool ` json:"enable_model_fallback" `
FallbackModelAnthropic string ` json:"fallback_model_anthropic" `
FallbackModelOpenAI string ` json:"fallback_model_openai" `
FallbackModelGemini string ` json:"fallback_model_gemini" `
FallbackModelAntigravity string ` json:"fallback_model_antigravity" `
2026-01-04 22:49:40 +08:00
// Identity patch configuration (Claude -> Gemini)
EnableIdentityPatch bool ` json:"enable_identity_patch" `
IdentityPatchPrompt string ` json:"identity_patch_prompt" `
2026-01-09 20:57:32 +08:00
// Ops monitoring (vNext)
2026-01-10 01:38:47 +08:00
OpsMonitoringEnabled * bool ` json:"ops_monitoring_enabled" `
OpsRealtimeMonitoringEnabled * bool ` json:"ops_realtime_monitoring_enabled" `
2026-01-09 20:57:32 +08:00
OpsQueryModeDefault * string ` json:"ops_query_mode_default" `
2026-01-10 01:38:47 +08:00
OpsMetricsIntervalSeconds * int ` json:"ops_metrics_interval_seconds" `
2025-12-18 13:50:39 +08:00
}
// UpdateSettings 更新系统设置
// PUT /api/v1/admin/settings
func ( h * SettingHandler ) UpdateSettings ( c * gin . Context ) {
var req UpdateSettingsRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
response . BadRequest ( c , "Invalid request: " + err . Error ( ) )
return
}
2026-01-02 17:40:57 +08:00
previousSettings , err := h . settingService . GetAllSettings ( c . Request . Context ( ) )
if err != nil {
response . ErrorFrom ( c , err )
return
}
2025-12-18 13:50:39 +08:00
// 验证参数
if req . DefaultConcurrency < 1 {
req . DefaultConcurrency = 1
}
if req . DefaultBalance < 0 {
req . DefaultBalance = 0
}
2026-01-04 17:02:38 +08:00
if req . SMTPPort <= 0 {
req . SMTPPort = 587
2025-12-18 13:50:39 +08:00
}
2025-12-31 21:05:33 +08:00
// Turnstile 参数验证
if req . TurnstileEnabled {
// 检查必填字段
if req . TurnstileSiteKey == "" {
response . BadRequest ( c , "Turnstile Site Key is required when enabled" )
return
}
2026-01-04 22:52:00 -06:00
// 如果未提供 secret key, 使用已保存的值( 留空保留当前值)
2025-12-31 21:05:33 +08:00
if req . TurnstileSecretKey == "" {
2026-01-04 22:52:00 -06:00
if previousSettings . TurnstileSecretKey == "" {
response . BadRequest ( c , "Turnstile Secret Key is required when enabled" )
return
}
req . TurnstileSecretKey = previousSettings . TurnstileSecretKey
2025-12-31 21:05:33 +08:00
}
// 当 site_key 或 secret_key 任一变化时验证(避免配置错误导致无法登录)
2026-01-04 22:52:00 -06:00
siteKeyChanged := previousSettings . TurnstileSiteKey != req . TurnstileSiteKey
secretKeyChanged := previousSettings . TurnstileSecretKey != req . TurnstileSecretKey
2025-12-31 21:05:33 +08:00
if siteKeyChanged || secretKeyChanged {
if err := h . turnstileService . ValidateSecretKey ( c . Request . Context ( ) , req . TurnstileSecretKey ) ; err != nil {
response . ErrorFrom ( c , err )
return
}
}
}
2026-01-26 08:45:43 +08:00
// TOTP 双因素认证参数验证
// 只有手动配置了加密密钥才允许启用 TOTP 功能
if req . TotpEnabled && ! previousSettings . TotpEnabled {
// 尝试启用 TOTP, 检查加密密钥是否已手动配置
if ! h . settingService . IsTotpEncryptionKeyConfigured ( ) {
response . BadRequest ( c , "Cannot enable TOTP: TOTP_ENCRYPTION_KEY environment variable must be configured first. Generate a key with 'openssl rand -hex 32' and set it in your environment." )
return
}
}
2026-01-09 13:52:27 +08:00
// LinuxDo Connect 参数验证
if req . LinuxDoConnectEnabled {
req . LinuxDoConnectClientID = strings . TrimSpace ( req . LinuxDoConnectClientID )
req . LinuxDoConnectClientSecret = strings . TrimSpace ( req . LinuxDoConnectClientSecret )
req . LinuxDoConnectRedirectURL = strings . TrimSpace ( req . LinuxDoConnectRedirectURL )
if req . LinuxDoConnectClientID == "" {
response . BadRequest ( c , "LinuxDo Client ID is required when enabled" )
return
}
if req . LinuxDoConnectRedirectURL == "" {
response . BadRequest ( c , "LinuxDo Redirect URL is required when enabled" )
return
}
2026-01-09 19:32:06 +08:00
if err := config . ValidateAbsoluteHTTPURL ( req . LinuxDoConnectRedirectURL ) ; err != nil {
2026-01-09 13:52:27 +08:00
response . BadRequest ( c , "LinuxDo Redirect URL must be an absolute http(s) URL" )
return
}
2026-01-09 19:32:06 +08:00
// 如果未提供 client_secret, 则保留现有值( 如有) 。
2026-01-09 13:52:27 +08:00
if req . LinuxDoConnectClientSecret == "" {
if previousSettings . LinuxDoConnectClientSecret == "" {
response . BadRequest ( c , "LinuxDo Client Secret is required when enabled" )
return
}
req . LinuxDoConnectClientSecret = previousSettings . LinuxDoConnectClientSecret
}
}
2026-01-10 01:38:47 +08:00
// Ops metrics collector interval validation (seconds).
if req . OpsMetricsIntervalSeconds != nil {
v := * req . OpsMetricsIntervalSeconds
if v < 60 {
v = 60
}
if v > 3600 {
v = 3600
}
req . OpsMetricsIntervalSeconds = & v
}
2025-12-26 15:40:24 +08:00
settings := & service . SystemSettings {
2026-01-09 13:52:27 +08:00
RegistrationEnabled : req . RegistrationEnabled ,
EmailVerifyEnabled : req . EmailVerifyEnabled ,
2026-01-20 15:56:26 +08:00
PromoCodeEnabled : req . PromoCodeEnabled ,
2026-01-24 22:33:45 +08:00
PasswordResetEnabled : req . PasswordResetEnabled ,
2026-01-26 08:45:43 +08:00
TotpEnabled : req . TotpEnabled ,
2026-01-09 13:52:27 +08:00
SMTPHost : req . SMTPHost ,
SMTPPort : req . SMTPPort ,
SMTPUsername : req . SMTPUsername ,
SMTPPassword : req . SMTPPassword ,
SMTPFrom : req . SMTPFrom ,
SMTPFromName : req . SMTPFromName ,
SMTPUseTLS : req . SMTPUseTLS ,
TurnstileEnabled : req . TurnstileEnabled ,
TurnstileSiteKey : req . TurnstileSiteKey ,
TurnstileSecretKey : req . TurnstileSecretKey ,
LinuxDoConnectEnabled : req . LinuxDoConnectEnabled ,
LinuxDoConnectClientID : req . LinuxDoConnectClientID ,
LinuxDoConnectClientSecret : req . LinuxDoConnectClientSecret ,
LinuxDoConnectRedirectURL : req . LinuxDoConnectRedirectURL ,
SiteName : req . SiteName ,
SiteLogo : req . SiteLogo ,
SiteSubtitle : req . SiteSubtitle ,
APIBaseURL : req . APIBaseURL ,
ContactInfo : req . ContactInfo ,
DocURL : req . DocURL ,
2026-01-10 18:37:44 +08:00
HomeContent : req . HomeContent ,
2026-01-19 19:25:16 +08:00
HideCcsImportButton : req . HideCcsImportButton ,
2026-01-09 13:52:27 +08:00
DefaultConcurrency : req . DefaultConcurrency ,
DefaultBalance : req . DefaultBalance ,
EnableModelFallback : req . EnableModelFallback ,
FallbackModelAnthropic : req . FallbackModelAnthropic ,
FallbackModelOpenAI : req . FallbackModelOpenAI ,
FallbackModelGemini : req . FallbackModelGemini ,
FallbackModelAntigravity : req . FallbackModelAntigravity ,
EnableIdentityPatch : req . EnableIdentityPatch ,
IdentityPatchPrompt : req . IdentityPatchPrompt ,
2026-01-09 20:57:32 +08:00
OpsMonitoringEnabled : func ( ) bool {
if req . OpsMonitoringEnabled != nil {
return * req . OpsMonitoringEnabled
}
return previousSettings . OpsMonitoringEnabled
} ( ) ,
OpsRealtimeMonitoringEnabled : func ( ) bool {
if req . OpsRealtimeMonitoringEnabled != nil {
return * req . OpsRealtimeMonitoringEnabled
}
return previousSettings . OpsRealtimeMonitoringEnabled
} ( ) ,
OpsQueryModeDefault : func ( ) string {
if req . OpsQueryModeDefault != nil {
return * req . OpsQueryModeDefault
}
return previousSettings . OpsQueryModeDefault
} ( ) ,
2026-01-10 01:38:47 +08:00
OpsMetricsIntervalSeconds : func ( ) int {
if req . OpsMetricsIntervalSeconds != nil {
return * req . OpsMetricsIntervalSeconds
}
return previousSettings . OpsMetricsIntervalSeconds
} ( ) ,
2025-12-18 13:50:39 +08:00
}
if err := h . settingService . UpdateSettings ( c . Request . Context ( ) , settings ) ; err != nil {
2025-12-25 20:52:47 +08:00
response . ErrorFrom ( c , err )
2025-12-18 13:50:39 +08:00
return
}
2026-01-02 17:40:57 +08:00
h . auditSettingsUpdate ( c , previousSettings , settings , req )
2025-12-18 13:50:39 +08:00
// 重新获取设置返回
updatedSettings , err := h . settingService . GetAllSettings ( c . Request . Context ( ) )
if err != nil {
2025-12-25 20:52:47 +08:00
response . ErrorFrom ( c , err )
2025-12-18 13:50:39 +08:00
return
}
2025-12-26 15:40:24 +08:00
response . Success ( c , dto . SystemSettings {
2026-01-09 13:52:27 +08:00
RegistrationEnabled : updatedSettings . RegistrationEnabled ,
EmailVerifyEnabled : updatedSettings . EmailVerifyEnabled ,
2026-01-20 15:56:26 +08:00
PromoCodeEnabled : updatedSettings . PromoCodeEnabled ,
2026-01-24 22:33:45 +08:00
PasswordResetEnabled : updatedSettings . PasswordResetEnabled ,
2026-01-26 08:45:43 +08:00
TotpEnabled : updatedSettings . TotpEnabled ,
TotpEncryptionKeyConfigured : h . settingService . IsTotpEncryptionKeyConfigured ( ) ,
2026-01-09 13:52:27 +08:00
SMTPHost : updatedSettings . SMTPHost ,
SMTPPort : updatedSettings . SMTPPort ,
SMTPUsername : updatedSettings . SMTPUsername ,
SMTPPasswordConfigured : updatedSettings . SMTPPasswordConfigured ,
SMTPFrom : updatedSettings . SMTPFrom ,
SMTPFromName : updatedSettings . SMTPFromName ,
SMTPUseTLS : updatedSettings . SMTPUseTLS ,
TurnstileEnabled : updatedSettings . TurnstileEnabled ,
TurnstileSiteKey : updatedSettings . TurnstileSiteKey ,
TurnstileSecretKeyConfigured : updatedSettings . TurnstileSecretKeyConfigured ,
LinuxDoConnectEnabled : updatedSettings . LinuxDoConnectEnabled ,
LinuxDoConnectClientID : updatedSettings . LinuxDoConnectClientID ,
LinuxDoConnectClientSecretConfigured : updatedSettings . LinuxDoConnectClientSecretConfigured ,
LinuxDoConnectRedirectURL : updatedSettings . LinuxDoConnectRedirectURL ,
SiteName : updatedSettings . SiteName ,
SiteLogo : updatedSettings . SiteLogo ,
SiteSubtitle : updatedSettings . SiteSubtitle ,
APIBaseURL : updatedSettings . APIBaseURL ,
ContactInfo : updatedSettings . ContactInfo ,
DocURL : updatedSettings . DocURL ,
2026-01-10 18:37:44 +08:00
HomeContent : updatedSettings . HomeContent ,
2026-01-19 19:25:16 +08:00
HideCcsImportButton : updatedSettings . HideCcsImportButton ,
2026-01-09 13:52:27 +08:00
DefaultConcurrency : updatedSettings . DefaultConcurrency ,
DefaultBalance : updatedSettings . DefaultBalance ,
EnableModelFallback : updatedSettings . EnableModelFallback ,
FallbackModelAnthropic : updatedSettings . FallbackModelAnthropic ,
FallbackModelOpenAI : updatedSettings . FallbackModelOpenAI ,
FallbackModelGemini : updatedSettings . FallbackModelGemini ,
FallbackModelAntigravity : updatedSettings . FallbackModelAntigravity ,
EnableIdentityPatch : updatedSettings . EnableIdentityPatch ,
IdentityPatchPrompt : updatedSettings . IdentityPatchPrompt ,
2026-01-11 11:11:37 +08:00
OpsMonitoringEnabled : updatedSettings . OpsMonitoringEnabled ,
OpsRealtimeMonitoringEnabled : updatedSettings . OpsRealtimeMonitoringEnabled ,
OpsQueryModeDefault : updatedSettings . OpsQueryModeDefault ,
OpsMetricsIntervalSeconds : updatedSettings . OpsMetricsIntervalSeconds ,
2025-12-26 15:40:24 +08:00
} )
2025-12-18 13:50:39 +08:00
}
2026-01-02 17:40:57 +08:00
func ( h * SettingHandler ) auditSettingsUpdate ( c * gin . Context , before * service . SystemSettings , after * service . SystemSettings , req UpdateSettingsRequest ) {
if before == nil || after == nil {
return
}
changed := diffSettings ( before , after , req )
if len ( changed ) == 0 {
return
}
subject , _ := middleware . GetAuthSubjectFromContext ( c )
role , _ := middleware . GetUserRoleFromContext ( c )
log . Printf ( "AUDIT: settings updated at=%s user_id=%d role=%s changed=%v" ,
time . Now ( ) . UTC ( ) . Format ( time . RFC3339 ) ,
subject . UserID ,
role ,
changed ,
)
}
func diffSettings ( before * service . SystemSettings , after * service . SystemSettings , req UpdateSettingsRequest ) [ ] string {
2026-01-04 21:06:12 +08:00
changed := make ( [ ] string , 0 , 20 )
2026-01-02 17:40:57 +08:00
if before . RegistrationEnabled != after . RegistrationEnabled {
changed = append ( changed , "registration_enabled" )
}
if before . EmailVerifyEnabled != after . EmailVerifyEnabled {
changed = append ( changed , "email_verify_enabled" )
}
2026-01-24 22:33:45 +08:00
if before . PasswordResetEnabled != after . PasswordResetEnabled {
changed = append ( changed , "password_reset_enabled" )
}
2026-01-26 08:45:43 +08:00
if before . TotpEnabled != after . TotpEnabled {
changed = append ( changed , "totp_enabled" )
}
2026-01-04 21:06:12 +08:00
if before . SMTPHost != after . SMTPHost {
2026-01-02 17:40:57 +08:00
changed = append ( changed , "smtp_host" )
}
2026-01-04 21:06:12 +08:00
if before . SMTPPort != after . SMTPPort {
2026-01-02 17:40:57 +08:00
changed = append ( changed , "smtp_port" )
}
2026-01-04 21:06:12 +08:00
if before . SMTPUsername != after . SMTPUsername {
2026-01-02 17:40:57 +08:00
changed = append ( changed , "smtp_username" )
}
2026-01-04 21:06:12 +08:00
if req . SMTPPassword != "" {
2026-01-02 17:40:57 +08:00
changed = append ( changed , "smtp_password" )
}
2026-01-04 21:06:12 +08:00
if before . SMTPFrom != after . SMTPFrom {
2026-01-02 17:40:57 +08:00
changed = append ( changed , "smtp_from_email" )
}
2026-01-04 21:06:12 +08:00
if before . SMTPFromName != after . SMTPFromName {
2026-01-02 17:40:57 +08:00
changed = append ( changed , "smtp_from_name" )
}
2026-01-04 21:06:12 +08:00
if before . SMTPUseTLS != after . SMTPUseTLS {
2026-01-02 17:40:57 +08:00
changed = append ( changed , "smtp_use_tls" )
}
if before . TurnstileEnabled != after . TurnstileEnabled {
changed = append ( changed , "turnstile_enabled" )
}
if before . TurnstileSiteKey != after . TurnstileSiteKey {
changed = append ( changed , "turnstile_site_key" )
}
if req . TurnstileSecretKey != "" {
changed = append ( changed , "turnstile_secret_key" )
}
2026-01-09 13:52:27 +08:00
if before . LinuxDoConnectEnabled != after . LinuxDoConnectEnabled {
changed = append ( changed , "linuxdo_connect_enabled" )
}
if before . LinuxDoConnectClientID != after . LinuxDoConnectClientID {
changed = append ( changed , "linuxdo_connect_client_id" )
}
if req . LinuxDoConnectClientSecret != "" {
changed = append ( changed , "linuxdo_connect_client_secret" )
}
if before . LinuxDoConnectRedirectURL != after . LinuxDoConnectRedirectURL {
changed = append ( changed , "linuxdo_connect_redirect_url" )
}
2026-01-02 17:40:57 +08:00
if before . SiteName != after . SiteName {
changed = append ( changed , "site_name" )
}
if before . SiteLogo != after . SiteLogo {
changed = append ( changed , "site_logo" )
}
if before . SiteSubtitle != after . SiteSubtitle {
changed = append ( changed , "site_subtitle" )
}
2026-01-04 21:06:12 +08:00
if before . APIBaseURL != after . APIBaseURL {
2026-01-02 17:40:57 +08:00
changed = append ( changed , "api_base_url" )
}
if before . ContactInfo != after . ContactInfo {
changed = append ( changed , "contact_info" )
}
2026-01-04 21:06:12 +08:00
if before . DocURL != after . DocURL {
2026-01-02 17:40:57 +08:00
changed = append ( changed , "doc_url" )
}
2026-01-10 18:37:44 +08:00
if before . HomeContent != after . HomeContent {
changed = append ( changed , "home_content" )
}
2026-01-19 19:25:16 +08:00
if before . HideCcsImportButton != after . HideCcsImportButton {
changed = append ( changed , "hide_ccs_import_button" )
}
2026-01-02 17:40:57 +08:00
if before . DefaultConcurrency != after . DefaultConcurrency {
changed = append ( changed , "default_concurrency" )
}
if before . DefaultBalance != after . DefaultBalance {
changed = append ( changed , "default_balance" )
}
2026-01-04 21:06:12 +08:00
if before . EnableModelFallback != after . EnableModelFallback {
changed = append ( changed , "enable_model_fallback" )
}
if before . FallbackModelAnthropic != after . FallbackModelAnthropic {
changed = append ( changed , "fallback_model_anthropic" )
}
if before . FallbackModelOpenAI != after . FallbackModelOpenAI {
changed = append ( changed , "fallback_model_openai" )
}
if before . FallbackModelGemini != after . FallbackModelGemini {
changed = append ( changed , "fallback_model_gemini" )
}
if before . FallbackModelAntigravity != after . FallbackModelAntigravity {
changed = append ( changed , "fallback_model_antigravity" )
}
2026-01-09 13:52:27 +08:00
if before . EnableIdentityPatch != after . EnableIdentityPatch {
changed = append ( changed , "enable_identity_patch" )
}
if before . IdentityPatchPrompt != after . IdentityPatchPrompt {
changed = append ( changed , "identity_patch_prompt" )
}
2026-01-09 20:57:32 +08:00
if before . OpsMonitoringEnabled != after . OpsMonitoringEnabled {
changed = append ( changed , "ops_monitoring_enabled" )
}
if before . OpsRealtimeMonitoringEnabled != after . OpsRealtimeMonitoringEnabled {
changed = append ( changed , "ops_realtime_monitoring_enabled" )
}
if before . OpsQueryModeDefault != after . OpsQueryModeDefault {
changed = append ( changed , "ops_query_mode_default" )
}
2026-01-10 01:38:47 +08:00
if before . OpsMetricsIntervalSeconds != after . OpsMetricsIntervalSeconds {
changed = append ( changed , "ops_metrics_interval_seconds" )
}
2026-01-02 17:40:57 +08:00
return changed
}
2026-01-04 17:02:38 +08:00
// TestSMTPRequest 测试SMTP连接请求
type TestSMTPRequest struct {
SMTPHost string ` json:"smtp_host" binding:"required" `
SMTPPort int ` json:"smtp_port" `
SMTPUsername string ` json:"smtp_username" `
SMTPPassword string ` json:"smtp_password" `
SMTPUseTLS bool ` json:"smtp_use_tls" `
2025-12-18 13:50:39 +08:00
}
2026-01-04 19:27:53 +08:00
// TestSMTPConnection 测试SMTP连接
2025-12-18 13:50:39 +08:00
// POST /api/v1/admin/settings/test-smtp
2026-01-04 19:27:53 +08:00
func ( h * SettingHandler ) TestSMTPConnection ( c * gin . Context ) {
2026-01-04 17:02:38 +08:00
var req TestSMTPRequest
2025-12-18 13:50:39 +08:00
if err := c . ShouldBindJSON ( & req ) ; err != nil {
response . BadRequest ( c , "Invalid request: " + err . Error ( ) )
return
}
2026-01-04 17:02:38 +08:00
if req . SMTPPort <= 0 {
req . SMTPPort = 587
2025-12-18 13:50:39 +08:00
}
// 如果未提供密码,从数据库获取已保存的密码
2026-01-04 17:02:38 +08:00
password := req . SMTPPassword
2025-12-18 13:50:39 +08:00
if password == "" {
2026-01-04 19:27:53 +08:00
savedConfig , err := h . emailService . GetSMTPConfig ( c . Request . Context ( ) )
2025-12-18 13:50:39 +08:00
if err == nil && savedConfig != nil {
password = savedConfig . Password
}
}
2026-01-04 19:27:53 +08:00
config := & service . SMTPConfig {
2026-01-04 17:02:38 +08:00
Host : req . SMTPHost ,
Port : req . SMTPPort ,
Username : req . SMTPUsername ,
2025-12-18 13:50:39 +08:00
Password : password ,
2026-01-04 17:02:38 +08:00
UseTLS : req . SMTPUseTLS ,
2025-12-18 13:50:39 +08:00
}
2026-01-04 19:27:53 +08:00
err := h . emailService . TestSMTPConnectionWithConfig ( config )
2025-12-18 13:50:39 +08:00
if err != nil {
2025-12-25 20:52:47 +08:00
response . ErrorFrom ( c , err )
2025-12-18 13:50:39 +08:00
return
}
response . Success ( c , gin . H { "message" : "SMTP connection successful" } )
}
// SendTestEmailRequest 发送测试邮件请求
type SendTestEmailRequest struct {
Email string ` json:"email" binding:"required,email" `
2026-01-04 17:02:38 +08:00
SMTPHost string ` json:"smtp_host" binding:"required" `
SMTPPort int ` json:"smtp_port" `
SMTPUsername string ` json:"smtp_username" `
SMTPPassword string ` json:"smtp_password" `
SMTPFrom string ` json:"smtp_from_email" `
SMTPFromName string ` json:"smtp_from_name" `
SMTPUseTLS bool ` json:"smtp_use_tls" `
2025-12-18 13:50:39 +08:00
}
// SendTestEmail 发送测试邮件
// POST /api/v1/admin/settings/send-test-email
func ( h * SettingHandler ) SendTestEmail ( c * gin . Context ) {
var req SendTestEmailRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
response . BadRequest ( c , "Invalid request: " + err . Error ( ) )
return
}
2026-01-04 17:02:38 +08:00
if req . SMTPPort <= 0 {
req . SMTPPort = 587
2025-12-18 13:50:39 +08:00
}
// 如果未提供密码,从数据库获取已保存的密码
2026-01-04 17:02:38 +08:00
password := req . SMTPPassword
2025-12-18 13:50:39 +08:00
if password == "" {
2026-01-04 19:27:53 +08:00
savedConfig , err := h . emailService . GetSMTPConfig ( c . Request . Context ( ) )
2025-12-18 13:50:39 +08:00
if err == nil && savedConfig != nil {
password = savedConfig . Password
}
}
2026-01-04 19:27:53 +08:00
config := & service . SMTPConfig {
2026-01-04 17:02:38 +08:00
Host : req . SMTPHost ,
Port : req . SMTPPort ,
Username : req . SMTPUsername ,
2025-12-18 13:50:39 +08:00
Password : password ,
2026-01-04 17:02:38 +08:00
From : req . SMTPFrom ,
FromName : req . SMTPFromName ,
UseTLS : req . SMTPUseTLS ,
2025-12-18 13:50:39 +08:00
}
siteName := h . settingService . GetSiteName ( c . Request . Context ( ) )
subject := "[" + siteName + "] Test Email"
body := `
< ! DOCTYPE html >
< html >
< head >
< meta charset = "UTF-8" >
< style >
body { font - family : - apple - system , BlinkMacSystemFont , ' Segoe UI ' , Roboto , sans - serif ; background - color : # f5f5f5 ; margin : 0 ; padding : 20 px ; }
. container { max - width : 600 px ; margin : 0 auto ; background - color : # ffffff ; border - radius : 8 px ; overflow : hidden ; box - shadow : 0 2 px 8 px rgba ( 0 , 0 , 0 , 0.1 ) ; }
. header { background : linear - gradient ( 135 deg , # 667 eea 0 % , # 764 ba2 100 % ) ; color : white ; padding : 30 px ; text - align : center ; }
. content { padding : 40 px 30 px ; text - align : center ; }
. success { color : # 10 b981 ; font - size : 48 px ; margin - bottom : 20 px ; }
. footer { background - color : # f8f9fa ; padding : 20 px ; text - align : center ; color : # 999 ; font - size : 12 px ; }
< / style >
< / head >
< body >
< div class = "container" >
< div class = "header" >
< h1 > ` + siteName + ` < / h1 >
< / div >
< div class = "content" >
< div class = "success" > ✓ < / div >
< h2 > Email Configuration Successful ! < / h2 >
< p > This is a test email to verify your SMTP settings are working correctly . < / p >
< / div >
< div class = "footer" >
< p > This is an automated test message . < / p >
< / div >
< / div >
< / body >
< / html >
`
if err := h . emailService . SendEmailWithConfig ( config , req . Email , subject , body ) ; err != nil {
2025-12-25 20:52:47 +08:00
response . ErrorFrom ( c , err )
2025-12-18 13:50:39 +08:00
return
}
response . Success ( c , gin . H { "message" : "Test email sent successfully" } )
}
2025-12-20 15:11:43 +08:00
2026-01-04 19:27:53 +08:00
// GetAdminAPIKey 获取管理员 API Key 状态
2025-12-20 15:11:43 +08:00
// GET /api/v1/admin/settings/admin-api-key
2026-01-04 19:27:53 +08:00
func ( h * SettingHandler ) GetAdminAPIKey ( c * gin . Context ) {
maskedKey , exists , err := h . settingService . GetAdminAPIKeyStatus ( c . Request . Context ( ) )
2025-12-20 15:11:43 +08:00
if err != nil {
2025-12-25 20:52:47 +08:00
response . ErrorFrom ( c , err )
2025-12-20 15:11:43 +08:00
return
}
response . Success ( c , gin . H {
"exists" : exists ,
"masked_key" : maskedKey ,
} )
}
2026-01-04 19:27:53 +08:00
// RegenerateAdminAPIKey 生成/重新生成管理员 API Key
2025-12-20 15:11:43 +08:00
// POST /api/v1/admin/settings/admin-api-key/regenerate
2026-01-04 19:27:53 +08:00
func ( h * SettingHandler ) RegenerateAdminAPIKey ( c * gin . Context ) {
key , err := h . settingService . GenerateAdminAPIKey ( c . Request . Context ( ) )
2025-12-20 15:11:43 +08:00
if err != nil {
2025-12-25 20:52:47 +08:00
response . ErrorFrom ( c , err )
2025-12-20 15:11:43 +08:00
return
}
response . Success ( c , gin . H {
"key" : key , // 完整 key 只在生成时返回一次
} )
}
2026-01-04 19:27:53 +08:00
// DeleteAdminAPIKey 删除管理员 API Key
2025-12-20 15:11:43 +08:00
// DELETE /api/v1/admin/settings/admin-api-key
2026-01-04 19:27:53 +08:00
func ( h * SettingHandler ) DeleteAdminAPIKey ( c * gin . Context ) {
if err := h . settingService . DeleteAdminAPIKey ( c . Request . Context ( ) ) ; err != nil {
2025-12-25 20:52:47 +08:00
response . ErrorFrom ( c , err )
2025-12-20 15:11:43 +08:00
return
}
response . Success ( c , gin . H { "message" : "Admin API key deleted" } )
}
2026-01-11 21:54:52 -08:00
// GetStreamTimeoutSettings 获取流超时处理配置
// GET /api/v1/admin/settings/stream-timeout
func ( h * SettingHandler ) GetStreamTimeoutSettings ( c * gin . Context ) {
settings , err := h . settingService . GetStreamTimeoutSettings ( c . Request . Context ( ) )
if err != nil {
response . ErrorFrom ( c , err )
return
}
response . Success ( c , dto . StreamTimeoutSettings {
Enabled : settings . Enabled ,
Action : settings . Action ,
TempUnschedMinutes : settings . TempUnschedMinutes ,
ThresholdCount : settings . ThresholdCount ,
ThresholdWindowMinutes : settings . ThresholdWindowMinutes ,
} )
}
// UpdateStreamTimeoutSettingsRequest 更新流超时配置请求
type UpdateStreamTimeoutSettingsRequest struct {
Enabled bool ` json:"enabled" `
Action string ` json:"action" `
TempUnschedMinutes int ` json:"temp_unsched_minutes" `
ThresholdCount int ` json:"threshold_count" `
ThresholdWindowMinutes int ` json:"threshold_window_minutes" `
}
// UpdateStreamTimeoutSettings 更新流超时处理配置
// PUT /api/v1/admin/settings/stream-timeout
func ( h * SettingHandler ) UpdateStreamTimeoutSettings ( c * gin . Context ) {
var req UpdateStreamTimeoutSettingsRequest
if err := c . ShouldBindJSON ( & req ) ; err != nil {
response . BadRequest ( c , "Invalid request: " + err . Error ( ) )
return
}
settings := & service . StreamTimeoutSettings {
Enabled : req . Enabled ,
Action : req . Action ,
TempUnschedMinutes : req . TempUnschedMinutes ,
ThresholdCount : req . ThresholdCount ,
ThresholdWindowMinutes : req . ThresholdWindowMinutes ,
}
if err := h . settingService . SetStreamTimeoutSettings ( c . Request . Context ( ) , settings ) ; err != nil {
response . BadRequest ( c , err . Error ( ) )
return
}
// 重新获取设置返回
updatedSettings , err := h . settingService . GetStreamTimeoutSettings ( c . Request . Context ( ) )
if err != nil {
response . ErrorFrom ( c , err )
return
}
response . Success ( c , dto . StreamTimeoutSettings {
Enabled : updatedSettings . Enabled ,
Action : updatedSettings . Action ,
TempUnschedMinutes : updatedSettings . TempUnschedMinutes ,
ThresholdCount : updatedSettings . ThresholdCount ,
ThresholdWindowMinutes : updatedSettings . ThresholdWindowMinutes ,
} )
}