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>
This commit is contained in:
2026-09-13 19:01:06 +03:30
parent fa56308a35
commit ac1b1af398
8 changed files with 697 additions and 7 deletions

View File

@@ -53,6 +53,33 @@ ALLOWED_HOSTS = [h for h in os.environ.get('DJANGO_ALLOWED_HOSTS', '').split(','
CSRF_TRUSTED_ORIGINS = [o for o in os.environ.get('CSRF_TRUSTED_ORIGINS', '').split(',') if o]
# --- CORS -------------------------------------------------------------------
# فرانت‌اند (Vite/React) از یک origin دیگر صدا می‌زند، پس بدونِ این هدرها مرورگر
# هر درخواستِ /api/ را قبل از رسیدن به Django رد می‌کند.
#
# allowlist صریح است، نه CORS_ALLOW_ALL_ORIGINS: احرازِ هویتِ ما توکنِ Bearer در
# هدر است، پس origin ناشناس نباید بتواند از مرورگرِ کاربر به API ما بزند.
# CORS_ALLOW_CREDENTIALS هم عمداً خاموش می‌ماند — کوکی‌ای در کار نیست و روشن
# کردنش فقط سطحِ حمله اضافه می‌کند.
CORS_ALLOWED_ORIGINS = [
o for o in os.environ.get('CORS_ALLOWED_ORIGINS', '').split(',') if o
]
if DEBUG and not CORS_ALLOWED_ORIGINS:
# پورت‌های پیش‌فرضِ Vite/CRA برای توسعه‌ی محلی.
CORS_ALLOWED_ORIGINS = [
'http://localhost:3000', 'http://127.0.0.1:3000',
'http://localhost:5173', 'http://127.0.0.1:5173',
]
CORS_ALLOW_CREDENTIALS = False
# فقط مسیرهای API؛ /admin/ و فایل‌های استاتیک لازم نیست از origin دیگر خوانده شوند.
CORS_URLS_REGEX = r'^/api/.*$'
# Idempotency-Key و X-Internal-Key هدرهای خودمان‌اند و در فهرستِ پیش‌فرض نیستند.
CORS_ALLOW_HEADERS = (
'accept', 'accept-encoding', 'authorization', 'content-type', 'dnt',
'origin', 'user-agent', 'x-csrftoken', 'x-requested-with',
'idempotency-key', 'x-internal-key',
)
# Application definition
@@ -67,6 +94,7 @@ INSTALLED_APPS = [
# 3rd party
'rest_framework',
'drf_spectacular',
'corsheaders',
# Tadrisino apps (فاز اول - ۴ از ۱۱ اپ)
'accounts',
@@ -104,6 +132,10 @@ INSTALLED_APPS = [
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
# ⚠️ CorsMiddleware باید تا حدِ ممکن بالا و حتماً قبل از CommonMiddleware
# باشد، وگرنه پاسخ‌هایی که زودتر برمی‌گردند (ریدایرکتِ APPEND_SLASH، خطای
# ۴۰۰) هدرِ CORS نمی‌گیرند و در مرورگر به‌شکلِ خطای مبهمِ شبکه دیده می‌شوند.
'corsheaders.middleware.CorsMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',