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: 20px; }
.container { max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
.header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 30px; text-align: center; }
.content { padding: 40px 30px; text-align: center; }
.success { color: #10b981; font-size: 48px; margin-bottom: 20px; }
.footer { background-color: #f8f9fa; padding: 20px; text-align: center; color: #999; font-size: 12px; }
</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 ,
})
}