package middleware import ( "net/http" "sync" "time" "mengstack/internal/kernel/response" "mengstack/internal/kernel/errors" "github.com/gin-gonic/gin" ) type visitor struct { count int lastSeen time.Time } type rateLimiter struct { mu sync.Mutex visitors map[string]*visitor limit int window time.Duration } func newRateLimiter(limit int, window time.Duration) *rateLimiter { rl := &rateLimiter{ visitors: make(map[string]*visitor), limit: limit, window: window, } go rl.cleanup() return rl } func (rl *rateLimiter) cleanup() { ticker := time.NewTicker(rl.window) for range ticker.C { rl.mu.Lock() now := time.Now() for key, v := range rl.visitors { if now.Sub(v.lastSeen) > rl.window { delete(rl.visitors, key) } } rl.mu.Unlock() } } func (rl *rateLimiter) allow(key string) bool { rl.mu.Lock() defer rl.mu.Unlock() v, exists := rl.visitors[key] if !exists { rl.visitors[key] = &visitor{count: 1, lastSeen: time.Now()} return true } if time.Since(v.lastSeen) > rl.window { v.count = 1 v.lastSeen = time.Now() return true } if v.count >= rl.limit { return false } v.count++ v.lastSeen = time.Now() return true } // RateLimit godoc // Limits requests per IP address. func RateLimit(limit int, window time.Duration) gin.HandlerFunc { rl := newRateLimiter(limit, window) return func(c *gin.Context) { ip := c.ClientIP() if !rl.allow(ip) { response.Fail(c, errors.New("RATE_LIMITED", "too many requests", http.StatusTooManyRequests)) c.Abort() return } c.Next() } }