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:
@@ -1,3 +1,5 @@
|
||||
import logging
|
||||
|
||||
from django.core.cache import cache
|
||||
from django.db import IntegrityError, transaction
|
||||
from django.db.models import Count, F
|
||||
@@ -43,6 +45,8 @@ from .serializers import (
|
||||
QuestionReportResponseSerializer,
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuestAwareContentThrottle(ScopedRateThrottle):
|
||||
"""
|
||||
@@ -163,6 +167,18 @@ class GradesListView(APIView):
|
||||
return Response(GradesListResponseSerializer(_grades_payload()).data)
|
||||
|
||||
|
||||
def _record_streak(user_id):
|
||||
"""
|
||||
فعالیتِ امروز را در استریک ثبت میکند. خطایش هرگز نباید ثبتِ پاسخ یا
|
||||
تمام شدنِ درس را خراب کند — استریک تزئینی است، پیشرفت نیست.
|
||||
"""
|
||||
try:
|
||||
from progress.services import record_activity
|
||||
record_activity(user_id)
|
||||
except Exception: # noqa: BLE001
|
||||
logger.exception('streak: ثبتِ فعالیتِ کاربر %s شکست خورد', user_id)
|
||||
|
||||
|
||||
def _node_status(lesson_ids, completed_ids, is_first_unlocked_candidate, quiz_completed=False):
|
||||
# آزمونِ مبحث تمام شده → مبحث تمام است، مستقل از تکتکِ درسها.
|
||||
if quiz_completed:
|
||||
@@ -437,6 +453,7 @@ class QuestionAnswerView(APIView):
|
||||
'heartsRemaining': hearts_remaining if hearts_remaining is not None else 5,
|
||||
'mascotMood': 'happy' if choice.is_correct else 'encourage',
|
||||
}
|
||||
_record_streak(user_id)
|
||||
return Response(AnswerResponseSerializer(data).data)
|
||||
|
||||
|
||||
@@ -593,6 +610,7 @@ class LessonCompleteView(APIView):
|
||||
'coinsEarned': coins_earned,
|
||||
'stats': stats_payload,
|
||||
}
|
||||
_record_streak(user_id)
|
||||
return Response(LessonCompleteResponseSerializer(data).data)
|
||||
|
||||
|
||||
@@ -904,6 +922,7 @@ class NodeQuizCompleteView(APIView):
|
||||
raise
|
||||
stats = UserStats.objects.filter(user_id=user_id).first()
|
||||
|
||||
_record_streak(user_id)
|
||||
return respond(completion, stats)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user