From 1f38465d88ade6768773e4fd8037f750e8095c35 Mon Sep 17 00:00:00 2001 From: Zareian Date: Sun, 27 Sep 2026 01:06:48 +0330 Subject: [PATCH] move the API's own address to mathapi.tadrisino2.ir The API moved to mathapi.tadrisino2.ir on 2026-09-26 (nginx server_name changed at 23:22) and api.math.tadrisino2.ir no longer resolves. The code default for PUBLIC_BASE_URL and the docs' curl examples still named the dead host, so every paymentUrl, the Zarinpal callback and the Bazaar redirect the backend builds pointed nowhere. Historical status notes in CLAUDE.md keep the old name on purpose. config/tests.py now reads the API host from PUBLIC_BASE_URL instead of a literal. 624 tests green. Co-Authored-By: Claude Opus 5.5 --- .env.example | 2 +- CLAUDE.md | 5 +++-- README.md | 4 ++-- config/settings.py | 2 +- config/tests.py | 6 ++++-- docs/frontend/README.md | 4 ++-- docs/operations.md | 26 ++++++++++++++------------ docs/security-before-production.md | 6 +++--- 8 files changed, 30 insertions(+), 25 deletions(-) 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، ادمین) را دور می‌زند.