Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:11:07 +08:00

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
}