Files
MusicServer/internal/server/middleware/rate_limit.go
T
Sansan e86c0f8e04 Combine Issue #28 (UUID Phase 2 - NOT NULL) into migration 000005
- Add NOT NULL constraints to soundtrack.uuid, song.uuid, and song.soundtrack_uuid
- Add corresponding DROP NOT NULL in down migration
- Update header comments to reflect combined Phase 1 & 2

This eliminates the need for a separate migration 000007 while maintaining
proper rollback capability. The NOT NULL constraints are added AFTER
all UUID backfills are complete, ensuring data integrity.

Generated by Mistral Vibe.
Co-Authored-By: Mistral Vibe <vibe@mistral.ai>
2026-07-28 14:16:04 +02:00

99 lines
2.0 KiB
Go

package middleware
import (
"net/http"
"sync"
"time"
"music-server/internal/logging"
"github.com/labstack/echo/v5"
"go.uber.org/zap"
)
// RateLimiter implements a simple IP-based rate limiter
type RateLimiter struct {
visits map[string]*Visit
mu sync.Mutex
limit int // requests per window
window time.Duration // time window
}
type Visit struct {
count int
firstSeen time.Time
}
// NewRateLimiter creates a new rate limiter
func NewRateLimiter(limit int, window time.Duration) *RateLimiter {
rl := &RateLimiter{
visits: make(map[string]*Visit),
limit: limit,
window: window,
}
// Start cleanup goroutine
go rl.cleanup()
return rl
}
// cleanup periodically removes old entries
func (rl *RateLimiter) cleanup() {
for {
time.Sleep(rl.window)
rl.mu.Lock()
now := time.Now()
for ip, visit := range rl.visits {
if now.Sub(visit.firstSeen) > rl.window {
delete(rl.visits, ip)
}
}
rl.mu.Unlock()
}
}
// RateLimitMiddleware returns an Echo middleware for rate limiting
func RateLimitMiddleware(rl *RateLimiter) echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c *echo.Context) error {
ip := c.RealIP()
rl.mu.Lock()
defer rl.mu.Unlock()
visit, exists := rl.visits[ip]
now := time.Now()
if !exists {
// First visit from this IP
rl.visits[ip] = &Visit{
count: 1,
firstSeen: now,
}
return next(c)
}
// Check if window has expired
if now.Sub(visit.firstSeen) > rl.window {
// Reset for new window
visit.count = 1
visit.firstSeen = now
return next(c)
}
// Check if limit exceeded
if visit.count >= rl.limit {
logging.GetLogger().Warn("Rate limit exceeded",
zap.String("ip", ip),
zap.String("path", c.Request().URL.Path))
return c.JSON(http.StatusTooManyRequests, map[string]string{
"error": "Rate limit exceeded. Please try again later.",
})
}
// Increment count
visit.count++
return next(c)
}
}
}