Tutor (prompt tutor-v3-stepwise-plain): plain text instead of LaTeX/markdown,
with rag/services/mathtext.py converting whatever still slips through; full
step-by-step solutions with a check before the final answer; three tones by
grade; the last 6 messages of the conversation go to the model and follow-ups
are no longer served from another user's cache; book solutions the checker
disputes are kept out of the context; a questionId gives the tutor the
practice question and the student's wrong answer. /api/health reports when
the AI credit runs out. rag_model_benchmark compares chat models on verified
book problems (run on the server; credit ran out mid-run).
Map: only lessons that have questions count toward finishing an island,
lessonIds[0] / nextLessonId is always the next lesson to play, and a finished
island stays finished. The Seyar characters (sigma/delta/pi) are seeded.
Audit fixes, each reproduced before fixing:
- the plan is stored on the order, so paying a monthly order after starting
a yearly checkout no longer grants a year
- opening checkout no longer demotes an active subscription; renewals extend
from the current expiry; expired subscriptions are not reported active
- one discount code per order and only before the gateway; a 100% code
completes the purchase; lost Zarinpal confirmations are re-verified
- the unused /api/payment/webhook "mark paid" endpoint is removed
- XP counts only answers recorded on the server
- 6-digit OTP, each code dies after 5 wrong guesses; POST /api/auth/logout
- a guest who signs up keeps their account and progress (test phones excluded)
- affiliate commission on the first real payment; guests excluded
- parents with several children no longer get a 500
- SQLite runs transactions IMMEDIATE, since it ignores select_for_update
- admin login is rate-limited; display names drop phone numbers and links;
userQuestion is capped at 2000 characters
- server-side heart enforcement exists behind HEARTS_ENFORCED (off until the
frontend handles 403 no_hearts)
Not done on purpose: rotating the server's secret key and moving to Postgres.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
تا حالا اگر کسی روی سرور فایلی را دستی عوض کرده بود، `git pull --ff-only`
شکست میخورد و کلِ اسکریپت همانجا میایستاد. چون هیچکدام از مراحلِ بعدی
اجرا نمیشد، سرور با کدِ قدیمی میماند — بیصدا. یک بار همین دو روز طول کشید.
حالا مرحلهی اول fetch میکند، اگر تغییرِ دستی (یا کامیتِ محلی) باشد آن را در
.deploy-backups/*.patch نگه میدارد، و با `git reset --hard origin/<branch>`
کنارش میگذارد و ادامه میدهد. سرور در هر وضعیتی باشد، یک اجرا دقیقاً همان
چیزی میشود که روی gitea است.
`git clean` عمداً زده نمیشود و نباید اضافه شود: db.sqlite3 و .env و
.django_secret_key ردیابینشده و داخلِ همین پوشهاند.
با مخزنِ ساختگی هر سه حالت آزموده شد: تغییرِ دستی، درختِ تمیز، و کامیتِ محلیِ
واگرا. در هر سه، .env و db.sqlite3 دستنخورده ماندند.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
پنلِ ادمین روی سرور بدونِ CSS بالا آمد و هیچ خطایی هم در لاگ نبود.
علت ترتیبِ کارها بود: این مرحله وقتی DEBUG روشن بود رد میشد، چون در آن حالت
whitenoise از finders میخواند و جمعآوری لازم نیست. کسی با DEBUG روشن دیپلوی
کرد و بعد DEBUG را خاموش کرد — کارِ درستی هم بود — و از آن لحظه جنگو دنبالِ
فایلهایی میگشت که هیچوقت جمع نشده بودند.
یعنی درستیِ دیپلوی به ترتیبِ کارِ یک آدم گره خورده بود. چند ثانیه صرفهجویی،
به قیمتِ شکستی که فقط در مرورگر دیده میشود. حالا بیقید و شرط اجرا میشود؛
idempotent است و در هر دو حالت درست کار میکند.
staticfiles/ هم به gitignore اضافه شد: حالا که این مرحله همیشه اجرا میشود،
روی ماشینِ هر توسعهدهندهای ساخته میشود و ۴٫۴ مگابایت فایلِ تولیدی نباید
با یک `git add .` واردِ مخزن شود.
بررسی شد که با DEBUG=False کلِ مسیر کار میکند: استاتیک ۲۰۰، صفحهی ادمین
۲۰۰، API ۲۰۰، و ورود با OTP از پنل. کدِ 0000 همانطور که باید، رد میشود.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
سه بار پشتِ سر هم همین الگو تکرار شد: یک متغیرِ محیطی ست نبود، جنگو موقعِ
بالا آمدن مرد، و از بیرون فقط «502 Bad Gateway» دیده شد. کسی که سرور را بالا
میآورد هیچ سرنخی نداشت، و تشخیص هر بار یک رفتوبرگشتِ یکروزه بود.
ALLOWED_HOSTS و CSRF_TRUSTED_ORIGINS حالا دامنههای شناختهشدهی خودمان را
پیشفرض دارند. CSRF مخصوصاً مهم است: بدونش ورود به پنلِ ادمین از پشتِ پراکسیِ
HTTPS «CSRF verification failed» میدهد و علتش از پیام معلوم نیست.
SECRET_KEY اگر ست نباشد، در پروداکشن یک کلیدِ تصادفیِ واقعی ساخته و در
.django_secret_key نگه داشته میشود (۰۶۰۰، خارج از گیت). همانقدر
غیرقابلِ حدس، بدونِ یک قدمِ دستیِ دیگر؛ و چون روی دیسک میماند، ریاستارت
نشستهای کاربران را باطل نمیکند.
CACHE_URL نبودن دیگر مانعِ بالا آمدن نیست. مشترک نبودنِ کش بینِ workerها یک
ضعفِ واقعیست ولی دلیلِ خوبی برای خوابیدنِ کلِ سرویس نیست؛ حالا بالا میآید و
/api/health صریحاً هشدار میدهد. تستِ مربوطه هم از «باید بالا نیاید» به
«باید بالا بیاید ولی بیصدا نماند» تغییر کرد.
متغیرِ محیطی همچنان بر همهی این پیشفرضها اولویت دارد.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two bugs made self-ingested content useless, both silently:
- _payload wrote `chapter_number` (int) while math retrieval filters on
`chapter` (the full "فصل ۲: کسر" string), so anything we ingest could
never match a search — no error, just permanently invisible content.
- ingest_pdf flattened the vision extraction into one text blob and threw
away type/keywords/generalSolution/problem/solution. LessonChunk is the
source of truth, so rebuilding meant paying for vision again — and the
problem/solution pairs are the input for building lesson questions.
LessonChunk now stores those fields and _payload emits them under the same
keys the other team's collections use, so ours and theirs stay one shape.
Also fixes a regression from the whitenoise commit: the Manifest storage
variant raises when staticfiles.json is absent, which broke all 23 admin
rendering tests (Django forces DEBUG=False in tests, so USE_FINDERS is off)
and would have taken the admin panel down entirely the day DEBUG=0 ships
without a collectstatic run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>