Content ------- Every node in grades 8, 9, 10, 11 and 12 now holds a full four-question quiz — 121 more questions, filling the two حسابان دوازدهم nodes in فصل ۵: کاربردهای مشتق that were empty and the two ریاضی هشتم nodes that were short. The repo's own seed guard was already failing on those two empty nodes; it passes now. Grades 3, 6 and 7 are the other team's books and were left alone. 39 of their 145 nodes still hold fewer than four questions, but none is empty, so every node on the map still plays. An audit over all 1,593 questions in grades 8–12 found no structural fault: no question with the wrong number of options, no question with zero or two correct answers, no empty or duplicated option, no leftover LaTeX, and no two identical questions inside one lesson. Contract -------- Three endpoints were missing or malformed in the OpenAPI schema, which matters more than it looks: that schema is the contract the frontend generates its client from, so a view missing there is a view the other repo cannot call. - NotificationMarkReadView was dropped from the schema entirely, because spectacular could not guess a request body for its PATCH and gives up on the whole view rather than the one field. It declares request=None. - PaymentRedirectView was dropped for the same reason; it answers with a 302 and no JSON body, which it now says. - /api/health and /api/health/ collided on operationId and were emitted as health_retrieve and health_retrieve_2. The slash variant exists only for a monitor that calls it that way, so it is excluded from the schema. `spectacular` now runs clean — no errors, no warnings. Tidying ------- Condensed the two longest comment blocks (18 and 14 lines) in rag_verify_solutions and settings, keeping the measurement and the rule and dropping the retelling. README gains the bulk question-authoring workflow; the playbook's subagent section now points at the real tool instead of describing the steps by hand. 458 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
18 KiB
Tadrisino API
بکاند اپلیکیشن آموزشی Tadrisino — Django + Django REST Framework.
احراز هویت با OTP، محتوای درسی و پیشرفت کاربر، دستیار هوش مصنوعی، اشتراک و پرداخت، تخفیف، اعلانها، همکاری در فروش، پشتیبانی و حساب خانواده.
| اپ | مسئولیت |
|---|---|
accounts |
احراز هویت، session، پروفایل کاربر |
content |
پایهها، مباحث، درسها، سوالها، پیشرفت |
progress |
آمار، لیدربرد، streak |
ai_tutor |
مکالمات دستیار هوش مصنوعی |
rag |
لایهی RAG: بازیابی از Qdrant + تولید با GPT، پشتِ POST /api/ai/ask |
subscription / payment / discount |
پلن، سفارش، تراکنش، کد تخفیف |
notifications, affiliate, support, family |
امکانات جانبی |
statuslog |
لاگ عمومی تغییر status، مستقل از بقیهی اپها |
۱. راهاندازی محیط توسعه
پیشنیازها
- پایتون ۳.۱۲
- pip
تنظیم محیط
python3 -m venv venv
source venv/bin/activate # ویندوز: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env
.env.example (تمام متغیرهایی که واقعاً توسط اپ خونده میشن):
DJANGO_DEBUG=1
DJANGO_SECRET_KEY=
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
CACHE_URL=
KAVENEGAR_API_KEY=
GRAFANA_ADMIN_PASSWORD=admin
مقادیر .env رو به شل خودتون export کنید (یا از direnv/python-dotenv استفاده کنید):
export DJANGO_DEBUG=1 # ویندوز (PowerShell): $env:DJANGO_DEBUG=1
DEBUGدیفالتشFalseاست. برای فعالکردنِ کمککنندههای dev-only در Swagger (DEV_MASTER_OTPو فیلدِdevOtpتوی پاسخها)، محلیDJANGO_DEBUG=1رو ست کنید.
سریعترین راه اجرای پروژه
python manage.py migrate
python manage.py runserver
اپ روی http://127.0.0.1:8000 بالا میاد.
مایگریشنها
python manage.py migrate
دیتای تستی (Seed)
یک دیتاست کوچک و idempotent میسازه که همهی اپها رو پوشش میده (کاربر، محتوا، پیشرفت،
اشتراک، پرداخت، تخفیف، دستیار هوش مصنوعی، اعلان، افیلیت، پشتیبانی، خانواده). اجرای مکرر
امن است — با get_or_create هیچ رکورد تکراریای نمیسازه. پیشفرض فقط وقتی DEBUG=True
اجرا میشه، مگر با --force.
python manage.py seed_test_data # لوکال (DEBUG=True)
python manage.py seed_test_data --force # staging (DEBUG=False)
python manage.py seed_test_data --reset # پاککردنِ دیتای قبلیِ seed و ساختِ دوباره
مستندات API
| Swagger UI | http://127.0.0.1:8000/api/docs/ |
| Redoc | http://127.0.0.1:8000/api/redoc/ |
| OpenAPI schema | http://127.0.0.1:8000/api/schema/ |
| پنل ادمین | http://127.0.0.1:8000/admin/ |
شروع سریع در Swagger:
POST /api/auth/sessionبا{"type":"guest","deviceId":"d_test1"}→ یکtokenبگیرید.- روی دکمهی Authorize بزنید و فقط خودِ
tokenرو (بدون کلمهی Bearer) وارد کنید. - برای تست لاگین با شماره:
POST /api/auth/otp/requestبزنید، یا در محیط توسعه کد 0000 رو با هر شمارهای درPOST /api/auth/loginامتحان کنید (dev-only، نیاز بهDEBUG=True).
دستورهای پرکاربرد
python manage.py createsuperuser # اختیاری، برای /admin
python manage.py test # اجرای کل تستسوییت
دستیار هوش مصنوعی (RAG)
POST /api/ai/ask پاسخ را از روی محتوای واقعیِ کتاب میسازد: embedding سوال →
جستجوی Qdrant محدود به همان درس/فصل → تولید با GPT. تیکههای استفادهشده در
usedChunks برمیگردند.
- نیازمندِ
OPENAI_API_KEYوQDRANT_API_KEYدر.env(اپrag؛ تنظیماتِOPENAI_*/RAG_*درconfig/settings.py). بدونِ کلید، همان پاسخِ stub قبلی. - تست در Swagger:
POST /api/ai/askبا{"userQuestion":"...","subject":"ریاضی","grade":"ششم","lessonNumber":2}. "stream": trueپاسخ را بهصورت SSE توکنبهتوکن میفرستد (قراردادِ JSON بدونِ این فلگ دستنخورده).python manage.py rag_catalogفهرستِ زندهی collectionهای Qdrant را نشان میدهد (فقطخواندنی).- افزودنِ محتوای خودمان:
POST /api/rag/ingest/textو/api/rag/ingest/pdf(داخلی، هدرِX-Internal-Key).
۲. راهنمای دیپلوی پروداکشن
بالا آوردنِ نسخهی جدید — یک دستور
./deploy.sh
همین. اسکریپت به ترتیب کدِ تازه را میگیرد، وابستگیها را نصب میکند، مهاجرتها را اعمال میکند، فایلهای استاتیک را جمع میکند، سرویس را ریاستارت میکند، و در پایان خودش بررسی میکند که واقعاً بالا آمده یا نه. اگر بالا نیامد، علتهای رایج را چاپ میکند.
چرا این مهم است: قبلاً اینها چند کارِ دستی به ترتیبِ درست بودند و فراموش شدنِ
هر کدام اپ را میخواباند، با خطایی که گویا نبود. مثلاً اضافه شدنِ یک پکیجِ جدید
به requirements.txt بدونِ pip install باعثِ ModuleNotFoundError موقعِ بالا
آمدن میشود و کاربر فقط 502 Bad Gateway میبیند. اسکریپت همیشه pip install
را اجرا میکند تا این حالت اصلاً پیش نیاید.
بارِ اول، نامِ سرویس را بدهید تا دفعههای بعد خودکار ریاستارت شود:
TADRISINO_SERVICE=<نامِ سرویس> ./deploy.sh
متغیرهای اختیاری: SKIP_PULL=1 (بدونِ git pull)، TADRISINO_HEALTH_URL،
PIP_INDEX_URL (اگر pypi.org تایماوت میدهد).
بررسیِ سلامت از بیرون
curl https://<host>/api/health
بدونِ احرازِ هویت و بدونِ SSH میگوید دیتابیس سالم است یا نه، مهاجرتِ
اعمالنشده مانده یا نه، کش کار میکند یا نه، و چند پایه/درس/سوال روی آن سرور
هست. 200 یعنی سالم، 503 یعنی چیزی واقعاً خراب است. هیچ سکرتی برنمیگرداند.
این همان چیزیست که جلوی «کد رفته ولی مهاجرت اجرا نشده» را میگیرد — حالتی که اپ ظاهراً بالا میآید و بعد زیرِ بارِ واقعی میشکند.
مدیریتِ محتوا بدونِ ترمینال
هیچ محتوایی لازم نیست با دستور روی سرور گذاشته شود.
| کار | کجا |
|---|---|
| افزودن/ویرایشِ یک سوال | /admin/content/question/ → «افزودن» (انتخابِ درس با جستجو) |
| وارد کردنِ فلهای سوال | همان صفحه → «وارد کردنِ سوالها از فایل» |
| گرفتنِ خروجیِ سوالها | چند سوال را انتخاب کنید → اکشنِ «دریافتِ فایل» |
| وارد کردنِ برنامهی درسی (پایه/فصل/درس) | /admin/content/grade/ → «وارد کردنِ برنامهی درسی از فایل» |
| گرفتنِ خروجیِ برنامهی درسی | پایهها را انتخاب کنید → اکشنِ «دریافتِ فایلِ برنامهی درسی» |
خروجیِ هر کدام دقیقاً همان قالبیست که ورودیاش میپذیرد، پس چرخهی «بگیر → ویرایش کن → برگردان» بسته است و برای گرفتنِ نمونه لازم نیست کسی مستندات بخواند.
دو تضمین:
- سوالها: فایلِ نامعتبر هیچ چیزی نمینویسد — یا همه وارد میشوند یا هیچکدام.
- برنامهی درسی: چیزی پاک نمیشود. هر چه در فایل هست ساخته/بهروز میشود و هر چه نیست دستنخورده میماند، پس یک فایلِ ناقص محتوای سرور را از بین نمیبرد.
ساختِ انبوهِ سوال از کتاب
وقتی کتابی تازه روی Qdrant آمده و باید برایش سوال ساخته شود، مسیرِ رایگان این است:
# اسکلتِ برنامهی درسی از فهرستِ خودِ کتاب (بدونِ هزینه)
python manage.py seed_curriculum_from_textbook --subject ریاضی --grade نهم --dry-run
python manage.py seed_curriculum_from_textbook --subject ریاضی --grade نهم
# جفتهای (درس، مسئلهی تأییدشده) را برای سوالنویسی بیرون بده (بدونِ هزینه)
python manage.py generate_questions_from_chunks --subject ریاضی --grade نهم --export /tmp/batches
خروجی چند فایلِ JSON است بهعلاوهی یک *-RULES.txt که قواعدِ ساختِ سوال را
دارد. سوالها را با آن قواعد بنویسید و از «وارد کردنِ فلهای سوال» در جدولِ
بالا واردشان کنید. همان فرمان بدونِ --export کار را به مدلِ پولی میسپارد،
ولی گرانتر است و کیفیتِ بهتری نمیدهد. جزئیات و هزینههای اندازهگیریشده در
docs/extraction-playbook.md.
--dry-run روی هر دو فرمان کار میکند و پیش از هر نوشتنی نشان میدهد چه ساخته
خواهد شد.
دادهی تستی روی سرور
seed_test_data فقط برای محیطِ لوکال است و نباید روی سروری که کاربرِ واقعی
دارد اجرا شود؛ ردیفهایی با پیشوندِ [SEED]/[DEMO] میسازد که در API دیده
میشوند. محتوای واقعی از مسیرِ جدولِ بالا میرود، نه از این دستور.
متغیرهای محیطی موردنیاز
| متغیر | لازم در پروداکشن | توضیح |
|---|---|---|
DJANGO_DEBUG |
باید ستنشده یا 0 باشه |
دیفالتهای امنیتیِ پروداکشن رو فعال میکنه |
DJANGO_SECRET_KEY |
بله | بدونش وقتی DEBUG=False است اپ اصلاً بالا نمیاد |
DJANGO_ALLOWED_HOSTS |
بله | لیستِ host ها، کاما-جدا |
CACHE_URL |
بله | آدرسِ Redis، مثلاً redis://host:6379/0 — برای rate limitِ مشترک وقتی DEBUG=False لازمه |
KAVENEGAR_API_KEY |
بله | ارسال واقعیِ OTP از طریق کاوهنگار |
GRAFANA_ADMIN_PASSWORD |
فقط اگه از استکِ Grafana استفاده میکنید | بخشِ Docker پایین رو ببینید |
تنظیمات پروداکشن
DEBUGباید ست نشه (یا0باشه). باDEBUG=False:SECRET_KEYاجباریه — هیچ fallback ناامنی وجود نداره.CACHE_URL(Redis) اجباریه —LocMemCacheفقط برای dev هست.DEV_MASTER_OTPو فیلدِdevOtpتوی پاسخها غیرفعال میشن.
- برای ترافیک واقعی بهجای SQLیت پیشفرض، از یک دیتابیسِ پروداکشنگرید استفاده کنید (پروژه با همون SQLite شیپ میشه؛ عوضکردنِ engine یک تصمیمِ زیرساختیه، خارج از این ریپو).
- از طریق یک سرورِ WSGI/ASGI (مثلاً gunicorn) جلوی اپ سرو کنید — تنظیماتِ Docker موجود
همچنان
runserverاجرا میکنه و فقط برای dev/local مستند شده (پایین رو ببینید).
تنظیمات Docker/دیپلوی
docker-compose.yml موجود، اپ رو بههمراه یک استکِ لاگِ متمرکز
(Loki + Grafana Alloy + Grafana) اجرا میکنه. این یک تنظیمِ dev/local هست، نه دیپلویِ
پروداکشن (سرویسِ django همچنان runserver اجرا میکنه، نه یک سرورِ پروداکشن).
cp .env.example .env # اختیاری — بدونش compose از دیفالتهای dev استفاده میکنه
docker compose up --build
| سرویس | آدرس | توضیح |
|---|---|---|
| اپ Django | http://localhost:8000 |
|
| Grafana | http://localhost:3000 |
دسترسیِ anonymous admin، فقط محیطِ محلی |
| Loki | http://localhost:3100 |
ذخیرهسازیِ لاگ، در معرضِ کاربر نیست |
| Alloy | http://localhost:12345 |
رابطِ دیباگِ اختیاری |
ترتیبِ لازمِ بالا آمدن
Compose خودش این رو مدیریت میکنه:
- اول
lokiبالا میاد. alloyوgrafanaبعد ازlokiبالا میان (depends_on).djangoهیچ وابستگیای به استکِ لاگ نداره — خودشmigrateبعدrunserverرو اجرا میکنه و منتظرِ Loki/Alloy/Grafana نمیمونه.
دستورهای manage.py داخلِ Docker
اگه فقط از طریقِ Docker اجرا میکنید (بدونِ venv محلی)، python manage.py ... رو مستقیم
روی سیستم خودتون اجرا نکنید — باید داخلِ کانتینرِ django اجرا بشه:
docker compose exec django python manage.py createsuperuser
docker compose exec django python manage.py seed_test_data
docker compose exec django python manage.py test
(migrate نیازی به این نیست — طبقِ بالا، خودکار قبل از runserver اجرا میشه.)
مایگریشنها
python manage.py migrate --noinput
در Docker، این بهصورت خودکار بهعنوان بخشی از دستورِ سرویسِ django، قبل از runserver اجرا میشه.
راهنمای seed برای staging/تست
- هرگز
seed_test_dataرو روی یک دیتابیسِ پروداکشنِ واقعی اجرا نکنید. - روی staging (
DEBUG=False)، صریحاً با--forceاجرا کنید:python manage.py seed_test_data --force - برای پاککردن و ساختِ دوبارهی دیتای seed بین چرخههای تست، از
--resetاستفاده کنید. همهی رکوردهای seedشده در فیلدهای متنیِ قابلمشاهده با تگِ[SEED]مشخص شدن، برای شناساییِ راحتتر و پاکسازی.
چطور API دیپلویشده رو verify کنیم
curl -i https://<host>/api/schema/ # باید 200 برگردونه (OpenAPI schema)
curl -i https://<host>/api/docs/ # باید 200 برگردونه (Swagger UI)
curl -i -X POST https://<host>/api/auth/session \
-H "Content-Type: application/json" \
-d '{"type":"guest","deviceId":"verify-1"}' # باید یک token برگردونه
نکات مهمِ پروداکشن
DEV_MASTER_OTP(کد0000) و فیلدِdevOtpتوی پاسخها فقط وقتیDEBUG=Trueوجود دارن — قبل از رفتن روی محیطِ واقعی مطمئن بشیدDEBUGخاموشه.- احراز هویت با Bearer token امضاشده و stateless (
django.core.signing)، هماهنگ با مدلSession— بدون کتابخانهی JWT خارجی. accounts.Userیک مدل بیزنسیِ مستقله، نهAUTH_USER_MODELجنگو.- همهی لاگهای ساختاریافتهی درخواست (
method/path/status/duration/user، بدونِ بدنه/هدر/سکرت) روی stdout و — وقتیLOG_DIRست باشه (مثلِ داخلِ Docker) — در/var/log/django/app.logنوشته میشن؛ قابلِ مشاهده در Grafana از طریقِ استکِ بالا. POST /api/ai/askبا تنظیمِOPENAI_API_KEY/QDRANT_API_KEYپاسخِ واقعیِ RAG میدهد (اپrag)؛ بدونِ کلید همان stub. جزئیات: بخشِ «دستیار هوش مصنوعی (RAG)» بالاتر.- ارسالِ OTP از طریقِ کاوهنگار (
KAVENEGAR_API_KEY) انجام میشه؛ شکستِ ارسال بهجای موفقیتِ ساختگی، خطای صریحِ503برمیگردونه. - هر خطای پیشبینینشده (نه فقط خطاهای شناختهشدهی API) همیشه پاسخِ JSON برمیگردونه،
نه صفحهی HTML پیشفرضِ جنگو، و کامل با traceback لاگ میشه (
config/exceptions.py).