A domain change is coming (the Zarinpal terminal rejects the current callback domain). ALLOWED_HOSTS, CSRF_TRUSTED_ORIGINS, CORS defaults, the Bazaar redirect URI and deploy.sh's public health URL each spelled the domain out. They now derive from PUBLIC_BASE_URL and FRONTEND_URL, which keep today's domain as their default; env values for the three lists are still added on top, so the old domain can stay allowed while DNS moves. ZARINPAL_CALLBACK_URL used os.environ.get(key, default), so an empty key in .env produced an empty callback and every payment would fail closed. It now uses the same `.strip() or` fallback as its neighbours; the new test caught it. config/tests_domains.py loads settings in a subprocess with a new domain and fails if any derived value still mentions the old one. docs/operations.md gains the step-by-step domain move (DNS, .env, Zarinpal/Bazaar panels, frontend config.js, verification). CLAUDE.md records that the frontend is now ours and where the rebuild stands. 624 tests green. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
190 lines
12 KiB
Plaintext
190 lines
12 KiB
Plaintext
# نمونهی فایلِ تنظیمات — هم برای ماشینِ خودتان، هم برای سرور.
|
||
#
|
||
# cp .env.example .env
|
||
#
|
||
# تقریباً هیچکدام اجباری نیستند. هر چیزی که از قبل قابلِ تصمیم بود در خودِ
|
||
# کد پیشفرض شده: دامنهها، origin های مجاز، و کلیدِ امضا (اولین اجرا خودش
|
||
# یک کلیدِ تصادفی میسازد و در .django_secret_key نگه میدارد). حتی با یک
|
||
# فایلِ خالی هم سرویس بالا میآید.
|
||
#
|
||
# روی سرور، بعد از یکبار ساختنِ این فایل، هر آپدیت فقط:
|
||
# git pull && ./deploy.sh
|
||
|
||
# -----------------------------------------------------------------------
|
||
# پایه
|
||
# -----------------------------------------------------------------------
|
||
# DEBUG اینجا عمداً 0 است.
|
||
#
|
||
# این فایل هم روی لپتاپِ توسعهدهنده کپی میشود هم روی سرور، و حالتِ پیشفرض
|
||
# باید آنی باشد که اگر کسی فراموشش کند، بیخطر بماند. یک بار همین فایل با
|
||
# مقدارِ 1 روی سرور نشست و ماهها آنجا ماند.
|
||
#
|
||
# ⚠️ DEBUG=1 روی سرورِ عمومی دو ریسک دارد:
|
||
# • صفحههای خطا کلِ تنظیمات و traceback را نشان میدهند
|
||
# • کدِ ورودِ آزمایشیِ 0000 برای هر شمارهای پذیرفته میشود، یعنی هر
|
||
# کسی میتواند بهجای هر کاربری وارد شود
|
||
#
|
||
# روی ماشینِ خودتان (و فقط آنجا) این را 1 کنید تا خطاها کامل دیده شوند.
|
||
DJANGO_DEBUG=0
|
||
|
||
# خالی بگذارید تا خودش ساخته و در .django_secret_key ذخیره شود.
|
||
DJANGO_SECRET_KEY=
|
||
|
||
# بدونِ Redis سرویس بالا میآید ولی محدودیتِ نرخ بینِ workerها مشترک نیست؛
|
||
# /api/health همین را هشدار میدهد. برای پروداکشنِ واقعی پرش کنید.
|
||
CACHE_URL=
|
||
|
||
# نامِ سرویسِ systemd تا deploy.sh خودش ریاستارت کند (فقط روی سرور).
|
||
# TADRISINO_SERVICE=
|
||
|
||
# -----------------------------------------------------------------------
|
||
# آدرسِ سرویس — دو آدرس، بقیه از آنها ساخته میشوند
|
||
# -----------------------------------------------------------------------
|
||
# ALLOWED_HOSTS، CSRF_TRUSTED_ORIGINS، CORS_ALLOWED_ORIGINS، callbackِ زرینپال،
|
||
# برگشت از پرداخت و redirectِ بازار همه از این دو ساخته میشوند. پیشفرضشان
|
||
# دامنهی فعلی است و لازم نیست ست شوند. برای جابهجایی دامنه فقط همین دو را
|
||
# باز کنید (ترتیبِ کار در docs/operations.md، «عوض کردنِ دامنه»):
|
||
#
|
||
# PUBLIC_BASE_URL=https://api.math.tadrisino2.ir
|
||
# FRONTEND_URL=https://math.tadrisino2.ir
|
||
#
|
||
# این سه فقط برای دامنهی *اضافه* لازماند (مثلاً دامنهی قبلی در دورانِ جابهجایی)؛
|
||
# مقدارشان به پیشفرضها اضافه میشود، جایگزینشان نمیشود:
|
||
#
|
||
# DJANGO_ALLOWED_HOSTS=old.example.ir
|
||
# CSRF_TRUSTED_ORIGINS=https://old.example.ir
|
||
# CORS_ALLOWED_ORIGINS=https://old-app.example.ir
|
||
|
||
# اختیاری — بدونش OTP واقعی ارسال نمیشه (فقط خطای صریح ۵۰۳ برمیگرده،
|
||
# طبق رفتارِ موجودِ accounts/services.py)
|
||
KAVENEGAR_API_KEY=
|
||
|
||
# شمارههای تستِ تیم — «شماره:کد» با کامای جداکننده.
|
||
#
|
||
# بدونِ این، روی سروری که DEBUG=0 است و کاوهنگار ندارد، هیچ توسعهدهندهای
|
||
# نمیتواند وارد API شود مگر اینکه کسی دستی از پنلِ ادمین یک OTPCode بسازد.
|
||
# با این، هر عضوِ تیم شماره و کدِ خودش را دارد و به کسی وابسته نیست.
|
||
#
|
||
# برای این شمارهها پیامکی ارسال نمیشود و کدشان هیچوقت در پاسخِ API
|
||
# برنمیگردد. هر شمارهی دیگری مسیرِ کاملاً عادیِ OTP را میرود.
|
||
#
|
||
# کد را تصادفی بگذارید (نه 1234)، شماره را دقیقاً همانطور که کلاینت
|
||
# میفرستد بنویسید، و پیش از باز کردنِ سرویس روی کاربرانِ واقعی خالیاش کنید —
|
||
# /api/health تا وقتی پر باشد هشدار میدهد.
|
||
#
|
||
# TEST_LOGIN_PHONES=09120000001:834512,09120000002:110947
|
||
TEST_LOGIN_PHONES=
|
||
|
||
# رمزِ ادمینِ Grafana (کاربرِ admin) — فقط برای دسترسیِ محلی
|
||
GRAFANA_ADMIN_PASSWORD=admin
|
||
|
||
# -----------------------------------------------------------------------
|
||
# RAG / AI tutor (اپ rag) — موقعِ تست پر کنید
|
||
# -----------------------------------------------------------------------
|
||
# بدونِ OPENAI_API_KEY و QDRANT_API_KEY، مسیرِ RAG غیرفعاله و
|
||
# POST /api/ai/ask همون پاسخِ stubِ قبلی رو میده (سازگاریِ عقبرو).
|
||
# فعلاً از GapGPT (پروکسیِ OpenAI برای ایران) استفاده میکنیم.
|
||
OPENAI_API_KEY=
|
||
OPENAI_BASE_URL=https://api.gapgpt.app/v1
|
||
# اگه بعداً خواستی مستقیم OpenAI واقعی رو تست کنی: کلیدِ واقعی رو این پایین
|
||
# پارک کن، بعد فقط دو خطِ بالا رو باهاش عوض کن. هیچ منطقِ سوییچی در کد نیست
|
||
# تا ساده و بیاشتباه بمونه.
|
||
# OPENAI_BASE_URL=https://api.openai.com/v1
|
||
OPENAI_API_KEY_OPENAI_DIRECT=
|
||
OPENAI_CHAT_MODEL=gpt-4.1-mini
|
||
OPENAI_VISION_MODEL=gpt-4.1-mini
|
||
# این رو تغییر نده؛ باید با مدلی که تیمِ دیگه باهاش collectionها رو ساخته یکی باشه.
|
||
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
|
||
|
||
QDRANT_URL=https://qdrant.tadrisino2.ir:443
|
||
QDRANT_API_KEY=
|
||
|
||
# نامِ collection = f"{subject} {grade}".lower() . collectionهای موجود روی
|
||
# سرورِ تیمِ دیگه (بررسیشده): «علوم تجربی سوم» ، «مطالعات اجتماعی چهارم» ،
|
||
# «مطالعات اجتماعی ششم» ، «ریاضی سوم» ، «ریاضی ششم» ، «ریاضی هفتم».
|
||
# یعنی جزءِ گرید فقط «سوم/چهارم/ششم/هفتم» است (بدونِ «دبستان»).
|
||
#
|
||
# کلیدِ فیلترِ درس دستی تنظیم نمیشه — rag/services/known_collections.py خودش
|
||
# تشخیص میده: ریاضی → `chapter` (و شمارهی فصل رو به رشتهی دقیق تبدیل میکنه)،
|
||
# بقیه → `lesson_number`.
|
||
RAG_COLLECTION_NAME_TEMPLATE={subject} {grade}
|
||
RAG_RETRIEVAL_TOP_K=4
|
||
# RAG_FORCE_MOCK=1 یعنی حتی با وجودِ کلیدها هم مسیرِ RAG خاموش بمونه.
|
||
RAG_FORCE_MOCK=
|
||
|
||
# -----------------------------------------------------------------------
|
||
# زرینپال (اپ payment) — درگاهِ اصلیِ پرداختِ ما
|
||
# -----------------------------------------------------------------------
|
||
# بدونِ ZARINPAL_MERCHANT_ID، مسیرِ پرداخت fail-closed میمونه (خطای صریحِ
|
||
# ۵۰۳ بهجایِ پرداختِ ساختگیِ موفق).
|
||
# callbackِ زرینپال از PUBLIC_BASE_URL (بالاتر) ساخته میشود.
|
||
ZARINPAL_MERCHANT_ID=
|
||
# 1 = محیطِ sandbox زرینپال (برای تست بدونِ پرداختِ واقعی)
|
||
ZARINPAL_SANDBOX=1
|
||
# اختیاری — بعدِ تاییدِ پرداخت، کاربر به این URL (مثلاً یک deep link اپ) با
|
||
# ?orderId=...&status=... ریدایرکت میشه. خالی = پاسخِ JSON ساده.
|
||
ZARINPAL_REDIRECT_URL=
|
||
|
||
# -----------------------------------------------------------------------
|
||
# تنظیماتِ مقیاسپذیری (همه دیفالتِ امن دارن)
|
||
# -----------------------------------------------------------------------
|
||
# persistent DB connection (ثانیه). با SQLite بیاثر؛ بعد از Postgres مهم.
|
||
DB_CONN_MAX_AGE=60
|
||
# کشِ کوتاهِ ردیفِ کاربر در auth تا هر request یک SELECT نزنه. 0 = خاموش
|
||
# (پیشفرض). توصیهی پروداکشن با Redis: 30. با هر save/delete روی User باطل میشه.
|
||
AUTH_USER_CACHE_TTL=0
|
||
# سقفِ ارزانِ خواندن روی endpointهای پرترافیکِ GET (home/leaderboard/summary/...).
|
||
READ_THROTTLE_RATE=120/min
|
||
|
||
# ⚠️ تعدادِ پروکسیهایی که جلوی اپ نشستهاند (nginx = ۱). محدودیتِ نرخِ
|
||
# کاربرِ مهمان بر اساسِ IP است و IP از X-Forwarded-For خوانده میشود؛ اگر این
|
||
# عدد از تعدادِ واقعیِ پروکسیها بیشتر باشد، کلاینت میتواند هدر را جعل کند و
|
||
# محدودیت را دور بزند. کمتر باشد، همه پشتِ IP پروکسی یکی شمرده میشوند.
|
||
NUM_PROXIES=1
|
||
|
||
# -----------------------------------------------------------------------
|
||
# بقیه — همه پیشفرضِ کارا دارند و معمولاً لازم نیست دست بخورند
|
||
# -----------------------------------------------------------------------
|
||
# پیامک: بدونِ SENDER هم کاوهنگار با خطِ پیشفرضِ حساب میفرستد، ولی اگر خطِ
|
||
# اختصاصی دارید اینجا بگذارید. قالبِ متنِ پیامک هم قابلِ تغییر است ({code} لازم).
|
||
KAVENEGAR_SENDER=
|
||
# OTP_SMS_TEMPLATE=کد ورود شما به تدریسینو: {code}
|
||
|
||
# کلیدِ endpointهای داخلی (ingest در اپ rag، ساختِ سفارشِ سرور-به-سرور). با
|
||
# DEBUG=1 مقدارِ توسعه دارد؛ با DEBUG=0 خالی است و آن مسیرها بسته میمانند
|
||
# تا وقتی مقدارِ واقعی بگذارید.
|
||
INTERNAL_SERVICE_KEY=
|
||
|
||
# با صفر قلب جلوی جواب دادن گرفته شود؟ (۱ = بله). فقط وقتی فرانت برای ۴۰۳ِ
|
||
# no_hearts صفحهی «قلبها تمام شد» دارد.
|
||
# HEARTS_ENFORCED=0
|
||
|
||
# پرداختِ درونبرنامهایِ کافهبازار. بدونِ این چهار تا، مسیرِ بازار
|
||
# fail-closed میماند (payment/bazaar.py). REFRESH_TOKEN یکبار و دستی از
|
||
# پنلِ devapi گرفته میشود.
|
||
BAZAAR_CLIENT_ID=
|
||
BAZAAR_CLIENT_SECRET=
|
||
BAZAAR_REFRESH_TOKEN=
|
||
# BAZAAR_PACKAGE_NAME=ir.tadrisino.app
|
||
# BAZAAR_API_BASE=https://pardakht.cafebazaar.ir/devapi/v2
|
||
|
||
# آدرسهای زرینپال از روی ZARINPAL_SANDBOX بالا انتخاب میشوند؛ فقط اگر
|
||
# زرینپال دامنهاش را عوض کرد این سه را دستی بدهید.
|
||
# ZARINPAL_API_BASE=
|
||
# ZARINPAL_STARTPAY_BASE=
|
||
# ZARINPAL_CALLBACK_URL=
|
||
|
||
# نوشتنِ لاگ در فایل علاوه بر stdout (داخلِ Docker ست میشود). خالی = فقط stdout.
|
||
LOG_DIR=
|
||
# سقفِ گیرندهی اکشنِ «ارسالِ پیامک به انتخابشدهها» در پنلِ ادمین. این اکشن
|
||
# داخلِ همان request اجرا میشود، پس سقف جلوی تایماوت را میگیرد.
|
||
ADMIN_SMS_MAX_RECIPIENTS=200
|
||
# سقفِ نرخِ /api/health برای پایشِ بیرونی.
|
||
HEALTH_THROTTLE_RATE=60/min
|
||
# تایماوت و پارامترهای RAG. EMBEDDING_DIM باید با مدلِ embedding بالا بخواند.
|
||
OPENAI_TIMEOUT_SECONDS=60
|
||
QDRANT_TIMEOUT_SECONDS=30
|
||
OPENAI_EMBEDDING_DIM=1536
|
||
# کشِ پاسخِ دستیار (ثانیه) — سوالِ تکراری دوباره به مدل نمیرود.
|
||
RAG_ANSWER_CACHE_TTL=1800
|