package services import ( "log" "os" "path/filepath" "strings" "time" "fotbal-club/internal/config" "fotbal-club/internal/models" "fotbal-club/pkg/email" "gorm.io/gorm" ) // StartNewsletterScheduler starts a background job that sends automated newsletters. // It can be configured via env NEWSLETTER_INTERVAL_HOURS and is guarded by config.NewsletterEnabled. func StartNewsletterScheduler(db *gorm.DB, emailSvc email.EmailService) { interval := 24 * time.Hour // default daily for demo; real use might be weekly if v := os.Getenv("NEWSLETTER_INTERVAL_HOURS"); v != "" { if h, err := time.ParseDuration(v + "h"); err == nil { interval = h } } // Run once on startup only if enabled if config.AppConfig != nil && config.AppConfig.NewsletterEnabled { go runNewsletterCycle(db, emailSvc) } else { log.Printf("[newsletter] scheduler is disabled at startup; waiting for enable toggle") } // Periodic ticker go func() { ticker := time.NewTicker(interval) defer ticker.Stop() for range ticker.C { if config.AppConfig != nil && config.AppConfig.NewsletterEnabled { runNewsletterCycle(db, emailSvc) } else { log.Printf("[newsletter] skipped cycle (disabled)") } } }() } func runNewsletterCycle(db *gorm.DB, emailSvc email.EmailService) { log.Printf("[newsletter] starting automated newsletter cycle") cacheDir := filepath.Join("cache", "prefetch") // Fetch subscribers in batches var subs []models.NewsletterSubscription if err := db.Find(&subs).Error; err != nil { log.Printf("[newsletter] failed to load subscribers: %v", err) return } for _, s := range subs { if !s.IsActive { continue } // Normalize preferences to new structure ct := make([]string, 0, 4) if v, ok := s.Preferences["blogs"].(bool); ok && v { ct = append(ct, "blogs") } if v, ok := s.Preferences["events"].(bool); ok && v { ct = append(ct, "events") } if v, ok := s.Preferences["matches"].(bool); ok && v { ct = append(ct, "matches") } if v, ok := s.Preferences["scores"].(bool); ok && v { ct = append(ct, "scores") } // Backward-compat: weekly implies blogs; matches implies matches if v, ok := s.Preferences["weekly"].(bool); ok && v && !contains(ct, "blogs") { ct = append(ct, "blogs") } if v, ok := s.Preferences["matches"].(bool); ok && v && !contains(ct, "matches") { ct = append(ct, "matches") } // Optional competitions list if stored under key like competitions comps := []string{} if raw, ok := s.Preferences["competitions"].(string); ok && raw != "" { // comma-separated codes for _, p := range strings.Split(raw, ",") { if v := strings.TrimSpace(p); v != "" { comps = append(comps, v) } } } prefs := NewsletterPrefs{ Email: s.Email, ContentTypes: ct, Competitions: comps, Frequency: "daily", } subj, content := BuildNewsletterDigest(cacheDir, prefs) if strings.TrimSpace(content) == "" { continue } data := &email.NewsletterData{ Subject: subj, Content: content, Recipients: []string{s.Email}, } if err := emailSvc.SendNewsletter(data); err != nil { log.Printf("[newsletter] failed to send to %s: %v", s.Email, err) } // small sleep to avoid hammering SMTP time.Sleep(200 * time.Millisecond) } log.Printf("[newsletter] automated cycle finished") } func contains(list []string, v string) bool { for _, x := range list { if x == v { return true } } return false }