بالا آوردنِ نسخهی جدید تا الان چند کارِ دستی به ترتیبِ درست بود (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>
77 lines
3.0 KiB
Python
77 lines
3.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 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),
|
||
|
||
# بررسیِ سلامتِ نسخهی مستقرشده — بدونِ احراز، برای پایشِ بیرونی.
|
||
path('api/health', HealthView.as_view(), name='health'),
|
||
|
||
# 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')),
|
||
]
|