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

@@ -191,6 +191,9 @@ func (s *NotificationService) UpdatePreferences(userID uint, req *UpdatePreferen
if req.WarrantyExpiring != nil {
prefs.WarrantyExpiring = *req.WarrantyExpiring
}
if req.DailyDigest != nil {
prefs.DailyDigest = *req.DailyDigest
}
if req.EmailTaskCompleted != nil {
prefs.EmailTaskCompleted = *req.EmailTaskCompleted
}
@@ -206,6 +209,9 @@ func (s *NotificationService) UpdatePreferences(userID uint, req *UpdatePreferen
if req.WarrantyExpiringHour != nil {
prefs.WarrantyExpiringHour = req.WarrantyExpiringHour
}
if req.DailyDigestHour != nil {
prefs.DailyDigestHour = req.DailyDigestHour
}
if err := s.notificationRepo.UpdatePreferences(prefs); err != nil {
return nil, err
@@ -374,6 +380,7 @@ type NotificationPreferencesResponse 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"`
@@ -382,21 +389,24 @@ type NotificationPreferencesResponse 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"`
}
// NewNotificationPreferencesResponse creates a NotificationPreferencesResponse
func NewNotificationPreferencesResponse(p *models.NotificationPreference) *NotificationPreferencesResponse {
return &NotificationPreferencesResponse{
TaskDueSoon: p.TaskDueSoon,
TaskOverdue: p.TaskOverdue,
TaskCompleted: p.TaskCompleted,
TaskAssigned: p.TaskAssigned,
ResidenceShared: p.ResidenceShared,
WarrantyExpiring: p.WarrantyExpiring,
EmailTaskCompleted: p.EmailTaskCompleted,
TaskDueSoonHour: p.TaskDueSoonHour,
TaskOverdueHour: p.TaskOverdueHour,
TaskDueSoon: p.TaskDueSoon,
TaskOverdue: p.TaskOverdue,
TaskCompleted: p.TaskCompleted,
TaskAssigned: p.TaskAssigned,
ResidenceShared: p.ResidenceShared,
WarrantyExpiring: p.WarrantyExpiring,
DailyDigest: p.DailyDigest,
EmailTaskCompleted: p.EmailTaskCompleted,
TaskDueSoonHour: p.TaskDueSoonHour,
TaskOverdueHour: p.TaskOverdueHour,
WarrantyExpiringHour: p.WarrantyExpiringHour,
DailyDigestHour: p.DailyDigestHour,
}
}
@@ -408,6 +418,7 @@ type UpdatePreferencesRequest 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"`
@@ -417,6 +428,7 @@ type UpdatePreferencesRequest 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"`
}
// DeviceResponse represents a device in API response