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
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
|
|
} |