mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
de day #74
This commit is contained in:
@@ -146,6 +146,8 @@ func MigrateDB(db *gorm.DB) error {
|
||||
&models.EventAttachment{},
|
||||
&models.UploadedFile{},
|
||||
&models.FileUsage{},
|
||||
&models.ShortLink{},
|
||||
&models.LinkClick{},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+65
-11
@@ -10,6 +10,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -36,6 +37,52 @@ type EmailData struct {
|
||||
FromName string
|
||||
}
|
||||
|
||||
// rewriteLinksForTracking wraps all http/https and site-relative links in the provided HTML
|
||||
// with the tracking redirect that includes the email log id (m) and token (t).
|
||||
// - makeAbs builds absolute URLs against PublicAPIBaseURL
|
||||
// - frontendBase is the absolute frontend base (e.g., https://club.cz)
|
||||
// - publicAPIBase is the absolute API base (e.g., https://api.club.cz/api/v1)
|
||||
func rewriteLinksForTracking(htmlIn string, makeAbs func(string, url.Values) string, logID int, token string, frontendBase string, publicAPIBase string) string {
|
||||
if strings.TrimSpace(htmlIn) == "" {
|
||||
return htmlIn
|
||||
}
|
||||
aTagRE := regexp.MustCompile(`(?i)<a\b[^>]*href=["'][^"']+["'][^>]*>`)
|
||||
hrefRE := regexp.MustCompile(`(?i)href=["']([^"']+)["']`)
|
||||
isTrackedPrefix := strings.TrimSuffix(publicAPIBase, "/") + "/email/click"
|
||||
|
||||
return aTagRE.ReplaceAllStringFunc(htmlIn, func(anchor string) string {
|
||||
m := hrefRE.FindStringSubmatch(anchor)
|
||||
if len(m) < 2 {
|
||||
return anchor
|
||||
}
|
||||
href := strings.TrimSpace(m[1])
|
||||
lower := strings.ToLower(href)
|
||||
if href == "" || href == "#" || strings.HasPrefix(lower, "mailto:") || strings.HasPrefix(lower, "tel:") || strings.HasPrefix(lower, "javascript:") {
|
||||
return anchor
|
||||
}
|
||||
// Skip if already tracked
|
||||
if strings.HasPrefix(href, isTrackedPrefix) {
|
||||
return anchor
|
||||
}
|
||||
// Build absolute target
|
||||
var target string
|
||||
if strings.HasPrefix(lower, "http://") || strings.HasPrefix(lower, "https://") {
|
||||
target = href
|
||||
} else if strings.HasPrefix(href, "/") {
|
||||
target = strings.TrimSuffix(frontendBase, "/") + href
|
||||
} else {
|
||||
target = strings.TrimSuffix(frontendBase, "/") + "/" + href
|
||||
}
|
||||
tracked := makeAbs("/email/click", url.Values{
|
||||
"m": {fmt.Sprintf("%d", logID)},
|
||||
"t": {token},
|
||||
"u": {target},
|
||||
})
|
||||
newAttr := `href="` + tracked + `"`
|
||||
return hrefRE.ReplaceAllString(anchor, newAttr)
|
||||
})
|
||||
}
|
||||
|
||||
type EmailService interface {
|
||||
SendEmail(data *EmailData) error
|
||||
SendContactForm(data *ContactFormData) error
|
||||
@@ -1005,53 +1052,60 @@ func (s *emailService) SendNewsletter(data *NewsletterData) error {
|
||||
Subject: data.Subject,
|
||||
Content: data.Content,
|
||||
Date: time.Now().Format("02. 01. 2006"),
|
||||
UnsubscribeURL: makeAbs("/api/v1/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {unsubscribePath}}),
|
||||
ManageURL: makeAbs("/api/v1/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {manageURL}}),
|
||||
UnsubscribeURL: makeAbs("/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {unsubscribePath}}),
|
||||
ManageURL: makeAbs("/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {manageURL}}),
|
||||
ClubName: clubName,
|
||||
ClubLogoURL: clubLogo,
|
||||
PrimaryColor: primaryColor,
|
||||
SecondaryColor: secondaryColor,
|
||||
AccentColor: accentColor,
|
||||
// socials assigned below
|
||||
OpenPixelURL: makeAbs("/api/v1/email/open.gif", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}}),
|
||||
OpenPixelURL: makeAbs("/email/open.gif", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}}),
|
||||
WebsiteURL: func() string {
|
||||
if siteURL == "" {
|
||||
return ""
|
||||
}
|
||||
return makeAbs("/api/v1/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {siteURL}})
|
||||
return makeAbs("/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {siteURL}})
|
||||
}(),
|
||||
ClubURL: func() string {
|
||||
if clubURL == "" {
|
||||
return ""
|
||||
}
|
||||
return makeAbs("/api/v1/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {clubURL}})
|
||||
return makeAbs("/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {clubURL}})
|
||||
}(),
|
||||
ContactEmail: contactEmail,
|
||||
ContactURL: func() string {
|
||||
if contactURL == "" {
|
||||
return ""
|
||||
}
|
||||
return makeAbs("/api/v1/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {contactURL}})
|
||||
return makeAbs("/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {contactURL}})
|
||||
}(),
|
||||
}
|
||||
|
||||
// Wrap socials if present
|
||||
if v := getStringField(set, "FacebookURL"); v != "" {
|
||||
personalTemplateData.FacebookURL = makeAbs("/api/v1/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {v}})
|
||||
personalTemplateData.FacebookURL = makeAbs("/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {v}})
|
||||
}
|
||||
if v := getStringField(set, "InstagramURL"); v != "" {
|
||||
personalTemplateData.InstagramURL = makeAbs("/api/v1/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {v}})
|
||||
personalTemplateData.InstagramURL = makeAbs("/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {v}})
|
||||
}
|
||||
if v := getStringField(set, "YouTubeURL"); v != "" {
|
||||
personalTemplateData.YouTubeURL = makeAbs("/api/v1/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {v}})
|
||||
personalTemplateData.YouTubeURL = makeAbs("/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {v}})
|
||||
}
|
||||
if personalTemplateData.YouTubeURL == "" {
|
||||
if v := getStringField(set, "YoutubeURL"); v != "" {
|
||||
personalTemplateData.YouTubeURL = makeAbs("/api/v1/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {v}})
|
||||
personalTemplateData.YouTubeURL = makeAbs("/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {v}})
|
||||
}
|
||||
}
|
||||
if v := getStringField(set, "TwitterURL"); v != "" {
|
||||
personalTemplateData.TwitterURL = makeAbs("/api/v1/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {v}})
|
||||
personalTemplateData.TwitterURL = makeAbs("/email/click", url.Values{"m": {fmt.Sprintf("%d", elog.ID)}, "t": {trackTok}, "u": {v}})
|
||||
}
|
||||
|
||||
// Rewrite links in Content to include tracking (per recipient)
|
||||
frontendBase := strings.TrimSuffix(s.config.FrontendBaseURL, "/")
|
||||
publicAPIBase := strings.TrimSuffix(s.config.PublicAPIBaseURL, "/")
|
||||
if strings.TrimSpace(personalTemplateData.Content) != "" {
|
||||
personalTemplateData.Content = rewriteLinksForTracking(personalTemplateData.Content, makeAbs, int(elog.ID), trackTok, frontendBase, publicAPIBase)
|
||||
}
|
||||
|
||||
emailData := &EmailData{
|
||||
|
||||
+9
-1
@@ -3,6 +3,7 @@ package utils
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -20,7 +21,14 @@ type JWTClaims struct {
|
||||
|
||||
// GenerateJWT generates a new JWT token for the given user
|
||||
func GenerateJWT(userID uint, email, role string) (string, error) {
|
||||
expirationTime := time.Now().Add(24 * time.Hour)
|
||||
// Respect configurable expiration via JWT_EXPIRATION_HOURS; default 24h
|
||||
expHours := 24
|
||||
if v := os.Getenv("JWT_EXPIRATION_HOURS"); v != "" {
|
||||
if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 24*365 {
|
||||
expHours = n
|
||||
}
|
||||
}
|
||||
expirationTime := time.Now().Add(time.Duration(expHours) * time.Hour)
|
||||
|
||||
claims := &JWTClaims{
|
||||
UserID: userID,
|
||||
|
||||
Reference in New Issue
Block a user