mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-04 10:42:57 +00:00
626 lines
19 KiB
Go
626 lines
19 KiB
Go
package services
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"log"
|
||
"path/filepath"
|
||
"strings"
|
||
"time"
|
||
|
||
"fotbal-club/internal/config"
|
||
"fotbal-club/internal/models"
|
||
"fotbal-club/pkg/email"
|
||
"fotbal-club/pkg/logger"
|
||
"fotbal-club/pkg/utils"
|
||
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// NewsletterAutomation handles all automated newsletter sending
|
||
type NewsletterAutomation struct {
|
||
db *gorm.DB
|
||
emailSvc email.EmailService
|
||
cacheDir string
|
||
lastWeekly time.Time
|
||
lastMatchCheck time.Time
|
||
}
|
||
|
||
// NewNewsletterAutomation creates a new automation service
|
||
func NewNewsletterAutomation(db *gorm.DB, emailSvc email.EmailService) *NewsletterAutomation {
|
||
return &NewsletterAutomation{
|
||
db: db,
|
||
emailSvc: emailSvc,
|
||
cacheDir: filepath.Join("cache", "prefetch"),
|
||
}
|
||
}
|
||
|
||
// Start begins the newsletter automation loop
|
||
func (na *NewsletterAutomation) Start() {
|
||
log.Printf("[newsletter-automation] Starting automated newsletter service")
|
||
|
||
// Run initial check after 1 minute
|
||
time.AfterFunc(1*time.Minute, func() {
|
||
na.RunCycle()
|
||
})
|
||
|
||
// Then run every 15 minutes
|
||
ticker := time.NewTicker(15 * time.Minute)
|
||
go func() {
|
||
for range ticker.C {
|
||
na.RunCycle()
|
||
}
|
||
}()
|
||
}
|
||
|
||
// RunCycle executes all newsletter checks
|
||
func (na *NewsletterAutomation) RunCycle() {
|
||
if !na.isEnabled() {
|
||
log.Printf("[newsletter-automation] Skipped: disabled in settings")
|
||
return
|
||
}
|
||
|
||
log.Printf("[newsletter-automation] Running cycle...")
|
||
|
||
// Check for weekly digest
|
||
na.checkWeeklyDigest()
|
||
|
||
// Check for upcoming matches (reminders)
|
||
na.checkUpcomingMatches()
|
||
|
||
// Check for finished matches (results)
|
||
na.checkFinishedMatches()
|
||
|
||
log.Printf("[newsletter-automation] Cycle complete")
|
||
}
|
||
|
||
// SendBlogNotification sends immediate notification when a blog is published
|
||
func (na *NewsletterAutomation) SendBlogNotification(article *models.Article) error {
|
||
if !na.isEnabled() {
|
||
return fmt.Errorf("newsletter automation is disabled")
|
||
}
|
||
|
||
// Check if already sent
|
||
var existing models.BlogNotification
|
||
if err := na.db.Where("article_id = ?", article.ID).First(&existing).Error; err == nil {
|
||
log.Printf("[newsletter-automation] Blog notification already sent for article %d", article.ID)
|
||
return nil
|
||
}
|
||
|
||
// Get subscribers interested in blogs
|
||
subs := na.getSubscribersForType("blogs", article.CategoryName)
|
||
if len(subs) == 0 {
|
||
log.Printf("[newsletter-automation] No subscribers for blog notifications")
|
||
return nil
|
||
}
|
||
|
||
// Build email content
|
||
subject := fmt.Sprintf("Nový článek: %s", article.Title)
|
||
baseFE := strings.TrimSuffix(config.AppConfig.FrontendBaseURL, "/")
|
||
articleURL := fmt.Sprintf("%s/news/%s", baseFE, article.Slug)
|
||
|
||
html := na.buildBlogNotificationHTML(article, articleURL)
|
||
|
||
// Send to each subscriber
|
||
recipients := make([]string, 0, len(subs))
|
||
for _, sub := range subs {
|
||
recipients = append(recipients, sub.Email)
|
||
}
|
||
|
||
err := na.sendNewsletterToRecipients(recipients, subject, html, "blog_release")
|
||
if err != nil {
|
||
logger.Error("[newsletter-automation] Failed to send blog notification: %v", err)
|
||
return err
|
||
}
|
||
|
||
// Record notification
|
||
notif := models.BlogNotification{
|
||
ArticleID: article.ID,
|
||
SentAt: time.Now(),
|
||
RecipientsCount: len(recipients),
|
||
CreatedAt: time.Now(),
|
||
}
|
||
na.db.Create(¬if)
|
||
|
||
log.Printf("[newsletter-automation] Blog notification sent for article %d to %d recipients", article.ID, len(recipients))
|
||
return nil
|
||
}
|
||
|
||
func (na *NewsletterAutomation) checkWeeklyDigest() {
|
||
var settings models.Settings
|
||
na.db.First(&settings)
|
||
|
||
if !settings.EnableWeekly {
|
||
return
|
||
}
|
||
|
||
// Get configured day and hour
|
||
targetDay := strings.ToLower(strings.TrimSpace(settings.NewsletterWeeklyDay))
|
||
if targetDay == "" {
|
||
targetDay = "sun" // Default to Sunday
|
||
}
|
||
targetHour := settings.NewsletterWeeklyHour
|
||
if targetHour < 0 || targetHour > 23 {
|
||
targetHour = 9 // Default to 9 AM
|
||
}
|
||
|
||
now := time.Now()
|
||
currentDay := strings.ToLower(now.Weekday().String()[:3])
|
||
currentHour := now.Hour()
|
||
|
||
// Check if it's the right day and hour (with minute precision to prevent multiple sends)
|
||
currentMinute := now.Minute()
|
||
if currentDay != targetDay || currentHour != targetHour || currentMinute > 5 {
|
||
// Only run in the first 5 minutes of the target hour to avoid repeats
|
||
return
|
||
}
|
||
|
||
// Check if already sent today (using database for persistence)
|
||
var todaySent models.NewsletterSentLog
|
||
todayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
|
||
if err := na.db.Where("newsletter_type = ? AND sent_at >= ?", "weekly", todayStart).First(&todaySent).Error; err == nil {
|
||
log.Printf("[newsletter-automation] Weekly digest already sent today at %s", todaySent.SentAt.Format("15:04:05"))
|
||
return
|
||
}
|
||
|
||
// Also check in-memory as backup
|
||
if na.lastWeekly.Year() == now.Year() && na.lastWeekly.YearDay() == now.YearDay() {
|
||
return
|
||
}
|
||
|
||
// Get all subscribers interested in weekly digest
|
||
subs := na.getSubscribersForType("weekly", "")
|
||
if len(subs) == 0 {
|
||
log.Printf("[newsletter-automation] No subscribers for weekly digest")
|
||
return
|
||
}
|
||
|
||
log.Printf("[newsletter-automation] Sending weekly digest to %d subscribers", len(subs))
|
||
|
||
// Build weekly content for each subscriber based on their preferences
|
||
for _, sub := range subs {
|
||
prefs := na.parsePreferences(sub)
|
||
subject, html := BuildNewsletterDigest(na.cacheDir, prefs)
|
||
|
||
if strings.TrimSpace(html) == "" {
|
||
continue
|
||
}
|
||
|
||
err := na.sendNewsletterToRecipients([]string{sub.Email}, subject, html, "weekly")
|
||
if err != nil {
|
||
logger.Error("[newsletter-automation] Failed to send weekly digest to %s: %v", sub.Email, err)
|
||
}
|
||
|
||
time.Sleep(200 * time.Millisecond) // Rate limiting
|
||
}
|
||
|
||
na.lastWeekly = now
|
||
log.Printf("[newsletter-automation] Weekly digest sent")
|
||
}
|
||
|
||
func (na *NewsletterAutomation) checkUpcomingMatches() {
|
||
var settings models.Settings
|
||
na.db.First(&settings)
|
||
|
||
// Determine effective enabling: use settings, or auto-activate if there are subscribers and a match is within 2 hours
|
||
enabled := settings.EnableMatchReminders
|
||
leadHours := settings.NewsletterReminderLeadHours
|
||
if leadHours <= 0 {
|
||
leadHours = 48 // Default 2 days
|
||
}
|
||
|
||
// Load match data from cache
|
||
facr := readJSON(filepath.Join(na.cacheDir, "facr_club_info.json"))
|
||
matches := facrAllMatches(facr)
|
||
|
||
now := time.Now()
|
||
|
||
if !enabled {
|
||
subs := na.getSubscribersForType("matches", "")
|
||
if len(subs) == 0 {
|
||
return
|
||
}
|
||
auto := false
|
||
for _, match := range matches {
|
||
matchTime := parseDateTimeISO(match.Date, match.Time)
|
||
if matchTime.IsZero() || matchTime.Before(now) {
|
||
continue
|
||
}
|
||
if matchTime.Sub(now).Hours() <= 2 {
|
||
auto = true
|
||
break
|
||
}
|
||
}
|
||
if !auto {
|
||
return
|
||
}
|
||
// Auto mode: restrict reminder window to 2 hours before kickoff
|
||
leadHours = 2
|
||
enabled = true
|
||
}
|
||
|
||
for _, match := range matches {
|
||
matchTime := parseDateTimeISO(match.Date, match.Time)
|
||
if matchTime.IsZero() || matchTime.Before(now) {
|
||
continue
|
||
}
|
||
|
||
hoursUntil := matchTime.Sub(now).Hours()
|
||
|
||
// Check for lead-hour reminder (48h normally, 2h in auto mode)
|
||
if hoursUntil <= float64(leadHours) && hoursUntil > float64(leadHours-1) {
|
||
na.sendMatchReminder(match, "reminder_48h", leadHours)
|
||
}
|
||
|
||
// Check for day-of reminder (match starts in 0-6 hours)
|
||
if hoursUntil <= 6 && hoursUntil > 0 {
|
||
na.sendMatchReminder(match, "reminder_day", 0)
|
||
}
|
||
}
|
||
}
|
||
|
||
func (na *NewsletterAutomation) sendMatchReminder(match Match, notifType string, hoursBeforeText int) {
|
||
// Check if already sent
|
||
var existing models.MatchNotification
|
||
matchKey := fmt.Sprintf("%s-%s-%s", match.Date, match.Home, match.Away)
|
||
if err := na.db.Where("match_id = ? AND notification_type = ?", matchKey, notifType).First(&existing).Error; err == nil {
|
||
return
|
||
}
|
||
|
||
// Get subscribers interested in matches and this competition
|
||
subs := na.getSubscribersForType("matches", match.Competition)
|
||
if len(subs) == 0 {
|
||
return
|
||
}
|
||
|
||
// Build email content
|
||
var subject string
|
||
if notifType == "reminder_48h" {
|
||
subject = fmt.Sprintf("Nadcházející zápas za %d hodin: %s vs %s", hoursBeforeText, match.Home, match.Away)
|
||
} else {
|
||
subject = fmt.Sprintf("Zápas dnes: %s vs %s", match.Home, match.Away)
|
||
}
|
||
|
||
html := na.buildMatchReminderHTML(match, notifType)
|
||
|
||
recipients := make([]string, 0, len(subs))
|
||
for _, sub := range subs {
|
||
recipients = append(recipients, sub.Email)
|
||
}
|
||
|
||
err := na.sendNewsletterToRecipients(recipients, subject, html, "match_reminder")
|
||
if err != nil {
|
||
logger.Error("[newsletter-automation] Failed to send match reminder: %v", err)
|
||
return
|
||
}
|
||
|
||
// Record notification
|
||
notif := models.MatchNotification{
|
||
MatchID: matchKey,
|
||
NotificationType: notifType,
|
||
SentAt: time.Now(),
|
||
RecipientsCount: len(recipients),
|
||
CreatedAt: time.Now(),
|
||
}
|
||
na.db.Create(¬if)
|
||
|
||
log.Printf("[newsletter-automation] Match reminder sent: %s (%s) to %d recipients", matchKey, notifType, len(recipients))
|
||
}
|
||
|
||
func (na *NewsletterAutomation) checkFinishedMatches() {
|
||
var settings models.Settings
|
||
na.db.First(&settings)
|
||
|
||
// Determine effective enabling. If disabled, auto-activate when there are subscribers and a recent result exists.
|
||
enabled := settings.EnableResults
|
||
|
||
// Load match data
|
||
facr := readJSON(filepath.Join(na.cacheDir, "facr_club_info.json"))
|
||
matches := facrAllMatches(facr)
|
||
|
||
now := time.Now()
|
||
lookback := 6 * time.Hour // Check matches finished in last 6 hours
|
||
|
||
bypassQuiet := false
|
||
if !enabled {
|
||
subs := na.getSubscribersForType("scores", "")
|
||
if len(subs) == 0 {
|
||
return
|
||
}
|
||
auto := false
|
||
for _, match := range matches {
|
||
if match.Score == "" || !strings.Contains(match.Score, ":") {
|
||
continue
|
||
}
|
||
matchTime := parseDateTimeISO(match.Date, match.Time)
|
||
if matchTime.IsZero() || matchTime.After(now) {
|
||
continue
|
||
}
|
||
if now.Sub(matchTime) <= lookback {
|
||
auto = true
|
||
break
|
||
}
|
||
}
|
||
if !auto {
|
||
return
|
||
}
|
||
// Auto mode: send immediately when we have a result, ignoring quiet hours
|
||
bypassQuiet = true
|
||
enabled = true
|
||
}
|
||
|
||
// Respect quiet hours only when explicitly enabled in settings (not in auto mode)
|
||
if !bypassQuiet {
|
||
currentHour := time.Now().Hour()
|
||
quietStart := settings.NewsletterQuietStart
|
||
quietEnd := settings.NewsletterQuietEnd
|
||
|
||
// Consider quiet hours configured when both bounds are within 0..23 and not equal
|
||
if quietStart >= 0 && quietStart <= 23 && quietEnd >= 0 && quietEnd <= 23 && quietStart != quietEnd {
|
||
inQuiet := false
|
||
if quietStart < quietEnd {
|
||
// Same-day interval, e.g., 08:00–22:00 => quiet when between start and end
|
||
inQuiet = currentHour >= quietStart && currentHour < quietEnd
|
||
} else {
|
||
// Cross-midnight interval, e.g., 22:00–08:00 => quiet when hour >= start OR hour < end
|
||
inQuiet = currentHour >= quietStart || currentHour < quietEnd
|
||
}
|
||
if inQuiet {
|
||
log.Printf("[newsletter-automation] In quiet hours (%02d:00-%02d:00), skipping result notifications", quietStart, quietEnd)
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
for _, match := range matches {
|
||
if match.Score == "" || !strings.Contains(match.Score, ":") {
|
||
continue // No score yet
|
||
}
|
||
|
||
matchTime := parseDateTimeISO(match.Date, match.Time)
|
||
if matchTime.IsZero() || matchTime.After(now) {
|
||
continue
|
||
}
|
||
|
||
// Check if match finished recently
|
||
timeSinceMatch := now.Sub(matchTime)
|
||
if timeSinceMatch > lookback {
|
||
continue
|
||
}
|
||
|
||
na.sendMatchResult(match)
|
||
}
|
||
}
|
||
|
||
func (na *NewsletterAutomation) sendMatchResult(match Match) {
|
||
// Check if already sent
|
||
matchKey := fmt.Sprintf("%s-%s-%s", match.Date, match.Home, match.Away)
|
||
var existing models.MatchNotification
|
||
if err := na.db.Where("match_id = ? AND notification_type = ?", matchKey, "result").First(&existing).Error; err == nil {
|
||
return
|
||
}
|
||
|
||
// Get subscribers interested in results
|
||
subs := na.getSubscribersForType("scores", match.Competition)
|
||
if len(subs) == 0 {
|
||
return
|
||
}
|
||
|
||
subject := fmt.Sprintf("Výsledek: %s %s %s", match.Home, match.Score, match.Away)
|
||
html := na.buildMatchResultHTML(match)
|
||
|
||
recipients := make([]string, 0, len(subs))
|
||
for _, sub := range subs {
|
||
recipients = append(recipients, sub.Email)
|
||
}
|
||
|
||
err := na.sendNewsletterToRecipients(recipients, subject, html, "match_result")
|
||
if err != nil {
|
||
logger.Error("[newsletter-automation] Failed to send match result: %v", err)
|
||
return
|
||
}
|
||
|
||
// Record notification
|
||
notif := models.MatchNotification{
|
||
MatchID: matchKey,
|
||
NotificationType: "result",
|
||
SentAt: time.Now(),
|
||
RecipientsCount: len(recipients),
|
||
CreatedAt: time.Now(),
|
||
}
|
||
na.db.Create(¬if)
|
||
|
||
log.Printf("[newsletter-automation] Match result sent: %s to %d recipients", matchKey, len(recipients))
|
||
}
|
||
|
||
// Helper functions
|
||
|
||
func (na *NewsletterAutomation) isEnabled() bool {
|
||
if config.AppConfig == nil {
|
||
return false
|
||
}
|
||
return config.AppConfig.NewsletterEnabled
|
||
}
|
||
|
||
func (na *NewsletterAutomation) getSubscribersForType(contentType, category string) []models.NewsletterSubscription {
|
||
var subs []models.NewsletterSubscription
|
||
na.db.Where("is_active = ?", true).Find(&subs)
|
||
|
||
filtered := make([]models.NewsletterSubscription, 0)
|
||
for _, sub := range subs {
|
||
// Check if subscriber wants this content type
|
||
if val, ok := sub.Preferences[contentType].(bool); ok && val {
|
||
// If category filtering is needed and specified
|
||
if category != "" {
|
||
// Check if subscriber has category preferences
|
||
if cats, ok := sub.Preferences["categories"].(string); ok && cats != "" {
|
||
categoryList := strings.Split(cats, ",")
|
||
found := false
|
||
for _, cat := range categoryList {
|
||
if strings.EqualFold(strings.TrimSpace(cat), category) {
|
||
found = true
|
||
break
|
||
}
|
||
}
|
||
if !found {
|
||
continue
|
||
}
|
||
}
|
||
}
|
||
filtered = append(filtered, sub)
|
||
}
|
||
}
|
||
|
||
return filtered
|
||
}
|
||
|
||
func (na *NewsletterAutomation) parsePreferences(sub models.NewsletterSubscription) NewsletterPrefs {
|
||
prefs := NewsletterPrefs{
|
||
Email: sub.Email,
|
||
ContentTypes: []string{},
|
||
Competitions: []string{},
|
||
Frequency: "daily",
|
||
}
|
||
|
||
// Parse content types
|
||
if v, ok := sub.Preferences["blogs"].(bool); ok && v {
|
||
prefs.ContentTypes = append(prefs.ContentTypes, "blogs")
|
||
}
|
||
if v, ok := sub.Preferences["events"].(bool); ok && v {
|
||
prefs.ContentTypes = append(prefs.ContentTypes, "events")
|
||
}
|
||
if v, ok := sub.Preferences["matches"].(bool); ok && v {
|
||
prefs.ContentTypes = append(prefs.ContentTypes, "matches")
|
||
}
|
||
if v, ok := sub.Preferences["scores"].(bool); ok && v {
|
||
prefs.ContentTypes = append(prefs.ContentTypes, "scores")
|
||
}
|
||
|
||
// Parse categories/competitions
|
||
if cats, ok := sub.Preferences["categories"].(string); ok && cats != "" {
|
||
for _, c := range strings.Split(cats, ",") {
|
||
if v := strings.TrimSpace(c); v != "" {
|
||
prefs.Competitions = append(prefs.Competitions, v)
|
||
}
|
||
}
|
||
}
|
||
|
||
return prefs
|
||
}
|
||
|
||
func (na *NewsletterAutomation) sendNewsletterToRecipients(recipients []string, subject, htmlContent, newsletterType string) error {
|
||
data := &email.NewsletterData{
|
||
Subject: subject,
|
||
Content: htmlContent,
|
||
Recipients: recipients,
|
||
}
|
||
|
||
err := na.emailSvc.SendNewsletter(data)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
// Log sent newsletter
|
||
contentIDsJSON, _ := json.Marshal([]string{})
|
||
logEntry := models.NewsletterSentLog{
|
||
NewsletterType: newsletterType,
|
||
Subject: subject,
|
||
ContentIDs: string(contentIDsJSON),
|
||
RecipientsCount: len(recipients),
|
||
SentAt: time.Now(),
|
||
CreatedAt: time.Now(),
|
||
}
|
||
na.db.Create(&logEntry)
|
||
|
||
return nil
|
||
}
|
||
|
||
func (na *NewsletterAutomation) buildBlogNotificationHTML(article *models.Article, articleURL string) string {
|
||
// Short description: prefer excerpt; otherwise derive from content
|
||
desc := strings.TrimSpace(article.Excerpt)
|
||
if desc == "" {
|
||
plain := utils.SanitizeString(article.Content)
|
||
if len(plain) > 260 {
|
||
cut := 240
|
||
if cut < len(plain) {
|
||
for cut < len(plain) && plain[cut] != ' ' {
|
||
cut++
|
||
}
|
||
}
|
||
if cut > len(plain) {
|
||
cut = len(plain)
|
||
}
|
||
plain = strings.TrimSpace(plain[:cut]) + "…"
|
||
}
|
||
desc = plain
|
||
}
|
||
|
||
// Category badge (if available)
|
||
cat := strings.TrimSpace(article.CategoryName)
|
||
var catHTML string
|
||
if cat != "" {
|
||
catHTML = fmt.Sprintf(`<div style="margin-bottom:10px;"><span style="display:inline-block;background:#e3f2fd;color:#1e3a8a;border:1px solid #90cdf4;border-radius:999px;padding:4px 10px;font-size:12px;font-weight:600;">%s</span></div>`, htmlEsc(cat))
|
||
}
|
||
|
||
// Cover image (optional)
|
||
var imgHTML string
|
||
if strings.TrimSpace(article.ImageURL) != "" {
|
||
imgHTML = fmt.Sprintf(`<div style="margin:0 0 15px 0;"><img src="%s" alt="cover" style="width:100%%;height:auto;border-radius:6px;"/></div>`, htmlEsc(article.ImageURL))
|
||
}
|
||
|
||
html := fmt.Sprintf(`
|
||
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
|
||
<h2 style="color: #1e3a8a; margin-bottom: 12px;">Nový článek na webu</h2>
|
||
<div style="border-left: 4px solid #2563eb; padding: 18px; background: #f8fafc; margin: 16px 0; border-radius:6px;">
|
||
%s
|
||
<h3 style="margin: 0 0 10px 0; color: #1e3a8a; font-size:22px;">%s</h3>
|
||
%s
|
||
<p style="color: #4a5568; line-height: 1.6; margin: 0 0 12px 0;">%s</p>
|
||
<a href="%s" style="display: inline-block; padding: 12px 20px; background: #2563eb; color: white; text-decoration: none; border-radius: 6px; font-weight: 600;">Číst článek</a>
|
||
</div>
|
||
</div>
|
||
`, catHTML, htmlEsc(article.Title), imgHTML, htmlEsc(desc), articleURL)
|
||
|
||
return html
|
||
}
|
||
|
||
func (na *NewsletterAutomation) buildMatchReminderHTML(match Match, notifType string) string {
|
||
var intro string
|
||
if notifType == "reminder_48h" {
|
||
intro = "Připomínáme nadcházející zápas:"
|
||
} else {
|
||
intro = "Zápas je dnes!"
|
||
}
|
||
|
||
html := fmt.Sprintf(`
|
||
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
|
||
<h2 style="color: #1e3a8a; margin-bottom: 20px;">%s</h2>
|
||
|
||
<div style="border-left: 4px solid #38a169; padding: 20px; background: #f0fff4; margin: 20px 0;">
|
||
<h3 style="margin: 0 0 10px 0; color: #1e3a8a; font-size: 24px;">%s vs %s</h3>
|
||
<p style="color: #276749; margin: 5px 0;"><strong>Datum:</strong> %s</p>
|
||
<p style="color: #276749; margin: 5px 0;"><strong>Čas:</strong> %s</p>
|
||
<p style="color: #276749; margin: 5px 0;"><strong>Soutěž:</strong> %s</p>
|
||
</div>
|
||
</div>
|
||
`, intro, htmlEsc(match.Home), htmlEsc(match.Away), htmlEsc(match.Date), htmlEsc(match.Time), htmlEsc(match.Competition))
|
||
|
||
return html
|
||
}
|
||
|
||
func (na *NewsletterAutomation) buildMatchResultHTML(match Match) string {
|
||
html := fmt.Sprintf(`
|
||
<div style="max-width: 600px; margin: 0 auto; font-family: Arial, sans-serif;">
|
||
<h2 style="color: #1e3a8a; margin-bottom: 20px;">Výsledek zápasu</h2>
|
||
|
||
<div style="border-left: 4px solid #d69e2e; padding: 20px; background: #fffbeb; margin: 20px 0;">
|
||
<h3 style="margin: 0 0 10px 0; color: #1e3a8a; font-size: 24px;">%s <span style="color: #d69e2e;">%s</span> %s</h3>
|
||
<p style="color: #975a16; margin: 5px 0;"><strong>Datum:</strong> %s</p>
|
||
<p style="color: #975a16; margin: 5px 0;"><strong>Soutěž:</strong> %s</p>
|
||
</div>
|
||
</div>
|
||
`, htmlEsc(match.Home), htmlEsc(match.Score), htmlEsc(match.Away), htmlEsc(match.Date), htmlEsc(match.Competition))
|
||
|
||
return html
|
||
}
|