From 8f7f56f3dfc80a2fe2cba697696b433214212a1e Mon Sep 17 00:00:00 2001 From: Sayyid Hamid Mahdavi Date: Sun, 15 Mar 2026 15:08:32 +0330 Subject: [PATCH] basic healthcheck and status --- apps/core/views.py | 38 ++++++++++++++++++++++++++++++++++++++ apps/pg/models.py | 6 ++++++ apps/pg/urls.py | 5 +++++ utils/exceptions.py | 44 +++++++++++++++++++++++++++++++++++++++----- 4 files changed, 88 insertions(+), 5 deletions(-) diff --git a/apps/core/views.py b/apps/core/views.py index 3d3cbe5..ac24b77 100644 --- a/apps/core/views.py +++ b/apps/core/views.py @@ -1,10 +1,48 @@ from django.contrib.auth.decorators import login_required from django.shortcuts import render +from django.utils import timezone from django.utils.decorators import method_decorator from django.views.generic import TemplateView +from oauth2_provider.contrib.rest_framework import IsAuthenticatedOrTokenHasScope +from rest_framework import generics +from rest_framework.exceptions import APIException, ErrorDetail +from rest_framework.permissions import AllowAny +from rest_framework.response import Response +from rest_framework.views import APIView + +from utils.exceptions import StatusError + # Create your views here. @method_decorator(login_required, name='dispatch') class HomeView(TemplateView): template_name = 'core/home.html' + +class StatusView(APIView): + permission_classes = [AllowAny] + + def get(self, request, format=None): + if self.request.GET.get('raise_error'): + raise StatusError( + # code='service_unavailable_at_now', + detail={ + # "code": 'service_unavailable_at_now', + "timestamp": timezone.now().isoformat(), + "message": "برخی سرویس‌های خارجی در دسترس نیستند لطفا بعدا تلاش کنید" + }, + ) + + data = { + "status": "ok", + } + return Response(data) + + +class HealthcheckView(APIView): + permission_classes = [AllowAny] + def get(self, request, format=None): + data = { + "is_healthy": True + } + return Response(data) diff --git a/apps/pg/models.py b/apps/pg/models.py index f7f0282..2f636bd 100644 --- a/apps/pg/models.py +++ b/apps/pg/models.py @@ -30,6 +30,12 @@ class GatewayChoise(TextChoices): SEP = 'sep', _('sep') +class GatewayPolicy(BasePGModel): + application = models.ForeignKey(oauth2_settings.APPLICATION_MODEL, on_delete=models.PROTECT, + related_name='+', null=True, blank=True, unique=True) + preference_gateway = models.JSONField(default=list, blank=True) + + # TODO: use generic relation class PaymentRequest(models.Model): id = None diff --git a/apps/pg/urls.py b/apps/pg/urls.py index 5eb12ad..020cfb3 100644 --- a/apps/pg/urls.py +++ b/apps/pg/urls.py @@ -1,6 +1,7 @@ from django.urls import path, include from .views import PaymentRequestView, PaymentRequestCreateApiView, PaymentConfirmView, \ ApplicationPaymentRequestCreateApiView, CalculateCommissionView +from ..core.views import HealthcheckView, StatusView app_name = "pg" @@ -10,4 +11,8 @@ urlpatterns = [ path('api/charge_create/', PaymentRequestCreateApiView.as_view(), name='charge-request-create-api-view'), path('api/application/charge_create/', ApplicationPaymentRequestCreateApiView.as_view(), name='application-user-charge-request-create-api-view'), path('api/calculate_commission/', CalculateCommissionView.as_view(), name='calculate-commission-view'), + path('api/healthcheck', HealthcheckView.as_view(), name='healthcheck-view'), + path('api/status', StatusView.as_view(), name='status-view'), + ] + diff --git a/utils/exceptions.py b/utils/exceptions.py index 1fb3b04..4a9cd3a 100644 --- a/utils/exceptions.py +++ b/utils/exceptions.py @@ -10,16 +10,34 @@ def exception_handler(exc, context): response = drf_exception_handler(exc, context) if response is not None: - # ساختار دلخواه خود را تعریف می‌کنیم + try: + if isinstance(exc.detail, dict): + if 'code' in exc.detail: + error_code = exc.detail['code'] + else: + attr = next(iter(exc.detail)) + error_code = exc.detail[attr].code + else: + error_code = exc.detail.code or exc.code + except: + error_code = '' response_data = { 'success': False, 'status_code': response.status_code, - 'message': 'An error occurred', - 'details': {} + 'error_code': error_code, + 'message': str(exc), + 'details': exc.detail, } if isinstance(exc, APIException): - response_data['message'] = exc.detail if isinstance(exc.detail, str) else "Validation error" + if isinstance(exc.detail, str): + message = exc.detail + elif isinstance(exc.detail, dict) and ('message' in exc.detail or 'string' in exc.detail): + message = exc.detail.get('message') or exc.detail.get('string') + else: + message = error_code + + response_data['message'] = message response_data['details'] = exc.detail if isinstance(exc.detail, dict) else {} response.data = response_data @@ -44,8 +62,24 @@ class ErrorMiddleware: { "success": False, "status_code": 500, + 'error_code': 500, "message": "Internal server error", "details": str(exception) # فقط در حالت توسعه! در تولید بهتر است لاگ شود. }, status=500 - ) \ No newline at end of file + ) + +from rest_framework.exceptions import APIException +from rest_framework import status +from django.utils.translation import gettext_lazy as _ + + +class UnprocessableEntity(APIException): + status_code = 422 + default_detail = 'The request was well-formed but cannot be processed due to semantic errors.' + default_code = 'unprocessable_entity' + +class StatusError(APIException): + status_code = status.HTTP_503_SERVICE_UNAVAILABLE + default_ = _('SERVICE_UNAVAILABLE') + default_code = 'service_unavailable'