Docs brought to the current state: CLAUDE.md rewritten, README formal, handbook and diagrams redrawn
- CLAUDE.md: 1,225 → ~430 lines, current state only (the dated status log stays in git history). Server rules moved to the top: what is ours on 249/247 and what is not, non-negotiable rules, announce-before-acting, password handling, and the full "deploy it" procedure with verification. - README: formal entry page with current numbers (10 grades, 18,522 questions, 61 API operations, 973 tests, PostgreSQL/Redis). - Removed docs/frontend/ (Figma-era guide with guest login and its own api.ts; the frontend repo has the real client). Swagger's external-docs link now points at the frontend repo. - architecture/operations/security/content-authoring/loadtest/textbooks: SQLite, guest login, demo accounts, question packs and the port-80 note replaced by what is true now. - Technical handbook text updated throughout; all seven diagrams redrawn (PostgreSQL, phone-only login, subscriptions, current data model); PDF rebuilt. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
174
README.md
174
README.md
@@ -1,117 +1,118 @@
|
||||
# Tadrisino API
|
||||
|
||||
بکاندِ اپلیکیشنِ آموزشیِ تدریسینو — یک REST API روی Django که برنامهی درسی،
|
||||
آزمونها، پیشرفتِ کاربر، دستیارِ هوشِ مصنوعی، اشتراک و پرداخت را سرو میکند.
|
||||
بکاندِ **تدریسینو**، اپِ آموزشِ ریاضی به سبکِ دولینگو برای دانشآموزانِ پایهی سوم تا دوازدهم. دانشآموز روی نقشهای
|
||||
از جزیرهها جلو میرود، هر جزیره آزمونی کوتاه از مسئلههای خودِ کتابِ درسی است، و هر جا گیر کند از دستیارِ هوشِ
|
||||
مصنوعی میپرسد که جوابش را از همان کتاب میسازد.
|
||||
|
||||
فرانتاند ریپوی جداگانهای است ([`MathApp_frontend`](https://gitea.tadrisino2.ir/Tadrisino/MathApp_frontend)).
|
||||
**قراردادِ بینِ دو طرف، اسکیمای OpenAPI همین ریپوست** — نه حافظهی کسی، نه یک
|
||||
فایلِ دستی. هر تغییری در API باید از آنجا دیده شود.
|
||||
این مخزن یک REST API روی Django است: برنامهی درسی و آزمون، پیشرفت و گیمیفیکیشن، دستیارِ مبتنی بر RAG، اشتراک
|
||||
و پرداخت. فرانتاند مخزنِ جداگانهای است ([`Tadrisino/MathApp_frontend`](https://gitea.tadrisino2.ir/Tadrisino/MathApp_frontend)).
|
||||
**قراردادِ میانِ دو طرف اسکیمای OpenAPIِ همین مخزن است**؛ هر تغییری در API از آنجا دیده میشود.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **وضعیت** | روی پروداکشن، در حالِ سرویسدهی |
|
||||
| **آدرسِ زنده** | `https://mathapi.tadrisino2.ir` · [سلامت](https://mathapi.tadrisino2.ir/api/health) |
|
||||
| **استک** | Python 3.11+ · Django 5.2 · DRF · SQLite · Qdrant (RAG) |
|
||||
| **API** | ۵۹ اندپوینت · مستنداتِ زنده در `/api/docs/` |
|
||||
| **تست** | ۷۷۹ تست، همه سبز (`python manage.py test`) |
|
||||
| **محتوا** | ۸ پایه · ۵۸ فصل · ۲۶۳ نود · ۲٬۶۱۷ درس · ۲٬۵۴۵ سوال |
|
||||
| **سرویسِ زنده** | `https://mathapi.tadrisino2.ir` · اپ: `https://math.tadrisino2.ir` |
|
||||
| **فناوری** | Python 3.11 · Django 5.2 · Django REST Framework · PostgreSQL 14 · Redis · Qdrant · gunicorn پشتِ nginx و ArvanCloud |
|
||||
| **API** | ۶۱ عملیات روی ۵۹ مسیر، مستند با OpenAPI (`/api/docs/`) |
|
||||
| **محتوا** | ۱۰ پایه · ۵ کتاب · ۸۹ فصل · ۱٬۱۴۵ جزیره · ۵٬۴۰۶ درس · ۱۸٬۵۲۲ سوال |
|
||||
| **تست** | ۹۷۳ تستِ خودکار، همه سبز |
|
||||
| **سرویسهای بیرونی** | GapGPT (مدلِ زبانی) · Qdrant (جستجوی کتاب) · کاوهنگار (پیامک) · زرینپال و کافهبازار (پرداخت) |
|
||||
|
||||
---
|
||||
|
||||
## امکانات
|
||||
|
||||
| بخش | چه میکند |
|
||||
|---|---|
|
||||
| **ورود** | فقط با شمارهی موبایل و کدِ پیامکی؛ توکنِ دسترسیِ امضاشدهی یکساعته و توکنِ تمدید |
|
||||
| **برنامهی درسی** | پایه ← کتاب ← فصل ← جزیره ← درس ← سوال؛ کلِ محتوا بهصورتِ JSONِ کامیتشده در `content/seed/` |
|
||||
| **بازی** | آزمونِ هر جزیره، قفلِ ترتیبی، قلب، ستاره و امتیاز، زنجیرهی روزانه، جدولِ رتبهبندی، همراهها |
|
||||
| **دستیار** | پاسخِ گامبهگام از روی همان کتاب (Qdrant + GapGPT)، فقط ریاضی، با شکلِ توصیفی که اپ میکشد |
|
||||
| **اشتراک** | فصلِ اولِ هر کتاب رایگان؛ اشتراکِ یک/سه/ششماهه از زرینپال یا کافهبازار، با کدِ تخفیف و برنامهی معرفی |
|
||||
| **پنلِ ادمین** | ویرایشِ محتوا، ورود و خروجِ فلهای سوال، پلنها، سفارشها (لغو و بازپرداخت)، تیکتها، گزارشهای کاربران |
|
||||
| **پایش** | `/api/health` (دیتابیس، مهاجرت، کش، سرویسهای بیرونی، بکاپ)، لاگِ JSON برای هر درخواست، دفترِ رویدادِ پرداخت |
|
||||
|
||||
---
|
||||
|
||||
## شروعِ سریع
|
||||
|
||||
```bash
|
||||
python3 -m venv venv && source venv/bin/activate
|
||||
python3.11 -m venv venv && source venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
cp .env.example .env # برای توسعهی محلی: DJANGO_DEBUG=1 را ست کنید
|
||||
cp .env.example .env # برای توسعهی محلی DJANGO_DEBUG=1 را ست کنید
|
||||
python manage.py migrate
|
||||
python manage.py seed_content # برنامهی درسی و سوالهای واقعی از content/seed/
|
||||
python manage.py runserver
|
||||
python manage.py seed_content # برنامهی درسی و بانکِ سوال از content/seed/
|
||||
python manage.py createsuperuser # برای /admin/
|
||||
python manage.py runserver # http://127.0.0.1:8000
|
||||
```
|
||||
|
||||
سرویس روی `http://127.0.0.1:8000` بالا میآید. `.env` خودکار خوانده میشود —
|
||||
لازم نیست چیزی را `export` کنید.
|
||||
بدونِ `DB_NAME` در `.env` دیتابیسِ محلی SQLite است و نیازی به نصبِ Postgres نیست. `.env` خودکار خوانده میشود.
|
||||
|
||||
| | |
|
||||
| آدرس | چیست |
|
||||
|---|---|
|
||||
| Swagger UI | `/api/docs/` |
|
||||
| Redoc | `/api/redoc/` |
|
||||
| اسکیمای OpenAPI | `/api/schema/` |
|
||||
| پنلِ ادمین | `/admin/` |
|
||||
| سلامتِ سرویس | `/api/health` |
|
||||
| `/api/docs/` · `/api/redoc/` · `/api/schema/` | مستنداتِ زندهی API (روی سرور فقط با ورود به پنلِ ادمین) |
|
||||
| `/admin/` | پنلِ ادمین |
|
||||
| `/api/health` | سلامتِ سرویس |
|
||||
|
||||
**اولین درخواست در Swagger:** در حالتِ `DJANGO_DEBUG=1` با `POST /api/auth/otp/request`
|
||||
شمارهای بفرستید و با `POST /api/auth/login` و کدِ `DEV_MASTER_OTP` وارد شوید؛ `token` را
|
||||
بردارید، روی **Authorize** کلیک کنید و همان رشته را (بدونِ کلمهی `Bearer`) وارد کنید.
|
||||
ورودِ مهمان نداریم.
|
||||
|
||||
دستورهای پرکاربردِ دیگر:
|
||||
**اولین درخواست:** با `DJANGO_DEBUG=1`، `POST /api/auth/otp/request` با یک شماره، سپس `POST /api/auth/login` با
|
||||
کدی که در فیلدِ `devOtp` پاسخ آمده. `token` را در Swagger زیرِ **Authorize** بگذارید.
|
||||
|
||||
```bash
|
||||
python manage.py test # کلِ تستسوییت
|
||||
python manage.py createsuperuser # دسترسی به /admin
|
||||
python manage.py seed_test_data # دادهی ساختگی برای توسعه (فقط محلی)
|
||||
python manage.py test --noinput # کلِ تستها (سریال؛ --parallel روی macOS گیر میکند)
|
||||
python manage.py test --noinput content # تستهای یک اپ
|
||||
python manage.py spectacular --file schema.yaml --format openapi # خروجیِ قرارداد برای فرانت
|
||||
```
|
||||
|
||||
برای استکِ لاگِ محلی (Loki و Grafana) [`docs/docker.md`](docs/docker.md) را ببینید.
|
||||
|
||||
---
|
||||
|
||||
## معماری
|
||||
|
||||
هر اپ یک مسئولیت دارد و مدلهای خودش را نگه میدارد:
|
||||
یک برنامهی جنگو در قالبِ «مونولیتِ ماژولار»: هر اپ یک مسئولیت دارد و مدلهای خودش را نگه میدارد.
|
||||
|
||||
| اپ | مسئولیت |
|
||||
|---|---|
|
||||
| `accounts` | هویت، session، ورود با OTP |
|
||||
| `content` | برنامهی درسی، درس، سوال، پیشرفت روی نقشه |
|
||||
| `progress` | آمار، لیدربرد، streak، شخصیتهای بازی |
|
||||
| `ai_tutor` · `rag` | دستیارِ هوشِ مصنوعی: بازیابی از Qdrant + تولید با GPT |
|
||||
| `subscription` · `payment` · `discount` | پلن، سفارش، تراکنش، کدِ تخفیف |
|
||||
| `notifications` · `affiliate` · `support` · `campaigns` | امکاناتِ جانبی |
|
||||
| `statuslog` | لاگِ عمومیِ تغییرِ وضعیت، مستقل از بقیه |
|
||||
| `config` | تنظیمات، روتهای اصلی، middleware |
|
||||
| `accounts` | هویت، ورود با کدِ پیامکی، نشست، پروفایل |
|
||||
| `content` | کتابها، برنامهی درسی، درس، سوال، آزمونِ جزیره، بازخوردِ کاربر |
|
||||
| `progress` | آمار، زنجیرهی روزانه، قلب، رتبهبندی، همراهها، گزارشِ هفتگی |
|
||||
| `ai_tutor` · `rag` | دستیار: گفتگو، بازیابی از Qdrant، تولید با GapGPT، ورودِ کتاب |
|
||||
| `subscription` · `payment` · `discount` · `affiliate` | پلن و اشتراک، سفارش و تراکنش، کدِ تخفیف، برنامهی معرفی |
|
||||
| `notifications` · `support` · `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` تعریف میشود. اندپوینتِ جدید یعنی **هر دو**.
|
||||
- **احرازِ هویت سیستمِ خودِ پروژه است.** `accounts.User` عمداً `AUTH_USER_MODEL` نیست؛ توکن با `django.core.signing`
|
||||
امضا میشود (`accounts/authentication.py`). هدرِ نامعتبر یعنی درخواستِ ناشناس، و هر ویو خودش تصمیم میگیرد ورود
|
||||
لازم است یا نه.
|
||||
- **اپها با قرارداد جدا شدهاند، نه با ForeignKey.** ارجاعِ بینِ اپها با `user_id` یا `object_id` + `label` است تا
|
||||
هر اپ بعداً جداشدنی بماند.
|
||||
- **محدودیتِ نرخ متمرکز است.** هر اندپوینتِ حساس یک `throttle_scope` دارد و نرخش در `config/settings.py` است؛ IPِ
|
||||
واقعی فقط از پروکسیهای ArvanCloud پذیرفته میشود.
|
||||
|
||||
جزئیاتِ بیشتر: [`docs/architecture.md`](docs/architecture.md).
|
||||
شرحِ کامل و دلیلِ هر تصمیم در [`docs/architecture.md`](docs/architecture.md) و
|
||||
[`docs/technical-handbook.md`](docs/technical-handbook.md) آمده است.
|
||||
|
||||
---
|
||||
|
||||
## دیپلوی
|
||||
## استقرار
|
||||
|
||||
کلِ انتشار یک خط است:
|
||||
سرویس روی یک سرورِ مشترکِ شرکت اجرا میشود و مالِ این پروژه فقط پوشهی `~/mathapp-v2` و سرویسِ
|
||||
`zareian-mathapp` است. کلِ انتشار:
|
||||
|
||||
```bash
|
||||
ssh -t administrator@78.38.35.249 \
|
||||
'cd ~/mathapp-v2 && ./deploy.sh; sudo systemctl restart zareian-mathapp'
|
||||
ssh -t administrator@78.38.35.249 'cd ~/mathapp-v2 && ./deploy.sh; sudo systemctl restart zareian-mathapp'
|
||||
```
|
||||
|
||||
`deploy.sh` کد را میگیرد، وابستگیها را نصب میکند، مهاجرت میزند، محتوا را
|
||||
seed میکند، فایلهای استاتیک را جمع میکند و در پایان **ثابت میکند که سرویس
|
||||
واقعاً ریاستارت شده** — نه اینکه فقط فرض کند. ریاستارت جداست چون `sudo`
|
||||
داخلِ اسکریپت نمیتواند رمز بپرسد.
|
||||
|
||||
پس از هر انتشار، از بیرون و بدونِ SSH تأیید کنید:
|
||||
`deploy.sh` کد را با `origin/main` یکی میکند، وابستگیها را نصب میکند، پیش از `migrate` از دیتابیس بکاپ میگیرد،
|
||||
محتوا را seed و فایلهای استاتیک را جمع میکند و سلامتِ سرویس را میسنجد. سپس از بیرون:
|
||||
|
||||
```bash
|
||||
curl https://mathapi.tadrisino2.ir/api/health
|
||||
curl https://mathapi.tadrisino2.ir/api/health # {"status": "ok"}
|
||||
```
|
||||
|
||||
`200` یعنی سالم، `503` یعنی چیزی واقعاً خراب است. هیچ سکرتی برنمیگرداند.
|
||||
|
||||
رویهی کامل — متغیرهای محیطی، ورودِ توسعهدهندهها، بکاپ، عیبیابی:
|
||||
[`docs/operations.md`](docs/operations.md).
|
||||
هر تغییرِ دیتابیس لحظهای به سرورِ بکاپِ جداگانهای میرود. رویهی کامل (متغیرهای محیطی، بکاپ و بازیابی، nginx،
|
||||
عوض کردنِ دامنه، فروش در کافهبازار): [`docs/operations.md`](docs/operations.md).
|
||||
|
||||
---
|
||||
|
||||
@@ -119,31 +120,20 @@ curl https://mathapi.tadrisino2.ir/api/health
|
||||
|
||||
| سند | موضوع |
|
||||
|---|---|
|
||||
| [`docs/operations.md`](docs/operations.md) | دیپلوی، متغیرهای محیطی، سلامت، بکاپ، ورودِ توسعهدهندهها |
|
||||
| [`docs/technical-handbook.md`](docs/technical-handbook.md) · [PDF](docs/technical-handbook.pdf) | **مستندِ فنیِ کامل و تحویلِ پروژه:** معماری، مدلِ سناریو و رابطهای، API، Qdrant، گیمیفیکیشن، امنیت، استقرار، مقیاس |
|
||||
| [`docs/security-before-production.md`](docs/security-before-production.md) | **پیش از کاربرِ واقعی:** هرچه برای دمو عمداً باز مانده، خطرش و راهِ بستنش |
|
||||
| [`docs/content-authoring.md`](docs/content-authoring.md) | افزودن و ویرایشِ محتوا از پنلِ ادمین، ساختِ انبوهِ سوال |
|
||||
| [`docs/technical-handbook.md`](docs/technical-handbook.md) · [PDF](docs/technical-handbook.pdf) | مستندِ فنیِ کامل برای تحویل و پیوستنِ توسعهدهندهی تازه |
|
||||
| [`docs/architecture.md`](docs/architecture.md) | تصمیمهای معماری و دلیلشان |
|
||||
| [`docs/frontend/README.md`](docs/frontend/README.md) | راهنمای اتصالِ فرانتاند + کلاینتِ تایپدار |
|
||||
| [`docs/docker.md`](docs/docker.md) | استکِ لاگِ محلی (Loki + Alloy + Grafana) |
|
||||
| [`docs/extraction-playbook.md`](docs/extraction-playbook.md) | استخراجِ محتوا از کتاب با مدلِ ویژن و هزینههایش |
|
||||
| [`docs/operations.md`](docs/operations.md) | استقرار، پیکربندی، سلامت، بکاپ و بازیابی، nginx، پرداخت |
|
||||
| [`docs/security-before-production.md`](docs/security-before-production.md) | آنچه پیش از کاربرِ واقعی باید بسته شود، و حفاظهای موجود |
|
||||
| [`docs/content-authoring.md`](docs/content-authoring.md) | ویرایشِ محتوا از پنلِ ادمین و ساختِ انبوهِ سوال |
|
||||
| [`docs/extraction-playbook.md`](docs/extraction-playbook.md) | خواندنِ کتابِ درسی با مدلِ تصویری، هزینه و انتخابِ مدل |
|
||||
| [`docs/ai-cost-and-guardrails.md`](docs/ai-cost-and-guardrails.md) · [`docs/model-prices.md`](docs/model-prices.md) | هزینهی دستیار، حاشیهی سودِ اشتراکها، دفترِ قیمتِ مدلها |
|
||||
| [`tools/loadtest/README.md`](tools/loadtest/README.md) | آزمونِ بار |
|
||||
| [`CLAUDE.md`](CLAUDE.md) | راهنمای agentهای کدنویس، بهویژه قواعدِ کار روی سرورِ مشترک |
|
||||
|
||||
---
|
||||
|
||||
## محدودیتهای شناختهشده
|
||||
## وضعیت
|
||||
|
||||
اینها عمداً نوشته شدهاند تا کسی دوباره کشفشان نکند:
|
||||
|
||||
- **`CACHE_URL` روی سرور خالی است، عمداً**، پس محدودیتِ نرخ بینِ workerها مشترک نیست. Redisِ روی آن ماشین
|
||||
مالِ تیمهای دیگر است و به آن وصل نمیشویم؛ Redisِ اختصاصیِ خودمان یک تصمیمِ باز پیش از لانچ است.
|
||||
`/api/health` این را هشدار میدهد.
|
||||
- **`KAVENEGAR_API_KEY` تنظیم نشده**، پس `POST /api/auth/otp/request` برای
|
||||
شمارههای واقعی `503` میدهد. راهِ ورودِ تیمِ توسعه در
|
||||
[`docs/operations.md`](docs/operations.md) توضیح داده شده.
|
||||
- **پوششِ محتوا یکدست نیست.** پایههای هشتم تا دوازدهم کاملند (هر نود چهار
|
||||
سوال)؛ در پایههای سوم/ششم/هفتم ۳۹ نود از ۱۴۵ کمتر از چهار سوال دارند —
|
||||
هیچکدام خالی نیست، پس روی نقشه نودِ غیرقابلِ بازی وجود ندارد.
|
||||
- **محتوای ویدیویی وجود ندارد**، هرچند `Lesson.kind` و `video_url` میتوانند
|
||||
بیانش کنند.
|
||||
- **`DEV_MASTER_OTP` (کد `0000`) باید پشتِ `DEBUG` بماند.** روشن بودنش یعنی هر
|
||||
کسی میتواند بهجای هر کاربری وارد شود. `/api/health` هشدارش میدهد.
|
||||
سرویس زنده است و با حسابهای آزمونگر سنجیده میشود؛ هنوز به روی کاربرِ عمومی باز نشده. آنچه پیش از آن باید بسته
|
||||
شود در [`docs/security-before-production.md`](docs/security-before-production.md) فهرست شده است. پرداختِ واقعی (زرینپال
|
||||
و کافهبازار) تا انتهای مسیر هنوز آزموده نشده است.
|
||||
|
||||
Reference in New Issue
Block a user