Files
MyClub/internal/services/newsletter_scheduler.go
T
Tomáš Dvořák 12cba639b9 upload
2025-10-16 13:32:05 +02:00

115 lines
3.4 KiB
Go

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
}