A shared guest account was hand-seeded into the production DB on 2026-09-22 so the frontend developer could see the notification and leaderboard screens before any real traffic exists: deviceId "frontend-test-device", user 6, with 24 Notification rows and 20 LeaderboardEntry rows across both periods, ordered differently per period so the ?period= parameter is visibly doing something. None of that was recorded in the repo. db.sqlite3 is gitignored, so the account existed only in the head of whoever set it up — exactly the kind of thing that disappears at a handoff. It is now in docs/frontend/README.md, where the frontend developer looks, and in docs/operations.md, where whoever runs the server looks. Verified against production before writing it down: the account still resolves to user 6 with isNewUser false, notifications return rows, and both leaderboard periods return six entries. The notifications payload is keyed "items", not "results", which is noted because it is easy to read as an empty response. The operations entry carries the warning that matters: this is hand-written data in a production database, purge_demo_data will not catch it (it only matches [SEED]/[DEMO] prefixes on Grade/Plan/Character/User), and it has to go before real users arrive or they will see invented notifications. The Swagger description now says a deviceId can be any string and that pre-seeded ones exist on request, but deliberately does not print the specific value: /api/docs/ is reachable from the public internet, and a public API reference should not advertise a shared account that anyone can sign into. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tadrisino API
بکاندِ اپلیکیشنِ آموزشیِ تدریسینو — یک REST API روی Django که برنامهی درسی، آزمونها، پیشرفتِ کاربر، دستیارِ هوشِ مصنوعی، اشتراک و پرداخت را سرو میکند.
فرانتاند ریپوی جداگانهای است (MathApp_frontend).
قراردادِ بینِ دو طرف، اسکیمای OpenAPI همین ریپوست — نه حافظهی کسی، نه یک
فایلِ دستی. هر تغییری در API باید از آنجا دیده شود.
| وضعیت | روی پروداکشن، در حالِ سرویسدهی |
| آدرسِ زنده | https://api.math.tadrisino2.ir · سلامت |
| استک | Python 3.11+ · Django 5.2 · DRF · SQLite · Qdrant (RAG) |
| API | ۵۶ اندپوینت · مستنداتِ زنده در /api/docs/ |
| تست | ۴۸۳ تست، همه سبز (python manage.py test) |
| محتوا | ۸ پایه · ۵۸ فصل · ۲۶۳ نود · ۲٬۶۱۷ درس · ۲٬۴۶۳ سوال |
شروعِ سریع
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # برای توسعهی محلی: DJANGO_DEBUG=1 را ست کنید
python manage.py migrate
python manage.py seed_content # برنامهی درسی و سوالهای واقعی از content/seed/
python manage.py runserver
سرویس روی http://127.0.0.1:8000 بالا میآید. .env خودکار خوانده میشود —
لازم نیست چیزی را export کنید.
| Swagger UI | /api/docs/ |
| Redoc | /api/redoc/ |
| اسکیمای OpenAPI | /api/schema/ |
| پنلِ ادمین | /admin/ |
| سلامتِ سرویس | /api/health |
اولین درخواست در Swagger: POST /api/auth/session با
{"type":"guest","deviceId":"d_test1"} بزنید، token را بردارید، روی
Authorize کلیک کنید و همان رشته را (بدونِ کلمهی Bearer) وارد کنید.
دستورهای پرکاربردِ دیگر:
python manage.py test # کلِ تستسوییت
python manage.py createsuperuser # دسترسی به /admin
python manage.py seed_test_data # دادهی ساختگی برای توسعه (فقط محلی)
معماری
هر اپ یک مسئولیت دارد و مدلهای خودش را نگه میدارد:
| اپ | مسئولیت |
|---|---|
accounts |
هویت، session، ورود با OTP |
content |
برنامهی درسی، درس، سوال، پیشرفت روی نقشه |
progress |
آمار، لیدربرد، streak، شخصیتهای بازی |
ai_tutor · rag |
دستیارِ هوشِ مصنوعی: بازیابی از Qdrant + تولید با GPT |
subscription · payment · discount |
پلن، سفارش، تراکنش، کدِ تخفیف |
notifications · affiliate · support · family · campaigns |
امکاناتِ جانبی |
statuslog |
لاگِ عمومیِ تغییرِ وضعیت، مستقل از بقیه |
config |
تنظیمات، روتهای اصلی، middleware |
سه تصمیمِ معماری که غیرِبدیهیاند و دانستنشان لازم است:
- احرازِ هویت، سیستمِ خودمان است نه جنگو. توکنِ bearerِ امضاشده و stateless
با
django.core.signing(بدونِ کتابخانهی JWT)، پشتیبانیشده با مدلِaccounts.Session.accounts.UserعمداًAUTH_USER_MODELنیست — یک مدلِ بیزنسیِ مستقل است. هدرِ نامعتبر یعنی درخواستِ ناشناس، نه خطا؛ هر ویو خودش تصمیم میگیرد که آیا ورود لازم است یا نه (accounts/authentication.py). - اپها با قرارداد از هم جدا شدهاند، نه با ForeignKey. ارجاعِ بینِ اپها با
user_idوobject_id+labelانجام میشود تا هر اپ بعداً قابلِ جدا شدن باشد. FK بینِ اپها اضافه نکنید. - محدودیتِ نرخ (throttle) متمرکز است. هر اندپوینتِ حساس یک
throttle_scopeدارد و نرخش درREST_FRAMEWORK['DEFAULT_THROTTLE_RATES']داخلِconfig/settings.pyتعریف میشود. اندپوینتِ جدید یعنی هر دو.
جزئیاتِ بیشتر: docs/architecture.md.
دیپلوی
کلِ انتشار یک خط است:
ssh -t administrator@78.38.35.249 \
'cd ~/mathapp-v2 && ./deploy.sh; sudo systemctl restart zareian-mathapp'
deploy.sh کد را میگیرد، وابستگیها را نصب میکند، مهاجرت میزند، محتوا را
seed میکند، فایلهای استاتیک را جمع میکند و در پایان ثابت میکند که سرویس
واقعاً ریاستارت شده — نه اینکه فقط فرض کند. ریاستارت جداست چون sudo
داخلِ اسکریپت نمیتواند رمز بپرسد.
پس از هر انتشار، از بیرون و بدونِ SSH تأیید کنید:
curl https://api.math.tadrisino2.ir/api/health
200 یعنی سالم، 503 یعنی چیزی واقعاً خراب است. هیچ سکرتی برنمیگرداند.
رویهی کامل — متغیرهای محیطی، ورودِ توسعهدهندهها، بکاپ، عیبیابی:
docs/operations.md.
مستندات
| سند | موضوع |
|---|---|
docs/operations.md |
دیپلوی، متغیرهای محیطی، سلامت، بکاپ، ورودِ توسعهدهندهها |
docs/content-authoring.md |
افزودن و ویرایشِ محتوا از پنلِ ادمین، ساختِ انبوهِ سوال |
docs/architecture.md |
تصمیمهای معماری و دلیلشان |
docs/frontend/README.md |
راهنمای اتصالِ فرانتاند + کلاینتِ تایپدار |
docs/docker.md |
استکِ لاگِ محلی (Loki + Alloy + Grafana) |
docs/extraction-playbook.md |
استخراجِ محتوا از کتاب با مدلِ ویژن و هزینههایش |
محدودیتهای شناختهشده
اینها عمداً نوشته شدهاند تا کسی دوباره کشفشان نکند:
- دیتابیس هنوز SQLite است، نه Postgres. برای حجمِ فعلی کافیست، ولی تکنویسنده بودنش اولین گلوگاهِ مقیاس است.
- بکاپِ خودکار وجود ندارد.
db.sqlite3در گیت نیست و تنها نسخهی دادهی کاربران است. روشِ بکاپِ دستی درdocs/operations.mdآمده؛ خودکار کردنش اولین کارِ ارزشمندِ بعدی است. CACHE_URLروی سرور ست نشده، پس محدودیتِ نرخ بینِ workerها مشترک نیست. Redis روی همان ماشین بالاست./api/healthاین را هشدار میدهد.KAVENEGAR_API_KEYتنظیم نشده، پسPOST /api/auth/otp/requestبرای شمارههای واقعی503میدهد. راهِ ورودِ تیمِ توسعه درdocs/operations.mdتوضیح داده شده.- پوششِ محتوا یکدست نیست. پایههای هشتم تا دوازدهم کاملند (هر نود چهار سوال)؛ در پایههای سوم/ششم/هفتم ۳۹ نود از ۱۴۵ کمتر از چهار سوال دارند — هیچکدام خالی نیست، پس روی نقشه نودِ غیرقابلِ بازی وجود ندارد.
- محتوای ویدیویی وجود ندارد، هرچند
Lesson.kindوvideo_urlمیتوانند بیانش کنند. DEV_MASTER_OTP(کد0000) باید پشتِDEBUGبماند. روشن بودنش یعنی هر کسی میتواند بهجای هر کاربری وارد شود./api/healthهشدارش میدهد.