see the real client IP behind ArvanCloud, and mark all notifications read in one call

Production is ArvanCloud -> nginx -> gunicorn, but NUM_PROXIES was 1, so DRF
took ArvanCloud's edge IP as the client: every guest and every OTP request
behind one edge shared a single throttle bucket (20 guest sessions an hour for
everyone). Default is now 2, and /api/health reports clientIp so the proxy
count can be checked from outside.

PATCH /api/notifications/read-all replaces the frontend's one-PATCH-per-item
loop, which spent the shared "read" budget 75 requests at a time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 05:03:27 +03:30
parent 147af3ac10
commit c874c186b1
8 changed files with 83 additions and 4 deletions

View File

@@ -394,7 +394,15 @@ REST_FRAMEWORK = {
# تازه از GuestAware*Throttleها بگیره (bypassِ چرخشِ حساب مهمان). این
# مقدار باید دقیقاً با تعدادِ لایه‌ی پراکسی/LB جلوی اپ یکی باشه (مثلاً
# Cloudflare+Nginx=۲)، وگرنه IP واقعی اشتباه خونده می‌شه.
'NUM_PROXIES': int(os.environ.get('NUM_PROXIES', '1')),
#
# پروداکشن دو لایه دارد: ArvanCloud (TLS، `server: ArvanCloud`) → nginx روی
# همین ماشین → gunicorn. XFF در جنگو «IPِ کاربر, IPِ آروان» است؛ با ۱
# آخرین مقدار، یعنی IPِ آروان، IPِ کاربر حساب می‌شد و همه‌ی مهمان‌ها و همه‌ی
# درخواست‌های OTP پشتِ یک edgeِ آروان یک سهمِ مشترک داشتند (ورودِ مهمان
# ۲۰ در ساعت برای *همه*). برای اجرای محلی بی‌پراکسی هم بی‌خطر است: وقتی
# XFF نیست یا کوتاه‌تر است، DRF همان REMOTE_ADDR/اولین مقدار را می‌گیرد.
# `/api/health` در `clientIp` نشان می‌دهد سرور چه IPای می‌بیند.
'NUM_PROXIES': int(os.environ.get('NUM_PROXIES', '2')),
'DEFAULT_THROTTLE_RATES': {
'otp_request': '5/hour',
'otp_login': '10/hour',