make streaks and the leaderboard live, and stop three AI chats from freezing the API

Found in a deeper pass over what the frontend's flow actually touches:

- Nothing in the app ever wrote StreakRecord: "days in a row" was 0 forever
  for every real user. Answers and lesson/island completions now record the
  day (Tehran time); a broken chain shows 0. A streak error never fails the
  answer itself.
- /api/leaderboard only changed when someone ran rebuild_leaderboard by
  hand. It is now computed live (60s cache); weekly counts island XP too.
- gunicorn ran 3 sync workers with a 30s timeout, so every "ask Pi" stream
  pinned a whole worker (~10s, GapGPT buffers the answer) and three at once
  queued the entire API. gunicorn.conf.py (read automatically from the
  working directory) switches to gthread, 8 threads, 120s timeout.
- Every /api/ response now says Cache-Control: private, no-store, so turning
  on caching in the ArvanCloud panel can never serve one student another's data.
- /api/user/summary named no active plan ever ("subscription app not
  implemented yet" had long stopped being true).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 06:39:50 +03:30
parent c874c186b1
commit 701d8badcc
11 changed files with 375 additions and 27 deletions

View File

@@ -109,6 +109,20 @@ PaymentRequest را می‌زند، `Order.gateway_authority` را ذخیره م
می‌کند؛ قبلاً retrieval رد می‌شد و جواب عمومی بود. `lessonId` اگر بیاید همچنان
دقیق‌تر است، چون فصل را هم معلوم می‌کند.
## استریک و جدولِ رتبه‌بندی، زنده از فعالیت
تا ۲۰۲۶-۰۹-۲۴ هیچ‌جای اپ `StreakRecord` نمی‌نوشت و `GET /api/leaderboard` فقط
جدولی را می‌خواند که با اجرای دستیِ `rebuild_leaderboard` پر می‌شد؛ یعنی برای
کاربرِ واقعی «روز پیاپی» همیشه صفر بود و XPاش هیچ‌وقت در جدول دیده نمی‌شد.
- **استریک** (`progress/services.py`): پاسخ به سوال، پایانِ درس و پایانِ جزیره
امروز را (به وقتِ تهران) روزِ فعال ثبت می‌کنند. نمایش با
`effective_current_streak` است: اگر آخرین روزِ فعال نه امروز است نه دیروز،
صفر نشان داده می‌شود. خطای استریک هرگز پاسخ یا تکمیل را خراب نمی‌کند.
- **رتبه‌بندی** (`progress/leaderboard.py`): زنده حساب و ۶۰ ثانیه کش می‌شود.
`all_time` از `UserStats.xp`، `weekly` از XPی درس‌ها و جزیره‌های ۷ روزِ اخیر.
`rebuild_leaderboard` هم از همین تابع می‌خواند و فقط جدولِ ادمین را پر می‌کند.
## دیتابیس
هنوز SQLite است (`OPTIONS.timeout=20` تا قفل‌های هم‌زمانیِ نوشتن را تحمل کند).