package scheduler import ( "context" "time" "github.com/robfig/cron/v3" "go.uber.org/zap" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/config" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/pkg/leaderelection" "codeberg.org/mapleopentech/monorepo/cloud/maplepress-backend/internal/service/ipcleanup" ) // IPCleanupScheduler handles scheduled IP address cleanup for GDPR compliance // CWE-359: IP addresses must be deleted after 90 days type IPCleanupScheduler struct { cron *cron.Cron cleanupService *ipcleanup.CleanupService leaderElection leaderelection.LeaderElection logger *zap.Logger enabled bool schedulePattern string } // ProvideIPCleanupScheduler creates a new IPCleanupScheduler from config func ProvideIPCleanupScheduler( cfg *config.Config, cleanupService *ipcleanup.CleanupService, leaderElection leaderelection.LeaderElection, logger *zap.Logger, ) *IPCleanupScheduler { // IP cleanup enabled if configured (defaults to true for GDPR compliance) enabled := cfg.Scheduler.IPCleanupEnabled // Default: run daily at 2 AM schedulePattern := cfg.Scheduler.IPCleanupSchedule // Create cron with logger cronLog := &cronLogger{logger: logger.Named("cron")} c := cron.New( cron.WithLogger(cronLog), cron.WithChain( cron.Recover(cronLog), ), ) return &IPCleanupScheduler{ cron: c, cleanupService: cleanupService, leaderElection: leaderElection, logger: logger.Named("ip-cleanup-scheduler"), enabled: enabled, schedulePattern: schedulePattern, } } // Start starts the IP cleanup scheduler func (s *IPCleanupScheduler) Start() error { if !s.enabled { s.logger.Info("IP cleanup scheduler is disabled") return nil } s.logger.Info("starting IP cleanup scheduler for GDPR compliance", zap.String("schedule", s.schedulePattern), zap.String("retention_period", "90 days")) // Schedule the IP cleanup job _, err := s.cron.AddFunc(s.schedulePattern, s.cleanupIPs) if err != nil { s.logger.Error("failed to schedule IP cleanup job", zap.Error(err)) return err } // Start the cron scheduler s.cron.Start() s.logger.Info("IP cleanup scheduler started successfully") return nil } // Stop stops the IP cleanup scheduler func (s *IPCleanupScheduler) Stop() { if !s.enabled { return } s.logger.Info("stopping IP cleanup scheduler") ctx := s.cron.Stop() <-ctx.Done() s.logger.Info("IP cleanup scheduler stopped") } // cleanupIPs is the cron job function that cleans up expired IP addresses func (s *IPCleanupScheduler) cleanupIPs() { // Only execute if this instance is the leader if !s.leaderElection.IsLeader() { s.logger.Debug("skipping IP cleanup - not the leader instance", zap.String("instance_id", s.leaderElection.GetInstanceID())) return } s.logger.Info("executing scheduled IP cleanup for GDPR compliance as leader instance", zap.String("instance_id", s.leaderElection.GetInstanceID())) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() err := s.cleanupService.CleanupExpiredIPs(ctx) if err != nil { s.logger.Error("IP cleanup failed", zap.Error(err)) return } s.logger.Info("IP cleanup completed successfully") }