Merge origin/dev (Feature 20 scheduler) into Feature 21 Home dashboard
CI / ci (push) Successful in 8m7s
CI / ci (pull_request) Successful in 8m7s

Keep both the scheduler registrations and the Home service wiring in
cmd/server, pass the scheduler wake function to plan.NewService in the
Home and server tests, and document that boot catch-up processes every
unclaimed fire time within 24 hours, oldest first (F20-02).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-10 19:46:46 -04:00
co-authored by Claude Opus 5.5
40 changed files with 4461 additions and 230 deletions
+44 -4
View File
@@ -31,6 +31,7 @@ import (
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/plan"
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/push"
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/recipes"
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/scheduler"
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/secretbox"
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/server"
"code.cubecraftlabs.com/Maniacs_Mansion/Meal-Quest/internal/shopping"
@@ -130,7 +131,12 @@ func run(cfg config.Config, logger *slog.Logger) error {
return err
}
dinnerSvc, err := dinners.NewService(db, grounder, runner, logger)
// The scheduler is made before the services whose changes wake it.
sched, err := scheduler.New(db, settings, logger)
if err != nil {
return err
}
dinnerSvc, err := dinners.NewService(db, grounder, runner, sched.Wake, logger)
if err != nil {
return err
}
@@ -152,7 +158,7 @@ func run(cfg config.Config, logger *slog.Logger) error {
return err
}
planSvc, err := plan.NewService(db, weekDrafter(ai, guard), runner, googleSvc, notifier, cfg.PublicURL, logger)
planSvc, err := plan.NewService(db, weekDrafter(ai, guard), runner, googleSvc, notifier, sched.Wake, cfg.PublicURL, logger)
if err != nil {
return err
}
@@ -193,17 +199,46 @@ func run(cfg config.Config, logger *slog.Logger) error {
return err
}
if err := sched.Register(suggestSvc.WeeklySchedule()); err != nil {
return err
}
if err := sched.Register(planSvc.DraftSchedule()); err != nil {
return err
}
for _, sch := range planSvc.ReminderSchedules() {
if err := sched.Register(sch); err != nil {
return err
}
}
if err := sched.Register(planSvc.RotationCheckSchedule()); err != nil {
return err
}
if err := sched.Register(shopSvc.ChatAutosendSchedule()); err != nil {
return err
}
if err := sched.Register(shopSvc.RolloverSchedule()); err != nil {
return err
}
purge, err := harness.PurgeSchedule(db, logger)
if err != nil {
return err
}
if err := sched.Register(purge); err != nil {
return err
}
router, err := server.NewRouter(logger, version, cfg.TrustedProxy, authFlow, sessions, settings, search, ai, chatSvc, dinnerSvc, suggestSvc, planSvc, shopSvc, googleSvc, pushSvc, ideasSvc, homeSvc, runner, photos, frontend.Assets())
if err != nil {
return err
}
// Start fails the jobs a previous process left running before any
// worker runs. The job workers and the notification worker stop, and
// are waited for, before the database closes.
// worker runs. The job workers, the notification worker and the
// scheduler stop, and are waited for, before the database closes.
workersCtx, stopWorkers := context.WithCancel(ctx)
defer func() {
stopWorkers()
sched.Wait()
runner.Wait()
notifier.Wait()
}()
@@ -219,6 +254,11 @@ func run(cfg config.Config, logger *slog.Logger) error {
if _, err := dinnerSvc.FailInterruptedGroundings(ctx); err != nil {
return err
}
// The scheduler starts last: its catch-up may enqueue jobs and
// notifications, so their workers must already run.
if err := sched.Start(workersCtx); err != nil {
return err
}
srv := &http.Server{
Addr: cfg.HTTPAddr,
Handler: router,