Add clear stuck jobs admin feature and simplify worker scheduling

- Add POST /api/admin/settings/clear-stuck-jobs endpoint to clear
  stuck/failed asynq worker jobs from Redis (retry queue, archived,
  orphaned task metadata)
- Add "Clear Stuck Jobs" button to admin settings UI
- Remove TASK_REMINDER_MINUTE config - all jobs now run at minute 0
- Simplify formatCron to only take hour parameter
- Update default notification times to CST-friendly hours

🤖 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-06 23:05:48 -06:00
parent 3b9e37d12b
commit af87bd943e
6 changed files with 150 additions and 17 deletions

View File

@@ -2,10 +2,10 @@ package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"time"
"github.com/hibiken/asynq"
"github.com/rs/zerolog/log"
@@ -105,21 +105,21 @@ func main() {
scheduler := asynq.NewScheduler(redisOpt, nil)
// Schedule task reminder notifications
reminderCron := formatCron(cfg.Worker.TaskReminderHour, cfg.Worker.TaskReminderMinute)
reminderCron := formatCron(cfg.Worker.TaskReminderHour)
if _, err := scheduler.Register(reminderCron, asynq.NewTask(jobs.TypeTaskReminder, nil)); err != nil {
log.Fatal().Err(err).Msg("Failed to register task reminder job")
}
log.Info().Str("cron", reminderCron).Msg("Registered task reminder job")
// Schedule overdue reminder at 9 AM UTC
overdueCron := formatCron(cfg.Worker.OverdueReminderHour, 0)
// Schedule overdue reminder
overdueCron := formatCron(cfg.Worker.OverdueReminderHour)
if _, err := scheduler.Register(overdueCron, asynq.NewTask(jobs.TypeOverdueReminder, nil)); err != nil {
log.Fatal().Err(err).Msg("Failed to register overdue reminder job")
}
log.Info().Str("cron", overdueCron).Msg("Registered overdue reminder job")
// Schedule daily digest at 11 AM UTC
dailyCron := formatCron(cfg.Worker.DailyNotifHour, 0)
// Schedule daily digest
dailyCron := formatCron(cfg.Worker.DailyNotifHour)
if _, err := scheduler.Register(dailyCron, asynq.NewTask(jobs.TypeDailyDigest, nil)); err != nil {
log.Fatal().Err(err).Msg("Failed to register daily digest job")
}
@@ -154,7 +154,7 @@ func main() {
log.Info().Msg("Worker stopped")
}
// formatCron creates a cron expression for a specific hour and minute (UTC)
func formatCron(hour, minute int) string {
return time.Date(0, 1, 1, hour, minute, 0, 0, time.UTC).Format("4 15 * * *")
// formatCron creates a cron expression for a specific hour (runs at minute 0)
func formatCron(hour int) string {
return fmt.Sprintf("0 %02d * * *", hour)
}