Files
MathApp/.env.example
Zareian 7d6abab15c document the twenty environment variables the code reads but never mentioned
A mechanical cross-check of os.environ.get() in settings against .env.example
found twenty variables the app reads that were written down nowhere. Most have
harmless defaults, but three would have cost someone real time:

- NUM_PROXIES decides how many X-Forwarded-For hops are trusted, and guest rate
  limiting is keyed on the resulting IP. Set too high, a client can forge the
  header and walk past the limit; too low and everyone collapses onto the proxy's
  own IP. CLAUDE.md warns about it; the file an operator actually edits did not.
- KAVENEGAR_SENDER is read alongside the API key, so someone configuring SMS from
  the README alone would have set only half of it.
- INTERNAL_SERVICE_KEY and PAYMENT_WEBHOOK_SECRET are empty under DEBUG=0 by
  design, which quietly keeps the ingest and webhook routes shut. That is the
  right behaviour and exactly the kind of thing that reads as a bug when it is
  not written down.

The whole Cafe Bazaar block was undocumented too.

Three variables in .env.example are not read by settings and were left alone:
GRAFANA_ADMIN_PASSWORD is consumed by docker-compose, TADRISINO_SERVICE by
deploy.sh, and OPENAI_API_KEY_OPENAI_DIRECT is a documented parking spot.

CLAUDE.md's current-state section was a day stale (458 tests, now 483) and said
nothing about the documentation restructure, the RTL docs shell, the test-login
allowlist or the frontend test account, so anyone picking this up would have
rediscovered all four. It now also records that backups are still manual and
that sqlite3 is not installed on the server.

Also verified mechanically, with no findings: no docstring references an
endpoint absent from the OpenAPI schema, no docstring names a setting or
constant that does not exist, and every internal documentation link resolves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-23 02:11:29 +03:30

185 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# نمونه‌ی فایلِ تنظیمات — هم برای ماشینِ خودتان، هم برای سرور.
#
# 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=
# -----------------------------------------------------------------------
# آدرسِ سرویس — این چهار تا پیش‌فرضِ درست دارند
# -----------------------------------------------------------------------
# تا پیش از این هر چهارتا باید دستی روی سرور اضافه می‌شدند، و چون در همین
# فایل نبودند هر بار از نو کشف می‌شدند: نبودِ اولی یعنی «Disallowed Host»،
# دومی یعنی ردِ ورود به پنلِ ادمین، سومی یعنی بلاک شدنِ فرانت در مرورگر، و
# چهارمی یعنی شکستنِ بی‌سروصدای مسیرِ پرداخت.
#
# حالا هر چهارتا مقدارِ پیش‌فرضِ دامنه‌ی خودمان را دارند و لازم نیست ست شوند.
# فقط اگر دامنه عوض شد، این‌ها را باز کنید:
#
# DJANGO_ALLOWED_HOSTS=api.math.tadrisino2.ir,math.tadrisino2.ir,localhost,127.0.0.1
# CSRF_TRUSTED_ORIGINS=https://api.math.tadrisino2.ir
# CORS_ALLOWED_ORIGINS=https://math.tadrisino2.ir
# PUBLIC_BASE_URL=https://api.math.tadrisino2.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-4o-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 می‌مونه (خطای صریحِ
# ۵۰۳ به‌جایِ پرداختِ ساختگیِ موفق).
# 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=
PAYMENT_WEBHOOK_SECRET=
# پرداختِ درون‌برنامه‌ایِ کافه‌بازار. بدونِ این چهار تا، مسیرِ بازار
# 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