Files
MathApp/README.md
Zareian cbe9b63dc4 make a deploy observable and one command instead of a checklist
بالا آوردنِ نسخه‌ی جدید تا الان چند کارِ دستی به ترتیبِ درست بود (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>
2026-09-13 20:28:59 +03:30

258 lines
15 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
### تنظیم محیط
```bash
python3 -m venv venv
source venv/bin/activate # ویندوز: venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env
```
`.env.example` (تمام متغیرهایی که واقعاً توسط اپ خونده می‌شن):
```env
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` استفاده کنید):
```bash
export DJANGO_DEBUG=1 # ویندوز (PowerShell): $env:DJANGO_DEBUG=1
```
> `DEBUG` دیفالتش `False` است. برای فعال‌کردنِ کمک‌کننده‌های dev-only در Swagger
> (`DEV_MASTER_OTP` و فیلدِ `devOtp` توی پاسخ‌ها)، محلی `DJANGO_DEBUG=1` رو ست کنید.
### سریع‌ترین راه اجرای پروژه
```bash
python manage.py migrate
python manage.py runserver
```
اپ روی `http://127.0.0.1:8000` بالا میاد.
### مایگریشن‌ها
```bash
python manage.py migrate
```
### دیتای تستی (Seed)
یک دیتاست کوچک و idempotent می‌سازه که همه‌ی اپ‌ها رو پوشش می‌ده (کاربر، محتوا، پیشرفت،
اشتراک، پرداخت، تخفیف، دستیار هوش مصنوعی، اعلان، افیلیت، پشتیبانی، خانواده). اجرای مکرر
امن است — با `get_or_create` هیچ رکورد تکراری‌ای نمی‌سازه. پیش‌فرض فقط وقتی `DEBUG=True`
اجرا می‌شه، مگر با `--force`.
```bash
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:**
1. `POST /api/auth/session` با `{"type":"guest","deviceId":"d_test1"}` → یک `token` بگیرید.
2. روی دکمه‌ی **Authorize** بزنید و فقط خودِ `token` رو (بدون کلمه‌ی Bearer) وارد کنید.
3. برای تست لاگین با شماره: `POST /api/auth/otp/request` بزنید، یا در محیط توسعه کد **0000**
رو با هر شماره‌ای در `POST /api/auth/login` امتحان کنید (dev-only، نیاز به `DEBUG=True`).
### دستورهای پرکاربرد
```bash
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`).
---
## ۲. راهنمای دیپلوی پروداکشن
### بالا آوردنِ نسخه‌ی جدید — یک دستور
```bash
./deploy.sh
```
همین. اسکریپت به ترتیب کدِ تازه را می‌گیرد، وابستگی‌ها را نصب می‌کند، مهاجرت‌ها
را اعمال می‌کند، فایل‌های استاتیک را جمع می‌کند، سرویس را ری‌استارت می‌کند، و در
پایان **خودش بررسی می‌کند که واقعاً بالا آمده یا نه**. اگر بالا نیامد، علت‌های
رایج را چاپ می‌کند.
چرا این مهم است: قبلاً این‌ها چند کارِ دستی به ترتیبِ درست بودند و فراموش شدنِ
هر کدام اپ را می‌خواباند، با خطایی که گویا نبود. مثلاً اضافه شدنِ یک پکیجِ جدید
به `requirements.txt` بدونِ `pip install` باعثِ `ModuleNotFoundError` موقعِ بالا
آمدن می‌شود و کاربر فقط `502 Bad Gateway` می‌بیند. اسکریپت همیشه `pip install`
را اجرا می‌کند تا این حالت اصلاً پیش نیاید.
بارِ اول، نامِ سرویس را بدهید تا دفعه‌های بعد خودکار ری‌استارت شود:
```bash
TADRISINO_SERVICE=<نامِ سرویس> ./deploy.sh
```
متغیرهای اختیاری: `SKIP_PULL=1` (بدونِ git pull)، `TADRISINO_HEALTH_URL`،
`PIP_INDEX_URL` (اگر pypi.org تایم‌اوت می‌دهد).
### بررسیِ سلامت از بیرون
```bash
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` اجرا می‌کنه، نه یک سرورِ پروداکشن).
```bash
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 خودش این رو مدیریت می‌کنه:
1. اول `loki` بالا میاد.
2. `alloy` و `grafana` بعد از `loki` بالا میان (`depends_on`).
3. `django` هیچ وابستگی‌ای به استکِ لاگ نداره — خودش `migrate` بعد `runserver` رو
اجرا می‌کنه و منتظرِ Loki/Alloy/Grafana نمی‌مونه.
### دستورهای manage.py داخلِ Docker
اگه فقط از طریقِ Docker اجرا می‌کنید (بدونِ venv محلی)، `python manage.py ...` رو مستقیم
روی سیستم خودتون اجرا نکنید — باید داخلِ کانتینرِ `django` اجرا بشه:
```bash
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` اجرا می‌شه.)
### مایگریشن‌ها
```bash
python manage.py migrate --noinput
```
در Docker، این به‌صورت خودکار به‌عنوان بخشی از دستورِ سرویسِ `django`، قبل از `runserver` اجرا می‌شه.
### راهنمای seed برای staging/تست
- هرگز `seed_test_data` رو روی یک دیتابیسِ پروداکشنِ واقعی اجرا نکنید.
- روی staging (`DEBUG=False`)، صریحاً با `--force` اجرا کنید:
```bash
python manage.py seed_test_data --force
```
- برای پاک‌کردن و ساختِ دوباره‌ی دیتای seed بین چرخه‌های تست، از `--reset` استفاده کنید.
همه‌ی رکوردهای seed‌شده در فیلدهای متنیِ قابل‌مشاهده با تگِ `[SEED]` مشخص شدن، برای
شناساییِ راحت‌تر و پاک‌سازی.
### چطور API دیپلوی‌شده رو verify کنیم
```bash
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`).