* dev: initiate external apis * dev: external api * dev: external public api implementation * dev: add prefix to all api tokens * dev: flag to enable disable api token api access * dev: webhook model create and apis * dev: webhook settings * fix: webhook logs * chore: removed drf spectacular * dev: remove retry_count and fix api logging for get requests * dev: refactor webhook logic * fix: celery retry mechanism * chore: event and action change * chore: migrations changes * dev: proxy setup for apis * chore: changed retry time and cleanup * chore: added issue comment and inbox issue api endpoints * fix: migration files * fix: added env variables * fix: removed issue attachment from proxy * fix: added new migration file * fix: restricted wehbook access * chore: changed urls * chore: fixed porject serializer * fix: set expire for api token * fix: retrive endpoint for api token * feat: Api Token screens & api integration * dev: webhook endpoint changes * dev: add fields for webhook updates * feat: Download Api secret key * chore: removed BASE API URL * feat: revoke token access * dev: migration fixes * feat: workspace webhooks (#2748) * feat: workspace webhook store, services integeration and rendered webhook list and create * chore: handled webhook update and rengenerate token in workspace webhooks * feat: regenerate key and delete functionality --------- Co-authored-by: Ramesh Kumar <rameshkumar@rameshs-MacBook-Pro.local> Co-authored-by: gurusainath <gurusainath007@gmail.com> Co-authored-by: Ramesh Kumar Chandra <rameshkumar2299@gmail.com> * fix: url validation added * fix: seperated env for webhook and api * Web hooks refactoring * add show option for generated hook key * Api token restructure * webhook minor fixes * fix build errors * chore: improvements in file structring * dev: rate limiting the open apis --------- Co-authored-by: pablohashescobar <nikhilschacko@gmail.com> Co-authored-by: LAKHAN BAHETI <lakhanbaheti9@gmail.com> Co-authored-by: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Co-authored-by: Ramesh Kumar <rameshkumar@rameshs-MacBook-Pro.local> Co-authored-by: gurusainath <gurusainath007@gmail.com> Co-authored-by: Ramesh Kumar Chandra <rameshkumar2299@gmail.com> Co-authored-by: Nikhil <118773738+pablohashescobar@users.noreply.github.com> Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com> Co-authored-by: rahulramesha <rahulramesham@gmail.com>
237 lines
7.2 KiB
Python
237 lines
7.2 KiB
Python
# Python imports
|
|
import zoneinfo
|
|
import json
|
|
|
|
# Django imports
|
|
from django.urls import resolve
|
|
from django.conf import settings
|
|
from django.utils import timezone
|
|
from django.db import IntegrityError
|
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
|
from django.core.serializers.json import DjangoJSONEncoder
|
|
|
|
# Third part imports
|
|
from rest_framework import status
|
|
from rest_framework import status
|
|
from rest_framework.viewsets import ModelViewSet
|
|
from rest_framework.response import Response
|
|
from rest_framework.exceptions import APIException
|
|
from rest_framework.views import APIView
|
|
from rest_framework.filters import SearchFilter
|
|
from rest_framework.permissions import IsAuthenticated
|
|
from sentry_sdk import capture_exception
|
|
from django_filters.rest_framework import DjangoFilterBackend
|
|
|
|
# Module imports
|
|
from plane.utils.paginator import BasePaginator
|
|
from plane.bgtasks.webhook_task import send_webhook
|
|
|
|
|
|
class TimezoneMixin:
|
|
"""
|
|
This enables timezone conversion according
|
|
to the user set timezone
|
|
"""
|
|
|
|
def initial(self, request, *args, **kwargs):
|
|
super().initial(request, *args, **kwargs)
|
|
if request.user.is_authenticated:
|
|
timezone.activate(zoneinfo.ZoneInfo(request.user.user_timezone))
|
|
else:
|
|
timezone.deactivate()
|
|
|
|
|
|
class WebhookMixin:
|
|
webhook_event = None
|
|
|
|
def finalize_response(self, request, response, *args, **kwargs):
|
|
response = super().finalize_response(request, response, *args, **kwargs)
|
|
|
|
if (
|
|
self.webhook_event
|
|
and self.request.method in ["POST", "PATCH", "DELETE"]
|
|
and response.status_code in [200, 201, 204]
|
|
and settings.ENABLE_WEBHOOK
|
|
):
|
|
send_webhook.delay(
|
|
event=self.webhook_event,
|
|
event_data=json.dumps(response.data, cls=DjangoJSONEncoder),
|
|
action=self.request.method,
|
|
slug=self.workspace_slug,
|
|
)
|
|
|
|
return response
|
|
|
|
|
|
class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator):
|
|
model = None
|
|
|
|
permission_classes = [
|
|
IsAuthenticated,
|
|
]
|
|
|
|
filter_backends = (
|
|
DjangoFilterBackend,
|
|
SearchFilter,
|
|
)
|
|
|
|
filterset_fields = []
|
|
|
|
search_fields = []
|
|
|
|
def get_queryset(self):
|
|
try:
|
|
return self.model.objects.all()
|
|
except Exception as e:
|
|
capture_exception(e)
|
|
raise APIException("Please check the view", status.HTTP_400_BAD_REQUEST)
|
|
|
|
def handle_exception(self, exc):
|
|
"""
|
|
Handle any exception that occurs, by returning an appropriate response,
|
|
or re-raising the error.
|
|
"""
|
|
try:
|
|
response = super().handle_exception(exc)
|
|
return response
|
|
except Exception as e:
|
|
if isinstance(e, IntegrityError):
|
|
return Response(
|
|
{"error": "The payload is not valid"},
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
if isinstance(e, ValidationError):
|
|
return Response(
|
|
{"error": "Please provide valid detail"},
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
if isinstance(e, ObjectDoesNotExist):
|
|
model_name = str(exc).split(" matching query does not exist.")[0]
|
|
return Response(
|
|
{"error": f"{model_name} does not exist."},
|
|
status=status.HTTP_404_NOT_FOUND,
|
|
)
|
|
|
|
if isinstance(e, KeyError):
|
|
capture_exception(e)
|
|
return Response(
|
|
{"error": f"key {e} does not exist"},
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
print(e) if settings.DEBUG else print("Server Error")
|
|
capture_exception(e)
|
|
return Response({"error": "Something went wrong please try again later"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
try:
|
|
response = super().dispatch(request, *args, **kwargs)
|
|
|
|
if settings.DEBUG:
|
|
from django.db import connection
|
|
|
|
print(
|
|
f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}"
|
|
)
|
|
|
|
return response
|
|
except Exception as exc:
|
|
response = self.handle_exception(exc)
|
|
return exc
|
|
|
|
@property
|
|
def workspace_slug(self):
|
|
return self.kwargs.get("slug", None)
|
|
|
|
@property
|
|
def project_id(self):
|
|
project_id = self.kwargs.get("project_id", None)
|
|
if project_id:
|
|
return project_id
|
|
|
|
if resolve(self.request.path_info).url_name == "project":
|
|
return self.kwargs.get("pk", None)
|
|
|
|
|
|
class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|
permission_classes = [
|
|
IsAuthenticated,
|
|
]
|
|
|
|
filter_backends = (
|
|
DjangoFilterBackend,
|
|
SearchFilter,
|
|
)
|
|
|
|
filterset_fields = []
|
|
|
|
search_fields = []
|
|
|
|
def filter_queryset(self, queryset):
|
|
for backend in list(self.filter_backends):
|
|
queryset = backend().filter_queryset(self.request, queryset, self)
|
|
return queryset
|
|
|
|
def handle_exception(self, exc):
|
|
"""
|
|
Handle any exception that occurs, by returning an appropriate response,
|
|
or re-raising the error.
|
|
"""
|
|
try:
|
|
response = super().handle_exception(exc)
|
|
return response
|
|
except Exception as e:
|
|
if isinstance(e, IntegrityError):
|
|
return Response(
|
|
{"error": "The payload is not valid"},
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
if isinstance(e, ValidationError):
|
|
return Response(
|
|
{"error": "Please provide valid detail"},
|
|
status=status.HTTP_400_BAD_REQUEST,
|
|
)
|
|
|
|
if isinstance(e, ObjectDoesNotExist):
|
|
model_name = str(exc).split(" matching query does not exist.")[0]
|
|
return Response(
|
|
{"error": f"{model_name} does not exist."},
|
|
status=status.HTTP_404_NOT_FOUND,
|
|
)
|
|
|
|
if isinstance(e, KeyError):
|
|
return Response({"error": f"key {e} does not exist"}, status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
if settings.DEBUG:
|
|
print(e)
|
|
capture_exception(e)
|
|
return Response({"error": "Something went wrong please try again later"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
|
|
|
|
def dispatch(self, request, *args, **kwargs):
|
|
try:
|
|
response = super().dispatch(request, *args, **kwargs)
|
|
|
|
if settings.DEBUG:
|
|
from django.db import connection
|
|
|
|
print(
|
|
f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}"
|
|
)
|
|
return response
|
|
|
|
except Exception as exc:
|
|
response = self.handle_exception(exc)
|
|
return exc
|
|
|
|
@property
|
|
def workspace_slug(self):
|
|
return self.kwargs.get("slug", None)
|
|
|
|
@property
|
|
def project_id(self):
|
|
return self.kwargs.get("project_id", None)
|