Add actionable push notifications and fix recurring task completion
Features: - Add task action buttons to push notifications (complete, view, cancel, etc.) - Add button types logic for different task states (overdue, in_progress, etc.) - Implement Chain of Responsibility pattern for task categorization - Add comprehensive kanban categorization documentation Fixes: - Reset recurring task status to Pending after completion so tasks appear in correct kanban column (was staying in "In Progress") - Fix PostgreSQL EXTRACT function error in overdue notifications query - Update seed data to properly set next_due_date for recurring tasks Admin: - Add tasks list to residence detail page - Fix task edit page to properly handle all fields 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -437,3 +437,90 @@ type RegisterDeviceRequest struct {
|
||||
RegistrationID string `json:"registration_id" binding:"required"`
|
||||
Platform string `json:"platform" binding:"required,oneof=ios android"`
|
||||
}
|
||||
|
||||
// === Task Notifications with Actions ===
|
||||
|
||||
// CreateAndSendTaskNotification creates and sends a task notification with actionable buttons
|
||||
// The backend always sends full notification data - the client decides how to display
|
||||
// based on its locally cached subscription status
|
||||
func (s *NotificationService) CreateAndSendTaskNotification(
|
||||
ctx context.Context,
|
||||
userID uint,
|
||||
notificationType models.NotificationType,
|
||||
task *models.Task,
|
||||
) error {
|
||||
// Check user notification preferences
|
||||
prefs, err := s.notificationRepo.GetOrCreatePreferences(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !s.isNotificationEnabled(prefs, notificationType) {
|
||||
return nil // Skip silently
|
||||
}
|
||||
|
||||
// Build notification content - always send full data
|
||||
title := GetTaskNotificationTitle(notificationType)
|
||||
body := task.Title
|
||||
|
||||
// Get button types and iOS category based on task state
|
||||
buttonTypes := GetButtonTypesForTask(task, 30) // 30 days threshold
|
||||
iosCategoryID := GetIOSCategoryForTask(task)
|
||||
|
||||
// Build data payload - always includes full task info
|
||||
// Client decides what to display based on local subscription status
|
||||
data := map[string]interface{}{
|
||||
"task_id": task.ID,
|
||||
"task_name": task.Title,
|
||||
"residence_id": task.ResidenceID,
|
||||
"type": string(notificationType),
|
||||
"button_types": buttonTypes,
|
||||
"ios_category": iosCategoryID,
|
||||
}
|
||||
|
||||
// Create notification record
|
||||
dataJSON, _ := json.Marshal(data)
|
||||
notification := &models.Notification{
|
||||
UserID: userID,
|
||||
NotificationType: notificationType,
|
||||
Title: title,
|
||||
Body: body,
|
||||
Data: string(dataJSON),
|
||||
TaskID: &task.ID,
|
||||
}
|
||||
|
||||
if err := s.notificationRepo.Create(notification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get device tokens
|
||||
iosTokens, androidTokens, err := s.notificationRepo.GetActiveTokensForUser(userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Convert data for push payload
|
||||
pushData := make(map[string]string)
|
||||
for k, v := range data {
|
||||
switch val := v.(type) {
|
||||
case string:
|
||||
pushData[k] = val
|
||||
case uint:
|
||||
pushData[k] = strconv.FormatUint(uint64(val), 10)
|
||||
default:
|
||||
jsonVal, _ := json.Marshal(val)
|
||||
pushData[k] = string(jsonVal)
|
||||
}
|
||||
}
|
||||
pushData["notification_id"] = strconv.FormatUint(uint64(notification.ID), 10)
|
||||
|
||||
// Send push notification with actionable support
|
||||
if s.pushClient != nil {
|
||||
err = s.pushClient.SendActionableNotification(ctx, iosTokens, androidTokens, title, body, pushData, iosCategoryID)
|
||||
if err != nil {
|
||||
s.notificationRepo.SetError(notification.ID, err.Error())
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return s.notificationRepo.MarkAsSent(notification.ID)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user