Commit Graph

67 Commits

Author SHA1 Message Date
67361f6711 boot with sane defaults instead of refusing to start
سه بار پشتِ سر هم همین الگو تکرار شد: یک متغیرِ محیطی ست نبود، جنگو موقعِ
بالا آمدن مرد، و از بیرون فقط «502 Bad Gateway» دیده شد. کسی که سرور را بالا
می‌آورد هیچ سرنخی نداشت، و تشخیص هر بار یک رفت‌وبرگشتِ یک‌روزه بود.

ALLOWED_HOSTS و CSRF_TRUSTED_ORIGINS حالا دامنه‌های شناخته‌شده‌ی خودمان را
پیش‌فرض دارند. CSRF مخصوصاً مهم است: بدونش ورود به پنلِ ادمین از پشتِ پراکسیِ
HTTPS «CSRF verification failed» می‌دهد و علتش از پیام معلوم نیست.

SECRET_KEY اگر ست نباشد، در پروداکشن یک کلیدِ تصادفیِ واقعی ساخته و در
.django_secret_key نگه داشته می‌شود (۰۶۰۰، خارج از گیت). همان‌قدر
غیرقابلِ حدس، بدونِ یک قدمِ دستیِ دیگر؛ و چون روی دیسک می‌ماند، ری‌استارت
نشست‌های کاربران را باطل نمی‌کند.

CACHE_URL نبودن دیگر مانعِ بالا آمدن نیست. مشترک نبودنِ کش بینِ workerها یک
ضعفِ واقعی‌ست ولی دلیلِ خوبی برای خوابیدنِ کلِ سرویس نیست؛ حالا بالا می‌آید و
/api/health صریحاً هشدار می‌دهد. تستِ مربوطه هم از «باید بالا نیاید» به
«باید بالا بیاید ولی بی‌صدا نماند» تغییر کرد.

متغیرِ محیطی همچنان بر همه‌ی این پیش‌فرض‌ها اولویت دارد.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 21:08:00 +03:30
eaa4b761dd stop a deploy from silently blocking the frontend or leaving 0000 open
سه حالتِ خراب که هیچ‌کدام در لاگِ سرور ردی نمی‌گذارند و فقط وقتی کشف می‌شدند
که یک آدم از بیرون اپ را دستی امتحان کند.

CORS_ALLOWED_ORIGINS فقط از env خوانده می‌شد و با DEBUG روشن به localhost
محدود می‌ماند. یعنی بک‌اندِ کاملاً سالم، ولی مرورگر همه‌ی درخواست‌های
math.tadrisino2.ir را بلاک می‌کند — خطایی که سمتِ سرور دیده نمی‌شود و فقط در
کنسولِ مرورگر پیداست. origin های شناخته‌شده حالا پیش‌فرضِ داخلِ کدند تا یک
متغیرِ فراموش‌شده اتصال را نشکند؛ env همچنان بازنویسی‌شان می‌کند.

/api/health حالا warnings هم می‌دهد. مهم‌ترینش: با DEBUG روشن روی سرورِ عمومی،
DEV_MASTER_OTP یعنی هر کسی با کدِ 0000 می‌تواند به‌جای هر شماره‌ای وارد شود.
این تا امروز فقط با خواندنِ تنظیماتِ سرور معلوم می‌شد، و ما به سرور دسترسی
نداریم. allowedOrigins هم برگردانده می‌شود تا مشکلِ CORS از بیرون قابلِ
تشخیص باشد.

deploy.sh قبل از دست زدن به دیتابیس متغیرهای لازم را بررسی می‌کند و با پیامِ
روشن می‌ایستد، به‌جای اینکه وسطِ کار بشکند و 502 بدهد.
.env.production.example همان‌ها را یک‌جا مستند می‌کند.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 20:46:23 +03:30
cbe9b63dc4 make a deploy observable and one command instead of a checklist
بالا آوردنِ نسخه‌ی جدید تا الان چند کارِ دستی به ترتیبِ درست بود (pull، نصبِ
وابستگی‌ها، مهاجرت، استاتیک، ری‌استارت) و فراموش شدنِ هر کدام اپ را می‌خواباند
با خطایی که از بیرون فقط «502 Bad Gateway» دیده می‌شد. همین الان هم سرور
دقیقاً به همین دلیل خوابیده: کامیتِ ac1b1af پکیجِ django-cors-headers را اضافه
کرد، و بدونِ pip install جنگو با ModuleNotFoundError بالا نمی‌آید.

deploy.sh همه را به ترتیب و idempotent انجام می‌دهد. pip install همیشه اجرا
می‌شود، نه فقط وقتی requirements عوض شده — تشخیصِ «عوض شده یا نه» خودش یک جای
فراموش‌شدنیِ دیگر است. آخرِ کار خودش سلامت را بررسی می‌کند و اگر بالا نیامده
باشد علت‌های رایج را چاپ می‌کند، به‌جای اینکه کسی بعداً از بیرون کشفش کند.

GET /api/health وضعیت را بدونِ احراز و بدونِ SSH بیرون می‌دهد: دیتابیس،
مهاجرت‌های اعمال‌نشده، کش، و شمارشِ پایه/درس/سوال روی همان سرور. مهم‌ترینش
مهاجرتِ اعمال‌نشده است — حالتی که اپ ظاهراً بالا می‌آید و بعد زیرِ بارِ واقعی
با خطای ستونِ ناموجود می‌شکند. عمداً هیچ سکرتی برنمی‌گرداند، فقط بله/خیرِ
«تنظیم شده».

۵۰۳ برمی‌گرداند وقتی دیتابیس/کش/مهاجرت خراب است تا پایشِ خودکار بتواند رویش
حساب کند؛ نبودِ محتوا خرابی حساب نمی‌شود ولی گزارش می‌شود.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 20:28:59 +03:30
ac1b1af398 open the API up to the frontend
سینیور خواست زودتر به فرانت‌اند وصل شویم تا از آنجا هم تست شود. این‌ها
سمتِ ما لازم بودند:

CORS با allowlist صریح (نه ALLOW_ALL): احرازِ هویتِ ما توکنِ Bearer در هدر
است، پس origin ناشناس نباید از مرورگرِ کاربر به API ما بزند.
CORS_ALLOW_CREDENTIALS عمداً خاموش می‌ماند — کوکی‌ای در کار نیست و روشن
کردنش فقط سطحِ حمله اضافه می‌کند. CorsMiddleware بالاتر از CommonMiddleware
گذاشته شد وگرنه ریدایرکتِ APPEND_SLASH و خطای ۴۰۰ هدرِ CORS نمی‌گیرند و در
مرورگر به‌شکلِ خطای مبهمِ شبکه دیده می‌شوند.

PATCH /api/user/profile اضافه شد؛ تا پیش از این هیچ راهی برای ثبتِ نامِ
کاربر نبود. GET /api/home بدونِ پارامتر حالا پایه‌ی ذخیره‌شده‌ی کاربر را
برمی‌گرداند و POST /api/ai/ask فقط با lessonId کار می‌کند — هر دو برای
اینکه فرانت لازم نباشد پایه را خودش نگه دارد. questionCount به
lessons/{id}/intro اضافه شد.

docs/frontend/api.ts و README.md کلاینتِ تایپ‌دار و راهنمای اتصال‌اند، از
روی OpenAPI واقعیِ همین سرویس تولید شده‌اند.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-13 19:01:06 +03:30
52d9694583 keep ingested chunks findable and structured
Two bugs made self-ingested content useless, both silently:

- _payload wrote `chapter_number` (int) while math retrieval filters on
  `chapter` (the full "فصل ۲: کسر" string), so anything we ingest could
  never match a search — no error, just permanently invisible content.
- ingest_pdf flattened the vision extraction into one text blob and threw
  away type/keywords/generalSolution/problem/solution. LessonChunk is the
  source of truth, so rebuilding meant paying for vision again — and the
  problem/solution pairs are the input for building lesson questions.

LessonChunk now stores those fields and _payload emits them under the same
keys the other team's collections use, so ours and theirs stay one shape.

Also fixes a regression from the whitenoise commit: the Manifest storage
variant raises when staticfiles.json is absent, which broke all 23 admin
rendering tests (Django forces DEBUG=False in tests, so USE_FINDERS is off)
and would have taken the admin panel down entirely the day DEBUG=0 ships
without a collectstatic run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-10 16:24:18 +03:30
f0e3e3c96c serve static files via whitenoise under gunicorn
Admin/DRF/Swagger CSS and JS never load under gunicorn since Django's
staticfiles app only auto-serves through `runserver`. WHITENOISE_USE_FINDERS
is tied to DEBUG so no collectstatic step is needed while DEBUG=1.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 22:41:04 +03:30
092fcf13f2 add CSRF_TRUSTED_ORIGINS setting for reverse-proxied domains
Admin login (and any POST form) fails with "Origin checking failed"
behind a proxy that terminates TLS, since Django had no trusted-origins
list configured at all.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-08 22:26:46 +03:30
b5476e1c5d connect Zarinpal as the real payment gateway
payment.services.call_payment_gateway was a stub returning a fake
payment.gateway/pay/{id} URL. It's replaced with a real Zarinpal
integration (payment/zarinpal.py, Payment API v4):

- create_order/CreateOrderView still never call Zarinpal directly —
  paymentUrl points at our own GET /api/payment/orders/{id}/pay
  (PaymentRedirectView), which requests the real Zarinpal authority on
  first visit and redirects to StartPay. This keeps order creation from
  depending on Zarinpal's uptime/latency, and means none of the existing
  purchase/idempotency tests needed to change.
- GET /api/payment/zarinpal/callback (ZarinpalCallbackView) is where
  Zarinpal redirects the user back to; it always re-verifies
  server-to-server via zarinpal.verify_payment before marking an order
  paid, so a forged Status=OK in the query string alone does nothing.
- zarinpal.py fails closed (503 / invalid) when ZARINPAL_MERCHANT_ID
  isn't configured, matching payment/bazaar.py's existing convention —
  never a fake successful payment.

Adds Order.gateway_authority, PUBLIC_BASE_URL/ZARINPAL_* settings, and
a payment_pay throttle scope.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-06 13:27:13 +03:30
eeb6bd6138 scale prep — persistent DB connections, read throttle, prod renderer, auth-cache knob
settings.py
  - DATABASES.default.CONN_MAX_AGE from DB_CONN_MAX_AGE env (default 60);
    harmless on SQLite, saves a TCP+auth handshake per request on Postgres.
  - DEFAULT_RENDERER_CLASSES: BrowsableAPIRenderer only when DEBUG — no
    HTML API browser or its overhead in production.
  - DEFAULT_THROTTLE_RATES['read'] = READ_THROTTLE_RATE env (default
    120/min): a cheap ceiling for the hot GET endpoints.
  - AUTH_USER_CACHE_TTL env (default 0 = off): TTL for the per-request
    user-row cache added in a later commit; 0 keeps current behaviour.
.env.example
  - document DB_CONN_MAX_AGE / AUTH_USER_CACHE_TTL / READ_THROTTLE_RATE.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-30 08:44:50 +03:30
ff47c8d4ba register rag app, RAG provider settings, autoload .env
settings.py
  - load .env via python-dotenv (override=False) so local runs and the RAG
    keys work without `export`; production/docker env still wins.
  - add 'rag' to INSTALLED_APPS.
  - new RAG / AI provider block: OPENAI_API_KEY / OPENAI_BASE_URL (GapGPT)
    / OPENAI_CHAT_MODEL / OPENAI_VISION_MODEL / OPENAI_EMBEDDING_MODEL /
    OPENAI_TIMEOUT_SECONDS, QDRANT_URL / QDRANT_API_KEY,
    RAG_COLLECTION_NAME_TEMPLATE / RAG_GRADE_TITLE_OVERRIDES /
    RAG_RETRIEVAL_TOP_K / RAG_FORCE_MOCK / RAG_ANSWER_CACHE_TTL.
  - add 'rag_ingest' to DEFAULT_THROTTLE_RATES.
  - under `manage.py test`: force RAG_FORCE_MOCK on and RAG_ANSWER_CACHE_TTL
    to 0 so no test hits OpenAI/Qdrant or a warm cache.
urls.py
  - mount rag.urls under /api/.
requirements.txt
  - python-dotenv; openai, qdrant-client; pymupdf / pdf2image / pillow
    (optional, only for PDF ingestion).
.env.example
  - document the OPENAI_* / QDRANT_* / RAG_* keys, the discovered Qdrant
    collection names, and the parked OPENAI_API_KEY_OPENAI_DIRECT slot
    (no provider-switch logic by design).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-29 02:30:12 +03:30
683bd40169 cleanup: trim bloated multi-line comments across the project 2026-08-26 03:22:20 +03:30
83435c5a5f error-handling: add global exception handling, guard Bazaar API calls 2026-08-26 03:22:20 +03:30
05ab6ffc36 admin & data: correct admin action redirect, add SQLite retry, seed_test_data command 2026-08-26 03:22:20 +03:30
a9200772a3 Add campaigns/ Add supervisor/ Add messaging features/ Separate SMS 2026-08-25 03:39:41 +03:30
80298b47b1 throttling, abuse-protection & logging/monitoring pass 2026-08-23 23:44:00 +03:30
cb18c93a3e Harden auth, AI tutor, payments, and subscriptions 2026-08-22 04:42:36 +03:30
331148037e Initial commit - clean project structure 2026-08-18 16:48:15 +03:30