readme: split into dev quick-start and production deploy guides

This commit is contained in:
2026-08-26 03:22:20 +03:30
parent 05ab6ffc36
commit 49d596f077

216
README.md
View File

@@ -5,113 +5,175 @@
احراز هویت با OTP، محتوای درسی و پیشرفت کاربر، دستیار هوش مصنوعی، اشتراک و پرداخت،
تخفیف، اعلان‌ها، همکاری در فروش، پشتیبانی و حساب خانواده.
## اجرا
| اپ | مسئولیت |
|---|---|
| `accounts` | احراز هویت، session، پروفایل کاربر |
| `content` | پایه‌ها، مباحث، درس‌ها، سوال‌ها، پیشرفت |
| `progress` | آمار، لیدربرد، streak |
| `ai_tutor` | مکالمات دستیار هوش مصنوعی |
| `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
```
# مهم: از این به بعد DEBUG دیفالتش False هست (برای امنیتِ پروداکشن).
# برای اجرای محلی/تست با Swagger (که به DEV_MASTER_OTP و devOtp توی
# پاسخ نیاز داره)، این env var رو ست کنید:
`.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 createsuperuser # اختیاری، برای /admin
python manage.py runserver
```
اپ روی `http://127.0.0.1:8000` بالا میاد.
در پروداکشن، `DJANGO_DEBUG` رو ست نکنید (یا `0` بذارید) و حتماً
`DJANGO_SECRET_KEY` و `DJANGO_ALLOWED_HOSTS` (کاما-جدا) رو هم از env بدید.
## اجرا با Docker (اپ + استکِ لاگِ متمرکز)
برای اجرای محلی، در کنارِ Django یک استکِ لاگِ متمرکز هم فراهم شده:
`Loki` (ذخیره‌سازیِ لاگ) + `Grafana Alloy` (جمع‌آوریِ لاگ از فایلِ اپ) +
`Grafana` (جستجو/فیلترِ لاگ‌ها). این فقط برای dev/local هست، نه یک
تنظیمِ پروداکشن (مثلاً هنوز از `runserver` استفاده می‌شه، نه gunicorn).
### مایگریشن‌ها
```bash
# اختیاری: تنظیماتِ خودتون رو ست کنید (وگرنه دیفالت‌های dev استفاده می‌شن)
cp .env.example .env
docker compose up --build
python manage.py migrate
```
- اپ Django: `http://localhost:8000`
- Grafana: `http://localhost:3000` (بدونِ نیاز به لاگین، anonymous admin فعاله؛ فقط برای محیطِ محلی)
### دیتای تستی (Seed)
یک دیتاست کوچک و idempotent می‌سازه که همه‌ی اپ‌ها رو پوشش می‌ده (کاربر، محتوا، پیشرفت،
اشتراک، پرداخت، تخفیف، دستیار هوش مصنوعی، اعلان، افیلیت، پشتیبانی، خانواده). اجرای مکرر
امن است — با `get_or_create` هیچ رکورد تکراری‌ای نمی‌سازه. پیش‌فرض فقط وقتی `DEBUG=True`
اجرا می‌شه، مگر با `--force`.
داشبوردِ «Tadrisino Django Logs» به‌صورتِ خودکار در Grafana لود می‌شه و
امکانِ فیلتر بر اساسِ سطحِ لاگ (`level`) و جستجوی متنی رو می‌ده.
**جریانِ لاگ:** Django همون خطوطِ JSON موجود رو (بدونِ تغییرِ پیام‌ها یا
محافظت‌های حساسیتی) هم روی stdout و هم در `/var/log/django/app.log`
(یک ولومِ Docker) می‌نویسه ← Grafana Alloy این فایل رو می‌خونه ← به Loki
push می‌کنه ← Grafana از Loki می‌خونه. جزئیاتِ بیشتر در کامنت‌های بالای
`docker-compose.yml`، `docker/alloy/config.alloy` و `config/settings.py`
(بخشِ `LOG_DIR`).
اجرای بدونِ Docker (روش قبلی، بالای همین فایل) دقیقاً همون‌طور که بود
کار می‌کنه — `LOG_DIR` فقط داخلِ Docker ست می‌شه.
## مستندات API
```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** رو با هر شماره‌ای در `/api/auth/login` امتحان کنید.
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 # اجرای کل تست‌سوییت
```
| اپ | مسئولیت | مدل‌های اصلی |
---
## ۲. راهنمای دیپلوی پروداکشن
### متغیرهای محیطی موردنیاز
| متغیر | لازم در پروداکشن | توضیح |
|---|---|---|
| `accounts` | هویت کاربر و session | `User`, `Profile`, `Session`, `OTPCode` |
| `content` | محتوای درسی و پیشرفت روی درس | `Grade`, `Unit`, `Node`, `Lesson`, `Question`, `Choice`, `NodeProgress` |
| `progress` | آمار، لیدربرد، streak | `UserStats`, `LeaderboardEntry`, `StreakRecord`, `UserHearts` |
| `ai_tutor` | دستیار هوش مصنوعی | `Conversation`, `Message`, `MessageFeedback` |
| `subscription` | پلن و اشتراک | `Plan`, `Subscription` |
| `payment` | سفارش و تراکنش | `Order`, `Transaction` |
| `discount` | کد تخفیف و آفر | `DiscountCode`, `Redemption`, `Offer`, `UserOffer` |
| `notifications` | اعلان‌ها | `Notification` |
| `affiliate` | همکاری در فروش | `AffiliateAccount`, `Conversion` |
| `support` | تیکت و گزارش باگ | `BugReport`, `Ticket`, `TicketMessage` |
| `family` | حساب خانواده / کنترل والدین | `FamilyLink`, `ParentalSettings`, `WeeklyReportLog` |
| `statuslog` | لاگ عمومی تغییر status، مستقل از بقیه‌ی اپ‌ها | `StatusChangeLog` |
| `config` | تنظیمات و url های اصلی پروژه | — |
| `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 مستند شده (پایین رو ببینید).
- **احراز هویت**: Bearer token امضاشده و stateless (`django.core.signing`، بدون کتابخانه‌ی JWT خارجی)، هماهنگ با مدل `Session`.
- **`accounts.User`** عمداً `AUTH_USER_MODEL` جنگو نیست؛ یک مدل بیزنسی مستقل است.
- بین اپ‌ها به‌جای FK واقعی از ارجاع سبک (`user_id`, `object_id` + `label`) استفاده شده تا هر اپ در آینده قابل جدا شدن باشد.
- `POST /api/ai/ask` فعلاً پاسخ stub برمی‌گرداند؛ محل اتصال مدل واقعی در `ai_tutor/views.py` (تابع `generate_tutor_answer`) مشخص شده.
- `DEV_MASTER_OTP` (کد `0000`) فقط برای تست در توسعه است — **پیش از دیپلوی پروداکشن باید حذف یا به `DEBUG` مقیّد شود.**
- **لاگِ ساختاریافته**: همه‌ی درخواست‌های `/api/` به‌صورتِ JSON لاگ می‌شن (متد/مسیر/وضعیت/مدت/کاربر، بدونِ بدنه/هدر/سکرت)؛ قابلِ مشاهده و جست‌وجو در Grafana (بخشِ Docker بالا).
### تنظیمات Docker/دیپلوی
`docker-compose.yml` موجود، اپ رو **به‌همراه** یک استکِ لاگِ متمرکز
(Loki + Grafana Alloy + Grafana) اجرا می‌کنه. این یک تنظیمِ **dev/local** هست، نه دیپلویِ
پروداکشن (سرویسِ `django` همچنان `runserver` اجرا می‌کنه، نه یک سرورِ پروداکشن).
## قبل از دیپلوی پروداکشن
```bash
cp .env.example .env # اختیاری — بدونش compose از دیفالت‌های dev استفاده می‌کنه
docker compose up --build
```
- [x] `DEBUG` و `SECRET_KEY` و `ALLOWED_HOSTS` از env خونده می‌شن (دیفالتِ
`DEBUG` روی `False` است)
- [x] `DEV_MASTER_OTP` و برگردوندنِ `devOtp` توی پاسخ، فقط وقتی `DEBUG=True`
فعالن؛ در پروداکشن غیرفعالن
- [x] rate limit (throttle) روی endpointهای احرازهویت و بقیه‌ی endpointهای
حساسِ پروژه (پرداخت، خرید اشتراک، تخفیف، AI، محتوا، خانواده،
پشتیبانی، affiliate، …) پیاده‌سازی شده؛ برای کاربرهای مهمان بر
اساسِ IP کلید می‌خوره تا چرخشِ حساب مهمان دورش نزنه
- [x] IDOR در `GET /api/home` و `GET /api/progress/nodes` اصلاح شد: قبلاً
بدون هیچ توکنی، با فرستادنِ `userId` دلخواه می‌شد آمار/پیشرفتِ هر
کاربرِ دیگه‌ای رو خوند؛ حالا auth اجباریه و فقط خودِ کاربر یا والدِ
تأییدشده‌اش (`FamilyLink` با `status='accepted'`) اجازه‌ی دسترسی دارن
- [ ] دیتابیس Postgres به‌جای SQLite
- [x] اتصال واقعی به سرویسِ پیامک برای ارسالِ OTP: دیگه استاب نیست؛ از طریق
کاوه‌نگار (kavenegar.com) ارسال می‌شه (کلید از `KAVENEGAR_API_KEY`)،
و هر شکست (شبکه/کلید/رد اپلیکیشنی) به‌جای موفقیتِ ساختگی، خطای صریح
۵۰۳ برمی‌گردونه
- [ ] بررسیِ دوره‌ای throttle rate ها با توجه به ترافیکِ واقعی
| سرویس | آدرس | توضیح |
|---|---|---|
| اپ 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 نمی‌مونه.
### مایگریشن‌ها
```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` فعلاً پاسخ stub برمی‌گردونه؛ محلِ اتصالِ مدلِ واقعی
`generate_tutor_answer` در `ai_tutor/views.py` است.
- ارسالِ OTP از طریقِ کاوه‌نگار (`KAVENEGAR_API_KEY`) انجام می‌شه؛ شکستِ ارسال به‌جای
موفقیتِ ساختگی، خطای صریحِ `503` برمی‌گردونه.