Files
MathApp/content/migrations/0009_usernodecompletion.py
Zareian b32194497c let a finished island quiz open the next island, and ground ask-AI in the student's book
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>
2026-09-24 02:49:02 +03:30

34 lines
1.6 KiB
Python

# Generated by Django 5.2.17 on 2026-09-23 23:10
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('content', '0008_grade_id_equals_number'),
]
operations = [
migrations.CreateModel(
name='UserNodeCompletion',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('user_id', models.IntegerField(db_index=True)),
('correct_count', models.PositiveIntegerField()),
('total_questions', models.PositiveIntegerField()),
('stars', models.PositiveSmallIntegerField()),
('xp_earned', models.PositiveIntegerField(default=0)),
('coins_earned', models.PositiveIntegerField(default=0)),
('idempotency_key', models.CharField(blank=True, max_length=100, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('node', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='completions', to='content.node')),
],
options={
'indexes': [models.Index(fields=['user_id', 'node'], name='content_use_user_id_f372a4_idx')],
'constraints': [models.UniqueConstraint(condition=models.Q(('idempotency_key__isnull', False), models.Q(('idempotency_key', ''), _negated=True)), fields=('user_id', 'node', 'idempotency_key'), name='uniq_node_completion_idempotency_key')],
},
),
]