check / check (push) Waiting to run
retention_days loses its column default of 30, and Webhook.RetentionDays its default tag, so a 0 is stored as 0. The 365000-day stand-in RetentionForeverDays and the BeforeSave hook that wrote it are gone. The reaper and the "forever" label read 0 as forever, and the forms accept 0 or 1 to MaxFiniteRetentionDays; a blank field still gives 30 on the new webhook form and keeps the stored value on the edit form. A negative value, which the forms refuse, is still read as forever so it cannot put the reaper's cutoff in the future. Model: opus-5-5
97 lines
3.3 KiB
Go
97 lines
3.3 KiB
Go
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"
|
|
}
|