- 20 Go source files, single 16MB binary - SQLite + FTS5 full-text search (pure Go, no CGO) - BCB integration: Selic, CDI, IPCA, USD/BRL, EUR/BRL - CVM integration: 2,524 companies from registry - Fiber v2 REST API with 42 handlers - Auto-seeds on first run (~5s for BCB + CVM) - Token bucket rate limiter, optional API key auth - Periodic sync scheduler (configurable) - Graceful shutdown, structured logging (slog) - All endpoints tested with real data
56 lines
1022 B
Go
56 lines
1022 B
Go
package middleware
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
)
|
|
|
|
type bucket struct {
|
|
tokens float64
|
|
lastCheck time.Time
|
|
}
|
|
|
|
type rateLimiter struct {
|
|
mu sync.Mutex
|
|
buckets map[string]*bucket
|
|
rate float64 // tokens per second
|
|
capacity float64
|
|
}
|
|
|
|
func NewRateLimiter(requestsPerMinute int) fiber.Handler {
|
|
rl := &rateLimiter{
|
|
buckets: make(map[string]*bucket),
|
|
rate: float64(requestsPerMinute) / 60.0,
|
|
capacity: float64(requestsPerMinute),
|
|
}
|
|
|
|
return func(c *fiber.Ctx) error {
|
|
ip := c.IP()
|
|
rl.mu.Lock()
|
|
b, ok := rl.buckets[ip]
|
|
if !ok {
|
|
b = &bucket{tokens: rl.capacity, lastCheck: time.Now()}
|
|
rl.buckets[ip] = b
|
|
}
|
|
|
|
now := time.Now()
|
|
elapsed := now.Sub(b.lastCheck).Seconds()
|
|
b.tokens += elapsed * rl.rate
|
|
if b.tokens > rl.capacity {
|
|
b.tokens = rl.capacity
|
|
}
|
|
b.lastCheck = now
|
|
|
|
if b.tokens < 1 {
|
|
rl.mu.Unlock()
|
|
return c.Status(429).JSON(fiber.Map{"error": "rate limit exceeded"})
|
|
}
|
|
b.tokens--
|
|
rl.mu.Unlock()
|
|
|
|
return c.Next()
|
|
}
|
|
}
|