Add per-user notification time preferences
Allow users to customize when they receive notification reminders: - Add task_due_soon_hour, task_overdue_hour, warranty_expiring_hour fields - Store times in UTC, clients convert to/from local timezone - Worker runs hourly, queries only users scheduled for that hour - Early exit optimization when no users need notifications - Admin UI displays custom notification times 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -195,6 +195,18 @@ func (s *NotificationService) UpdatePreferences(userID uint, req *UpdatePreferen
|
||||
prefs.EmailTaskCompleted = *req.EmailTaskCompleted
|
||||
}
|
||||
|
||||
// Update notification times (can be set to nil to use system default)
|
||||
// Note: We update if the field is present in the request (including null values)
|
||||
if req.TaskDueSoonHour != nil {
|
||||
prefs.TaskDueSoonHour = req.TaskDueSoonHour
|
||||
}
|
||||
if req.TaskOverdueHour != nil {
|
||||
prefs.TaskOverdueHour = req.TaskOverdueHour
|
||||
}
|
||||
if req.WarrantyExpiringHour != nil {
|
||||
prefs.WarrantyExpiringHour = req.WarrantyExpiringHour
|
||||
}
|
||||
|
||||
if err := s.notificationRepo.UpdatePreferences(prefs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -365,18 +377,26 @@ type NotificationPreferencesResponse struct {
|
||||
|
||||
// Email preferences
|
||||
EmailTaskCompleted bool `json:"email_task_completed"`
|
||||
|
||||
// Custom notification times (UTC hour 0-23, null means use system default)
|
||||
TaskDueSoonHour *int `json:"task_due_soon_hour"`
|
||||
TaskOverdueHour *int `json:"task_overdue_hour"`
|
||||
WarrantyExpiringHour *int `json:"warranty_expiring_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,
|
||||
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,
|
||||
WarrantyExpiringHour: p.WarrantyExpiringHour,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,6 +411,12 @@ type UpdatePreferencesRequest struct {
|
||||
|
||||
// Email preferences
|
||||
EmailTaskCompleted *bool `json:"email_task_completed"`
|
||||
|
||||
// Custom notification times (UTC hour 0-23)
|
||||
// Use a special wrapper to differentiate between "not sent" and "set to null"
|
||||
TaskDueSoonHour *int `json:"task_due_soon_hour"`
|
||||
TaskOverdueHour *int `json:"task_overdue_hour"`
|
||||
WarrantyExpiringHour *int `json:"warranty_expiring_hour"`
|
||||
}
|
||||
|
||||
// DeviceResponse represents a device in API response
|
||||
|
||||
Reference in New Issue
Block a user