Signed payment links, hashed refresh tokens, login-only leaderboard, load test tools

- /api/payment/orders/{id}/pay requires the short-lived signature that paymentUrl carries;
  without it (or with another order's) it is the same 404 as an unknown order
- Session.refresh_token stores the SHA-256 of the token; migration accounts.0007 hashes the
  existing rows so tokens clients already hold keep working
- /api/leaderboard requires a token (guests have one); names of children were scrapable
- RAG mock only for `manage.py test`, not any command with an argument named "test"
- tools/loadtest: user factory, load generator, fake GapGPT+Qdrant, with method notes
- security-before-production.md lists only open items; closed ones become a guards table
  with the tests that hold them. Comments describe current behaviour, not history

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-04 21:12:28 +03:30
parent b3f624a512
commit 645baf846b
22 changed files with 584 additions and 193 deletions

View File

@@ -15,13 +15,12 @@ gthread هر worker را چند نخی می‌کند: ۳ worker × ۸ نخ = ۲
تحمل می‌کند. timeout در gthread به ضربانِ خودِ worker است، نه طولِ هر
درخواست، پس استریمِ طولانی دیگر کشته نمی‌شود.
۸ → ۳۲ نخ (۲۰۲۶-۱۰-۰۴، لودتست): هر جوابِ دستیار یک نخ را تا آخرِ جواب نگه می‌دارد
(روی سرور میانه ۵ و p90 ۱۳ ثانیه). با ۳×۸ از حدودِ ۲۰ پرسشِ هم‌زمان، صفحه‌های عادیِ اپ
پشتِ استریم‌ها صف کشیدند (تا ۲۳ ثانیه در ۵۰ پرسش؛ فرانت بعد از ۱۵ ثانیه خطا می‌دهد)، چون
gthread اتصال را حتی وقتی همه‌ی نخ‌هایش مشغول‌اند می‌پذیرد و workerها یکنواخت پر نمی‌شوند.
با ۳×۳۲ تا ۷۵ پرسشِ هم‌زمان صفحه‌ها زیرِ ۱۱۰ میلی‌ثانیه ماندند. این روی لپ‌تاپ با مدل و
Qdrantِ شبیه‌سازی‌شده سنجیده شد؛ CPUِ مشترکِ سرور و رفتارِ GapGPT زیرِ این بار جدا
سنجیده نشده‌اند. نخِ منتظرِ شبکه تقریباً هزینه ندارد؛ هر نخ اتصالِ SQLiteِ خودش را دارد.
۳۲ نخ: هر جوابِ دستیار یک نخ را تا آخرِ جواب نگه می‌دارد (روی سرور میانه‌ی ۳ تا ۶ ثانیه،
گاهی ۴۰). gthread اتصال را حتی وقتی همه‌ی نخ‌هایش مشغول‌اند می‌پذیرد و workerها یکنواخت پر
نمی‌شوند، پس با نخِ کم، صفحه‌های عادیِ اپ پشتِ استریم‌های دستیار صف می‌کشند. در لودتستِ
۲۰۲۶-۱۰-۰۴ (لپ‌تاپ، مدل و Qdrantِ شبیه‌سازی‌شده با زمان‌بندیِ سرور) ۳×۳۲ تا ۷۵ پرسشِ هم‌زمان
صفحه‌ها را زیرِ ۰٫۱ ثانیه نگه داشت و از ۱۰۰ به بعد صف پیدا شد. CPUِ مشترکِ سرور در این بار
سنجیده نشده. نخِ منتظرِ شبکه تقریباً هزینه ندارد؛ هر نخ اتصالِ SQLiteِ خودش را دارد.
"""
worker_class = 'gthread'
@@ -39,10 +38,9 @@ max_requests_jitter = 200
def post_worker_init(worker):
"""
کتابخانه‌های Qdrant و OpenAI را همان لحظه‌ی بالا آمدنِ worker بار می‌کند، نه سرِ اولین
سوالِ دستیار. در لودتستِ ۲۰۲۶-۱۰-۰۴ اولین دورِ پرسش بعد از ری‌استارت روی سرور ~۶ ثانیه
پیش از رسیدن به مدل معطل ماند و دورهای بعد ~۱ ثانیه؛ روی لپ‌تاپ همین تفاوت ۱٫۷ ثانیه بود
(import ِ qdrant_client به‌تنهایی ~۰٫۷ ثانیه). با max_requests هر worker گاه‌به‌گاه از نو
بالا می‌آید، پس بدونِ این هر چند هزار درخواست یک دانش‌آموز همین تأخیر را می‌دید.
سوالِ دستیار. بدونِ این، اولین پرسش‌ها بعد از هر شروعِ worker (ری‌استارت، یا تعویض با
max_requests) چند ثانیه پیش از رسیدن به مدل معطل می‌مانند: روی سرور ~۶ ثانیه در برابرِ ~۱،
روی لپ‌تاپ ۱٫۷ ثانیه (import ِ qdrant_client به‌تنهایی ~۰٫۷).
ساختنِ کلاینت تماسِ شبکه‌ای ندارد. شکستِ این گرم‌کردن نباید worker را بخواباند (کلِ اپ
به‌خاطرِ یک بهینه‌سازی پایین نمی‌آید)؛ ثبت می‌شود و اولین سوال همان مسیرِ عادی را می‌رود.