From 21e19ed6a91545ee4713d1b9ae0e12c8f36d2797 Mon Sep 17 00:00:00 2001 From: roai_linux Date: Sat, 28 Mar 2026 15:37:02 +0330 Subject: [PATCH] feat: add rate_limit --- Back/core/config.py | 2 ++ Back/core/rate_limit.py | 11 -------- Back/docker-compose.yml | 54 ++++++++++++++++++++++++---------------- Back/livekit.yaml | 4 +-- Back/tests/test_redis.py | 15 +++++++++++ 5 files changed, 51 insertions(+), 35 deletions(-) create mode 100644 Back/tests/test_redis.py diff --git a/Back/core/config.py b/Back/core/config.py index e75e889..74f2097 100755 --- a/Back/core/config.py +++ b/Back/core/config.py @@ -14,6 +14,8 @@ class Settings(BaseSettings): DATABASE_URL: str REDIS_URL: str + REDIS_USERNAME: str + REDIS_PASSWORD: str LIVEKIT_API_KEY: str LIVEKIT_API_SECRET: str diff --git a/Back/core/rate_limit.py b/Back/core/rate_limit.py index cb483c0..a5b89fa 100644 --- a/Back/core/rate_limit.py +++ b/Back/core/rate_limit.py @@ -15,33 +15,22 @@ class RateLimiter: self.scope = scope async def __call__(self, request: Request): - # getting client ip client_ip = request.client.host if request.client else "127.0.0.1" - # when project is in docker and behind nginx, the real ip is in the headers real_ip = request.headers.get("x-real-ip", request.headers.get("x-forwarded-for", client_ip)) - # if there are multiple ips, take the first ip (the real user ip) real_ip = real_ip.split(",")[0].strip() - - # creating redis key based on scope if self.scope == "global": - # key for global limit (e.g., rate_limit:global:192.168.1.5) key = f"rate_limit:global:{real_ip}" else: - # key for endpoint limit (e.g., rate_limit:endpoint:192.168.1.5:/admin/login) path = request.scope["path"] key = f"rate_limit:endpoint:{real_ip}:{path}" - # adding 1 to the number of requests for this ip in redis current_count = await redis_client.incr(key) - # if this is the first request in this time window, set the expiration time (TTL) if current_count == 1: await redis_client.expire(key, self.window_seconds) - # if the number of requests exceeds the limit, access is blocked if current_count > self.requests: - # penalty: if someone spams, the time they are blocked is extended from zero again await redis_client.expire(key, self.window_seconds) raise HTTPException( diff --git a/Back/docker-compose.yml b/Back/docker-compose.yml index 890f7ac..457081a 100755 --- a/Back/docker-compose.yml +++ b/Back/docker-compose.yml @@ -9,7 +9,10 @@ services: volumes: - postgres_data:/var/lib/postgresql/data restart: always - + ports: + - "5432:5432" + networks: + - internal healthcheck: test: [ "CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}" ] interval: 5s @@ -22,9 +25,13 @@ services: volumes: - redis_data:/data restart: always - + command: redis-server --requirepass ${REDIS_PASSWORD} + ports: + - "6379:6379" + networks: + - internal healthcheck: - test: [ "CMD", "redis-cli", "ping" ] + test: [ "CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping" ] interval: 5s timeout: 3s retries: 5 @@ -35,30 +42,22 @@ services: ports: - "7780:7880" - "7781:7881" - - "50000-50100:50000-50100/udp" + - "51000-51100:51000-51100/udp" env_file: - .env volumes: - ./livekit.yaml:/etc/livekit/livekit.yaml command: [ "--config", "/etc/livekit/livekit.yaml", "--keys", "${LIVEKIT_API_KEY}: ${LIVEKIT_API_SECRET}" ] restart: always - - pgadmin: - image: dpage/pgadmin4:latest - container_name: neda_pgadmin - restart: always - ports: - - "5050:80" - environment: - PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL} - PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD} - PGADMIN_CONFIG_SERVER_MODE: 'True' - PGADMIN_CONFIG_UPGRADE_CHECK_ENABLED: 'False' - PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED: 'False' - volumes: - - pgadmin_data:/var/lib/pgadmin - depends_on: - - postgres + networks: + - public + - internal + healthcheck: + test: [ "CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:7880/health" ] # بررسی سلامت LiveKit + interval: 10s + timeout: 5s + retries: 3 + start_period: 10s pg_backup: image: prodrigestivill/postgres-backup-local @@ -75,7 +74,8 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} SCHEDULE: '@daily' BACKUP_KEEP_DAYS: 7 - + networks: + - internal depends_on: postgres: condition: service_healthy @@ -89,6 +89,9 @@ services: - "./:/app" env_file: - .env + networks: + - public + - internal depends_on: postgres: condition: service_healthy @@ -98,6 +101,13 @@ services: condition: service_started restart: always +networks: + public: + driver: bridge + internal: + driver: bridge + # internal: true + volumes: postgres_data: redis_data: diff --git a/Back/livekit.yaml b/Back/livekit.yaml index 85496e3..2a8b56b 100644 --- a/Back/livekit.yaml +++ b/Back/livekit.yaml @@ -2,8 +2,8 @@ port: 7880 rtc: tcp_port: 7881 - port_range_start: 50000 - port_range_end: 50100 + port_range_start: 51000 + port_range_end: 51100 use_external_ip: false # node_ip: "94.183.170.121" diff --git a/Back/tests/test_redis.py b/Back/tests/test_redis.py new file mode 100644 index 0000000..f421ce8 --- /dev/null +++ b/Back/tests/test_redis.py @@ -0,0 +1,15 @@ +import asyncio +import redis.asyncio as redis +from core.config import settings + +async def test_redis(): + print(f"Testing connection to: {settings.REDIS_URL}") + try: + client = redis.from_url(settings.REDIS_URL, decode_responses=True) + pong = await client.ping() + print(f"Ping successful: {pong}") + except Exception as e: + print(f"Connection failed: {e}") + +if __name__ == "__main__": + asyncio.run(test_redis())