""" مسیرهای اصلی پروژه‌ی Tadrisino — فاز ۱ (۴ از ۱۱ اپ) هر اپ زیر پیشوند /api/ خودش mount شده. مسیرهای هر endpoint دقیقاً طبق سند «Endpointهای API» طراحی‌شده تا مقایسه با Swagger راحت باشه. """ import logging from django.contrib import admin from django.http import JsonResponse from django.urls import include, path from django.views import defaults as django_default_views from drf_spectacular.utils import extend_schema from config.health import HealthView from drf_spectacular.views import ( SpectacularAPIView, SpectacularRedocView, SpectacularSwaggerView, ) @extend_schema(exclude=True) class HealthViewSlashAlias(HealthView): """نسخه‌ی اسلش‌دارِ `/api/health`، فقط برای پایشِ بیرونی — بیرون از اسکیما.""" logger = logging.getLogger(__name__) def handler404(request, exception=None): """برای مسیرهای /api/ همیشه JSON برمی‌گردونه؛ بقیه رفتارِ پیش‌فرضِ جنگو.""" if request.path.startswith('/api/'): return JsonResponse({'detail': 'یافت نشد.'}, status=404) return django_default_views.page_not_found(request, exception) def handler500(request): """شبکه‌ی ایمنیِ نهایی برای exceptionی که حتی به config/exceptions.py هم نرسیده (مثلاً در خودِ middleware/URL resolution). traceback کامل رو config/middleware.py قبلاً لاگ کرده؛ اینجا فقط یه پاسخِ JSON می‌ده.""" logger.error('unhandled_500 path=%s', request.path) if request.path.startswith('/api/'): return JsonResponse( {'detail': 'خطای غیرمنتظره‌ای رخ داد؛ لطفاً بعداً دوباره تلاش کنید.'}, status=500, ) return django_default_views.server_error(request) urlpatterns = [ path('admin/', admin.site.urls), # بررسیِ سلامتِ نسخه‌ی مستقرشده — بدونِ احراز، برای پایشِ بیرونی. # # هر دو شکل ثبت می‌شود، با و بدونِ اسلشِ آخر. دلیلش تجربه‌ی واقعی است: # پایشِ سرور `/api/health/` صدا می‌زد و چون فقط شکلِ بی‌اسلش ثبت بود، # کسی روی سرور همین فایل را دستی ویرایش کرد تا کار کند — و از آن لحظه # هر `git pull` بعدی روی همین فایل گیر می‌کرد. یک مسیرِ اضافه ارزانش # است در برابرِ یک فایلِ دست‌کاری‌شده روی پروداکشن. path('api/health', HealthView.as_view(), name='health'), # همان ویو، فقط برای پایشی که با اسلش صدا می‌زند. از اسکیما بیرون است تا # دو مسیرِ یکسان operationIdهای health_retrieve و health_retrieve_2 نسازند. # # ⚠️ زیرکلاس لازم است، نه exclude روی خودِ HealthView. extend_schema روی # یک کلاس همان کلاس را تغییر می‌دهد، و چون هر دو مسیر به یک کلاس اشاره # می‌کردند، `/api/health` هم با خودش از اسکیما بیرون می‌افتاد — یعنی # اندپوینتِ سلامت اصلاً در Swagger دیده نمی‌شد. path('api/health/', HealthViewSlashAlias.as_view(), name='health-slash'), # OpenAPI schema + Swagger UI + Redoc path('api/schema/', SpectacularAPIView.as_view(), name='schema'), # قالبِ خودمان فقط یک پوسته‌ی راست‌به‌چپ روی قالبِ drf-spectacular است؛ # بدونش متنِ فارسی در یک بلوکِ LTR رندر می‌شود و نقطه‌ی جمله و ترتیبِ # ستون‌های جدول جابه‌جا می‌شوند. توضیح در خودِ قالب. path('api/docs/', SpectacularSwaggerView.as_view( url_name='schema', template_name='tadrisino/swagger_ui.html'), name='swagger-ui'), path('api/redoc/', SpectacularRedocView.as_view( url_name='schema', template_name='tadrisino/redoc.html'), name='redoc'), # همه زیرِ /api/ جمع می‌شوند؛ هر اپ مسیرهای خودش را نگه می‌دارد، پس # ترتیبِ این فهرست اهمیتی ندارد و تداخلی بینشان نیست. path('api/', include('accounts.urls')), path('api/', include('content.urls')), path('api/', include('progress.urls')), path('api/', include('ai_tutor.urls')), path('api/', include('rag.urls')), path('api/', include('subscription.urls')), path('api/', include('payment.urls')), path('api/', include('discount.urls')), path('api/', include('notifications.urls')), path('api/', include('affiliate.urls')), path('api/', include('support.urls')), ]