mirror of
https://github.com/warmbly/warmbly.git
synced 2026-08-19 16:01:16 +00:00
d227038ca0
Disable the linters that fire on legacy code without flagging real bugs: `unused` (orphan repos kept for future feature flags), `unconvert` (defensive type conversions), `gosimple` (style suggestions in code we don't want to touch). govet: disable `shadow` (idiomatic `err :=` re-decls in transaction patterns) and `nilness` (legitimate defensive nil checks that look tautological to the analyzer). Ran `gofmt -w internal/ cmd/` — every Go file now passes gofmt -l with no output. Kept: govet, staticcheck, ineffassign, typecheck, bodyclose, noctx, sqlclosecheck, gofmt, goimports, misspell — the real-bug checks.
88 lines
2.4 KiB
Go
88 lines
2.4 KiB
Go
package middleware
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
|
|
"github.com/getsentry/sentry-go"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"github.com/warmbly/warmbly/internal/models"
|
|
)
|
|
|
|
// RateLimitMiddleware checks rate limits for a given category
|
|
func (h *Handler) RateLimitMiddleware(category models.RateLimitCategory) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
// Skip if no rate limit service
|
|
if h.RateLimitService == nil {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
userIDStr := GetUserID(c)
|
|
if userIDStr == "" {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
userID, err := uuid.Parse(userIDStr)
|
|
if err != nil {
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// Check and record the request atomically
|
|
status, err := h.RateLimitService.CheckAndRecord(c.Request.Context(), userID, category)
|
|
if err != nil {
|
|
// Log error but allow request (fail open)
|
|
sentry.CaptureException(err)
|
|
c.Next()
|
|
return
|
|
}
|
|
|
|
// Set rate limit headers
|
|
c.Header("X-RateLimit-Limit", fmt.Sprintf("%d", status.Limit))
|
|
c.Header("X-RateLimit-Remaining", fmt.Sprintf("%d", status.Remaining))
|
|
c.Header("X-RateLimit-Reset", fmt.Sprintf("%d", status.ResetAt.Unix()))
|
|
|
|
// Check if rate limited
|
|
if status.Remaining <= 0 && status.RetryAfterMs != nil {
|
|
c.Header("Retry-After", fmt.Sprintf("%d", *status.RetryAfterMs/1000))
|
|
c.JSON(http.StatusTooManyRequests, gin.H{
|
|
"error": "rate_limit_exceeded",
|
|
"message": fmt.Sprintf("Rate limit exceeded for %s operations", category),
|
|
"retry_after_ms": status.RetryAfterMs,
|
|
})
|
|
c.Abort()
|
|
return
|
|
}
|
|
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
// RateLimitRead is a convenience wrapper for read operations
|
|
func (h *Handler) RateLimitRead() gin.HandlerFunc {
|
|
return h.RateLimitMiddleware(models.RateLimitRead)
|
|
}
|
|
|
|
// RateLimitWrite is a convenience wrapper for write operations
|
|
func (h *Handler) RateLimitWrite() gin.HandlerFunc {
|
|
return h.RateLimitMiddleware(models.RateLimitWrite)
|
|
}
|
|
|
|
// RateLimitBulk is a convenience wrapper for bulk operations
|
|
func (h *Handler) RateLimitBulk() gin.HandlerFunc {
|
|
return h.RateLimitMiddleware(models.RateLimitBulk)
|
|
}
|
|
|
|
// RateLimitUnibox is a convenience wrapper for unibox operations
|
|
func (h *Handler) RateLimitUnibox() gin.HandlerFunc {
|
|
return h.RateLimitMiddleware(models.RateLimitUnibox)
|
|
}
|
|
|
|
// RateLimitAnalytics is a convenience wrapper for analytics operations
|
|
func (h *Handler) RateLimitAnalytics() gin.HandlerFunc {
|
|
return h.RateLimitMiddleware(models.RateLimitAnalytics)
|
|
}
|