Add Daily Digest notification preferences with custom time support

- Add daily_digest boolean and daily_digest_hour fields to NotificationPreference model
- Update HandleDailyDigest to check user preferences and custom notification times
- Change Daily Digest scheduler to run hourly (supports per-user custom times)
- Update notification service DTOs for new fields
- Add Daily Digest toggle and custom time to admin notification prefs page
- Fix notification handlers to only notify users at their designated hour

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-07 22:48:18 -06:00
parent 0ea0c766ea
commit f88409cfb4
7 changed files with 134 additions and 71 deletions

View File

@@ -33,6 +33,7 @@ type NotificationPrefResponse struct {
TaskAssigned bool `json:"task_assigned"`
ResidenceShared bool `json:"residence_shared"`
WarrantyExpiring bool `json:"warranty_expiring"`
DailyDigest bool `json:"daily_digest"`
// Email preferences
EmailTaskCompleted bool `json:"email_task_completed"`
@@ -41,6 +42,7 @@ type NotificationPrefResponse struct {
TaskDueSoonHour *int `json:"task_due_soon_hour"`
TaskOverdueHour *int `json:"task_overdue_hour"`
WarrantyExpiringHour *int `json:"warranty_expiring_hour"`
DailyDigestHour *int `json:"daily_digest_hour"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
@@ -117,10 +119,12 @@ func (h *AdminNotificationPrefsHandler) List(c *gin.Context) {
TaskAssigned: pref.TaskAssigned,
ResidenceShared: pref.ResidenceShared,
WarrantyExpiring: pref.WarrantyExpiring,
DailyDigest: pref.DailyDigest,
EmailTaskCompleted: pref.EmailTaskCompleted,
TaskDueSoonHour: pref.TaskDueSoonHour,
TaskOverdueHour: pref.TaskOverdueHour,
WarrantyExpiringHour: pref.WarrantyExpiringHour,
DailyDigestHour: pref.DailyDigestHour,
CreatedAt: pref.CreatedAt.Format("2006-01-02T15:04:05Z"),
UpdatedAt: pref.UpdatedAt.Format("2006-01-02T15:04:05Z"),
}
@@ -161,10 +165,12 @@ func (h *AdminNotificationPrefsHandler) Get(c *gin.Context) {
TaskAssigned: pref.TaskAssigned,
ResidenceShared: pref.ResidenceShared,
WarrantyExpiring: pref.WarrantyExpiring,
DailyDigest: pref.DailyDigest,
EmailTaskCompleted: pref.EmailTaskCompleted,
TaskDueSoonHour: pref.TaskDueSoonHour,
TaskOverdueHour: pref.TaskOverdueHour,
WarrantyExpiringHour: pref.WarrantyExpiringHour,
DailyDigestHour: pref.DailyDigestHour,
CreatedAt: pref.CreatedAt.Format("2006-01-02T15:04:05Z"),
UpdatedAt: pref.UpdatedAt.Format("2006-01-02T15:04:05Z"),
})
@@ -178,6 +184,7 @@ type UpdateNotificationPrefRequest struct {
TaskAssigned *bool `json:"task_assigned"`
ResidenceShared *bool `json:"residence_shared"`
WarrantyExpiring *bool `json:"warranty_expiring"`
DailyDigest *bool `json:"daily_digest"`
// Email preferences
EmailTaskCompleted *bool `json:"email_task_completed"`
@@ -186,6 +193,7 @@ type UpdateNotificationPrefRequest struct {
TaskDueSoonHour *int `json:"task_due_soon_hour"`
TaskOverdueHour *int `json:"task_overdue_hour"`
WarrantyExpiringHour *int `json:"warranty_expiring_hour"`
DailyDigestHour *int `json:"daily_digest_hour"`
}
// Update handles PUT /api/admin/notification-prefs/:id
@@ -231,6 +239,9 @@ func (h *AdminNotificationPrefsHandler) Update(c *gin.Context) {
if req.WarrantyExpiring != nil {
pref.WarrantyExpiring = *req.WarrantyExpiring
}
if req.DailyDigest != nil {
pref.DailyDigest = *req.DailyDigest
}
if req.EmailTaskCompleted != nil {
pref.EmailTaskCompleted = *req.EmailTaskCompleted
}
@@ -245,6 +256,9 @@ func (h *AdminNotificationPrefsHandler) Update(c *gin.Context) {
if req.WarrantyExpiringHour != nil {
pref.WarrantyExpiringHour = req.WarrantyExpiringHour
}
if req.DailyDigestHour != nil {
pref.DailyDigestHour = req.DailyDigestHour
}
if err := h.db.Save(&pref).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update notification preference"})
@@ -265,10 +279,12 @@ func (h *AdminNotificationPrefsHandler) Update(c *gin.Context) {
TaskAssigned: pref.TaskAssigned,
ResidenceShared: pref.ResidenceShared,
WarrantyExpiring: pref.WarrantyExpiring,
DailyDigest: pref.DailyDigest,
EmailTaskCompleted: pref.EmailTaskCompleted,
TaskDueSoonHour: pref.TaskDueSoonHour,
TaskOverdueHour: pref.TaskOverdueHour,
WarrantyExpiringHour: pref.WarrantyExpiringHour,
DailyDigestHour: pref.DailyDigestHour,
CreatedAt: pref.CreatedAt.Format("2006-01-02T15:04:05Z"),
UpdatedAt: pref.UpdatedAt.Format("2006-01-02T15:04:05Z"),
})
@@ -327,10 +343,12 @@ func (h *AdminNotificationPrefsHandler) GetByUser(c *gin.Context) {
TaskAssigned: pref.TaskAssigned,
ResidenceShared: pref.ResidenceShared,
WarrantyExpiring: pref.WarrantyExpiring,
DailyDigest: pref.DailyDigest,
EmailTaskCompleted: pref.EmailTaskCompleted,
TaskDueSoonHour: pref.TaskDueSoonHour,
TaskOverdueHour: pref.TaskOverdueHour,
WarrantyExpiringHour: pref.WarrantyExpiringHour,
DailyDigestHour: pref.DailyDigestHour,
CreatedAt: pref.CreatedAt.Format("2006-01-02T15:04:05Z"),
UpdatedAt: pref.UpdatedAt.Format("2006-01-02T15:04:05Z"),
})