forked from github/plane
Merge branch 'chore/api_endpoints' of github.com:makeplane/plane into chore/api_endpoints
This commit is contained in:
commit
21b6804ebc
@ -66,7 +66,6 @@ class IssueSerializer(BaseSerializer):
|
|||||||
project_id=self.context.get("project_id"),
|
project_id=self.context.get("project_id"),
|
||||||
is_active=True,
|
is_active=True,
|
||||||
member_id__in=data["assignees"],
|
member_id__in=data["assignees"],
|
||||||
is_active=True,
|
|
||||||
).values_list("member_id", flat=True)
|
).values_list("member_id", flat=True)
|
||||||
|
|
||||||
# Validate labels are from project
|
# Validate labels are from project
|
||||||
|
@ -36,28 +36,31 @@ class TimezoneMixin:
|
|||||||
else:
|
else:
|
||||||
timezone.deactivate()
|
timezone.deactivate()
|
||||||
|
|
||||||
|
|
||||||
class WebhookMixin:
|
class WebhookMixin:
|
||||||
webhook_event = None
|
webhook_event = None
|
||||||
|
|
||||||
def finalize_response(self, request, response, *args, **kwargs):
|
def finalize_response(self, request, response, *args, **kwargs):
|
||||||
response = super().finalize_response(request, response, *args, **kwargs)
|
response = super().finalize_response(request, response, *args, **kwargs)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
self.webhook_event
|
self.webhook_event
|
||||||
and self.request.method in ["POST", "PATCH", "DELETE"]
|
and self.request.method in ["POST", "PATCH"]
|
||||||
and response.status_code in [200, 201, 204]
|
and response.status_code in [200, 201, 204]
|
||||||
):
|
):
|
||||||
|
# Get the id
|
||||||
|
object_id = (
|
||||||
|
response.data.get("id") if isinstance(response.data, dict) else None
|
||||||
|
)
|
||||||
|
|
||||||
send_webhook.delay(
|
send_webhook.delay(
|
||||||
event=self.webhook_event,
|
event=self.webhook_event,
|
||||||
event_data=json.dumps(response.data, cls=DjangoJSONEncoder),
|
event_id=object_id,
|
||||||
action=self.request.method,
|
action=self.request.method,
|
||||||
slug=self.workspace_slug,
|
slug=self.workspace_slug,
|
||||||
)
|
)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
||||||
authentication_classes = [
|
authentication_classes = [
|
||||||
APIKeyAuthentication,
|
APIKeyAuthentication,
|
||||||
@ -139,13 +142,13 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|||||||
response = super().finalize_response(request, response, *args, **kwargs)
|
response = super().finalize_response(request, response, *args, **kwargs)
|
||||||
|
|
||||||
# Add custom headers if they exist in the request META
|
# Add custom headers if they exist in the request META
|
||||||
ratelimit_remaining = request.META.get('X-RateLimit-Remaining')
|
ratelimit_remaining = request.META.get("X-RateLimit-Remaining")
|
||||||
if ratelimit_remaining is not None:
|
if ratelimit_remaining is not None:
|
||||||
response['X-RateLimit-Remaining'] = ratelimit_remaining
|
response["X-RateLimit-Remaining"] = ratelimit_remaining
|
||||||
|
|
||||||
ratelimit_reset = request.META.get('X-RateLimit-Reset')
|
ratelimit_reset = request.META.get("X-RateLimit-Reset")
|
||||||
if ratelimit_reset is not None:
|
if ratelimit_reset is not None:
|
||||||
response['X-RateLimit-Reset'] = ratelimit_reset
|
response["X-RateLimit-Reset"] = ratelimit_reset
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@ -169,4 +172,4 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator):
|
|||||||
expand = [
|
expand = [
|
||||||
expand for expand in self.request.GET.get("expand", "").split(",") if expand
|
expand for expand in self.request.GET.get("expand", "").split(",") if expand
|
||||||
]
|
]
|
||||||
return expand if expand else None
|
return expand if expand else None
|
||||||
|
@ -2,15 +2,63 @@ import requests
|
|||||||
import uuid
|
import uuid
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
import hmac
|
||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from celery import shared_task
|
from celery import shared_task
|
||||||
from sentry_sdk import capture_exception
|
from sentry_sdk import capture_exception
|
||||||
|
|
||||||
from plane.db.models import Webhook, WebhookLog
|
from plane.db.models import (
|
||||||
|
Webhook,
|
||||||
|
WebhookLog,
|
||||||
|
Project,
|
||||||
|
Issue,
|
||||||
|
Cycle,
|
||||||
|
Module,
|
||||||
|
ModuleIssue,
|
||||||
|
CycleIssue,
|
||||||
|
IssueComment,
|
||||||
|
)
|
||||||
|
from plane.api.serializers import (
|
||||||
|
ProjectSerializer,
|
||||||
|
IssueSerializer,
|
||||||
|
CycleSerializer,
|
||||||
|
ModuleSerializer,
|
||||||
|
CycleIssueSerializer,
|
||||||
|
ModuleIssueSerializer,
|
||||||
|
IssueCommentSerializer,
|
||||||
|
)
|
||||||
|
|
||||||
|
SERIALIZER_MAPPER = {
|
||||||
|
"project": ProjectSerializer,
|
||||||
|
"issue": IssueSerializer,
|
||||||
|
"cycle": CycleSerializer,
|
||||||
|
"module": ModuleSerializer,
|
||||||
|
"cycle_issue": CycleIssueSerializer,
|
||||||
|
"module_issue": ModuleIssueSerializer,
|
||||||
|
"issue_comment": IssueCommentSerializer,
|
||||||
|
}
|
||||||
|
|
||||||
|
MODEL_MAPPER = {
|
||||||
|
"project": Project,
|
||||||
|
"issue": Issue,
|
||||||
|
"cycle": Cycle,
|
||||||
|
"module": Module,
|
||||||
|
"cycle_issue": CycleIssue,
|
||||||
|
"module_issue": ModuleIssue,
|
||||||
|
"issue_comment": IssueComment,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def get_model_data(event, event_id):
|
||||||
|
model = MODEL_MAPPER.get(event)
|
||||||
|
queryset = model.objects.get(pk=event_id)
|
||||||
|
serializer = SERIALIZER_MAPPER.get(event)
|
||||||
|
return serializer(queryset).data
|
||||||
|
|
||||||
|
|
||||||
@shared_task(
|
@shared_task(
|
||||||
@ -20,7 +68,7 @@ from plane.db.models import Webhook, WebhookLog
|
|||||||
max_retries=5,
|
max_retries=5,
|
||||||
retry_jitter=True,
|
retry_jitter=True,
|
||||||
)
|
)
|
||||||
def webhook_task(self, webhook, slug, event, event_data, action):
|
def webhook_task(self, webhook, slug, event, event_id, action):
|
||||||
try:
|
try:
|
||||||
webhook = Webhook.objects.get(id=webhook, workspace__slug=slug)
|
webhook = Webhook.objects.get(id=webhook, workspace__slug=slug)
|
||||||
|
|
||||||
@ -31,19 +79,26 @@ def webhook_task(self, webhook, slug, event, event_data, action):
|
|||||||
"X-Plane-Event": event,
|
"X-Plane-Event": event,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Your secret key
|
event_data = get_model_data(event=event, event_id=event_id)
|
||||||
|
|
||||||
|
# # Your secret key
|
||||||
|
event_data = (
|
||||||
|
json.loads(json.dumps(event_data, cls=DjangoJSONEncoder))
|
||||||
|
if event_data is not None
|
||||||
|
else None
|
||||||
|
)
|
||||||
|
|
||||||
|
# Use HMAC for generating signature
|
||||||
if webhook.secret_key:
|
if webhook.secret_key:
|
||||||
# Concatenate the data and the secret key
|
event_data_json = json.dumps(event_data) if event_data is not None else '{}'
|
||||||
message = event_data + webhook.secret_key
|
hmac_signature = hmac.new(
|
||||||
|
webhook.secret_key.encode("utf-8"),
|
||||||
# Create a SHA-256 hash of the message
|
event_data_json.encode("utf-8"),
|
||||||
sha256 = hashlib.sha256()
|
hashlib.sha256
|
||||||
sha256.update(message.encode("utf-8"))
|
)
|
||||||
signature = sha256.hexdigest()
|
signature = hmac_signature.hexdigest()
|
||||||
headers["X-Plane-Signature"] = signature
|
headers["X-Plane-Signature"] = signature
|
||||||
|
|
||||||
event_data = json.loads(event_data) if event_data is not None else None
|
|
||||||
|
|
||||||
action = {
|
action = {
|
||||||
"POST": "create",
|
"POST": "create",
|
||||||
"PATCH": "update",
|
"PATCH": "update",
|
||||||
@ -103,6 +158,7 @@ def webhook_task(self, webhook, slug, event, event_data, action):
|
|||||||
raise requests.RequestException()
|
raise requests.RequestException()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
print(e)
|
print(e)
|
||||||
capture_exception(e)
|
capture_exception(e)
|
||||||
@ -110,7 +166,7 @@ def webhook_task(self, webhook, slug, event, event_data, action):
|
|||||||
|
|
||||||
|
|
||||||
@shared_task()
|
@shared_task()
|
||||||
def send_webhook(event, event_data, action, slug):
|
def send_webhook(event, event_id, action, slug):
|
||||||
try:
|
try:
|
||||||
webhooks = Webhook.objects.filter(workspace__slug=slug, is_active=True)
|
webhooks = Webhook.objects.filter(workspace__slug=slug, is_active=True)
|
||||||
|
|
||||||
@ -130,7 +186,7 @@ def send_webhook(event, event_data, action, slug):
|
|||||||
webhooks = webhooks.filter(issue_comment=True)
|
webhooks = webhooks.filter(issue_comment=True)
|
||||||
|
|
||||||
for webhook in webhooks:
|
for webhook in webhooks:
|
||||||
webhook_task.delay(webhook.id, slug, event, event_data, action)
|
webhook_task.delay(webhook.id, slug, event, event_id, action)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if settings.DEBUG:
|
if settings.DEBUG:
|
||||||
|
Loading…
Reference in New Issue
Block a user