بالا آوردنِ نسخهی جدید تا الان چند کارِ دستی به ترتیبِ درست بود (pull، نصبِ
وابستگیها، مهاجرت، استاتیک، ریاستارت) و فراموش شدنِ هر کدام اپ را میخواباند
با خطایی که از بیرون فقط «502 Bad Gateway» دیده میشد. همین الان هم سرور
دقیقاً به همین دلیل خوابیده: کامیتِ ac1b1af پکیجِ django-cors-headers را اضافه
کرد، و بدونِ pip install جنگو با ModuleNotFoundError بالا نمیآید.
deploy.sh همه را به ترتیب و idempotent انجام میدهد. pip install همیشه اجرا
میشود، نه فقط وقتی requirements عوض شده — تشخیصِ «عوض شده یا نه» خودش یک جای
فراموششدنیِ دیگر است. آخرِ کار خودش سلامت را بررسی میکند و اگر بالا نیامده
باشد علتهای رایج را چاپ میکند، بهجای اینکه کسی بعداً از بیرون کشفش کند.
GET /api/health وضعیت را بدونِ احراز و بدونِ SSH بیرون میدهد: دیتابیس،
مهاجرتهای اعمالنشده، کش، و شمارشِ پایه/درس/سوال روی همان سرور. مهمترینش
مهاجرتِ اعمالنشده است — حالتی که اپ ظاهراً بالا میآید و بعد زیرِ بارِ واقعی
با خطای ستونِ ناموجود میشکند. عمداً هیچ سکرتی برنمیگرداند، فقط بله/خیرِ
«تنظیم شده».
۵۰۳ برمیگرداند وقتی دیتابیس/کش/مهاجرت خراب است تا پایشِ خودکار بتواند رویش
حساب کند؛ نبودِ محتوا خرابی حساب نمیشود ولی گزارش میشود.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
15 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/→ دکمهی «وارد کردنِ سوالها از فایل»- برای گرفتنِ نمونه: چند سوال را انتخاب کنید → اکشنِ «دریافتِ فایلِ سوالهای انتخابشده». خروجی دقیقاً همان قالبیست که ورودی میپذیرد.
فایلِ نامعتبر هیچ چیزی نمینویسد — یا همهی سوالها وارد میشوند یا هیچکدام، و خطا میگوید کدام ردیف ایراد دارد.
متغیرهای محیطی موردنیاز
| متغیر | لازم در پروداکشن | توضیح |
|---|---|---|
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).