package server import ( "sync" "time" ) // Simple token bucket keyed by arbitrary string. Thread-safe. type bucket struct { tokens float64 lastRef time.Time } type RateLimiter struct { mu sync.Mutex buckets map[string]*bucket capacity float64 refill float64 // tokens per second } // NewRateLimiter: e.g. capacity=5, per=10min → 5 tokens, refilled at 5/600 tokens/sec. func NewRateLimiter(capacity int, per time.Duration) *RateLimiter { return &RateLimiter{ buckets: map[string]*bucket{}, capacity: float64(capacity), refill: float64(capacity) / per.Seconds(), } } // Allow returns true if the key has a token to spend. func (l *RateLimiter) Allow(key string) bool { l.mu.Lock() defer l.mu.Unlock() b, ok := l.buckets[key] now := time.Now() if !ok { b = &bucket{tokens: l.capacity, lastRef: now} l.buckets[key] = b } elapsed := now.Sub(b.lastRef).Seconds() b.tokens = minF(l.capacity, b.tokens+elapsed*l.refill) b.lastRef = now if b.tokens >= 1 { b.tokens-- return true } return false } func minF(a, b float64) float64 { if a < b { return a } return b }