package scheduler import ( "fmt" "log" "time" "verification-platform-backend/internal/database" "verification-platform-backend/internal/model" ) type CleanupConfig struct { EnableAutoCleanup bool CleanupIntervalHours int CaptchaRetentionDays int VerifyCodeRetentionDays int ApiUsageRetentionDays int WebhookLogRetentionDays int DeviceSessionRetentionDays int } var defaultConfig = CleanupConfig{ EnableAutoCleanup: false, CleanupIntervalHours: 24, CaptchaRetentionDays: 1, VerifyCodeRetentionDays: 7, ApiUsageRetentionDays: 30, WebhookLogRetentionDays: 30, DeviceSessionRetentionDays: 7, } func GetCleanupConfig() CleanupConfig { cfg := defaultConfig var settings []model.Setting database.DB.Where("category = ?", "cleanup").Find(&settings) for _, s := range settings { switch s.Key { case "enable_auto_cleanup": cfg.EnableAutoCleanup = s.Value == "true" case "cleanup_interval_hours": cfg.CleanupIntervalHours = parseVal(s.Value, defaultConfig.CleanupIntervalHours) case "captcha_retention_days": cfg.CaptchaRetentionDays = parseVal(s.Value, defaultConfig.CaptchaRetentionDays) case "verify_code_retention_days": cfg.VerifyCodeRetentionDays = parseVal(s.Value, defaultConfig.VerifyCodeRetentionDays) case "api_usage_retention_days": cfg.ApiUsageRetentionDays = parseVal(s.Value, defaultConfig.ApiUsageRetentionDays) case "webhook_log_retention_days": cfg.WebhookLogRetentionDays = parseVal(s.Value, defaultConfig.WebhookLogRetentionDays) case "device_session_retention_days": cfg.DeviceSessionRetentionDays = parseVal(s.Value, defaultConfig.DeviceSessionRetentionDays) } } return cfg } func parseVal(value string, defaultVal int) int { if value == "" { return defaultVal } var result int if _, err := fmt.Sscanf(value, "%d", &result); err != nil || result <= 0 { return defaultVal } return result } func StartCleanupScheduler() { go func() { for { cfg := GetCleanupConfig() interval := time.Duration(cfg.CleanupIntervalHours) * time.Hour if interval < time.Hour { interval = time.Hour } time.Sleep(interval) if !cfg.EnableAutoCleanup { continue } RunCleanup(cfg) } }() log.Println("[Scheduler] Cleanup scheduler started") } func RunCleanup(cfg CleanupConfig) { now := time.Now() totalCleaned := 0 if cfg.CaptchaRetentionDays > 0 { cutoff := now.AddDate(0, 0, -cfg.CaptchaRetentionDays) result := database.DB.Where("expires_at < ?", cutoff).Delete(&model.Captcha{}) if result.RowsAffected > 0 { log.Printf("[Cleanup] Deleted %d expired captchas", result.RowsAffected) totalCleaned += int(result.RowsAffected) } } if cfg.VerifyCodeRetentionDays > 0 { cutoff := now.AddDate(0, 0, -cfg.VerifyCodeRetentionDays) emailResult := database.DB.Where("expires_at < ? AND used = ?", cutoff, true).Delete(&model.EmailVerifyCode{}) smsResult := database.DB.Where("expires_at < ? AND used = ?", cutoff, true).Delete(&model.SmsVerifyCode{}) count := emailResult.RowsAffected + smsResult.RowsAffected if count > 0 { log.Printf("[Cleanup] Deleted %d expired verify codes", count) totalCleaned += int(count) } } if cfg.ApiUsageRetentionDays > 0 { cutoff := now.AddDate(0, 0, -cfg.ApiUsageRetentionDays) result := database.DB.Where("created_at < ?", cutoff).Delete(&model.ApiUsage{}) if result.RowsAffected > 0 { log.Printf("[Cleanup] Deleted %d old api usage records", result.RowsAffected) totalCleaned += int(result.RowsAffected) } } if cfg.WebhookLogRetentionDays > 0 { cutoff := now.AddDate(0, 0, -cfg.WebhookLogRetentionDays) result := database.DB.Where("created_at < ?", cutoff).Delete(&model.WebhookLog{}) if result.RowsAffected > 0 { log.Printf("[Cleanup] Deleted %d old webhook logs", result.RowsAffected) totalCleaned += int(result.RowsAffected) } } if cfg.DeviceSessionRetentionDays > 0 { cutoff := now.AddDate(0, 0, -cfg.DeviceSessionRetentionDays) result := database.DB.Where("last_heartbeat < ? AND last_heartbeat IS NOT NULL", cutoff).Delete(&model.DeviceSession{}) if result.RowsAffected > 0 { log.Printf("[Cleanup] Deleted %d expired device sessions", result.RowsAffected) totalCleaned += int(result.RowsAffected) } } if totalCleaned > 0 { log.Printf("[Cleanup] Total cleaned: %d records", totalCleaned) } }