package scheduler import ( "context" "time" "autostore/internal/application/commands" "autostore/internal/application/interfaces" ) type ExpiredItemsScheduler struct { handleExpiredItemsCmd *commands.HandleExpiredItemsCommand logger interfaces.ILogger ticker *time.Ticker done chan struct{} } func NewExpiredItemsScheduler( handleExpiredItemsCmd *commands.HandleExpiredItemsCommand, logger interfaces.ILogger, ) *ExpiredItemsScheduler { return &ExpiredItemsScheduler{ handleExpiredItemsCmd: handleExpiredItemsCmd, logger: logger, done: make(chan struct{}), } } func (s *ExpiredItemsScheduler) Start(ctx context.Context) error { s.logger.Info(ctx, "Starting expired items scheduler") // Process expired items immediately on startup if err := s.processExpiredItems(ctx); err != nil { s.logger.Error(ctx, "Failed to process expired items on startup", "error", err) return err } // Calculate duration until next midnight now := time.Now() midnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) if now.After(midnight) { midnight = midnight.Add(24 * time.Hour) } durationUntilMidnight := midnight.Sub(now) // Start a timer for the first midnight firstMidnightTimer := time.NewTimer(durationUntilMidnight) go func() { for { select { case <-firstMidnightTimer.C: s.processExpiredItems(ctx) // After first midnight, set up daily ticker s.ticker = time.NewTicker(24 * time.Hour) firstMidnightTimer.Stop() for { select { case <-s.ticker.C: s.processExpiredItems(ctx) case <-s.done: s.ticker.Stop() return case <-ctx.Done(): s.ticker.Stop() return } } case <-s.done: firstMidnightTimer.Stop() return case <-ctx.Done(): firstMidnightTimer.Stop() return } } }() s.logger.Info(ctx, "Expired items scheduler started", "next_run", midnight.Format(time.RFC3339)) return nil } func (s *ExpiredItemsScheduler) Stop() error { s.logger.Info(context.Background(), "Stopping expired items scheduler") close(s.done) if s.ticker != nil { s.ticker.Stop() } return nil } func (s *ExpiredItemsScheduler) processExpiredItems(ctx context.Context) error { s.logger.Info(ctx, "Running scheduled expired items processing") if err := s.handleExpiredItemsCmd.Execute(ctx); err != nil { s.logger.Error(ctx, "Scheduled expired items processing failed", "error", err) return err } s.logger.Info(ctx, "Scheduled expired items processing completed") return nil }