This commit is contained in:
Tomas Dvorak
2025-11-02 01:04:02 +01:00
parent ac886502e0
commit b9cea0cd77
153 changed files with 43713 additions and 1700 deletions
+133 -94
View File
@@ -28,6 +28,78 @@ type ContactController struct {
emailService email.EmailService
}
func NewContactController(db *gorm.DB, emailService email.EmailService) *ContactController {
return &ContactController{DB: db, emailService: emailService}
}
type NewsletterSubscriptionRequest struct {
Email string `json:"email" binding:"required,email"`
Preferences map[string]bool `json:"preferences"`
}
// SubmitContactForm handles public contact form submissions
// POST /api/v1/contact
func (cc *ContactController) SubmitContactForm(c *gin.Context) {
var input struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required"`
Subject string `json:"subject" binding:"required"`
Message string `json:"message" binding:"required"`
}
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid payload"})
return
}
name := strings.TrimSpace(input.Name)
emailStr := strings.TrimSpace(input.Email)
subject := strings.TrimSpace(input.Subject)
message := strings.TrimSpace(input.Message)
if name == "" || emailStr == "" || subject == "" || message == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "All fields are required"})
return
}
if !strings.Contains(emailStr, "@") {
c.JSON(http.StatusBadRequest, gin.H{"error": "Valid email is required"})
return
}
if s, _ := services.FilterBadWords(subject); s != "" {
subject = s
}
if m, _ := services.FilterBadWords(message); m != "" {
message = m
}
ip := c.ClientIP()
ua := c.GetHeader("User-Agent")
msg := models.ContactMessage{
Name: name,
Email: emailStr,
Subject: subject,
Message: message,
Source: "contact",
IPAddress: ip,
UserAgent: ua,
}
if err := cc.DB.Create(&msg).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save message"})
return
}
_ = cc.emailService.SendContactForm(&email.ContactFormData{
Name: name,
Email: emailStr,
Subject: subject,
Message: message,
IPAddress: ip,
UserAgent: ua,
})
c.JSON(http.StatusOK, gin.H{"message": "Message received", "id": msg.ID})
}
func (cc *ContactController) AdminSmtpTest(c *gin.Context) {
if c.GetString("userRole") != "admin" {
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
@@ -387,11 +459,24 @@ func (cc *ContactController) SendNewsletterTest(c *gin.Context) {
for _, r := range recipients { _ = cc.emailService.SendNewsletterWelcomeBack(&email.NewsletterWelcomeBackData{Email: r}) }
case "setup":
for _, r := range recipients {
token, tErr := utils.GenerateSubscriberToken(r, 60*24)
if tErr != nil { logger.Error("Failed to generate token for setup test: %v", tErr); continue }
token, _ := utils.GenerateSubscriberToken(r, 60*24) // 1 day
baseFE := strings.TrimSuffix(config.AppConfig.FrontendBaseURL, "/")
setupURL := baseFE + "/newsletter/setup?token=" + url.QueryEscape(token)
setupEmail := &email.EmailData{Subject: "Test: Nastavte svůj newsletter", To: []string{r}, Template: "newsletter_setup", Data: struct{ SetupURL string }{SetupURL: setupURL}}
// Engagement: if a user already exists for this email, award points
var subUser models.User
if err := cc.DB.Where("LOWER(email) = LOWER(?)", r).First(&subUser).Error; err == nil && subUser.ID != 0 {
es := services.NewEngagementService(cc.DB)
_, _ = es.AwardPoints(subUser.ID, 12, "newsletter_subscribe", map[string]interface{}{"email": r})
_ = es.CheckAndAwardAchievements(subUser.ID)
}
setupEmail := &email.EmailData{
Subject: "Nastavte svůj newsletter",
To: []string{r},
Template: "newsletter_setup",
Data: struct{ SetupURL string }{SetupURL: setupURL},
}
_ = cc.emailService.SendEmail(setupEmail)
}
case "blogs", "events", "matches", "scores", "weekly":
@@ -409,85 +494,6 @@ func (cc *ContactController) SendNewsletterTest(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Test email(s) sent", "recipients": recipients, "type": t})
}
func NewContactController(db *gorm.DB, emailService email.EmailService) *ContactController {
return &ContactController{
DB: db,
emailService: emailService,
}
}
type ContactFormRequest struct {
Name string `json:"name" binding:"required"`
Email string `json:"email" binding:"required,email"`
Subject string `json:"subject" binding:"required"`
Message string `json:"message" binding:"required"`
Source string `json:"source"` // e.g., "contact", "sponsor"
}
// SubmitContactForm handles contact form submissions
// @Summary Submit contact form
// @Description Handles contact form submissions and sends an email notification
// @Tags contact
// @Accept json
// @Produce json
// @Param input body ContactFormRequest true "Contact form data"
// @Success 200 {object} map[string]string
// @Failure 400 {object} map[string]string
// @Router /api/v1/contact [post]
func (cc *ContactController) SubmitContactForm(c *gin.Context) {
var input ContactFormRequest
if err := c.ShouldBindJSON(&input); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// Normalize source field
source := strings.TrimSpace(input.Source)
if source == "" {
source = "contact"
}
// Save to database
contactMessage := models.ContactMessage{
Name: input.Name,
Email: input.Email,
Subject: input.Subject,
Message: input.Message,
Source: source,
IPAddress: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
IsRead: false,
}
if err := cc.DB.Create(&contactMessage).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to save contact message"})
return
}
// Send email notification asynchronously to prevent frontend timeout
go func() {
emailData := &email.ContactFormData{
Name: input.Name,
Email: input.Email,
Subject: input.Subject,
Message: input.Message,
IPAddress: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
}
if err := cc.emailService.SendContactForm(emailData); err != nil {
logger.Error("Failed to send contact form email: %v", err)
}
}()
c.JSON(http.StatusOK, gin.H{"message": "Your message has been sent successfully"})
}
type NewsletterSubscriptionRequest struct {
Email string `json:"email" binding:"required,email"`
Preferences map[string]bool `json:"preferences"`
}
// SubscribeToNewsletter handles newsletter subscriptions
// @Summary Subscribe to newsletter
// @Description Handles newsletter subscription requests
@@ -527,6 +533,14 @@ func (cc *ContactController) SubscribeToNewsletter(c *gin.Context) {
return
}
// Engagement: award points to matching user (if exists)
var u models.User
if err := cc.DB.Where("LOWER(email) = LOWER(?)", subscription.Email).First(&u).Error; err == nil && u.ID != 0 {
es := services.NewEngagementService(cc.DB)
_, _ = es.AwardPoints(u.ID, 12, "newsletter_subscribe", map[string]interface{}{"email": subscription.Email})
_ = es.CheckAndAwardAchievements(u.ID)
}
// Send welcome back email in a goroutine
go func(sub models.NewsletterSubscription) {
manageURL := ""
@@ -579,18 +593,20 @@ func (cc *ContactController) SubscribeToNewsletter(c *gin.Context) {
UpdatedAt: time.Now(),
}
if err := cc.DB.Create(&subscription).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to subscribe to newsletter"})
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)
// 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
// Engagement: if a user already exists for this email, award points
var u models.User
if err := cc.DB.Where("LOWER(email) = LOWER(?)", subscription.Email).First(&u).Error; err == nil && u.ID != 0 {
es := services.NewEngagementService(cc.DB)
_, _ = es.AwardPointsCapped(u.ID, 12, "newsletter_subscribe", map[string]interface{}{"email": subscription.Email})
_ = es.CheckAndAwardAchievements(u.ID)
}
// 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)
@@ -643,6 +659,10 @@ func (cc *ContactController) SubscribeToNewsletter(c *gin.Context) {
if err := cc.emailService.SendEmail(credEmail); err != nil {
logger.Error("Failed to send fan account created email: %v", err)
}
// Engagement: award points to new user
es := services.NewEngagementService(cc.DB)
_, _ = es.AwardPoints(u.ID, 12, "newsletter_subscribe", map[string]interface{}{"email": subscription.Email})
_ = es.CheckAndAwardAchievements(u.ID)
}
}
}
@@ -1233,7 +1253,26 @@ func (cc *ContactController) ForwardAllContactMessages(c *gin.Context) {
}
if len(messages) == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "No messages to forward"})
// Even if there are no messages now, ensure auto-forward is configured
if !input.SaveDefault {
var set models.Settings
if err := cc.DB.First(&set).Error; err != nil {
if err == gorm.ErrRecordNotFound {
set = models.Settings{}
set.ContactForwardEnabled = true
set.ContactForwardList = strings.Join(recipients, ", ")
_ = cc.DB.Create(&set).Error
}
} else {
set.ContactForwardEnabled = true
set.ContactForwardList = strings.Join(recipients, ", ")
_ = cc.DB.Save(&set).Error
}
}
c.JSON(http.StatusOK, gin.H{
"message": "Automatické přeposílání je nastaveno. Zatím nejsou žádné zprávy k přeposlání.",
"count": 0,
})
return
}