package audit import "time" const ( DefaultRetentionDays = 90 ForeverRetentionDays = 0 CleanupHour = 3 KeepRecentHours = 24 NoSizeLimitGB = 0 BytesPerGB = int64(1) << 30 RowOverheadBytes = int64(64) ) func RetentionOptions() []int { return []int{30, 90, 365, ForeverRetentionDays} } func ValidateRetentionDays(days int) error { for _, option := range RetentionOptions() { if option == days { return nil } } return ErrInvalidRetention } func SizeOptions() []int { return []int{NoSizeLimitGB, 1, 5, 10, 25, 50} } func ValidateRetentionMaxGB(maxGB int) error { for _, option := range SizeOptions() { if option == maxGB { return nil } } return ErrInvalidRetention } type Settings struct { RetentionDays int `json:"activity_retention_days"` RetentionMaxGB int `json:"activity_retention_max_gb"` LogSizeBytes int64 `json:"activity_log_size_bytes"` LastCleanupAt *time.Time `json:"activity_last_cleanup_at"` LastCleanupRemoved int `json:"activity_last_cleanup_removed"` NextCleanupAt *time.Time `json:"activity_next_cleanup_at"` } func DefaultSettings() Settings { return Settings{RetentionDays: DefaultRetentionDays} } func (s Settings) HasRetentionLimit() bool { return s.RetentionDays != ForeverRetentionDays || s.RetentionMaxGB != NoSizeLimitGB } func (s Settings) MaxLogSizeBytes() int64 { return int64(s.RetentionMaxGB) * BytesPerGB } func (s Settings) CleanupDue(now time.Time) bool { todayRun := time.Date(now.Year(), now.Month(), now.Day(), CleanupHour, 0, 0, 0, now.Location()) if now.Before(todayRun) { return false } return s.LastCleanupAt == nil || s.LastCleanupAt.Before(todayRun) } func (s Settings) WithNextCleanup(now time.Time) Settings { s.NextCleanupAt = nil if !s.HasRetentionLimit() { return s } next := time.Date(now.Year(), now.Month(), now.Day(), CleanupHour, 0, 0, 0, now.Location()) if !next.After(now) { next = time.Date(now.Year(), now.Month(), now.Day()+1, CleanupHour, 0, 0, 0, now.Location()) } s.NextCleanupAt = &next return s } func ClampCutoff(now time.Time, cutoff time.Time) time.Time { newest := now.Add(-KeepRecentHours * time.Hour) if cutoff.After(newest) { return newest } return cutoff } func GuardCutoff(now time.Time) time.Time { return ClampCutoff(now, now) } func RetentionCutoff(now time.Time, retentionDays int) time.Time { return ClampCutoff(now, now.Add(-time.Duration(retentionDays)*24*time.Hour)) } type CleanupResult struct { Removed int }