package database import ( "math" "strconv" "time" ) const ( // DefaultRetentionDays is the event retention period the new // webhook form applies when its retention field is left blank. DefaultRetentionDays = 30 // MaxFiniteRetentionDays is the largest finite retention period the // reaper's cutoff arithmetic can represent, and therefore the // largest one a caller may request. It is derived from that // arithmetic rather than picked: retentionCutoff computes // retentionDays * hoursPerDay * time.Hour, and a time.Duration is // an int64 nanosecond count, so math.MaxInt64 nanoseconds divided // by an hour and then by a day is the exact ceiling — 106751 days, // a little over 292 years. // // One day more overflows int64, wraps the product negative, and // turns the cutoff into a timestamp in the far future that matches // every row in the webhook's database. That is why this bound is // enforced on input and why retentionCutoff saturates underneath // it. MaxFiniteRetentionDays = int( math.MaxInt64 / int64(time.Hour) / hoursPerDay, ) ) // Webhook represents a webhook processing unit that groups entrypoints and targets // // Every method below takes a pointer receiver, so handlers put a // *Webhook into template data: html/template cannot call a pointer // method on a value held in a map, because a map element is not // addressable. type Webhook struct { BaseModel UserID string `gorm:"type:uuid;not null" json:"userId"` Name string `gorm:"not null" json:"name"` Description string `json:"description"` // RetentionDays is the number of days to retain events, and 0 // means retain them forever. RetentionDays int `json:"retentionDays"` // Relations. No model marshals the record it belongs to: // User.Webhooks leads back here, and the JSON could loop. User User `json:"-"` Entrypoints []Entrypoint `json:"entrypoints,omitempty"` Targets []Target `json:"targets,omitempty"` } // retainsForever reports whether a RetentionDays value means "keep // events indefinitely", which 0 does. It is the single definition of // that question, shared by Webhook.RetainsForever and by the reaper's // cutoff computation so the two cannot disagree about which webhooks // are exempt from reaping. // // A negative value, which the forms refuse, is read the same way: as a // day count it would put the cutoff in the future, where it matches // every event the webhook has. func retainsForever(retentionDays int) bool { return retentionDays <= 0 } // RetainsForever reports whether this webhook's events are kept // indefinitely. func (w *Webhook) RetainsForever() bool { return retainsForever(w.RetentionDays) } // RetentionCutoff returns the time before which this webhook's events // have expired, as the reaper computes it, and false when the webhook // retains them forever. func (w *Webhook) RetentionCutoff(now time.Time) (time.Time, bool) { return retentionCutoff(now, w.RetentionDays) } // RetentionLabel returns the webhook's retention policy as display // text: "forever" rather than "0 days" for a webhook that keeps its // events. func (w *Webhook) RetentionLabel() string { if w.RetainsForever() { return "forever" } if w.RetentionDays == 1 { return "1 day" } return strconv.Itoa(w.RetentionDays) + " days" }