d582f15681
- Implemented tests for import polling functionality, ensuring correct behavior for active and terminal import statuses. - Created tests for the login page, validating user interactions and error handling. - Added unit tests for money utility functions, covering parsing, formatting, and summation of monetary values. - Developed tests for query keys, ensuring proper scoping and invalidation roots. chore: setup testing environment and configuration - Added setup file for testing with Vitest and Testing Library. - Configured TypeScript and Vite for the frontend project. - Created Docker and Nginx configurations for local development and production environments. - Established a PostgreSQL initialization script for setting up application roles. docs: add README and environment configuration for infra - Included README for the infra directory, detailing local development setup and service descriptions. - Created an example environment configuration file for local development with placeholders for sensitive information.
43 lines
1.2 KiB
Python
43 lines
1.2 KiB
Python
import hashlib
|
|
import threading
|
|
import time
|
|
from collections import defaultdict, deque
|
|
|
|
from redis import Redis
|
|
from redis.exceptions import RedisError
|
|
|
|
from app.config import get_settings
|
|
|
|
_lock = threading.Lock()
|
|
_hits: dict[str, deque[float]] = defaultdict(deque)
|
|
|
|
|
|
def allow(key: str, limit: int, window_seconds: float) -> bool:
|
|
"""Distributed fixed-window limiter with a local fail-safe fallback."""
|
|
digest = hashlib.sha256(key.encode()).hexdigest()
|
|
window = int(time.time() // window_seconds)
|
|
try:
|
|
client = Redis.from_url(
|
|
get_settings().redis_url,
|
|
socket_connect_timeout=0.2,
|
|
socket_timeout=0.2,
|
|
)
|
|
redis_key = f"rate:{digest}:{window}"
|
|
with client.pipeline() as pipeline:
|
|
pipeline.incr(redis_key)
|
|
pipeline.expire(redis_key, int(window_seconds) + 1)
|
|
count, _ = pipeline.execute()
|
|
return int(count) <= limit
|
|
except RedisError:
|
|
pass
|
|
|
|
now = time.monotonic()
|
|
with _lock:
|
|
bucket = _hits[digest]
|
|
while bucket and bucket[0] <= now - window_seconds:
|
|
bucket.popleft()
|
|
if len(bucket) >= limit:
|
|
return False
|
|
bucket.append(now)
|
|
return True
|