The frontend plays node quizzes, but a node only completed once every one of its
8-13 lessons was completed separately, so students were stuck after the first
island of each unit. POST /api/nodes/{id}/quiz/complete now completes the node
(any score; score only sets stars/xp) and returns nextNodeId.
Also closes the other backend gaps found while auditing the frontend:
- /api/ai/ask without lessonId now picks the book from the user's grade instead
of skipping retrieval, so the "ask Pi" tab is grounded in the textbook.
- guest sessions return a refreshToken; they used to go silently 401 after 1h.
- lesson_complete throttle 10/hour -> 120/hour (a lesson here is 1-2 questions).
- progressPercent is per grade, not over all 2,617 lessons of every grade.
- a one-year plan (299,000 toman) is created by migration when no plan exists.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
59 lines
2.6 KiB
Python
59 lines
2.6 KiB
Python
"""
|
|
پلنِ اشتراکِ یکساله — اولین پلنِ واقعیِ سرور.
|
|
|
|
تا این migration روی پروداکشن هیچ Planی نبود و تبِ «اشتراک» در اپ خالی
|
|
بود. از راهِ migration ساخته میشود نه دستی از ادمین، تا هر سرورِ تازهای
|
|
با همان `deploy.sh` صاحبش شود.
|
|
|
|
قیمت تصمیمِ محصولی است و از پنلِ ادمین قابلِ تغییر است؛ این migration فقط
|
|
وقتی میسازدش که هیچ پلنی وجود نداشته باشد، پس ویرایشِ بعدیِ ادمین را هرگز
|
|
بازنویسی نمیکند. مبنای ۲۹۹٬۰۰۰ تومان: اپهای خودآموزِ مشابه (مثلاً زبانشناس)
|
|
اشتراکِ سالانه را حدودِ ماهی ۴۰ هزار تومان میفروشند؛ پکیجهای ویدیوییِ
|
|
کنکور چند میلیوناند و همردهی این اپ نیستند.
|
|
|
|
⚠️ تنها مزیتی که اشتراک امروز در کد واقعاً میدهد «قلبِ نامحدود» است
|
|
(subscription.services.has_unlimited_hearts). بندهای دیگرِ features برای
|
|
همه در دسترساند؛ اگر قرار است اشتراک چیزِ بیشتری بدهد، آن تصمیمِ محصولی است.
|
|
"""
|
|
from django.db import migrations
|
|
|
|
YEARLY_PLAN = {
|
|
'title': 'اشتراک یکساله',
|
|
'price': 299_000,
|
|
'currency': 'IRT',
|
|
'discount_percent': 0,
|
|
'popular': True,
|
|
'duration_days': 365,
|
|
'badge': 'بهترین انتخاب',
|
|
'features': [
|
|
'قلبِ نامحدود؛ هیچوقت وسطِ تمرین متوقف نمیشی',
|
|
'همهی فصلها و آزمونهای پایهات',
|
|
'دستیارِ هوشمند بر اساسِ کتابِ درسی',
|
|
'یک سال کامل دسترسی',
|
|
],
|
|
}
|
|
|
|
|
|
def create_yearly_plan(apps, schema_editor):
|
|
Plan = apps.get_model('subscription', 'Plan')
|
|
if Plan.objects.exists():
|
|
return
|
|
Plan.objects.create(**YEARLY_PLAN)
|
|
|
|
|
|
def remove_yearly_plan(apps, schema_editor):
|
|
Plan = apps.get_model('subscription', 'Plan')
|
|
# فقط پلنی که خودِ این migration ساخته و هنوز خریدی ندارد.
|
|
Plan.objects.filter(title=YEARLY_PLAN['title'], subscriptions__isnull=True).delete()
|
|
|
|
|
|
class Migration(migrations.Migration):
|
|
|
|
dependencies = [
|
|
('subscription', '0004_plan_badge_plan_features'),
|
|
]
|
|
|
|
operations = [
|
|
migrations.RunPython(create_yearly_plan, remove_yearly_plan),
|
|
]
|