dev day #100 - WE ARE FUCKING DONE, hotfixes incoming but we did it in 100 days, lets fucking go guys, anyone reading this...i love you

This commit is contained in:
Tomas Dvorak
2025-11-22 21:30:10 +01:00
parent f5b6f83974
commit aa036b6550
47 changed files with 3607 additions and 2177 deletions
+45 -63
View File
@@ -19,6 +19,7 @@ import (
"fotbal-club/internal/config"
"fotbal-club/internal/models"
"fotbal-club/pkg/logger"
"fotbal-club/pkg/utils"
"github.com/vanng822/go-premailer/premailer"
"gopkg.in/mail.v2"
@@ -476,11 +477,10 @@ func (s *emailService) SendPasswordReset(to string, resetLink string, useOverrid
if clubID := strings.TrimSpace(set.ClubID); clubID != "" {
// Use PNG format for better email client compatibility (SVG not widely supported)
clubLogo = fmt.Sprintf("https://logoapi.sportcreative.eu/logos/%s?format=png&width=400", clubID)
} else {
clubLogo = "https://via.placeholder.com/400x400.png?text=Logo"
}
}
if clubLogo == "" {
clubLogo = "https://via.placeholder.com/400x400.png?text=Logo"
}
primaryColor := strings.TrimSpace(set.PrimaryColor)
if primaryColor == "" {
primaryColor = "#1e3a8a"
@@ -870,24 +870,26 @@ func (s *emailService) SendContactForm(data *ContactFormData) error {
Agent: data.UserAgent,
}
// Build recipients: admin email + optional auto-forward list from Settings
recipients := make([]string, 0, 4)
if v := strings.TrimSpace(s.config.AdminEmail); v != "" {
recipients = append(recipients, v)
}
// Load settings to check auto-forwarding
// Build recipients (deduped later):
// 1) Club contact email from DB Settings (preferred default)
// 2) CONTACT_EMAIL from env (Config.ContactEmail)
// 3) ADMIN_EMAIL from env (Config.AdminEmail)
recipients := make([]string, 0, 8)
// Load settings for contact email and forwarding list
var set models.Settings
if s.db != nil {
_ = s.db.First(&set).Error
if set.ContactForwardEnabled && strings.TrimSpace(set.ContactForwardList) != "" {
parts := strings.FieldsFunc(set.ContactForwardList, func(r rune) bool { return r == ',' || r == ';' || r == ' ' || r == '\n' || r == '\t' })
for _, p := range parts {
if v := strings.TrimSpace(p); v != "" {
recipients = append(recipients, v)
}
}
if v := strings.TrimSpace(set.ContactEmail); v != "" {
recipients = append(recipients, v)
}
}
// Add environment-provided contact/admin fallbacks
if v := strings.TrimSpace(s.config.ContactEmail); v != "" {
recipients = append(recipients, v)
}
if v := strings.TrimSpace(s.config.AdminEmail); v != "" {
recipients = append(recipients, v)
}
// Deduplicate and ensure at least one recipient
uniq := make(map[string]struct{})
dedup := make([]string, 0, len(recipients))
@@ -951,8 +953,6 @@ func (s *emailService) SendNewsletter(d *NewsletterData) error {
if subj == "" || html == "" {
return fmt.Errorf("newsletter subject and content are required")
}
// Build dialer and effective From dynamically
dialer, effFrom, effFromName := s.buildDialerAndFrom()
// Prepare recipient list (dedupe and sanitize)
uniq := map[string]struct{}{}
recips := make([]string, 0, len(d.Recipients))
@@ -984,7 +984,7 @@ func (s *emailService) SendNewsletter(d *NewsletterData) error {
}
frontendBase := strings.TrimSuffix(s.config.FrontendBaseURL, "/")
// Send to each recipient
// Send to each recipient using the standard email template wrapper (base.html + newsletter.html)
var errs []error
for _, to := range recips {
// Create delivery log (best-effort)
@@ -1001,7 +1001,7 @@ func (s *emailService) SendNewsletter(d *NewsletterData) error {
_ = s.db.Create(&logRec).Error
}
// Rewrite links for tracking and add open pixel
// Rewrite links for tracking and add open pixel (rendered inside the template)
trackedHTML := rewriteLinksForTracking(html, makeAbs, int(logRec.ID), token, frontendBase, s.config.PublicAPIBaseURL)
pixelURL := makeAbs("/email/open.gif", url.Values{
"m": {fmt.Sprintf("%d", logRec.ID)},
@@ -1010,60 +1010,42 @@ func (s *emailService) SendNewsletter(d *NewsletterData) error {
if strings.TrimSpace(trackedHTML) == "" {
trackedHTML = html
}
trackedHTML = trackedHTML + fmt.Sprintf("<img src=\"%s\" width=\"1\" height=\"1\" style=\"display:none;\" alt=\"\" />", pixelURL)
m := mail.NewMessage()
// Properly encode UTF-8 From name
name := strings.TrimSpace(effFromName)
if i := strings.Index(name, "<"); i >= 0 {
name = strings.TrimSpace(name[:i])
// Build manage/unsubscribe URLs (besteffort)
manageURL := ""
if v, err := utils.GenerateSubscriberToken(strings.ToLower(strings.TrimSpace(to)), 60*24*30); err == nil && frontendBase != "" {
manageURL = frontendBase + "/newsletter/preferences?token=" + v
}
addr := strings.TrimSpace(effFrom)
if !strings.Contains(addr, "@") {
addr = strings.TrimSpace(s.config.SMTPFrom)
unsubscribeURL := ""
if frontendBase != "" {
unsubscribeURL = frontendBase + "/newsletter/unsubscribe/" + url.QueryEscape(strings.ToLower(strings.TrimSpace(to)))
}
if strings.Contains(strings.ToLower(name), "@") {
name = ""
// Render via SendEmail to ensure base wrapper and branding
ed := &EmailData{
Subject: subj,
To: []string{to},
Template: "newsletter",
Data: map[string]interface{}{
"Subject": subj,
"Content": trackedHTML,
"OpenPixelURL": pixelURL,
"ManageURL": manageURL,
"UnsubscribeURL": unsubscribeURL,
},
}
m.SetAddressHeader("From", addr, name)
m.SetHeader("To", to)
m.SetHeader("Subject", subj)
m.SetDateHeader("Date", time.Now())
m.SetHeader("X-Mailer", "Fotbal Club")
if d.Headers != nil {
for k, v := range d.Headers {
if len(v) > 0 {
m.SetHeader(k, v...)
}
}
}
m.SetBody("text/plain", "Pro zobrazení tohoto e-mailu použijte HTML klient.")
m.AddAlternative("text/html", trackedHTML)
// Retry send
var lastErr error
for i := 0; i < 3; i++ {
logger.Debug("SMTP newsletter send attempt %d: to=%s subject=%s", i+1, to, subj)
if err := dialer.DialAndSend(m); err == nil {
lastErr = nil
break
} else {
lastErr = err
logger.Error("SMTP newsletter send failed (attempt %d) to=%s: %v", i+1, to, err)
time.Sleep(time.Second * time.Duration(i+1))
}
}
if lastErr != nil {
errs = append(errs, fmt.Errorf("failed to send to %s: %w", to, lastErr))
if err := s.SendEmail(ed); err != nil {
errs = append(errs, fmt.Errorf("failed to send to %s: %w", to, err))
if s.db != nil && logRec.ID != 0 {
_ = s.db.Model(&models.EmailLog{}).Where("id = ?", logRec.ID).Updates(map[string]interface{}{
"status": "failed",
"send_error": lastErr.Error(),
"send_error": err.Error(),
}).Error
}
}
if lastErr == nil && s.db != nil && logRec.ID != 0 {
} else if s.db != nil && logRec.ID != 0 {
_ = s.db.Model(&models.EmailLog{}).Where("id = ?", logRec.ID).Update("status", "sent").Error
}
time.Sleep(100 * time.Millisecond)
}
if len(errs) > 0 {