سرور دو روز کدِ قدیمی را سرو کرد و هیچکس نفهمید. علتش این بود که `systemctl restart` شکست خورده بود ولی پروسهی قبلی هنوز بالا بود و همان ۲۰۰ را میداد — پس اسکریپت «✓ سرویس سالم بالا آمد» چاپ کرد و با کدِ صفر تمام شد. محتوا و فایلهای استاتیک هم روی دیسک بودند و سرو نمیشدند. حالا زمانِ بالا آمدنِ سرویس قبل و بعد مقایسه میشود. اگر عوض نشده باشد، ۲۰۰ گرفتن دیگر موفقیت حساب نمیشود و اسکریپت با خطا تمام میکند. نامِ عامِ `gunicorn` هم از فهرستِ حدسِ سرویس برداشته شد: روی سرورِ واقعی یک `gunicorn.service` هست که مالِ تیمِ دیگری است، و حدس زدنش یعنی ریاستارت کردنِ سرویسِ یک نفرِ دیگر. نامِ درستِ ما `gunicorn-mathapp` است. `api/health/` هم کنارِ `api/health` ثبت شد. پایشِ سرور شکلِ اسلشدار را صدا میزد و چون ثبت نبود، کسی روی سرور `config/urls.py` را دستی ویرایش کرده بود — و از آن به بعد هر `git pull` روی همان فایل گیر میکرد. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
84 lines
3.7 KiB
Python
84 lines
3.7 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),
|
||
|
||
# بررسیِ سلامتِ نسخهی مستقرشده — بدونِ احراز، برای پایشِ بیرونی.
|
||
#
|
||
# هر دو شکل ثبت میشود، با و بدونِ اسلشِ آخر. دلیلش تجربهی واقعی است:
|
||
# پایشِ سرور `/api/health/` صدا میزد و چون فقط شکلِ بیاسلش ثبت بود،
|
||
# کسی روی سرور همین فایل را دستی ویرایش کرد تا کار کند — و از آن لحظه
|
||
# هر `git pull` بعدی روی همین فایل گیر میکرد. یک مسیرِ اضافه ارزانش
|
||
# است در برابرِ یک فایلِ دستکاریشده روی پروداکشن.
|
||
path('api/health', HealthView.as_view(), name='health'),
|
||
path('api/health/', 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')),
|
||
]
|