limit.go 1.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546
  1. package web
  2. import (
  3. "fmt"
  4. "math"
  5. "slices"
  6. "time"
  7. )
  8. // window remembers when recent conversions started, so it can tell whether
  9. // another one would go over a limit of n conversions per period.
  10. type window []time.Time
  11. // prune drops entries older than period.
  12. func (w *window) prune(now time.Time, period time.Duration) {
  13. cut := 0
  14. for cut < len(*w) && now.Sub((*w)[cut]) >= period {
  15. cut++
  16. }
  17. *w = (*w)[cut:]
  18. }
  19. // full reports whether n conversions already started within period, and if
  20. // so when the oldest of them stops counting.
  21. func (w window) full(now time.Time, n int, period time.Duration) (bool, time.Time) {
  22. if len(w) < n {
  23. return false, time.Time{}
  24. }
  25. return true, w[len(w)-n].Add(period)
  26. }
  27. // remove takes back one entry made at t.
  28. func (w *window) remove(t time.Time) {
  29. if i := slices.Index(*w, t); i >= 0 {
  30. *w = slices.Delete(*w, i, i+1)
  31. }
  32. }
  33. // minutes describes d in whole minutes, rounded up.
  34. func minutes(d time.Duration) string {
  35. m := max(1, int(math.Ceil(d.Minutes())))
  36. if m == 1 {
  37. return "1 minute"
  38. }
  39. return fmt.Sprintf("%d minutes", m)
  40. }