This commit is contained in:
Tomas Dvorak
2025-10-23 22:26:50 +02:00
parent 63700eedb2
commit 70ea0c3c91
75 changed files with 3337 additions and 1160 deletions
+90 -10
View File
@@ -1,6 +1,7 @@
package controllers
import (
"crypto/rand"
"fmt"
"net/http"
"net/url"
@@ -26,6 +27,32 @@ type ContactController struct {
emailService email.EmailService
}
// GetNewsletterTokenForUser returns a short-lived newsletter preferences token for the authenticated user's email
// GET /api/v1/newsletter/token/me (auth required)
func (cc *ContactController) GetNewsletterTokenForUser(c *gin.Context) {
u, ok := c.Get("user")
if !ok || u == nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Not authenticated"})
return
}
user := u.(*models.User)
email := strings.TrimSpace(strings.ToLower(user.Email))
if email == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "User email not available"})
return
}
// Generate a 24h token for managing newsletter preferences
token, err := utils.GenerateSubscriberToken(email, 60*24)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate token"})
return
}
c.JSON(http.StatusOK, gin.H{"token": token})
}
// SendNewsletterDigest builds and sends a digest newsletter based on a template type (admin only)
// POST /api/v1/admin/newsletter/send-digest { type: "blogs|events|matches|scores|weekly", competitions?: "ABC, DEF" }
func (cc *ContactController) SendNewsletterDigest(c *gin.Context) {
@@ -921,18 +948,71 @@ func (cc *ContactController) SubscribeToNewsletter(c *gin.Context) {
return
}
// Generate a subscriber token to include in follow-up emails (preferences links)
token, _ := utils.GenerateSubscriberToken(subscription.Email, 60*24) // 1 day
baseFE := strings.TrimSuffix(config.AppConfig.FrontendBaseURL, "/")
setupURL := baseFE + "/newsletter/setup?token=" + url.QueryEscape(token)
unsubscribeURL := baseFE + "/newsletter/preferences?token=" + url.QueryEscape(token)
// Auto-create fan user account if not exists
var existingUser models.User
if err := cc.DB.Where("LOWER(email) = LOWER(?)", subscription.Email).First(&existingUser).Error; err == gorm.ErrRecordNotFound {
// Generate a strong random password (16 chars, mixed set)
genPassword := func(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyz"
const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
const digits = "0123456789"
const symbols = "!@#$%^&*()-_=+[]{}?"
pool := letters + upper + digits + symbols
b := make([]byte, n)
for i := 0; i < n; i++ {
var rb [1]byte
if _, err := rand.Read(rb[:]); err != nil {
b[i] = pool[(time.Now().UnixNano()+int64(i))%int64(len(pool))]
continue
}
b[i] = pool[int(rb[0])%len(pool)]
}
return string(b)
}
plain := genPassword(16)
hashed, hErr := utils.HashPassword(plain)
if hErr == nil {
u := models.User{
Email: strings.TrimSpace(strings.ToLower(subscription.Email)),
Password: hashed,
FirstName: "",
LastName: "",
Role: "fan",
IsActive: true,
}
if err := cc.DB.Create(&u).Error; err != nil {
logger.Error("Failed to auto-create fan user for newsletter: %v", err)
} else {
// Send credentials email
data := map[string]interface{}{
"Email": subscription.Email,
"Password": plain,
"LoginURL": baseFE + "/login",
"ResetURL": baseFE + "/forgot-password",
"ManageURL": setupURL,
"UnsubscribeURL": unsubscribeURL,
}
credEmail := &email.EmailData{
Subject: "Váš fan účet byl vytvořen",
To: []string{subscription.Email},
Template: "fan_account_created",
Data: data,
}
if err := cc.emailService.SendEmail(credEmail); err != nil {
logger.Error("Failed to send fan account created email: %v", err)
}
}
}
}
// Send setup email (link with token) AND welcome introduction email in goroutines
go func() {
// Generate token and build setup + unsubscribe URLs
token, tErr := utils.GenerateSubscriberToken(subscription.Email, 60*24) // 1 day
if tErr != nil {
logger.Error("Failed to generate subscriber token: %v", tErr)
return
}
baseFE := strings.TrimSuffix(config.AppConfig.FrontendBaseURL, "/")
setupURL := baseFE + "/newsletter/setup?token=" + url.QueryEscape(token)
unsubscribeURL := baseFE + "/newsletter/preferences?token=" + url.QueryEscape(token)
// 1) Setup email
setupEmail := &email.EmailData{
Subject: "Nastavte svůj newsletter",