readme: split into dev quick-start and production deploy guides
This commit is contained in:
216
README.md
216
README.md
@@ -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` برمیگردونه.
|
||||
|
||||
Reference in New Issue
Block a user