The Swagger header was a list of features. That is what the project does, not what a caller needs. It now opens with the three things anyone integrating against this API asks first: how do I get a token, what does an error look like, and when do I get rate limited -- plus the field-naming and date conventions, and a note that the schema is the contract and clients should be generated from it rather than written from memory. Swagger UI and Redoc both render Markdown, so it is structured rather than one long paragraph, which also fixes how badly the old mixed Persian/Latin line wrapped right-to-left. Two defects surfaced while checking the rendered schema: - /api/health was absent from the schema entirely. The trailing-slash alias was excluded with extend_schema(exclude=True)(HealthView), but that decorator mutates the class, and both routes pointed at the same class, so the canonical route disappeared with it. A subclass now carries the exclusion, and the endpoint is back, exactly once. Endpoint count is 56, not 55. - One endpoint was tagged 'محتوا (content)' while every other content endpoint used 'Curriculum (content)', which split the group into two sections. Tags are now declared with descriptions and ordered by the path a real user takes, rather than alphabetically. Comment pass over the codebase, keeping the substance and dropping the noise: the "⚠️ اضافه شده" prefixes dated comments against a moment no current reader shares; "فاز ۲ / بخش ۴" referenced a plan that no longer exists and only repeated the app name on the next line; "فاز اول - ۴ از ۱۱ اپ" had drifted (there are fourteen). INSTALLED_APPS is now grouped by responsibility. The TEST_LOGIN_PHONES block in settings shrank from 29 lines to 8 now that the same explanation lives in docs/operations.md and .env.example -- a comment beside the code should carry what a code reader needs, not a copy of the runbook. Checked mechanically that no comment references an identifier that no longer exists: zero. The dated measurements in rag/services/ingest.py and generate_questions_from_chunks.py were deliberately left alone -- a measurement carrying the date it was taken is the useful kind. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
4.5 KiB
Python
89 lines
4.5 KiB
Python
"""
|
||
مسیرهای اصلی پروژهی 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'),
|
||
path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||
path('api/redoc/', SpectacularRedocView.as_view(url_name='schema'), 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')),
|
||
path('api/', include('family.urls')),
|
||
]
|