You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

354 lines
8.4 KiB
Go

package valueobjects
import (
"fmt"
"time"
"github.com/knowfoolery/backend/shared/errors"
"github.com/knowfoolery/backend/shared/types"
)
type ResetFrequency string
const (
ResetFrequencyNever ResetFrequency = "never"
ResetFrequencyDaily ResetFrequency = "daily"
ResetFrequencyWeekly ResetFrequency = "weekly"
ResetFrequencyMonthly ResetFrequency = "monthly"
ResetFrequencyCustom ResetFrequency = "custom"
)
func (rf ResetFrequency) IsValid() bool {
switch rf {
case ResetFrequencyNever, ResetFrequencyDaily, ResetFrequencyWeekly, ResetFrequencyMonthly, ResetFrequencyCustom:
return true
default:
return false
}
}
type ResetAction string
const (
ResetActionClear ResetAction = "clear"
ResetActionArchive ResetAction = "archive"
ResetActionMerge ResetAction = "merge"
)
func (ra ResetAction) IsValid() bool {
switch ra {
case ResetActionClear, ResetActionArchive, ResetActionMerge:
return true
default:
return false
}
}
type ResetRules struct {
frequency ResetFrequency
action ResetAction
customInterval time.Duration
timezone string
preserveTopN int32
resetTime time.Time
nextResetAt time.Time
autoReset bool
notifyBeforeReset time.Duration
metadata map[string]interface{}
}
func NewResetRules(frequency ResetFrequency, action ResetAction) (*ResetRules, error) {
if !frequency.IsValid() {
return nil, errors.ErrInvalidResetFrequency
}
if !action.IsValid() {
return nil, errors.ErrInvalidResetAction
}
rr := &ResetRules{
frequency: frequency,
action: action,
timezone: "UTC",
preserveTopN: 0,
autoReset: false,
notifyBeforeReset: time.Hour * 24,
metadata: make(map[string]interface{}),
}
if frequency != ResetFrequencyNever {
rr.autoReset = true
rr.calculateNextReset()
}
return rr, nil
}
func (rr *ResetRules) Frequency() ResetFrequency {
return rr.frequency
}
func (rr *ResetRules) Action() ResetAction {
return rr.action
}
func (rr *ResetRules) CustomInterval() time.Duration {
return rr.customInterval
}
func (rr *ResetRules) Timezone() string {
return rr.timezone
}
func (rr *ResetRules) PreserveTopN() int32 {
return rr.preserveTopN
}
func (rr *ResetRules) ResetTime() time.Time {
return rr.resetTime
}
func (rr *ResetRules) NextResetAt() time.Time {
return rr.nextResetAt
}
func (rr *ResetRules) AutoReset() bool {
return rr.autoReset
}
func (rr *ResetRules) NotifyBeforeReset() time.Duration {
return rr.notifyBeforeReset
}
func (rr *ResetRules) Metadata() map[string]interface{} {
result := make(map[string]interface{})
for k, v := range rr.metadata {
result[k] = v
}
return result
}
func (rr *ResetRules) SetCustomInterval(interval time.Duration) error {
if rr.frequency != ResetFrequencyCustom {
return errors.ErrInvalidResetFrequency
}
if interval <= 0 {
return errors.ErrInvalidResetInterval
}
rr.customInterval = interval
rr.calculateNextReset()
return nil
}
func (rr *ResetRules) SetTimezone(timezone string) error {
if timezone == "" {
return errors.ErrInvalidTimezone
}
_, err := time.LoadLocation(timezone)
if err != nil {
return errors.ErrInvalidTimezone
}
rr.timezone = timezone
rr.calculateNextReset()
return nil
}
func (rr *ResetRules) SetPreserveTopN(n int32) error {
if n < 0 {
return errors.ErrInvalidPreserveCount
}
rr.preserveTopN = n
return nil
}
func (rr *ResetRules) SetResetTime(resetTime time.Time) error {
if resetTime.IsZero() {
return errors.ErrInvalidResetTime
}
rr.resetTime = resetTime
rr.calculateNextReset()
return nil
}
func (rr *ResetRules) SetAutoReset(autoReset bool) {
rr.autoReset = autoReset
if autoReset {
rr.calculateNextReset()
}
}
func (rr *ResetRules) SetNotifyBeforeReset(duration time.Duration) error {
if duration < 0 {
return errors.ErrInvalidNotificationTime
}
rr.notifyBeforeReset = duration
return nil
}
func (rr *ResetRules) SetMetadata(key string, value interface{}) error {
if key == "" {
return errors.ErrInvalidMetadata
}
rr.metadata[key] = value
return nil
}
func (rr *ResetRules) RemoveMetadata(key string) {
delete(rr.metadata, key)
}
func (rr *ResetRules) IsResetDue() bool {
if !rr.autoReset || rr.frequency == ResetFrequencyNever {
return false
}
return time.Now().After(rr.nextResetAt)
}
func (rr *ResetRules) IsNotificationDue() bool {
if !rr.autoReset || rr.frequency == ResetFrequencyNever {
return false
}
notificationTime := rr.nextResetAt.Add(-rr.notifyBeforeReset)
now := time.Now()
return now.After(notificationTime) && now.Before(rr.nextResetAt)
}
func (rr *ResetRules) TimeUntilReset() time.Duration {
if !rr.autoReset || rr.frequency == ResetFrequencyNever {
return 0
}
return time.Until(rr.nextResetAt)
}
func (rr *ResetRules) TimeUntilNotification() time.Duration {
if !rr.autoReset || rr.frequency == ResetFrequencyNever {
return 0
}
notificationTime := rr.nextResetAt.Add(-rr.notifyBeforeReset)
return time.Until(notificationTime)
}
func (rr *ResetRules) calculateNextReset() {
if !rr.autoReset || rr.frequency == ResetFrequencyNever {
return
}
now := time.Now()
loc, _ := time.LoadLocation(rr.timezone)
var next time.Time
switch rr.frequency {
case ResetFrequencyDaily:
if rr.resetTime.IsZero() {
next = time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, loc).AddDate(0, 0, 1)
} else {
resetHour, resetMin, resetSec := rr.resetTime.Clock()
next = time.Date(now.Year(), now.Month(), now.Day(), resetHour, resetMin, resetSec, 0, loc)
if next.Before(now) {
next = next.AddDate(0, 0, 1)
}
}
case ResetFrequencyWeekly:
daysUntilSunday := (7 - int(now.Weekday())) % 7
if daysUntilSunday == 0 {
daysUntilSunday = 7
}
next = now.AddDate(0, 0, daysUntilSunday)
next = time.Date(next.Year(), next.Month(), next.Day(), 0, 0, 0, 0, loc)
case ResetFrequencyMonthly:
next = time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, loc)
case ResetFrequencyCustom:
if rr.customInterval > 0 {
next = now.Add(rr.customInterval)
}
}
rr.nextResetAt = next
}
func (rr *ResetRules) UpdateFrequency(frequency ResetFrequency) error {
if !frequency.IsValid() {
return errors.ErrInvalidResetFrequency
}
rr.frequency = frequency
if frequency == ResetFrequencyNever {
rr.autoReset = false
rr.nextResetAt = time.Time{}
} else {
rr.autoReset = true
rr.calculateNextReset()
}
return nil
}
func (rr *ResetRules) UpdateAction(action ResetAction) error {
if !action.IsValid() {
return errors.ErrInvalidResetAction
}
rr.action = action
return nil
}
func (rr *ResetRules) String() string {
return fmt.Sprintf("ResetRules{Frequency: %s, Action: %s, AutoReset: %t, NextReset: %v}",
rr.frequency, rr.action, rr.autoReset, rr.nextResetAt)
}
type ResetRulesSnapshot struct {
Frequency ResetFrequency `json:"frequency"`
Action ResetAction `json:"action"`
CustomInterval time.Duration `json:"custom_interval"`
Timezone string `json:"timezone"`
PreserveTopN int32 `json:"preserve_top_n"`
ResetTime time.Time `json:"reset_time"`
NextResetAt time.Time `json:"next_reset_at"`
AutoReset bool `json:"auto_reset"`
NotifyBeforeReset time.Duration `json:"notify_before_reset"`
Metadata map[string]interface{} `json:"metadata"`
}
func (rr *ResetRules) ToSnapshot() ResetRulesSnapshot {
return ResetRulesSnapshot{
Frequency: rr.frequency,
Action: rr.action,
CustomInterval: rr.customInterval,
Timezone: rr.timezone,
PreserveTopN: rr.preserveTopN,
ResetTime: rr.resetTime,
NextResetAt: rr.nextResetAt,
AutoReset: rr.autoReset,
NotifyBeforeReset: rr.notifyBeforeReset,
Metadata: rr.Metadata(),
}
}
func (rr *ResetRules) FromSnapshot(snapshot ResetRulesSnapshot) error {
if !snapshot.Frequency.IsValid() {
return errors.ErrInvalidResetFrequency
}
if !snapshot.Action.IsValid() {
return errors.ErrInvalidResetAction
}
rr.frequency = snapshot.Frequency
rr.action = snapshot.Action
rr.customInterval = snapshot.CustomInterval
rr.timezone = snapshot.Timezone
rr.preserveTopN = snapshot.PreserveTopN
rr.resetTime = snapshot.ResetTime
rr.nextResetAt = snapshot.NextResetAt
rr.autoReset = snapshot.AutoReset
rr.notifyBeforeReset = snapshot.NotifyBeforeReset
rr.metadata = make(map[string]interface{})
for k, v := range snapshot.Metadata {
rr.metadata[k] = v
}
return nil
}