75f3dd141c
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
CI and Release / lint-frontend (push) Has been cancelled
CI and Release / dockerfile-scan (push) Has been cancelled
CI and Release / test-frontend (push) Has been cancelled
CI and Release / lint-verification-agent (push) Has been cancelled
CI and Release / test-verification-agent (push) Has been cancelled
CI and Release / e2e-verification-agent (push) Has been cancelled
CI and Release / test-backend (push) Has been cancelled
CI and Release / build-dev-image (push) Has been cancelled
CI and Release / push-dev-image (push) Has been cancelled
CI and Release / build-image (push) Has been cancelled
CI and Release / build-verification-image (push) Has been cancelled
CI and Release / determine-version (push) Has been cancelled
CI and Release / push-image (push) Has been cancelled
CI and Release / push-verification-image (12) (push) Has been cancelled
CI and Release / lint-backend (push) Has been cancelled
CI and Release / push-verification-image (17) (push) Has been cancelled
CI and Release / push-verification-image (18) (push) Has been cancelled
CI and Release / release (push) Has been cancelled
CI and Release / publish-helm-chart (push) Has been cancelled
CI and Release / push-verification-image (13) (push) Has been cancelled
CI and Release / push-verification-image (14) (push) Has been cancelled
CI and Release / push-verification-image (15) (push) Has been cancelled
CI and Release / push-verification-image (16) (push) Has been cancelled
86 lines
1.7 KiB
Go
86 lines
1.7 KiB
Go
package cache_utils
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/valkey-io/valkey-go"
|
|
)
|
|
|
|
type RateLimiter struct {
|
|
client valkey.Client
|
|
}
|
|
|
|
func NewRateLimiter(client valkey.Client) *RateLimiter {
|
|
return &RateLimiter{
|
|
client: client,
|
|
}
|
|
}
|
|
|
|
func (r *RateLimiter) CheckLimit(
|
|
identifier string,
|
|
endpoint string,
|
|
maxRequests int,
|
|
windowDuration time.Duration,
|
|
) (bool, error) {
|
|
requestID := uuid.New().String()
|
|
keyPrefix := fmt.Sprintf("ratelimit:%s:%s", endpoint, identifier)
|
|
fullKey := fmt.Sprintf("%s:%s", keyPrefix, requestID)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), DefaultCacheTimeout)
|
|
defer cancel()
|
|
|
|
// Set the key with TTL
|
|
setCmd := r.client.B().
|
|
Set().
|
|
Key(fullKey).
|
|
Value("1").
|
|
ExSeconds(int64(windowDuration.Seconds())).
|
|
Build()
|
|
if err := r.client.Do(ctx, setCmd).Error(); err != nil {
|
|
return true, fmt.Errorf("failed to set rate limit key: %w", err)
|
|
}
|
|
|
|
// Count keys matching the pattern
|
|
count, err := r.countKeys(keyPrefix)
|
|
if err != nil {
|
|
return true, fmt.Errorf("failed to count rate limit keys: %w", err)
|
|
}
|
|
|
|
return count <= maxRequests, nil
|
|
}
|
|
|
|
func (r *RateLimiter) countKeys(keyPrefix string) (int, error) {
|
|
pattern := keyPrefix + ":*"
|
|
cursor := uint64(0)
|
|
totalCount := 0
|
|
|
|
for {
|
|
ctx, cancel := context.WithTimeout(context.Background(), DefaultCacheTimeout)
|
|
|
|
scanCmd := r.client.B().Scan().Cursor(cursor).Match(pattern).Count(100).Build()
|
|
result := r.client.Do(ctx, scanCmd)
|
|
cancel()
|
|
|
|
if result.Error() != nil {
|
|
return 0, result.Error()
|
|
}
|
|
|
|
scanResult, err := result.AsScanEntry()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
totalCount += len(scanResult.Elements)
|
|
cursor = scanResult.Cursor
|
|
|
|
if cursor == 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
return totalCount, nil
|
|
}
|