diff --git a/.env.example b/.env.example index 9aa4620..b3f70a1 100644 --- a/.env.example +++ b/.env.example @@ -45,7 +45,7 @@ CACHE_URL= # دامنه‌ی فعلی است و لازم نیست ست شوند. برای جابه‌جایی دامنه فقط همین دو را # باز کنید (ترتیبِ کار در docs/operations.md، «عوض کردنِ دامنه»): # -# PUBLIC_BASE_URL=https://api.math.tadrisino2.ir +# PUBLIC_BASE_URL=https://mathapi.tadrisino2.ir # FRONTEND_URL=https://math.tadrisino2.ir # # این سه فقط برای دامنه‌ی *اضافه* لازم‌اند (مثلاً دامنه‌ی قبلی در دورانِ جابه‌جایی)؛ diff --git a/CLAUDE.md b/CLAUDE.md index 2d1465d..d8a3b44 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -120,7 +120,8 @@ than a coding one, and never assume a step was done just because it was describe ## Frontend ↔ backend (read before wiring the two together) The app is two repos and two domains: the React frontend `Tadrisino/MathApp_frontend` -(deployed at `math.tadrisino2.ir`) and this backend (`api.math.tadrisino2.ir`). +(deployed at `math.tadrisino2.ir`) and this backend (`mathapi.tadrisino2.ir`; it was +`api.math.tadrisino2.ir` until 2026-09-26, whose DNS record no longer exists). **Since 2026-09-26 the owner maintains both:** the frontend developer is on leave and `AliReza_Zareian` has `write` on the frontend repo (branch protection on its `main` unknown — not visible without admin). Local clone: `~/Desktop/MathApp_frontend`. @@ -292,7 +293,7 @@ reliably pull the final answer out of a solution and produced ~40% broken output ## The server: read this before running anything on it -`api.math.tadrisino2.ir` runs on a **shared company machine** (`administrator@78.38.35.249`, +`mathapi.tadrisino2.ir` runs on a **shared company machine** (`administrator@78.38.35.249`, hostname `haghzad`), not a box that belongs to this project. Our app is one tenant among many. Also on it, all in production use by other people: diff --git a/README.md b/README.md index b7673a6..2336199 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ | | | |---|---| | **وضعیت** | روی پروداکشن، در حالِ سرویس‌دهی | -| **آدرسِ زنده** | `https://api.math.tadrisino2.ir` · [سلامت](https://api.math.tadrisino2.ir/api/health) | +| **آدرسِ زنده** | `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`) | @@ -104,7 +104,7 @@ seed می‌کند، فایل‌های استاتیک را جمع می‌کند پس از هر انتشار، از بیرون و بدونِ SSH تأیید کنید: ```bash -curl https://api.math.tadrisino2.ir/api/health +curl https://mathapi.tadrisino2.ir/api/health ``` `200` یعنی سالم، `503` یعنی چیزی واقعاً خراب است. هیچ سکرتی برنمی‌گرداند. diff --git a/config/settings.py b/config/settings.py index 34243dc..abd28e3 100644 --- a/config/settings.py +++ b/config/settings.py @@ -100,7 +100,7 @@ def _with_defaults(name: str, defaults: list[str]) -> list[str]: # فرانت را بی‌صدا می‌شکند. `.strip() or` لازم است: کلید ممکن است در .env # باشد ولی خالی، و os.environ.get فقط وقتی کلید *نباشد* پیش‌فرض می‌دهد. PUBLIC_BASE_URL = ( - os.environ.get('PUBLIC_BASE_URL', '').strip() or 'https://api.math.tadrisino2.ir' + os.environ.get('PUBLIC_BASE_URL', '').strip() or 'https://mathapi.tadrisino2.ir' ).rstrip('/') # آدرسِ خودِ اپ (فرانت)؛ برگشت از درگاه و CORS از آن ساخته می‌شوند. FRONTEND_URL = ( diff --git a/config/tests.py b/config/tests.py index 4e73c87..1198fa3 100644 --- a/config/tests.py +++ b/config/tests.py @@ -2,6 +2,7 @@ import importlib import json import logging import os +from urllib.parse import urlsplit from unittest.mock import patch from django.conf import settings @@ -318,7 +319,8 @@ class ServerEnvDefaultsTests(SimpleTestCase): اینکه بدونِ هیچ متغیرِ محیطی‌ای مقدارِ کارآمد دارند. """ - HOST = 'api.math.tadrisino2.ir' + # دامنه‌ی API از PUBLIC_BASE_URL می‌آید، نه از یک رشته‌ی ثابت در تست. + HOST = urlsplit(settings.PUBLIC_BASE_URL).hostname def test_the_api_domain_is_allowed_without_any_env_var(self): self.assertIn(self.HOST, settings.DEFAULT_ALLOWED_HOSTS) @@ -341,7 +343,7 @@ class ServerEnvDefaultsTests(SimpleTestCase): """کلید در .env هست ولی خالی — os.environ.get پیش‌فرض نمی‌دهد.""" with patch.dict(os.environ, {'PUBLIC_BASE_URL': ''}): value = (os.environ.get('PUBLIC_BASE_URL', '').strip() - or 'https://api.math.tadrisino2.ir') + or 'https://mathapi.tadrisino2.ir') self.assertTrue(value.startswith('https://')) def test_admin_login_origin_is_trusted_without_any_env_var(self): diff --git a/docs/frontend/README.md b/docs/frontend/README.md index b6f0025..92aec90 100644 --- a/docs/frontend/README.md +++ b/docs/frontend/README.md @@ -44,7 +44,7 @@ export const api = createClient({ baseUrl: import.meta.env.VITE_API_URL ?? '/api برای تست لازم نیست پیامک واقعی برسد. **برای تستِ مسیرِ کامل روی سرورِ واقعی، شماره‌ی تست بگیرید.** روی -`api.math.tadrisino2.ir` سرویسِ پیامک هنوز تنظیم نشده، پس `requestOtp` برای یک +`mathapi.tadrisino2.ir` سرویسِ پیامک هنوز تنظیم نشده، پس `requestOtp` برای یک شماره‌ی معمولی خطای ۵۰۳ می‌دهد. بک‌اند چند شماره‌ی تست با کدِ ثابت دارد (`TEST_LOGIN_PHONES` در `.env` سرور): برای آن شماره‌ها پیامکی ارسال نمی‌شود و همان کد همیشه جواب می‌دهد، دقیقاً مثل مسیرِ واقعی. یک جفت «شماره + کد» برای @@ -57,7 +57,7 @@ export const api = createClient({ baseUrl: import.meta.env.VITE_API_URL ?? '/api صفحه‌های اعلان و لیدربرد مجبور نباشید منتظرِ تولیدِ داده‌ی واقعی بمانید: ```bash -curl -X POST https://api.math.tadrisino2.ir/api/auth/session \ +curl -X POST https://mathapi.tadrisino2.ir/api/auth/session \ -H 'Content-Type: application/json' \ -d '{"type":"guest","deviceId":"frontend-test-device"}' ``` diff --git a/docs/operations.md b/docs/operations.md index 658dce7..92bb29f 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -39,7 +39,7 @@ poll کردنِ `/api/health`. ## بررسیِ سلامت ```bash -curl https://api.math.tadrisino2.ir/api/health +curl https://mathapi.tadrisino2.ir/api/health ``` بدونِ احرازِ هویت و بدونِ SSH می‌گوید: دیتابیس سالم است یا نه، مهاجرتِ @@ -54,8 +54,8 @@ curl https://api.math.tadrisino2.ir/api/health تأییدِ سریعِ اینکه API واقعاً جواب می‌دهد: ```bash -curl -i https://api.math.tadrisino2.ir/api/schema/ -curl -i -X POST https://api.math.tadrisino2.ir/api/auth/session \ +curl -i https://mathapi.tadrisino2.ir/api/schema/ +curl -i -X POST https://mathapi.tadrisino2.ir/api/auth/session \ -H 'Content-Type: application/json' \ -d '{"type":"guest","deviceId":"verify-1"}' ``` @@ -88,7 +88,7 @@ PY sudo systemctl stop zareian-mathapp cp .deploy-backups/db-.sqlite3 db.sqlite3 sudo systemctl start zareian-mathapp -curl https://api.math.tadrisino2.ir/api/health +curl https://mathapi.tadrisino2.ir/api/health ``` ## متغیرهای محیطی @@ -101,7 +101,7 @@ curl https://api.math.tadrisino2.ir/api/health |---|---| | `DJANGO_DEBUG` | خاموش (`0`) — که حالتِ درستِ پروداکشن است | | `DJANGO_SECRET_KEY` | یک کلیدِ تصادفیِ قوی ساخته و در `.django_secret_key` (مجوز `0600`) ذخیره می‌شود | -| `PUBLIC_BASE_URL` · `FRONTEND_URL` | دامنه‌ی فعلی (`api.math.tadrisino2.ir` / `math.tadrisino2.ir`). بقیه‌ی تنظیم‌های دامنه از همین دو ساخته می‌شوند؛ بخشِ «عوض کردنِ دامنه» | +| `PUBLIC_BASE_URL` · `FRONTEND_URL` | دامنه‌ی فعلی (`mathapi.tadrisino2.ir` / `math.tadrisino2.ir`). بقیه‌ی تنظیم‌های دامنه از همین دو ساخته می‌شوند؛ بخشِ «عوض کردنِ دامنه» | | `DJANGO_ALLOWED_HOSTS` · `CSRF_TRUSTED_ORIGINS` · `CORS_ALLOWED_ORIGINS` | از دو آدرسِ بالا ساخته می‌شوند؛ مقدارِ env فقط دامنه‌ی *اضافه* است | | `CACHE_URL` | سرویس بالا می‌آید ولی نرخ بینِ workerها مشترک نیست؛ `/api/health` هشدار می‌دهد | | `KAVENEGAR_API_KEY` | `POST /api/auth/otp/request` خطای صریحِ `503` می‌دهد، نه موفقیتِ ساختگی | @@ -148,6 +148,9 @@ pendingِ همان کاربر را دوباره با زرین‌پال می‌س ## عوض کردنِ دامنه +آخرین جابه‌جایی: ۲۰۲۶-۰۹-۲۶، API از `api.math.tadrisino2.ir` به `mathapi.tadrisino2.ir` +(رکوردِ DNSِ آدرسِ قبلی حذف شده). فرانت همان `math.tadrisino2.ir` ماند. + دامنه در کد فقط یک‌جا آمده: پیش‌فرضِ `PUBLIC_BASE_URL` و `FRONTEND_URL` در `config/settings.py`. `ALLOWED_HOSTS`، `CSRF_TRUSTED_ORIGINS`، `CORS_ALLOWED_ORIGINS`، callbackِ زرین‌پال، برگشت از پرداخت، `BAZAAR_REDIRECT_URI` و آدرسِ سلامت در @@ -159,9 +162,8 @@ callbackِ زرین‌پال، برگشت از پرداخت، `BAZAAR_REDIRECT_U 1. **DNS، آروان و گواهی** برای دامنه‌های تازه، رو به همین سرور، و `server_name`ِ nginx — کارِ ادمینِ سرور، نه ما. 2. **بک‌اند:** در `.env` سرور `PUBLIC_BASE_URL` و `FRONTEND_URL` را عوض کنید. تا DNS - کامل جابه‌جا شود، دامنه‌های قبلی را هم نگه دارید: - `DJANGO_ALLOWED_HOSTS=api.math.tadrisino2.ir,math.tadrisino2.ir` و - `CORS_ALLOWED_ORIGINS=https://math.tadrisino2.ir`. بعد ری‌استارت. + کامل جابه‌جا شود، دامنه‌های قبلی را هم نگه دارید، مثلاً + `DJANGO_ALLOWED_HOSTS=<دامنه‌ی قبلیِ API>` و `CORS_ALLOWED_ORIGINS=https://<دامنه‌ی قبلیِ فرانت>`. بعد ری‌استارت. 3. **زرین‌پال:** دامنه‌ی تازه‌ی API را در پنلِ ترمینال ثبت کنید. خطای `-14` («دامنه‌ی callback با دامنه‌ی ثبت‌شده نمی‌خواند») دقیقاً نبودنِ همین است. **بازار** (اگر استفاده شود): Redirect URI در پنل باید عیناً @@ -189,9 +191,9 @@ TEST_LOGIN_PHONES=09120000001:834512,09120000002:110947 دیگری مسیرِ عادیِ OTP را می‌رود، بی‌هیچ تغییری. ```bash -curl -X POST https://api.math.tadrisino2.ir/api/auth/otp/request \ +curl -X POST https://mathapi.tadrisino2.ir/api/auth/otp/request \ -H 'Content-Type: application/json' -d '{"phone":"09120000001"}' -curl -X POST https://api.math.tadrisino2.ir/api/auth/login \ +curl -X POST https://mathapi.tadrisino2.ir/api/auth/login \ -H 'Content-Type: application/json' \ -d '{"phone":"09120000001","otp":"834512"}' ``` @@ -222,7 +224,7 @@ curl -X POST https://api.math.tadrisino2.ir/api/auth/login \ یک `deviceId` مشخص روی پروداکشن از قبل داده دارد: ```bash -curl -X POST https://api.math.tadrisino2.ir/api/auth/session \ +curl -X POST https://mathapi.tadrisino2.ir/api/auth/session \ -H 'Content-Type: application/json' \ -d '{"type":"guest","deviceId":"frontend-test-device"}' ``` @@ -240,7 +242,7 @@ curl -X POST https://api.math.tadrisino2.ir/api/auth/session \ ## سرور -`api.math.tadrisino2.ir` روی یک **ماشینِ مشترکِ شرکت** اجرا می‌شود +`mathapi.tadrisino2.ir` روی یک **ماشینِ مشترکِ شرکت** اجرا می‌شود (`administrator@78.38.35.249`)، نه سروری که مالِ این پروژه باشد. روی همان ماشین Gitea، سرویسِ ایمیل، DNS، چند دیتابیس و اپ‌های تیم‌های دیگر در حالِ کارند. diff --git a/docs/security-before-production.md b/docs/security-before-production.md index 88e3804..21940e8 100644 --- a/docs/security-before-production.md +++ b/docs/security-before-production.md @@ -97,7 +97,7 @@ می‌بینند. - **رفع:** پاسخِ عمومی فقط `{"status": "ok"|"degraded"}`؛ بقیه پشتِ هدرِ `X-Internal-Key` (`INTERNAL_SERVICE_KEY`) یا فقط برای staff. -- **چطور بسنجید:** `curl https://api.math.tadrisino2.ir/api/health` بدونِ هدر نباید +- **چطور بسنجید:** `curl https://mathapi.tadrisino2.ir/api/health` بدونِ هدر نباید `warnings` و `traffic` داشته باشد. ## ۵. مستنداتِ API عمومی @@ -122,7 +122,7 @@ ## ۷. پنلِ ادمین روی دامنه‌ی عمومی -- **چیست:** `/admin/` روی `api.math.tadrisino2.ir`، با ورودِ نام‌کاربری/رمزِ جنگو. از +- **چیست:** `/admin/` روی `mathapi.tadrisino2.ir`، با ورودِ نام‌کاربری/رمزِ جنگو. از ۲۰۲۶-۰۹-۲۵ سقفِ ۱۰ تلاش در ۱۵ دقیقه برای هر IP دارد (`AdminLoginThrottleMiddleware`)، ولی: شمارش per-worker است (مورد ۹)، IP با XFF جعل‌پذیر است (مورد ۸)، و ۲FA ندارد. - **خطر:** ادمین همه‌ی محتوا، پلن‌ها، کدهای تخفیف، سفارش‌ها و کاربرها را کنترل می‌کند. @@ -131,7 +131,7 @@ ## ۸. دسترسیِ مستقیم به پورتِ ۸۰ (دور زدنِ ArvanCloud) -- **چیست:** `http://78.38.35.249/api/health` با `Host: api.math.tadrisino2.ir` پاسخ می‌دهد +- **چیست:** `http://78.38.35.249/api/health` با `Host: mathapi.tadrisino2.ir` پاسخ می‌دهد (تأیید: HTTP 200). `NUM_PROXIES=2` فرض می‌کند همیشه دو پروکسی جلوی ماست. - **خطر:** کسی که IP را بداند `X-Forwarded-For` را جعل می‌کند و محدودیت‌های IP-محور (مهمان، OTP، ادمین) را دور می‌زند.