dev day #90 🥳

This commit is contained in:
Tomas Dvorak
2025-11-12 20:31:37 +01:00
parent 8762bde4bf
commit f3db65d350
103 changed files with 4053 additions and 2189 deletions
+141 -10
View File
@@ -33,16 +33,93 @@ func (cc *ContactController) GetContactMessages(c *gin.Context) {
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
return
}
// Pagination
page, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("page", "1")))
limit, _ := strconv.Atoi(strings.TrimSpace(c.DefaultQuery("limit", "50")))
if page <= 0 { page = 1 }
if limit <= 0 || limit > 200 { limit = 50 }
items, total, err := models.GetContactMessages(cc.DB, page, limit)
if err != nil {
if page <= 0 {
page = 1
}
if limit <= 0 || limit > 200 {
limit = 50
}
// Filters
search := strings.TrimSpace(c.DefaultQuery("search", ""))
isReadParam := strings.TrimSpace(c.DefaultQuery("isRead", ""))
var isRead *bool
if isReadParam != "" {
v := strings.ToLower(isReadParam)
if v == "true" || v == "1" {
t := true
isRead = &t
} else if v == "false" || v == "0" {
f := false
isRead = &f
}
}
// Sorting (map UI fields to DB columns)
sortBy := strings.TrimSpace(c.DefaultQuery("sortBy", "createdAt"))
sortOrder := strings.ToLower(strings.TrimSpace(c.DefaultQuery("sortOrder", "desc")))
if sortOrder != "asc" {
sortOrder = "desc"
}
sortField := "created_at"
switch sortBy {
case "name":
sortField = "name"
case "email":
sortField = "email"
case "subject":
sortField = "subject"
case "createdAt", "created_at":
sortField = "created_at"
}
// Build query
q := cc.DB.Model(&models.ContactMessage{})
if search != "" {
s := "%" + strings.ToLower(search) + "%"
q = q.Where("LOWER(name) LIKE ? OR LOWER(email) LIKE ? OR LOWER(subject) LIKE ? OR LOWER(message) LIKE ?", s, s, s, s)
}
if isRead != nil {
q = q.Where("is_read = ?", *isRead)
}
// Count total
var total int64
if err := q.Count(&total).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch messages"})
return
}
c.JSON(http.StatusOK, gin.H{"items": items, "total": total, "page": page, "page_size": limit})
// Fetch page
var items []models.ContactMessage
offset := (page - 1) * limit
if err := q.Order(sortField+" "+sortOrder).Offset(offset).Limit(limit).Find(&items).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch messages"})
return
}
// Compute pages (ceil)
pages := 1
if limit > 0 {
pages = (int(total) + limit - 1) / limit
if pages == 0 {
pages = 1
}
}
c.JSON(http.StatusOK, gin.H{
"data": items,
"pagination": gin.H{
"total": total,
"page": page,
"limit": limit,
"pages": pages,
},
})
}
// MarkMessageAsRead marks a message as read (admin)
@@ -75,19 +152,36 @@ func (cc *ContactController) recalcNewsletterAutomationEnabled() {
_ = cc.DB.Model(&models.NewsletterSubscription{}).Where("is_active = ?", true).Count(&active).Error
enabled := active > 0
// Persist flag
// Persist flag and, when enabling for the first time, ensure weekly digest is active with sane defaults
var s models.Settings
_ = cc.DB.First(&s).Error
if s.ID == 0 {
s = models.Settings{}
}
changed := false
if s.NewsletterEnabled != enabled {
s.NewsletterEnabled = enabled
if s.ID == 0 {
_ = cc.DB.Create(&s).Error
} else {
_ = cc.DB.Save(&s).Error
changed = true
}
if enabled {
// Auto-activate weekly digest and preset schedule if not configured
if !s.EnableWeekly {
s.EnableWeekly = true
changed = true
}
if strings.TrimSpace(s.NewsletterWeeklyDay) == "" {
s.NewsletterWeeklyDay = "sun"
changed = true
}
if s.NewsletterWeeklyHour < 0 || s.NewsletterWeeklyHour > 23 {
s.NewsletterWeeklyHour = 9
changed = true
}
}
if s.ID == 0 {
_ = cc.DB.Create(&s).Error
} else if changed {
_ = cc.DB.Save(&s).Error
}
// Update runtime
@@ -736,6 +830,8 @@ func (cc *ContactController) GetNewsletterStatus(c *gin.Context) {
var total, active int64
cc.DB.Model(&models.NewsletterSubscription{}).Count(&total)
cc.DB.Model(&models.NewsletterSubscription{}).Where("is_active = ?", true).Count(&active)
var s models.Settings
_ = cc.DB.First(&s).Error
var subs []models.NewsletterSubscription
_ = cc.DB.Where("is_active = ?", true).Limit(20).Find(&subs).Error
sample := make([]string, 0, len(subs))
@@ -751,6 +847,31 @@ func (cc *ContactController) GetNewsletterStatus(c *gin.Context) {
}
}
next := time.Now().Add(interval)
// Compute next scheduled weekly time (exact), using settings (default Sun 09:00)
weeklyDay := strings.ToLower(strings.TrimSpace(s.NewsletterWeeklyDay))
if weeklyDay == "" { weeklyDay = "sun" }
weeklyHour := s.NewsletterWeeklyHour
if weeklyHour < 0 || weeklyHour > 23 { weeklyHour = 9 }
// find next occurrence
now := time.Now()
target := time.Date(now.Year(), now.Month(), now.Day(), weeklyHour, 0, 0, 0, now.Location())
toWD := func(d string) time.Weekday {
switch d {
case "mon": return time.Monday
case "tue": return time.Tuesday
case "wed": return time.Wednesday
case "thu": return time.Thursday
case "fri": return time.Friday
case "sat": return time.Saturday
default: return time.Sunday
}
}
for i := 0; i < 8; i++ {
if target.Weekday() == toWD(weeklyDay) && target.After(now) {
break
}
target = target.Add(24 * time.Hour)
}
c.JSON(http.StatusOK, gin.H{
"total_subscribers": total,
"active_subscribers": active,
@@ -758,6 +879,16 @@ func (cc *ContactController) GetNewsletterStatus(c *gin.Context) {
"interval_minutes": int(interval.Minutes()),
"next_approximate": next,
"newsletter_enabled": config.AppConfig != nil && config.AppConfig.NewsletterEnabled,
// Scheduling detail
"weekly_enabled": s.EnableWeekly,
"weekly_day": weeklyDay,
"weekly_hour": weeklyHour,
"weekly_next_scheduled": target,
"matches_enabled": s.EnableMatchReminders,
"reminder_lead_hours": s.NewsletterReminderLeadHours,
"results_enabled": s.EnableResults,
"quiet_start": s.NewsletterQuietStart,
"quiet_end": s.NewsletterQuietEnd,
})
}