Content ------- Every node in grades 8, 9, 10, 11 and 12 now holds a full four-question quiz — 121 more questions, filling the two حسابان دوازدهم nodes in فصل ۵: کاربردهای مشتق that were empty and the two ریاضی هشتم nodes that were short. The repo's own seed guard was already failing on those two empty nodes; it passes now. Grades 3, 6 and 7 are the other team's books and were left alone. 39 of their 145 nodes still hold fewer than four questions, but none is empty, so every node on the map still plays. An audit over all 1,593 questions in grades 8–12 found no structural fault: no question with the wrong number of options, no question with zero or two correct answers, no empty or duplicated option, no leftover LaTeX, and no two identical questions inside one lesson. Contract -------- Three endpoints were missing or malformed in the OpenAPI schema, which matters more than it looks: that schema is the contract the frontend generates its client from, so a view missing there is a view the other repo cannot call. - NotificationMarkReadView was dropped from the schema entirely, because spectacular could not guess a request body for its PATCH and gives up on the whole view rather than the one field. It declares request=None. - PaymentRedirectView was dropped for the same reason; it answers with a 302 and no JSON body, which it now says. - /api/health and /api/health/ collided on operationId and were emitted as health_retrieve and health_retrieve_2. The slash variant exists only for a monitor that calls it that way, so it is excluded from the schema. `spectacular` now runs clean — no errors, no warnings. Tidying ------- Condensed the two longest comment blocks (18 and 14 lines) in rag_verify_solutions and settings, keeping the measurement and the rule and dropping the retelling. README gains the bulk question-authoring workflow; the playbook's subagent section now points at the real tool instead of describing the steps by hand. 458 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
89 lines
4.0 KiB
Python
89 lines
4.0 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,
|
||
)
|
||
|
||
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 نسازند.
|
||
path('api/health/', extend_schema(exclude=True)(HealthView).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'),
|
||
|
||
# اپهای فاز ۱
|
||
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')),
|
||
|
||
# اپهای فاز ۲ / بخش ۱ (subscription, payment, discount)
|
||
path('api/', include('subscription.urls')),
|
||
path('api/', include('payment.urls')),
|
||
path('api/', include('discount.urls')),
|
||
|
||
# اپهای فاز ۲ / بخش ۲ (notifications)
|
||
path('api/', include('notifications.urls')),
|
||
|
||
# اپهای فاز ۲ / بخش ۳ (affiliate)
|
||
path('api/', include('affiliate.urls')),
|
||
|
||
# اپهای فاز ۲ / بخش ۴ (support)
|
||
path('api/', include('support.urls')),
|
||
|
||
# اپهای فاز ۲ / بخش ۵ (family)
|
||
path('api/', include('family.urls')),
|
||
]
|