Replace old notification handlers with smart reminder system

- Add TaskReminderLog to AutoMigrate (table was missing)
- Remove HandleTaskReminder and HandleOverdueReminder registrations
- Register HandleSmartReminder which uses frequency-aware scheduling
  and tracks sent reminders to prevent duplicates

The old handlers sent ALL overdue tasks daily regardless of age.
Smart reminder tapers off (daily for 3 days, then every 3 days, stops at 14).

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-22 20:19:40 -06:00
parent 37a04db82e
commit 8962850003
2 changed files with 8 additions and 13 deletions

View File

@@ -134,8 +134,7 @@ func main() {
// Create Asynq mux and register handlers // Create Asynq mux and register handlers
mux := asynq.NewServeMux() mux := asynq.NewServeMux()
mux.HandleFunc(jobs.TypeTaskReminder, jobHandler.HandleTaskReminder) mux.HandleFunc(jobs.TypeSmartReminder, jobHandler.HandleSmartReminder)
mux.HandleFunc(jobs.TypeOverdueReminder, jobHandler.HandleOverdueReminder)
mux.HandleFunc(jobs.TypeDailyDigest, jobHandler.HandleDailyDigest) mux.HandleFunc(jobs.TypeDailyDigest, jobHandler.HandleDailyDigest)
mux.HandleFunc(jobs.TypeSendEmail, jobHandler.HandleSendEmail) mux.HandleFunc(jobs.TypeSendEmail, jobHandler.HandleSendEmail)
mux.HandleFunc(jobs.TypeSendPush, jobHandler.HandleSendPush) mux.HandleFunc(jobs.TypeSendPush, jobHandler.HandleSendPush)
@@ -144,18 +143,13 @@ func main() {
// Start scheduler for periodic tasks // Start scheduler for periodic tasks
scheduler := asynq.NewScheduler(redisOpt, nil) scheduler := asynq.NewScheduler(redisOpt, nil)
// Schedule task reminder notifications (runs every hour to support per-user custom times) // Schedule smart reminder notifications (runs every hour to support per-user custom times)
// The job handler filters users based on their preferred notification hour // Replaces old task reminder and overdue reminder with frequency-aware system
if _, err := scheduler.Register("0 * * * *", asynq.NewTask(jobs.TypeTaskReminder, nil)); err != nil { // Uses TaskReminderLog to prevent duplicate notifications
log.Fatal().Err(err).Msg("Failed to register task reminder job") if _, err := scheduler.Register("0 * * * *", asynq.NewTask(jobs.TypeSmartReminder, nil)); err != nil {
log.Fatal().Err(err).Msg("Failed to register smart reminder job")
} }
log.Info().Str("cron", "0 * * * *").Int("default_hour", cfg.Worker.TaskReminderHour).Msg("Registered task reminder job (runs hourly for per-user times)") log.Info().Str("cron", "0 * * * *").Int("default_hour", cfg.Worker.TaskReminderHour).Msg("Registered smart reminder job (runs hourly for per-user times)")
// Schedule overdue reminder (runs every hour to support per-user custom times)
if _, err := scheduler.Register("0 * * * *", asynq.NewTask(jobs.TypeOverdueReminder, nil)); err != nil {
log.Fatal().Err(err).Msg("Failed to register overdue reminder job")
}
log.Info().Str("cron", "0 * * * *").Int("default_hour", cfg.Worker.OverdueReminderHour).Msg("Registered overdue reminder job (runs hourly for per-user times)")
// Schedule daily digest (runs every hour to support per-user custom times) // Schedule daily digest (runs every hour to support per-user custom times)
// The job handler filters users based on their preferred notification hour // The job handler filters users based on their preferred notification hour

View File

@@ -144,6 +144,7 @@ func Migrate() error {
&models.NotificationPreference{}, &models.NotificationPreference{},
&models.APNSDevice{}, &models.APNSDevice{},
&models.GCMDevice{}, &models.GCMDevice{},
&models.TaskReminderLog{}, // Smart reminder tracking
// Subscription tables // Subscription tables
&models.SubscriptionSettings{}, &models.SubscriptionSettings{},