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:
@@ -10,8 +10,11 @@ seed_test_data بود، یعنی `GET /api/leaderboard` روی هر سرورِ
|
||||
|
||||
- `all_time` از `UserStats.xp` خوانده میشود، چون همان عددی است که خودِ اپ
|
||||
در طولِ عمرِ کاربر بالا میبرد و در `/api/user/summary` هم نشان داده میشود.
|
||||
- `weekly` از جمعِ `content.UserLessonCompletion.xp_earned` در بازهی اخیر
|
||||
ساخته میشود.
|
||||
- `weekly` از جمعِ XPی درسها و جزیرههای تمامشده در بازهی اخیر ساخته
|
||||
میشود (`UserLessonCompletion` و `UserNodeCompletion`).
|
||||
|
||||
از ۲۰۲۶-۰۹-۲۴ خودِ `GET /api/leaderboard` زنده از همین منبع میخواند؛ این
|
||||
دستور فقط جدولِ LeaderboardEntry را برای دیدن در ادمین پر میکند.
|
||||
|
||||
⚠️ چرا `UserAnswer.xp_earned` شمرده **نمیشود**: با اینکه آن فیلد پر میشود،
|
||||
`QuestionAnswerView` هیچوقت آن را به `UserStats.xp` اضافه نمیکند — تنها
|
||||
@@ -30,8 +33,6 @@ idempotent است: ردیفهای همان بازه اول پاک و دوبا
|
||||
"""
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.db import transaction
|
||||
from django.db.models import Sum
|
||||
from django.utils import timezone
|
||||
|
||||
PERIODS = ('weekly', 'all_time')
|
||||
|
||||
@@ -63,23 +64,12 @@ class Command(BaseCommand):
|
||||
|
||||
# -- جمعآوریِ امتیازها ---------------------------------------------------
|
||||
|
||||
def _collect(self, period, days):
|
||||
if period == 'all_time':
|
||||
from progress.models import UserStats
|
||||
# values_list مادی میشود (list)، نه iterator — طبق درسِ
|
||||
# ExecuteCampaignRealConcurrencyTests: کرسرِ بازِ خواندن روی SQLite
|
||||
# در حلقهای که مینویسد، جدول را برای هر نویسندهی همزمان قفل میکند.
|
||||
return list(UserStats.objects.values_list('user_id', 'xp'))
|
||||
|
||||
from content.models import UserLessonCompletion
|
||||
since = timezone.now() - timezone.timedelta(days=days)
|
||||
rows = (
|
||||
UserLessonCompletion.objects
|
||||
.filter(created_at__gte=since)
|
||||
.values('user_id')
|
||||
.annotate(total=Sum('xp_earned'))
|
||||
)
|
||||
return [(r['user_id'], r['total'] or 0) for r in rows]
|
||||
def _collect(self, period, days, include_zero=False):
|
||||
# همان منبعی که GET /api/leaderboard زنده میخواند (progress/leaderboard.py)
|
||||
# تا عددِ جدولِ ادمین و عددِ اپ از هم جدا نیفتند. limit بزرگ است چون
|
||||
# سقف را خودِ دستور با --limit اعمال میکند.
|
||||
from progress.leaderboard import compute_scores
|
||||
return compute_scores(period, days=days, limit=10**9, include_zero=include_zero)
|
||||
|
||||
# -- نوشتن ---------------------------------------------------------------
|
||||
|
||||
@@ -87,7 +77,7 @@ class Command(BaseCommand):
|
||||
from progress.models import LeaderboardEntry
|
||||
|
||||
days = opts['days']
|
||||
scores = self._collect(period, days)
|
||||
scores = self._collect(period, days, include_zero=opts['include_zero'])
|
||||
if not opts['include_zero']:
|
||||
scores = [(uid, xp) for uid, xp in scores if xp > 0]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user